* refactor(settings): read stored preferences with a Valibot schema Each field falls back to its default on its own, so one bad value keeps the rest, as before. Tests pin the behavior; they pass against the previous implementation too. * refactor(mcp): read stored MCP connections with a Valibot schema A connection schema checks the ID, trimmed name, transport, and URL rule; a bearer token is kept only for the connection's own credential. A new test covers the credential check and normalization, and passes against the previous implementation. * refactor(clipboard): read Figma's image batch response with a Valibot schema * refactor(mcp): read tool descriptors with a Valibot schema The descriptor type now follows the schema, and tests cover what a descriptor must contain. * refactor(ai): read stored model settings with Valibot schemas Connections and model profiles are schemas with their defaults and bounds; the thinking-level migration and the checks between connections, models, and role assignments stay as code. Characterization tests written against the previous implementation pin the behavior.
117 lines
3.2 KiB
TypeScript
117 lines
3.2 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
|
|
import { parseAIModelSettings } from '@/app/ai/models/store'
|
|
import type { AIModelConnection } from '@/app/ai/models/types'
|
|
|
|
const connection = {
|
|
id: 'connection-a',
|
|
providerID: 'openrouter',
|
|
customBaseURL: '',
|
|
customAPIType: 'completions',
|
|
credentialProfileId: 'default'
|
|
} satisfies AIModelConnection
|
|
|
|
const profile = {
|
|
id: 'model-a',
|
|
name: 'Design',
|
|
connectionId: 'connection-a',
|
|
modelID: 'custom/model',
|
|
customModelID: '',
|
|
maxOutputTokens: 16_384,
|
|
thinkingLevel: 'default',
|
|
capabilities: ['tools']
|
|
}
|
|
|
|
function settings(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
version: 1,
|
|
connections: [connection],
|
|
models: [profile],
|
|
assignments: { design: 'model-a', review: 'design', fast: null, vision: null },
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
describe('parseAIModelSettings', () => {
|
|
test('rejects settings it cannot use', () => {
|
|
for (const stored of [null, 'settings', { ...settings(), version: 2 }]) {
|
|
expect(parseAIModelSettings(stored)).toBeNull()
|
|
}
|
|
expect(parseAIModelSettings(settings({ models: [] }))).toBeNull()
|
|
})
|
|
|
|
test('drops connections and models that are malformed or point nowhere', () => {
|
|
const parsed = parseAIModelSettings(
|
|
settings({
|
|
connections: [
|
|
connection,
|
|
{ ...connection, id: 'connection-b', providerID: 'unknown-provider' },
|
|
{ ...connection, id: '' },
|
|
'connection'
|
|
],
|
|
models: [
|
|
profile,
|
|
{ ...profile, id: 'model-b', connectionId: 'connection-b' },
|
|
{ ...profile, id: 'profile-c' },
|
|
null
|
|
]
|
|
})
|
|
)
|
|
expect(parsed?.connections.map((candidate) => candidate.id)).toEqual(['connection-a'])
|
|
expect(parsed?.models.map((candidate) => candidate.id)).toEqual(['model-a'])
|
|
})
|
|
|
|
test('fills and bounds the fields of a stored model', () => {
|
|
const parsed = parseAIModelSettings(
|
|
settings({
|
|
connections: [{ ...connection, customAPIType: 'soap', customBaseURL: 7 }],
|
|
models: [
|
|
{
|
|
id: 'model-a',
|
|
connectionId: 'connection-a',
|
|
maxOutputTokens: 1_000_000,
|
|
harnessPermissionMode: 'everything',
|
|
capabilities: ['vision', 'tools', 'vision', 'audio']
|
|
}
|
|
]
|
|
})
|
|
)
|
|
expect(parsed?.connections[0]).toEqual({
|
|
...connection,
|
|
customBaseURL: '',
|
|
customAPIType: 'completions'
|
|
})
|
|
expect(parsed?.models[0]).toEqual({
|
|
id: 'model-a',
|
|
name: 'Model',
|
|
connectionId: 'connection-a',
|
|
modelID: '',
|
|
customModelID: '',
|
|
maxOutputTokens: 128_000,
|
|
thinkingLevel: 'default',
|
|
harnessPermissionMode: undefined,
|
|
capabilities: ['vision', 'tools']
|
|
})
|
|
})
|
|
|
|
test('assigns roles only to models that can take them', () => {
|
|
const parsed = parseAIModelSettings(
|
|
settings({
|
|
models: [profile, { ...profile, id: 'model-b', capabilities: ['tools', 'vision'] }],
|
|
assignments: {
|
|
design: 'model-missing',
|
|
review: 'model-b',
|
|
fast: 'model-x',
|
|
vision: 'design'
|
|
}
|
|
})
|
|
)
|
|
expect(parsed?.assignments).toEqual({
|
|
design: 'model-a',
|
|
review: 'model-b',
|
|
fast: null,
|
|
vision: null
|
|
})
|
|
})
|
|
})
|