0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
443 lines
18 KiB
TypeScript
443 lines
18 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { HttpProviderConfigSchema } from '../../src/providers/http';
|
|
|
|
// Mock fs to prevent actual file system access during tests
|
|
vi.mock('fs');
|
|
// Mock crypto to prevent actual signature generation in tests
|
|
vi.mock('crypto');
|
|
|
|
describe('Http Provider Generic Certificate Field Conversion', () => {
|
|
describe('preprocessSignatureAuthConfig via HttpProviderConfigSchema', () => {
|
|
describe('PFX certificate type conversion', () => {
|
|
it('should map generic fields to PFX-specific fields', () => {
|
|
const signatureAuth = {
|
|
type: 'pfx' as const,
|
|
certificateContent: 'MIIKXAIBAzCCChgGCSqGSIb3...', // base64 encoded PFX
|
|
certificatePassword: 'testPassword123',
|
|
certificateFilename: 'certificate.pfx',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth).toBeDefined();
|
|
expect(result.signatureAuth.type).toBe('pfx');
|
|
expect(result.signatureAuth.pfxContent).toBe('MIIKXAIBAzCCChgGCSqGSIb3...');
|
|
expect(result.signatureAuth.pfxPassword).toBe('testPassword123');
|
|
expect(result.signatureAuth.certificateFilename).toBe('certificate.pfx');
|
|
});
|
|
|
|
it('should preserve existing PFX-specific fields over generic fields', () => {
|
|
const signatureAuth = {
|
|
type: 'pfx' as const,
|
|
certificateContent: 'generic-content',
|
|
certificatePassword: 'generic-password',
|
|
pfxContent: 'specific-pfx-content',
|
|
pfxPassword: 'specific-pfx-password',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.pfxContent).toBe('specific-pfx-content');
|
|
expect(result.signatureAuth.pfxPassword).toBe('specific-pfx-password');
|
|
});
|
|
|
|
it('should auto-detect PFX type from .pfx filename', () => {
|
|
const signatureAuth = {
|
|
certificateContent: 'MIIKXAIBAzCCChgGCSqGSIb3...',
|
|
certificatePassword: 'testPassword',
|
|
certificateFilename: 'mycert.pfx',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('pfx');
|
|
expect(result.signatureAuth.pfxContent).toBe('MIIKXAIBAzCCChgGCSqGSIb3...');
|
|
expect(result.signatureAuth.pfxPassword).toBe('testPassword');
|
|
});
|
|
|
|
it('should auto-detect PFX type from .p12 filename', () => {
|
|
const signatureAuth = {
|
|
certificateContent: 'MIIKXAIBAzCCChgGCSqGSIb3...',
|
|
certificatePassword: 'testPassword',
|
|
certificateFilename: 'mycert.p12',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('pfx');
|
|
expect(result.signatureAuth.pfxContent).toBe('MIIKXAIBAzCCChgGCSqGSIb3...');
|
|
expect(result.signatureAuth.pfxPassword).toBe('testPassword');
|
|
});
|
|
});
|
|
|
|
describe('JKS certificate type conversion', () => {
|
|
it('should map generic fields to JKS-specific fields', () => {
|
|
const signatureAuth = {
|
|
type: 'jks' as const,
|
|
certificateContent: 'UEsDBBQAAAAIAF1nYXRld2F5...', // base64 encoded JKS
|
|
certificatePassword: 'keystorePassword',
|
|
certificateFilename: 'keystore.jks',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth).toBeDefined();
|
|
expect(result.signatureAuth.type).toBe('jks');
|
|
expect(result.signatureAuth.keystoreContent).toBe('UEsDBBQAAAAIAF1nYXRld2F5...');
|
|
expect(result.signatureAuth.keystorePassword).toBe('keystorePassword');
|
|
expect(result.signatureAuth.certificateFilename).toBe('keystore.jks');
|
|
});
|
|
|
|
it('should preserve existing JKS-specific fields over generic fields', () => {
|
|
const signatureAuth = {
|
|
type: 'jks' as const,
|
|
certificateContent: 'generic-content',
|
|
certificatePassword: 'generic-password',
|
|
keystoreContent: 'specific-jks-content',
|
|
keystorePassword: 'specific-jks-password',
|
|
keyAlias: 'myalias',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.keystoreContent).toBe('specific-jks-content');
|
|
expect(result.signatureAuth.keystorePassword).toBe('specific-jks-password');
|
|
expect(result.signatureAuth.keyAlias).toBe('myalias');
|
|
});
|
|
|
|
it('should auto-detect JKS type from .jks filename', () => {
|
|
const signatureAuth = {
|
|
certificateContent: 'UEsDBBQAAAAIAF1nYXRld2F5...',
|
|
certificatePassword: 'keystorePassword',
|
|
certificateFilename: 'keystore.jks',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('jks');
|
|
expect(result.signatureAuth.keystoreContent).toBe('UEsDBBQAAAAIAF1nYXRld2F5...');
|
|
expect(result.signatureAuth.keystorePassword).toBe('keystorePassword');
|
|
});
|
|
});
|
|
|
|
describe('PEM certificate type conversion', () => {
|
|
it('should map generic fields to PEM-specific fields', () => {
|
|
const pemContent = '-----BEGIN PRIVATE KEY-----\nMIIEvQ...\n-----END PRIVATE KEY-----';
|
|
const base64PemContent = Buffer.from(pemContent).toString('base64');
|
|
|
|
const signatureAuth = {
|
|
type: 'pem' as const,
|
|
certificateContent: base64PemContent,
|
|
certificatePassword: 'optionalPassword',
|
|
certificateFilename: 'private.pem',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth).toBeDefined();
|
|
expect(result.signatureAuth.type).toBe('pem');
|
|
expect(result.signatureAuth.privateKey).toBe(pemContent);
|
|
expect(result.signatureAuth.certificatePassword).toBe('optionalPassword');
|
|
expect(result.signatureAuth.certificateFilename).toBe('private.pem');
|
|
});
|
|
|
|
it('should preserve existing PEM-specific fields over generic fields', () => {
|
|
const signatureAuth = {
|
|
type: 'pem' as const,
|
|
certificateContent: Buffer.from('generic-key').toString('base64'),
|
|
privateKey: 'specific-private-key',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.privateKey).toBe('specific-private-key');
|
|
});
|
|
|
|
it('should auto-detect PEM type from .pem filename', () => {
|
|
const pemContent = '-----BEGIN PRIVATE KEY-----\nMIIEvQ...\n-----END PRIVATE KEY-----';
|
|
const base64PemContent = Buffer.from(pemContent).toString('base64');
|
|
|
|
const signatureAuth = {
|
|
certificateContent: base64PemContent,
|
|
certificateFilename: 'private.pem',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('pem');
|
|
expect(result.signatureAuth.privateKey).toBe(pemContent);
|
|
});
|
|
|
|
it('should auto-detect PEM type from .key filename', () => {
|
|
const pemContent = '-----BEGIN PRIVATE KEY-----\nMIIEvQ...\n-----END PRIVATE KEY-----';
|
|
const base64PemContent = Buffer.from(pemContent).toString('base64');
|
|
|
|
const signatureAuth = {
|
|
certificateContent: base64PemContent,
|
|
certificateFilename: 'private.key',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('pem');
|
|
expect(result.signatureAuth.privateKey).toBe(pemContent);
|
|
});
|
|
});
|
|
|
|
describe('Edge cases and error handling', () => {
|
|
it('should handle case-insensitive filename extensions', () => {
|
|
const signatureAuth = {
|
|
certificateContent: 'MIIKXAIBAzCCChgGCSqGSIb3...',
|
|
certificatePassword: 'testPassword',
|
|
certificateFilename: 'CERTIFICATE.PFX',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('pfx');
|
|
expect(result.signatureAuth.pfxContent).toBe('MIIKXAIBAzCCChgGCSqGSIb3...');
|
|
});
|
|
|
|
it('should handle mixed case extensions', () => {
|
|
const signatureAuth = {
|
|
certificateContent: 'UEsDBBQAAAAIAF1nYXRld2F5...',
|
|
certificatePassword: 'password',
|
|
certificateFilename: 'keystore.JkS',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('jks');
|
|
expect(result.signatureAuth.keystoreContent).toBe('UEsDBBQAAAAIAF1nYXRld2F5...');
|
|
});
|
|
|
|
it('should throw error for unknown certificate type when explicitly specified', () => {
|
|
const signatureAuth = {
|
|
type: 'unknown' as any,
|
|
certificateContent: 'some-content',
|
|
certificatePassword: 'password',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
expect(() => {
|
|
HttpProviderConfigSchema.parse({ signatureAuth });
|
|
}).toThrow();
|
|
});
|
|
|
|
it('should throw error when type cannot be detected from filename', () => {
|
|
const signatureAuth = {
|
|
certificateContent: 'some-content',
|
|
certificatePassword: 'password',
|
|
certificateFilename: 'certificate.unknown',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
expect(() => {
|
|
HttpProviderConfigSchema.parse({ signatureAuth });
|
|
}).toThrow(/Cannot determine certificate type from filename/);
|
|
});
|
|
|
|
it('should handle configuration without generic fields', () => {
|
|
const signatureAuth = {
|
|
type: 'pfx' as const,
|
|
pfxPath: '/path/to/cert.pfx',
|
|
pfxPassword: 'password',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth).toBeDefined();
|
|
expect(result.signatureAuth.type).toBe('pfx');
|
|
expect(result.signatureAuth.pfxPath).toBe('/path/to/cert.pfx');
|
|
expect(result.signatureAuth.pfxPassword).toBe('password');
|
|
expect(result.signatureAuth.certificateContent).toBeUndefined();
|
|
});
|
|
|
|
it('should pass through configuration when no generic fields are present', () => {
|
|
const signatureAuth = {
|
|
type: 'jks' as const,
|
|
keystorePath: '/path/to/keystore.jks',
|
|
keystorePassword: 'password',
|
|
keyAlias: 'mykey',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth).toBeDefined();
|
|
expect(result.signatureAuth.type).toBe('jks');
|
|
expect(result.signatureAuth.keystorePath).toBe('/path/to/keystore.jks');
|
|
expect(result.signatureAuth.keystorePassword).toBe('password');
|
|
expect(result.signatureAuth.keyAlias).toBe('mykey');
|
|
});
|
|
|
|
it('should reject null signatureAuth', () => {
|
|
// Zod schema doesn't accept null values in the union
|
|
expect(() => {
|
|
HttpProviderConfigSchema.parse({ signatureAuth: null });
|
|
}).toThrow();
|
|
});
|
|
|
|
it('should handle undefined signatureAuth gracefully', () => {
|
|
const result = HttpProviderConfigSchema.parse({});
|
|
|
|
expect(result.signatureAuth).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('Legacy compatibility', () => {
|
|
it('should handle legacy PEM configuration without type field', () => {
|
|
const signatureAuth = {
|
|
privateKeyPath: '/path/to/private.key',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth).toBeDefined();
|
|
expect(result.signatureAuth.privateKeyPath).toBe('/path/to/private.key');
|
|
});
|
|
|
|
it('should handle legacy JKS configuration without type field', () => {
|
|
const signatureAuth = {
|
|
keystorePath: '/path/to/keystore.jks',
|
|
keystorePassword: 'password',
|
|
keyAlias: 'mykey',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth).toBeDefined();
|
|
expect(result.signatureAuth.keystorePath).toBe('/path/to/keystore.jks');
|
|
expect(result.signatureAuth.keystorePassword).toBe('password');
|
|
expect(result.signatureAuth.keyAlias).toBe('mykey');
|
|
});
|
|
|
|
it('should handle legacy PFX configuration without type field', () => {
|
|
const signatureAuth = {
|
|
pfxPath: '/path/to/cert.pfx',
|
|
pfxPassword: 'password',
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth).toBeDefined();
|
|
expect(result.signatureAuth.pfxPath).toBe('/path/to/cert.pfx');
|
|
expect(result.signatureAuth.pfxPassword).toBe('password');
|
|
});
|
|
});
|
|
|
|
describe('Mixed scenarios', () => {
|
|
it('should handle generic fields with explicit type override', () => {
|
|
const signatureAuth = {
|
|
type: 'jks' as const, // Explicitly set to JKS
|
|
certificateContent: 'UEsDBBQAAAAIAF1nYXRld2F5...',
|
|
certificatePassword: 'password',
|
|
certificateFilename: 'certificate.pfx', // Filename suggests PFX but type overrides
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('jks'); // Type should be respected
|
|
expect(result.signatureAuth.keystoreContent).toBe('UEsDBBQAAAAIAF1nYXRld2F5...');
|
|
expect(result.signatureAuth.keystorePassword).toBe('password');
|
|
});
|
|
|
|
it('should handle partial generic fields', () => {
|
|
const signatureAuth = {
|
|
type: 'pfx' as const,
|
|
certificateContent: 'MIIKXAIBAzCCChgGCSqGSIb3...',
|
|
// No certificatePassword provided
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('pfx');
|
|
expect(result.signatureAuth.pfxContent).toBe('MIIKXAIBAzCCChgGCSqGSIb3...');
|
|
expect(result.signatureAuth.pfxPassword).toBeUndefined();
|
|
});
|
|
|
|
it('should correctly merge generic and specific fields', () => {
|
|
const signatureAuth = {
|
|
type: 'jks' as const,
|
|
certificateContent: 'UEsDBBQAAAAIAF1nYXRld2F5...',
|
|
certificatePassword: 'password',
|
|
keyAlias: 'specificAlias', // JKS-specific field
|
|
signatureDataTemplate: '{{signatureTimestamp}}',
|
|
signatureAlgorithm: 'SHA256',
|
|
signatureValidityMs: 300000,
|
|
};
|
|
|
|
const result = HttpProviderConfigSchema.parse({ signatureAuth });
|
|
|
|
expect(result.signatureAuth.type).toBe('jks');
|
|
expect(result.signatureAuth.keystoreContent).toBe('UEsDBBQAAAAIAF1nYXRld2F5...');
|
|
expect(result.signatureAuth.keystorePassword).toBe('password');
|
|
expect(result.signatureAuth.keyAlias).toBe('specificAlias');
|
|
});
|
|
});
|
|
});
|
|
});
|