070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
311 lines
9.2 KiB
TypeScript
311 lines
9.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
blockingByokDraftFields,
|
|
cleanByokApiKey,
|
|
normalizeByokBaseUrl,
|
|
resolveByokModelPreference,
|
|
validateByokDraft,
|
|
} from '../../src/components/byok/validation';
|
|
|
|
describe('BYOK draft validation', () => {
|
|
it('cleans pasted API keys without changing the stored value itself', () => {
|
|
expect(cleanByokApiKey(' \u200Bsk-ant-test\n\t')).toBe('sk-ant-test');
|
|
});
|
|
|
|
it('normalizes base URL drafts for future field commits', () => {
|
|
expect(normalizeByokBaseUrl('api.openai.com/', 'openai')).toEqual({
|
|
value: 'https://api.openai.com/v1',
|
|
changed: true,
|
|
addedProtocol: true,
|
|
addedOpenAiVersionPath: true,
|
|
});
|
|
expect(normalizeByokBaseUrl('https://api.anthropic.com/', 'anthropic')).toEqual({
|
|
value: 'https://api.anthropic.com',
|
|
changed: true,
|
|
addedProtocol: false,
|
|
addedOpenAiVersionPath: false,
|
|
});
|
|
});
|
|
|
|
it('blocks missing required fields before a provider connection test', () => {
|
|
const validation = validateByokDraft('anthropic', {
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
model: '',
|
|
});
|
|
|
|
expect(validation.ok).toBe(false);
|
|
expect(blockingByokDraftFields(validation)).toEqual([
|
|
'api_key',
|
|
'base_url',
|
|
'model',
|
|
]);
|
|
});
|
|
|
|
it('detects obvious API keys pasted into the wrong first-party tab', () => {
|
|
const anthropic = validateByokDraft('anthropic', {
|
|
apiKey: 'sk-openai-key',
|
|
baseUrl: 'https://api.anthropic.com',
|
|
model: 'claude-sonnet-4-5',
|
|
});
|
|
expect(anthropic.ok).toBe(false);
|
|
expect(anthropic.issues).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
field: 'api_key',
|
|
level: 'error',
|
|
code: 'api_key_wrong_protocol',
|
|
detectedProtocol: 'openai',
|
|
}),
|
|
]),
|
|
);
|
|
|
|
const openai = validateByokDraft('openai', {
|
|
apiKey: 'sk-ant-key',
|
|
baseUrl: 'https://api.openai.com/v1',
|
|
model: 'gpt-4o',
|
|
});
|
|
expect(openai.ok).toBe(false);
|
|
expect(openai.issues).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
field: 'api_key',
|
|
level: 'error',
|
|
code: 'api_key_wrong_protocol',
|
|
detectedProtocol: 'anthropic',
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('does not over-block third-party compatible gateways', () => {
|
|
expect(
|
|
validateByokDraft('anthropic', {
|
|
apiKey: 'sk-deepseek-key',
|
|
baseUrl: 'https://api.deepseek.com/anthropic',
|
|
model: 'deepseek-chat',
|
|
}).ok,
|
|
).toBe(true);
|
|
expect(
|
|
validateByokDraft('openai', {
|
|
apiKey: 'minimax-enterprise-key',
|
|
baseUrl: 'https://api.minimax.io/v1',
|
|
model: 'MiniMax-M1',
|
|
}).ok,
|
|
).toBe(true);
|
|
expect(
|
|
validateByokDraft('google', {
|
|
apiKey: 'enterprise-gemini-key',
|
|
baseUrl: 'https://gemini.internal.example.com/v1beta',
|
|
model: 'gemini-2.0-flash',
|
|
}).ok,
|
|
).toBe(true);
|
|
});
|
|
|
|
it('treats a syntactically-valid internal-IP base URL as non-blocking, deferring to the daemon (#3225)', () => {
|
|
// The Test / model-fetch actions gate on blockingByokDraftIssues; an
|
|
// internal endpoint must not be blocked here or the operator can never
|
|
// reach the daemon's OD_ALLOWED_INTERNAL_HOSTS decision.
|
|
const result = validateByokDraft('openai', {
|
|
apiKey: 'sk-internal',
|
|
baseUrl: 'http://10.0.0.5:4000/v1',
|
|
model: 'gpt-4o',
|
|
});
|
|
expect(result.issues).not.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ code: 'base_url_invalid' }),
|
|
]),
|
|
);
|
|
expect(result.ok).toBe(true);
|
|
});
|
|
|
|
it('still flags a genuinely malformed base URL as base_url_invalid', () => {
|
|
const result = validateByokDraft('openai', {
|
|
apiKey: 'sk-internal',
|
|
baseUrl: 'ftp://api.example.com',
|
|
model: 'gpt-4o',
|
|
});
|
|
expect(result.issues).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ code: 'base_url_invalid' }),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('can enforce first-party key shape when the first-party Base URL looks mistyped', () => {
|
|
const withoutHint = validateByokDraft('anthropic', {
|
|
apiKey: 'sk-openai-key',
|
|
baseUrl: 'https://api.anthropic.comsssss',
|
|
model: 'claude-sonnet-4-5',
|
|
});
|
|
expect(withoutHint.issues).not.toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
field: 'api_key',
|
|
code: 'api_key_wrong_protocol',
|
|
}),
|
|
]),
|
|
);
|
|
|
|
const withHint = validateByokDraft(
|
|
'anthropic',
|
|
{
|
|
apiKey: 'sk-openai-key',
|
|
baseUrl: 'https://api.anthropic.comsssss',
|
|
model: 'claude-sonnet-4-5',
|
|
},
|
|
{ keyValidationBaseUrl: 'https://api.anthropic.com' },
|
|
);
|
|
expect(withHint.ok).toBe(false);
|
|
expect(withHint.issues).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
field: 'api_key',
|
|
level: 'error',
|
|
code: 'api_key_wrong_protocol',
|
|
detectedProtocol: 'openai',
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('rejects Anthropic/OpenAI base URLs on the Google Gemini tab', () => {
|
|
const anthropicHost = validateByokDraft('google', {
|
|
apiKey: 'AQ.TestKeyForUnitTests01234567890123456789012',
|
|
baseUrl: 'https://api.anthropic.com',
|
|
model: 'gemini-2.0-flash',
|
|
});
|
|
expect(anthropicHost.ok).toBe(false);
|
|
expect(anthropicHost.issues).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
field: 'base_url',
|
|
code: 'base_url_invalid',
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('accepts Google Gemini AQ. service-account-bound keys on the first-party endpoint', () => {
|
|
expect(
|
|
validateByokDraft('google', {
|
|
apiKey: 'AQ.TestKeyForUnitTests01234567890123456789012',
|
|
baseUrl: 'https://generativelanguage.googleapis.com',
|
|
model: 'gemini-2.0-flash',
|
|
}).ok,
|
|
).toBe(true);
|
|
expect(
|
|
validateByokDraft('google', {
|
|
apiKey: 'AIzaSyD-Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1',
|
|
baseUrl: 'https://generativelanguage.googleapis.com',
|
|
model: 'gemini-2.0-flash',
|
|
}).ok,
|
|
).toBe(true);
|
|
});
|
|
|
|
it('still enforces key shape on first-party OpenAI and Google endpoints', () => {
|
|
const openai = validateByokDraft('openai', {
|
|
apiKey: 'enterprise-openai-key',
|
|
baseUrl: 'https://api.openai.com/v1',
|
|
model: 'gpt-4o',
|
|
});
|
|
expect(openai.ok).toBe(false);
|
|
expect(openai.issues).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
field: 'api_key',
|
|
level: 'error',
|
|
code: 'api_key_malformed',
|
|
}),
|
|
]),
|
|
);
|
|
|
|
const google = validateByokDraft('google', {
|
|
apiKey: 'sk-openai-key',
|
|
baseUrl: 'https://generativelanguage.googleapis.com',
|
|
model: 'gemini-2.0-flash',
|
|
});
|
|
expect(google.ok).toBe(false);
|
|
expect(google.issues).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
field: 'api_key',
|
|
level: 'error',
|
|
code: 'api_key_wrong_protocol',
|
|
detectedProtocol: 'openai',
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('blocks invalid base URLs and can validate model-fetch drafts without a model', () => {
|
|
const invalid = validateByokDraft('openai', {
|
|
apiKey: 'sk-openai-key',
|
|
baseUrl: 'api.openai.com/v1',
|
|
model: 'gpt-4o',
|
|
});
|
|
expect(invalid.ok).toBe(false);
|
|
expect(blockingByokDraftFields(invalid)).toEqual(['base_url']);
|
|
|
|
const modelFetchDraft = validateByokDraft(
|
|
'openai',
|
|
{
|
|
apiKey: 'sk-openai-key',
|
|
baseUrl: 'https://api.openai.com/v1',
|
|
model: '',
|
|
},
|
|
{ requireModel: false },
|
|
);
|
|
expect(modelFetchDraft.ok).toBe(true);
|
|
});
|
|
|
|
it('defines the model preference order for later model-fetch PRs', () => {
|
|
expect(
|
|
resolveByokModelPreference({
|
|
currentModel: 'custom-model',
|
|
accountModels: [{ id: 'account-model', label: 'Account model' }],
|
|
providerDefaultModel: 'provider-model',
|
|
}),
|
|
).toEqual({ model: 'custom-model', source: 'explicit' });
|
|
|
|
expect(
|
|
resolveByokModelPreference({
|
|
currentModel: '',
|
|
accountModels: [{ id: 'account-model', label: 'Account model' }],
|
|
providerDefaultModel: 'provider-model',
|
|
}),
|
|
).toEqual({ model: 'account-model', source: 'account' });
|
|
|
|
expect(
|
|
resolveByokModelPreference({
|
|
currentModel: '',
|
|
accountModels: [],
|
|
providerDefaultModel: 'provider-model',
|
|
}),
|
|
).toEqual({ model: 'provider-model', source: 'provider_default' });
|
|
});
|
|
|
|
it('skips disabled provider models when choosing an automatic preference', () => {
|
|
expect(
|
|
resolveByokModelPreference({
|
|
currentModel: '',
|
|
accountModels: [
|
|
{ id: 'disabled-model', label: 'Disabled model', enabled: false },
|
|
{ id: 'enabled-model', label: 'Enabled model' },
|
|
],
|
|
providerDefaultModel: 'provider-model',
|
|
}),
|
|
).toEqual({ model: 'enabled-model', source: 'account' });
|
|
|
|
expect(
|
|
resolveByokModelPreference({
|
|
currentModel: '',
|
|
accountModels: [
|
|
{ id: 'disabled-default', label: 'Disabled default', enabled: false },
|
|
],
|
|
providerDefaultModel: 'disabled-default',
|
|
}),
|
|
).toEqual({ model: '', source: 'empty' });
|
|
});
|
|
});
|