0d3cb498a3
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
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
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) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
155 lines
5.2 KiB
TypeScript
155 lines
5.2 KiB
TypeScript
import { beforeEach, describe, expect, it } from 'vitest';
|
|
import {
|
|
DEFAULT_OPENAI_ORIGINATOR,
|
|
OPENAI_ORIGINATOR_HEADER,
|
|
OpenAiGenericProvider,
|
|
} from '../../../src/providers/openai/index';
|
|
import { mockProcessEnv } from '../../util/utils';
|
|
|
|
describe('OpenAI Provider', () => {
|
|
describe('OpenAiGenericProvider', () => {
|
|
const provider = new OpenAiGenericProvider('test-model', {
|
|
config: {
|
|
apiKey: 'test-key',
|
|
organization: 'test-org',
|
|
},
|
|
});
|
|
|
|
beforeEach(() => {
|
|
mockProcessEnv({}, { clear: true });
|
|
});
|
|
|
|
it('should generate correct API URL', () => {
|
|
expect(provider.getApiUrl()).toBe('https://api.openai.com/v1');
|
|
});
|
|
|
|
it('should use custom API host', () => {
|
|
const customProvider = new OpenAiGenericProvider('test-model', {
|
|
config: { apiHost: 'custom.openai.com' },
|
|
});
|
|
expect(customProvider.getApiUrl()).toBe('https://custom.openai.com/v1');
|
|
});
|
|
|
|
it('should use custom API base URL', () => {
|
|
const customProvider = new OpenAiGenericProvider('test-model', {
|
|
config: { apiBaseUrl: 'https://custom.api.com/openai' },
|
|
});
|
|
expect(customProvider.getApiUrl()).toBe('https://custom.api.com/openai');
|
|
});
|
|
|
|
it('should get organization', () => {
|
|
expect(provider.getOrganization()).toBe('test-org');
|
|
});
|
|
|
|
it('should get organization from env', () => {
|
|
mockProcessEnv({ OPENAI_ORGANIZATION: 'env-org' });
|
|
const envProvider = new OpenAiGenericProvider('test-model');
|
|
expect(envProvider.getOrganization()).toBe('env-org');
|
|
});
|
|
|
|
it('should include the default originator header and allow explicit overrides', () => {
|
|
expect(provider.getOpenAiRequestHeaders()).toEqual({
|
|
[OPENAI_ORIGINATOR_HEADER]: DEFAULT_OPENAI_ORIGINATOR,
|
|
'OpenAI-Organization': 'test-org',
|
|
});
|
|
|
|
expect(
|
|
provider.getOpenAiRequestHeaders({
|
|
[OPENAI_ORIGINATOR_HEADER]: 'custom-originator',
|
|
}),
|
|
).toMatchObject({
|
|
[OPENAI_ORIGINATOR_HEADER]: 'custom-originator',
|
|
});
|
|
});
|
|
|
|
// These two cases assert the FULL header object with toEqual on purpose: the bug
|
|
// being guarded is a *duplicate* case-variant header sneaking into the output, so
|
|
// the test must fail if any extra key (e.g. a second canonical-case header) appears.
|
|
// toMatchObject would allow such an extra key through and miss the regression.
|
|
it('should treat originator overrides case-insensitively', () => {
|
|
expect(
|
|
provider.getOpenAiRequestHeaders({
|
|
'x-openai-originator': 'custom-originator',
|
|
}),
|
|
).toEqual({
|
|
'x-openai-originator': 'custom-originator',
|
|
'OpenAI-Organization': 'test-org',
|
|
});
|
|
});
|
|
|
|
it('should treat organization header overrides case-insensitively', () => {
|
|
expect(
|
|
provider.getOpenAiRequestHeaders({
|
|
'openai-organization': 'custom-org',
|
|
}),
|
|
).toEqual({
|
|
'X-OpenAI-Originator': 'promptfoo',
|
|
'openai-organization': 'custom-org',
|
|
});
|
|
});
|
|
|
|
it('should not attribute compatible endpoints unless explicitly configured', () => {
|
|
const customProvider = new OpenAiGenericProvider('test-model', {
|
|
config: { apiBaseUrl: 'https://custom.api.com/openai' },
|
|
});
|
|
|
|
expect(customProvider.getOpenAiRequestHeaders()).not.toHaveProperty(OPENAI_ORIGINATOR_HEADER);
|
|
expect(
|
|
customProvider.getOpenAiRequestHeaders({
|
|
[OPENAI_ORIGINATOR_HEADER]: 'custom-originator',
|
|
}),
|
|
).toMatchObject({
|
|
[OPENAI_ORIGINATOR_HEADER]: 'custom-originator',
|
|
});
|
|
});
|
|
|
|
it('should get API key', () => {
|
|
expect(provider.getApiKey()).toBe('test-key');
|
|
});
|
|
|
|
it('should get API key from env', () => {
|
|
mockProcessEnv({ OPENAI_API_KEY: 'env-key' });
|
|
const envProvider = new OpenAiGenericProvider('test-model');
|
|
expect(envProvider.getApiKey()).toBe('env-key');
|
|
});
|
|
|
|
it('should get API key from custom env var', () => {
|
|
mockProcessEnv({ CUSTOM_API_KEY: 'custom-key' });
|
|
const customProvider = new OpenAiGenericProvider('test-model', {
|
|
config: { apiKeyEnvar: 'CUSTOM_API_KEY' },
|
|
});
|
|
expect(customProvider.getApiKey()).toBe('custom-key');
|
|
});
|
|
|
|
it('should generate correct ID', () => {
|
|
expect(provider.id()).toBe('openai:test-model');
|
|
});
|
|
|
|
it('should generate custom ID with API host', () => {
|
|
const customProvider = new OpenAiGenericProvider('test-model', {
|
|
config: { apiHost: 'custom.openai.com' },
|
|
});
|
|
expect(customProvider.id()).toBe('test-model');
|
|
});
|
|
|
|
it('should have correct string representation', () => {
|
|
expect(provider.toString()).toBe('[OpenAI Provider test-model]');
|
|
});
|
|
|
|
it('should require API key by default', () => {
|
|
expect(provider.requiresApiKey()).toBe(true);
|
|
});
|
|
|
|
it('should allow disabling API key requirement', () => {
|
|
const customProvider = new OpenAiGenericProvider('test-model', {
|
|
config: { apiKeyRequired: false },
|
|
});
|
|
expect(customProvider.requiresApiKey()).toBe(false);
|
|
});
|
|
|
|
it('should throw not implemented for callApi', async () => {
|
|
await expect(provider.callApi('test prompt')).rejects.toThrow('Not implemented');
|
|
});
|
|
});
|
|
});
|