Files
thu-maic--openmaic/tests/store/settings-validation.test.ts
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

291 lines
9.5 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
isProviderUsable,
validateProvider,
validateModel,
resolveSelectedModel,
hasUsableLLMProvider,
isLLMProviderConfigured,
type ProviderCfgLike,
} from '@/lib/store/settings-validation';
describe('isProviderUsable', () => {
it('returns true when provider has client API key', () => {
expect(isProviderUsable({ apiKey: 'sk-xxx' })).toBe(true);
});
it('returns true when provider is server-configured', () => {
expect(isProviderUsable({ isServerConfigured: true })).toBe(true);
});
it('returns true when provider has both client key and server config', () => {
expect(isProviderUsable({ apiKey: 'sk-xxx', isServerConfigured: true })).toBe(true);
});
it('returns false when has neither client key nor server config', () => {
expect(isProviderUsable({ apiKey: '', isServerConfigured: false })).toBe(false);
});
it('returns false when apiKey is empty and not server-configured', () => {
expect(isProviderUsable({ apiKey: '' })).toBe(false);
});
it('returns false for undefined config', () => {
expect(isProviderUsable(undefined)).toBe(false);
});
it('returns false for empty object', () => {
expect(isProviderUsable({})).toBe(false);
});
it('returns true for keyless provider with explicit baseUrl', () => {
expect(isProviderUsable({ requiresApiKey: false, baseUrl: 'http://localhost:11434/v1' })).toBe(
true,
);
});
it('returns false for keyless provider without baseUrl', () => {
expect(isProviderUsable({ requiresApiKey: false })).toBe(false);
});
it('returns false for keyless provider with empty baseUrl', () => {
expect(isProviderUsable({ requiresApiKey: false, baseUrl: '' })).toBe(false);
});
it('returns true for keyless provider when server-configured', () => {
expect(isProviderUsable({ requiresApiKey: false, isServerConfigured: true })).toBe(true);
});
it('returns false for keyless provider with apiKey but no baseUrl', () => {
expect(isProviderUsable({ requiresApiKey: false, apiKey: 'some-key' })).toBe(false);
});
it('returns false for a server-disabled provider even with a client API key (#665)', () => {
expect(isProviderUsable({ apiKey: 'sk-xxx', serverDisabled: true })).toBe(false);
expect(isProviderUsable({ isServerConfigured: true, serverDisabled: true })).toBe(false);
});
});
describe('validateProvider', () => {
const cfg = (overrides: Partial<ProviderCfgLike> = {}): ProviderCfgLike => ({
apiKey: '',
isServerConfigured: false,
...overrides,
});
it('keeps current provider when it is server-configured', () => {
const configMap = {
'provider-a': cfg({ isServerConfigured: true }),
'provider-b': cfg(),
};
expect(validateProvider('provider-a', configMap, ['provider-b'])).toBe('provider-a');
});
it('keeps current provider when it has client API key', () => {
const configMap = {
'provider-a': cfg({ apiKey: 'sk-xxx' }),
'provider-b': cfg(),
};
expect(validateProvider('provider-a', configMap, ['provider-b'])).toBe('provider-a');
});
it('falls back to first usable provider when current is unusable', () => {
const configMap = {
'provider-a': cfg(),
'provider-b': cfg({ isServerConfigured: true }),
};
expect(validateProvider('provider-a', configMap, ['provider-b'])).toBe('provider-b');
});
it('re-points away from a server-disabled current provider that has a client key (#665)', () => {
const configMap = {
'provider-a': cfg({ apiKey: 'sk-xxx', serverDisabled: true }),
'provider-b': cfg({ isServerConfigured: true }),
};
expect(validateProvider('provider-a', configMap, ['provider-b'])).toBe('provider-b');
});
it('returns empty string when no fallback is usable and no default', () => {
const configMap = {
'provider-a': cfg(),
'provider-b': cfg(),
};
expect(validateProvider('provider-a', configMap, ['provider-b'])).toBe('');
});
it('falls back to defaultId when no fallback is usable', () => {
const configMap = {
'provider-a': cfg(),
'provider-b': cfg(),
};
expect(validateProvider('provider-a', configMap, ['provider-b'], 'browser-native')).toBe(
'browser-native',
);
});
it('prefers usable fallback over defaultId', () => {
const configMap = {
'provider-a': cfg(),
'provider-b': cfg({ isServerConfigured: true }),
};
expect(validateProvider('provider-a', configMap, ['provider-b'], 'browser-native')).toBe(
'provider-b',
);
});
it('returns current id unchanged when it is empty', () => {
const configMap = { 'provider-a': cfg({ isServerConfigured: true }) };
expect(validateProvider('', configMap, ['provider-a'])).toBe('');
});
});
describe('validateModel', () => {
it('keeps model when still in available list', () => {
expect(validateModel('gpt-4o', [{ id: 'gpt-4o' }, { id: 'gpt-4o-mini' }])).toBe('gpt-4o');
});
it('falls back to first model when current is not in list', () => {
expect(validateModel('gpt-4-turbo', [{ id: 'gpt-4o' }, { id: 'gpt-4o-mini' }])).toBe('gpt-4o');
});
it('returns empty string when list is empty', () => {
expect(validateModel('gpt-4o', [])).toBe('');
});
it('returns current id unchanged when it is empty', () => {
expect(validateModel('', [{ id: 'gpt-4o' }])).toBe('');
});
});
describe('resolveSelectedModel', () => {
it('keeps model when still in available list', () => {
expect(resolveSelectedModel('gpt-4o', [{ id: 'gpt-4o' }, { id: 'gpt-4o-mini' }])).toBe(
'gpt-4o',
);
});
it('falls back to first model when current is not in list', () => {
expect(resolveSelectedModel('gpt-4-turbo', [{ id: 'gpt-4o' }, { id: 'gpt-4o-mini' }])).toBe(
'gpt-4o',
);
});
it('falls back to first model when current is empty (the invariant: usable provider ⇒ concrete model)', () => {
expect(resolveSelectedModel('', [{ id: 'gpt-4o' }, { id: 'gpt-4o-mini' }])).toBe('gpt-4o');
});
it('returns empty string only when the model list is empty', () => {
expect(resolveSelectedModel('gpt-4o', [])).toBe('');
expect(resolveSelectedModel('', [])).toBe('');
});
it('never yields empty when the provider has at least one model', () => {
for (const current of ['', 'unknown', 'glm-4']) {
expect(resolveSelectedModel(current, [{ id: 'glm-4' }])).not.toBe('');
}
});
});
describe('hasUsableLLMProvider', () => {
const ok = { apiKey: 'sk-x', models: [{ id: 'm1' }], defaultBaseUrl: 'https://x' };
it('returns true when a provider has credentials, ≥1 model, and an endpoint', () => {
expect(hasUsableLLMProvider({ openai: ok })).toBe(true);
});
it('returns true for a server-configured provider without a client key', () => {
expect(
hasUsableLLMProvider({
openai: { isServerConfigured: true, models: [{ id: 'm1' }] },
}),
).toBe(true);
});
it('returns false when the only provider has no models', () => {
expect(hasUsableLLMProvider({ openai: { ...ok, models: [] } })).toBe(false);
});
it('returns false when the only provider lacks credentials and requires a key', () => {
expect(
hasUsableLLMProvider({
openai: { requiresApiKey: true, apiKey: '', models: [{ id: 'm1' }], defaultBaseUrl: 'x' },
}),
).toBe(false);
});
it('returns false for an empty or nullish config', () => {
expect(hasUsableLLMProvider({})).toBe(false);
expect(hasUsableLLMProvider(undefined)).toBe(false);
expect(hasUsableLLMProvider(null)).toBe(false);
});
it('treats a keyless provider with only a registry defaultBaseUrl as NOT configured', () => {
// ollama/lemonade ship requiresApiKey:false + a defaultBaseUrl but an
// empty user baseUrl by default — must NOT count as usable, otherwise
// the page gate is true while reconcile never selects it (#580 keyless).
expect(
hasUsableLLMProvider({
ollama: {
requiresApiKey: false,
apiKey: '',
baseUrl: '',
models: [{ id: 'llama3.3' }],
defaultBaseUrl: 'http://localhost:11434/v1',
},
}),
).toBe(false);
});
it('treats a keyless provider as configured once the user sets an explicit baseUrl', () => {
expect(
hasUsableLLMProvider({
ollama: {
requiresApiKey: false,
apiKey: '',
baseUrl: 'http://my-ollama:11434/v1',
models: [{ id: 'llama3.3' }],
defaultBaseUrl: 'http://localhost:11434/v1',
},
}),
).toBe(true);
});
it('treats a keyless provider as configured when server-configured', () => {
expect(
hasUsableLLMProvider({
ollama: {
requiresApiKey: false,
apiKey: '',
baseUrl: '',
isServerConfigured: true,
models: [{ id: 'llama3.3' }],
},
}),
).toBe(true);
});
});
describe('isLLMProviderConfigured', () => {
// Regression: a freshly-applied token-plan provider may have no `models`
// field yet (probe populates it later). The validator must not throw.
it('returns false (not throw) when models is undefined', () => {
expect(() =>
isLLMProviderConfigured({ apiKey: 'sk-x', baseUrl: 'https://b/v1' } as never),
).not.toThrow();
expect(isLLMProviderConfigured({ apiKey: 'sk-x', baseUrl: 'https://b/v1' } as never)).toBe(
false,
);
});
it('returns true when key + baseUrl + ≥1 model', () => {
expect(
isLLMProviderConfigured({
apiKey: 'sk-x',
baseUrl: 'https://b/v1',
requiresApiKey: true,
models: [{ id: 'm1' }],
} as never),
).toBe(true);
});
});