Files
promptfoo--promptfoo/test/providers/http-generic-certificate.test.ts
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

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');
});
});
});
});