Files
deepseek-harness/packages/client/ui-models/tests/components.spec.tsx
T
imccyu ec601ca13d build(vendor): rescope the vendored Cordis packages into @deepseek-ai
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it
prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`,
`verify-translation-pairing --write` for the touched bilingual pairs,
`gen-doc-graphs`, and one typert snapshot whose ids embed character offsets.
`pnpm run rescope-vendor --check` verifies the result.

Renames nine vendored packages (cordis, cosmokit, schemastery and the six
@cordisjs plugins) and every reference that resolves them: manifest names and
dependency keys, module specifiers including declare-module merges, cordis.yml
plugin names, tsconfig paths, every Markdown fence, and `docs/` prose.
Directory names, upstream versions, and dependency ranges are unchanged, so
vendor/README.md still reads as an upstream snapshot; its manifest table gains
an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed
at each fork's origin.

The tutorial tier follows the rename end to end: its yaml fences named plugins
the Loader can no longer resolve, its `ts ignore-check` fences disagreed with
the compiled fences beside them, and its prose quoted both. The contracts that
told readers to keep upstream names — the root convention and the vendoring
cookbook's tree comment and manifest invariant — now say to rescope instead.

Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle
purity gate now names the vendored libraries a browser bundle inlines, and the
files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:13 +08:00

1268 lines
57 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @vitest-environment jsdom
/** Section, setup-card, and hand-written editor behavior over a scripted wire face. */
import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import Schema from '@deepseek-ai/schemastery'
import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react'
import type { RpcResponse, SettingsNamespaceView } from '@deepseek-ai/dsh-client-connection/client'
import {
ModelsSection, needsSetup, providerCopy, providerTargetLabel, removeProviderProfile,
} from '../src/client/ModelsSection.tsx'
import type { ModelsSectionInjected, ModelsSectionProps } from '../src/client/ModelsSection.tsx'
import { pathOps } from '../src/client/ProviderEditor.tsx'
import {
DeepSeekModelsEditor, formatCapacity, modelDrafts, parseCapacity, validateDeepSeekModels,
} from '../src/client/DeepSeekModelsEditor.tsx'
import { apiKeyFailure } from '../src/client/apiKey.ts'
import { deriveKeyRef, ModelsSettingsStore } from '../src/client/store.ts'
import type { ProviderRow } from '../src/client/store.ts'
import { en } from '../src/client/locales.ts'
afterEach(cleanup)
const t: ModelsSectionInjected['t'] = key => en[key]
const OPENAI_TARGET = { provider: 'openai', displayName: 'openai' }
const openaiCopy = (template: string): string => providerCopy(template, OPENAI_TARGET)
/** Open one row's capacity disclosure (1-based, as the labels read). */
function expandRow(position: number): void {
fireEvent.click(screen.getByLabelText(`${en.modelAdvanced} ${String(position)}`))
}
/** The capacity inputs of every open row, in row order. */
function capacityInputs(label: string): HTMLInputElement[] {
return screen.getAllByLabelText<HTMLInputElement>(new RegExp(label))
}
const PiAiConfig = Schema.object({
providers: Schema.dict(Schema.object({
apiKeyEnv: Schema.string().role('credential-ref'),
baseURL: Schema.string(),
reasoning: Schema.union(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']),
headers: Schema.dict(Schema.string()),
})),
})
const DeepSeekConfig = Schema.object({
apiKeyEnv: Schema.string().role('credential-ref'),
baseURL: Schema.string().pattern(/^https:\/\//),
reasoningEffort: Schema.union(['off', 'high', 'max']),
defaultContextWindow: Schema.number().step(1).min(1),
models: Schema.array(Schema.object({
id: Schema.string().required(),
name: Schema.string(),
description: Schema.string(),
contextWindow: Schema.number().step(1).min(1),
// The adapter declares its catalog as a schema default rather than a
// composition entry, which is what the restore-defaults path has to read.
})).default([
{
id: 'deepseek-v4-flash',
name: 'DeepSeek-V4-Flash',
description: '',
contextWindow: 1_000_000,
},
{
id: 'deepseek-v4-pro',
name: 'DeepSeek-V4-Pro',
description: '',
contextWindow: 1_000_000,
},
]),
})
const DEFAULT_DEEPSEEK_MODELS = [
{
id: 'deepseek-v4-flash',
name: 'DeepSeek-V4-Flash',
description: 'Preserved hidden detail',
contextWindow: 1_000_000,
},
{ id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro', contextWindow: 1_000_000 },
]
function wireNamespaces(): SettingsNamespaceView[] {
return [
{
ns: 'llm-deepseek',
schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as unknown,
value: {
apiKeyEnv: 'DEEPSEEK_API_KEY',
baseURL: 'https://base',
defaultContextWindow: 1_000_000,
maxTokens: 256_000,
models: DEFAULT_DEEPSEEK_MODELS,
},
base: { defaultContextWindow: 1_000_000, maxTokens: 256_000, models: DEFAULT_DEEPSEEK_MODELS },
user: { baseURL: 'https://base' },
applies: 'live',
secrets: [],
revision: 0,
},
{
ns: 'llm-plain',
schema: JSON.parse(JSON.stringify(Schema.object({
profiles: Schema.dict(Schema.object({ note: Schema.string() })),
}).toJSON())) as unknown,
value: {},
applies: 'live',
secrets: [],
revision: 0,
},
{
ns: 'llm-pi-ai',
schema: JSON.parse(JSON.stringify(PiAiConfig.toJSON())) as unknown,
value: { providers: { openai: { apiKeyEnv: 'OPENAI_API_KEY', baseURL: 'https://proxy', headers: { 'X-Team': 'a' } }, zombie: {} } },
user: { providers: { openai: { apiKeyEnv: 'OPENAI_API_KEY', baseURL: 'https://proxy', headers: { 'X-Team': 'a' } }, zombie: {} } },
applies: 'live',
secrets: [],
revision: 0,
},
]
}
let nextRpc = 0
function ok<T>(value: T): RpcResponse<T> {
return { rpcId: `r-${nextRpc++}` as never, result: { ok: true, value } }
}
function fail<T>(message: string, code = 'settings-rejected'): RpcResponse<T> {
return {
rpcId: `r-${nextRpc++}` as never,
result: { ok: false, error: { code, message, details: { ns: 'x' } } as never },
}
}
function scriptedFace(overrides: {
update?: ReturnType<typeof vi.fn>
replace?: ReturnType<typeof vi.fn>
mutate?: ReturnType<typeof vi.fn>
set?: ReturnType<typeof vi.fn>
unset?: ReturnType<typeof vi.fn>
} = {}) {
const update = overrides.update ?? vi.fn(() => Promise.resolve(ok(wireNamespaces()[2])))
const replace = overrides.replace ?? vi.fn(() => Promise.resolve(ok(wireNamespaces()[2])))
const mutate = overrides.mutate ?? vi.fn(() => Promise.resolve(ok(wireNamespaces()[2])))
const set = overrides.set ?? vi.fn(() => Promise.resolve(ok({})))
const unset = overrides.unset ?? vi.fn(() => Promise.resolve(ok({})))
const face = {
llm: {
providers: vi.fn(() => Promise.resolve(ok({
providers: [
{ provider: 'deepseek-official', displayName: 'DeepSeek', settingsNs: 'llm-deepseek', settingsPath: [], active: true },
{ provider: 'openai', displayName: 'openai', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'openai'], active: true },
{ provider: 'anthropic', displayName: 'anthropic', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'anthropic'], active: false },
{ provider: 'zombie', displayName: 'zombie', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'zombie'], active: false },
{ provider: 'broken', displayName: 'broken', settingsNs: 'llm-pi-ai', settingsPath: ['nope', 'x'], active: false },
{ provider: 'plain', displayName: 'plain', settingsNs: 'llm-plain', settingsPath: ['profiles', 'plain'], active: false },
],
}))),
models: vi.fn(() => Promise.resolve(ok({ groups: [], failures: [] }))),
},
settings: {
describe: vi.fn(() => Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: wireNamespaces() }))),
update,
replace,
mutate,
},
credentials: {
describe: vi.fn((payload: { refs: string[] }) => Promise.resolve(ok({
credentials: Object.fromEntries(payload.refs.map(ref => [ref, {
configured: ref === 'OPENAI_API_KEY',
...ref === 'OPENAI_API_KEY' ? { source: 'file' } : {},
writable: true,
}])),
}))),
set,
unset,
},
}
return { face, update, replace, mutate, set, unset }
}
type WireFace = ConstructorParameters<typeof ModelsSettingsStore>[0]
async function mountSection(overrides: Parameters<typeof scriptedFace>[0] = {}) {
const { face, update, replace, mutate, set, unset } = scriptedFace(overrides)
const controller = new ModelsSettingsStore(face as unknown as WireFace)
await controller.load()
const injected: ModelsSectionInjected = {
controller,
useSnapshot: bindSnapshotSelector(controller.store),
api: face as never,
t,
}
const view = render(<ModelsSection {...injected} />)
return { view, face, update, replace, mutate, set, unset, controller }
}
describe('ModelsSection', () => {
it('renders nothing before the slot injects its dependencies', () => {
const uninjected = {} as ModelsSectionProps
render(<ModelsSection {...uninjected} />)
expect(document.body.textContent).toBe('')
})
it('renders the unkeyed whole-section provider as an open setup card beside the rows', async () => {
await mountSection()
// DeepSeek has no configured credential and no stored apiKey → setup card.
expect(screen.getByText('DeepSeek')).toBeTruthy()
expect(screen.getByLabelText(en.keyInput)).toBeTruthy()
expect(screen.getByText('openai')).toBeTruthy()
expect(screen.queryByText('Active')).toBeNull()
expect(screen.queryByText('Inactive')).toBeNull()
const configured = screen.getByRole('img', { name: en.credentialConfigured })
expect(configured.getAttribute('title')).toBe(en.credentialConfigured)
expect(configured.className).toContain('credentialDotConfigured')
expect(configured.closest('li')?.textContent).toContain('openai')
expect(screen.queryByRole('img', { name: en.credentialMissing })).toBeNull()
expect(screen.getByText(en.add)).toBeTruthy()
})
it('marks only a confirmed missing reference and leaves native or unavailable state unmarked', async () => {
const { face } = scriptedFace()
face.credentials.describe.mockImplementation((payload: { refs: string[] }) => Promise.resolve(ok({
credentials: Object.fromEntries(payload.refs.map(ref => [ref, { configured: false, writable: true }])),
})))
const controller = new ModelsSettingsStore(face as unknown as WireFace)
await controller.load()
render(<ModelsSection
controller={controller}
useSnapshot={bindSnapshotSelector(controller.store)}
api={face as never}
t={t}
/>)
const missing = screen.getByRole('img', { name: en.credentialMissing })
expect(missing.getAttribute('title')).toBe(en.credentialMissing)
expect(missing.className).toContain('credentialDotMissing')
expect(missing.closest('li')?.textContent).toContain('openai')
expect(screen.queryByRole('img', { name: en.credentialConfigured })).toBeNull()
expect(screen.getByText('zombie').closest('li')?.querySelector('[role="img"]')).toBeNull()
})
it('turns the setup card into a row once the credential reports configured', async () => {
const { face } = await mountSection()
face.credentials.describe.mockImplementation((payload: { refs: string[] }) => Promise.resolve(ok({
credentials: Object.fromEntries(payload.refs.map(ref => [ref, { configured: true, writable: true }])),
})))
const controller = new ModelsSettingsStore(face as unknown as WireFace)
await controller.load()
cleanup()
render(<ModelsSection
controller={controller}
useSnapshot={bindSnapshotSelector(controller.store)}
api={face as never}
t={t}
/>)
// Now a row with an Edit button, not an open card.
expect(screen.getAllByText(en.edit).length).toBeGreaterThan(1)
expect(screen.queryByLabelText(en.keyInput)).toBeNull()
})
it('decides setup need from the joined credential state', () => {
const entry = { provider: 'p', displayName: 'p', settingsNs: 'llm-deepseek', settingsPath: [], active: true }
const row = (credential: ProviderRow['credential']): ProviderRow => ({
entry,
configured: true,
removable: false,
apiKeyEnv: 'X',
credential,
})
expect(needsSetup(row(undefined))).toBe(true)
expect(needsSetup(row({ configured: true, writable: true }))).toBe(false)
const nested = { ...row(undefined), entry: { ...entry, settingsPath: ['providers', 'x'] } }
expect(needsSetup(nested)).toBe(false)
})
it('derives conventional credential references from route ids', () => {
expect(deriveKeyRef('anthropic')).toBe('ANTHROPIC_API_KEY')
expect(deriveKeyRef('minimax-cn')).toBe('MINIMAX_CN_API_KEY')
})
it('uses one stable provider identity in action copy', () => {
const target = { provider: 'deepseek-official', displayName: 'DeepSeek' }
expect(providerTargetLabel(target)).toBe('DeepSeek (deepseek-official)')
expect(providerCopy(en.deleteTitle, target)).toBe('Delete DeepSeek (deepseek-official)?')
expect(providerTargetLabel(OPENAI_TARGET)).toBe('openai')
})
it('names only changed fields instead of rebuilding the section', () => {
expect(pathOps(['providers', 'openai'], { baseURL: 'https://old', reasoning: 'high' }, { reasoning: 'high' }))
.toEqual([{ op: 'unset', path: ['providers', 'openai', 'baseURL'] }])
expect(pathOps([], { b: 1 }, { b: 2, d: 3 }))
.toEqual([{ op: 'set', path: ['b'], value: 2 }, { op: 'set', path: ['d'], value: 3 }])
expect(pathOps([], undefined, {})).toEqual([])
expect(pathOps([], { a: 1 }, { a: 1 })).toEqual([])
})
it('stores a typed key write-only from the setup card without touching settings', async () => {
const { set, update, face } = await mountSection()
const key = screen.getByLabelText<HTMLInputElement>(en.keyInput)
fireEvent.change(key, { target: { value: ' sk-live ' } })
fireEvent.click(screen.getByText(en.apply))
await waitFor(() => { expect(set).toHaveBeenCalledWith({ ref: 'DEEPSEEK_API_KEY', value: 'sk-live' }) })
expect(update).not.toHaveBeenCalled()
await waitFor(() => { expect(face.settings.describe.mock.calls.length).toBeGreaterThan(1) })
expect((await screen.findByRole('status')).textContent).toBe(
providerCopy(en.savedProvider, { provider: 'deepseek-official', displayName: 'DeepSeek' }),
)
fireEvent.click(screen.getByText(en.add))
expect(screen.queryByRole('status')).toBeNull()
})
it('applies customized deepseek fields as path ops', async () => {
const { mutate } = await mountSection({
mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
})
fireEvent.click(screen.getByText(en.customized))
const baseURL = screen.getByLabelText<HTMLInputElement>(en.baseUrl)
// The deepseek placeholder is pinned to the public endpoint, not the
// effective value (which may reflect a launch-environment override).
expect(baseURL.placeholder).toBe('https://api.deepseek.com')
fireEvent.change(baseURL, { target: { value: 'https://next2' } })
fireEvent.click(screen.getByText(en.apply))
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
// Only the field that actually changed: reasoningEffort was already
// 'high' in the loaded profile, so it produces no op.
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-deepseek',
ops: [{ op: 'set', path: ['baseURL'], value: 'https://next2' }],
expectedRevision: 0,
})
})
it('materializes inherited models and adds an arbitrary DeepSeek id', async () => {
const { mutate } = await mountSection({
mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
})
fireEvent.click(screen.getByText(en.customized))
expect(screen.getByText(en.modelsInherited)).toBeTruthy()
expect(screen.getAllByLabelText(new RegExp(en.modelId)).map(input => (input as HTMLInputElement).value))
.toEqual(['deepseek-v4-flash', 'deepseek-v4-pro'])
fireEvent.click(screen.getByText(en.addModel))
const ids = screen.getAllByLabelText(new RegExp(en.modelId))
const names = screen.getAllByLabelText(new RegExp(en.modelName))
expandRow(3)
fireEvent.change(ids[2] as HTMLInputElement, { target: { value: 'private-preview' } })
fireEvent.change(names[2] as HTMLInputElement, { target: { value: 'Private Preview' } })
// Only row 3 is open, so its capacity is addressed by its own label.
fireEvent.change(screen.getByLabelText(`${en.contextWindow} 3`), { target: { value: '131072' } })
fireEvent.click(screen.getByText(en.apply))
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-deepseek',
ops: [{
op: 'set',
path: ['models'],
value: [
...DEFAULT_DEEPSEEK_MODELS,
{ id: 'private-preview', name: 'Private Preview', contextWindow: 131_072 },
],
}],
expectedRevision: 0,
})
})
it('rejects duplicate DeepSeek model ids before writing', async () => {
const { mutate } = await mountSection()
fireEvent.click(screen.getByText(en.customized))
fireEvent.click(screen.getByText(en.addModel))
const ids = screen.getAllByLabelText(new RegExp(en.modelId))
fireEvent.change(ids[2] as HTMLInputElement, { target: { value: 'deepseek-v4-flash' } })
fireEvent.click(screen.getByText(en.apply))
await screen.findByText(`Model 3: ${en.modelIdDuplicate}`)
expect(mutate).not.toHaveBeenCalled()
})
it('validates every adapter-owned model catalog invariant', () => {
expect(modelDrafts(undefined)).toEqual([])
expect(modelDrafts([null, 'bad', { id: 'ok' }])).toEqual([{}, {}, { id: 'ok' }])
expect(validateDeepSeekModels([{}])).toEqual({ index: 0, key: 'modelIdRequired' })
expect(validateDeepSeekModels([{ id: 'same' }, { id: 'same' }]))
.toEqual({ index: 1, key: 'modelIdDuplicate' })
expect(validateDeepSeekModels([{ id: 'model', name: '' }]))
.toEqual({ index: 0, key: 'modelNameInvalid' })
expect(validateDeepSeekModels([{ id: 'model', contextWindow: null }]))
.toEqual({ index: 0, key: 'modelContextInvalid' })
expect(validateDeepSeekModels([{ id: 'model', contextWindow: 1.5 }]))
.toEqual({ index: 0, key: 'modelContextInvalid' })
expect(validateDeepSeekModels([{ id: 'model', contextWindow: 0 }]))
.toEqual({ index: 0, key: 'modelContextInvalid' })
expect(validateDeepSeekModels([{ id: 'model', contextWindow: 1 }])).toBeUndefined()
expect(validateDeepSeekModels([{ id: 'model', maxTokens: null }]))
.toEqual({ index: 0, key: 'modelMaxTokensInvalid' })
expect(validateDeepSeekModels([{ id: 'model', maxTokens: 1.5 }]))
.toEqual({ index: 0, key: 'modelMaxTokensInvalid' })
expect(validateDeepSeekModels([{ id: 'model', maxTokens: 0 }]))
.toEqual({ index: 0, key: 'modelMaxTokensInvalid' })
expect(validateDeepSeekModels([{ id: 'model', maxTokens: 8192 }])).toBeUndefined()
})
it('reads context windows written as counts, thousands, or millions', () => {
expect(parseCapacity('')).toBeUndefined()
expect(parseCapacity(' ')).toBeUndefined()
expect(parseCapacity('131072')).toBe(131_072)
expect(parseCapacity(' 256K ')).toBe(256_000)
expect(parseCapacity('256k')).toBe(256_000)
expect(parseCapacity('1M')).toBe(1_000_000)
expect(parseCapacity('1m')).toBe(1_000_000)
// 1M is 1000K, not 1024K: capacities are quoted in decimal.
expect(parseCapacity('1M')).toBe(parseCapacity('1000K'))
// 2.3 * 1e6 is a few ULPs high in binary floating point; an integral
// intent must not become a fractional count the validator rejects.
expect(parseCapacity('2.3M')).toBe(2_300_000)
expect(Number.isInteger(parseCapacity('1.5M'))).toBe(true)
// A genuinely fractional count survives as one, for the validator to reject.
expect(parseCapacity('0.0001K')).toBeCloseTo(0.1)
expect(parseCapacity('abc')).toBeNaN()
expect(parseCapacity('1G')).toBeNaN()
expect(parseCapacity('1M1')).toBeNaN()
})
it('spells a stored count in the shortest form that round-trips', () => {
expect(formatCapacity(1_000_000)).toBe('1M')
expect(formatCapacity(256_000)).toBe('256K')
expect(formatCapacity(1_500_000)).toBe('1500K')
expect(formatCapacity(131_072)).toBe('131072')
// Values the validator will reject are shown as-is rather than dressed up.
expect(formatCapacity(Number.NaN)).toBe('NaN')
expect(formatCapacity(0)).toBe('0')
for (const text of ['1M', '256K', '131072', '1500K']) {
expect(formatCapacity(parseCapacity(text) as number)).toBe(text)
}
})
it('accepts a suffixed context window and stores the plain count', async () => {
const { mutate } = await mountSection({
mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
})
fireEvent.click(screen.getByText(en.customized))
expandRow(1)
expandRow(2)
const windows = capacityInputs(en.contextWindow)
// The inherited 1000000 reads back short.
expect((windows[0] as HTMLInputElement).value).toBe('1M')
// Keystrokes stay verbatim while the row has focus, so typing `1000` does
// not rewrite itself to `1K` mid-word.
fireEvent.change(windows[0] as HTMLInputElement, { target: { value: '1000' } })
expect((windows[0] as HTMLInputElement).value).toBe('1000')
fireEvent.change(windows[0] as HTMLInputElement, { target: { value: '1000K' } })
expect((windows[0] as HTMLInputElement).value).toBe('1000K')
// Blur settles the row to the canonical spelling of the same count.
fireEvent.blur(windows[0] as HTMLInputElement)
expect((windows[0] as HTMLInputElement).value).toBe('1M')
fireEvent.change(windows[1] as HTMLInputElement, { target: { value: '256K' } })
fireEvent.blur(windows[1] as HTMLInputElement)
fireEvent.click(screen.getByText(en.apply))
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-deepseek',
ops: [{
op: 'set',
path: ['models'],
value: [
{ ...DEFAULT_DEEPSEEK_MODELS[0], contextWindow: 1_000_000 },
{ ...DEFAULT_DEEPSEEK_MODELS[1], contextWindow: 256_000 },
],
}],
expectedRevision: 0,
})
})
it('keeps unreadable context-window text on screen and refuses the write', async () => {
const { mutate } = await mountSection()
fireEvent.click(screen.getByText(en.customized))
expandRow(1)
expandRow(2)
const windows = capacityInputs(en.contextWindow)
fireEvent.change(windows[0] as HTMLInputElement, { target: { value: '1 gazillion' } })
// Blurring a row that is not the edited one leaves the buffer alone.
fireEvent.blur(windows[1] as HTMLInputElement)
fireEvent.blur(windows[0] as HTMLInputElement)
// The text the user typed is still there to correct.
expect((windows[0] as HTMLInputElement).value).toBe('1 gazillion')
fireEvent.click(screen.getByText(en.apply))
await screen.findByText(`Model 1: ${en.modelContextInvalid}`)
expect(mutate).not.toHaveBeenCalled()
})
it.each([
['the schema default', undefined],
['the composition entry', { models: [{ id: 'pinned-by-deployment' }] }],
])('restores %s the moment the override is dropped, not after a reload', async (_label, base) => {
// The regression: reset read the EFFECTIVE value, which still carries the
// stored override until the unset is applied — so the rows did not change
// and the catalog only looked restored after reopening the card.
const { face } = scriptedFace()
const stored = { models: [{ id: 'user-only-model', name: 'User Only' }] }
const overridden: SettingsNamespaceView = {
ns: 'llm-deepseek',
schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as unknown,
value: { ...stored, defaultContextWindow: 1_000_000 },
...base === undefined ? {} : { base },
user: stored,
applies: 'live',
secrets: [],
revision: 0,
}
const { ProviderEditor } = await import('../src/client/ProviderEditor.tsx')
render(<ProviderEditor
provider="deepseek-official"
displayName="DeepSeek"
namespace={overridden}
settingsPath={[]}
api={face as never}
t={t}
readOnly={false}
onClose={() => {}}
/>)
fireEvent.click(screen.getByText(en.customized))
expect(screen.getByText(en.modelsCustomized)).toBeTruthy()
expect(screen.getAllByLabelText(new RegExp(en.modelId)).map(input => (input as HTMLInputElement).value))
.toEqual(['user-only-model'])
fireEvent.click(screen.getByText(en.resetModels))
expect(screen.getByText(en.modelsInherited)).toBeTruthy()
expect(screen.getAllByLabelText(new RegExp(en.modelId)).map(input => (input as HTMLInputElement).value))
.toEqual(base === undefined ? ['deepseek-v4-flash', 'deepseek-v4-pro'] : ['pinned-by-deployment'])
})
it('keeps every row\'s unreadable text, not just the last one edited', async () => {
// The regression: one active buffer meant editing a second row displaced
// the first, which then fell back to rendering its stored NaN as `NaN` —
// losing the text the user was told they could still correct.
await mountSection()
fireEvent.click(screen.getByText(en.customized))
expandRow(1)
expandRow(2)
const windows = capacityInputs(en.contextWindow)
fireEvent.change(windows[0] as HTMLInputElement, { target: { value: 'not a number' } })
fireEvent.blur(windows[0] as HTMLInputElement)
fireEvent.change(windows[1] as HTMLInputElement, { target: { value: '2M' } })
expect((windows[0] as HTMLInputElement).value).toBe('not a number')
expect((windows[1] as HTMLInputElement).value).toBe('2M')
})
it('re-keys the typed text around a removed row', async () => {
await mountSection()
fireEvent.click(screen.getByText(en.customized))
const windows = (): HTMLInputElement[] => capacityInputs(en.contextWindow)
const removeRow = (at: number): void => {
fireEvent.click(screen.getAllByLabelText(new RegExp(en.removeModel))[at] as HTMLElement)
}
// Three rows, with text parked on the outer two.
fireEvent.click(screen.getByText(en.addModel))
expandRow(1)
expandRow(2)
expandRow(3)
fireEvent.change(windows()[0] as HTMLInputElement, { target: { value: 'top text' } })
fireEvent.blur(windows()[0] as HTMLInputElement)
fireEvent.change(windows()[2] as HTMLInputElement, { target: { value: 'bottom text' } })
fireEvent.blur(windows()[2] as HTMLInputElement)
// Dropping the middle row leaves the row above untouched and carries the
// row below down with its own text, rather than stranding it.
removeRow(1)
expect(windows()).toHaveLength(2)
expect((windows()[0] as HTMLInputElement).value).toBe('top text')
expect((windows()[1] as HTMLInputElement).value).toBe('bottom text')
// Dropping a row that holds text takes that text with it; the survivor
// keeps its own rather than inheriting the deleted row's.
removeRow(0)
expect(windows()).toHaveLength(1)
expect((windows()[0] as HTMLInputElement).value).toBe('bottom text')
})
it('drops the typed text when reset replaces the rows it annotated', async () => {
// The regression: reset removed the override but left the buffer, so an
// inherited row displayed text no settings layer stores — and because an
// unreadable buffer never settles, it stayed there indefinitely.
const { mutate } = await mountSection({
mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
})
fireEvent.click(screen.getByText(en.customized))
expandRow(1)
const windows = capacityInputs(en.contextWindow)
fireEvent.change(windows[0] as HTMLInputElement, { target: { value: 'garbage' } })
fireEvent.blur(windows[0] as HTMLInputElement)
fireEvent.click(screen.getByText(en.resetModels))
// Reset collapses every row, so the restored capacity needs opening again.
expandRow(1)
const restored = capacityInputs(en.contextWindow)
expect((restored[0] as HTMLInputElement).value).toBe('1M')
// Reset put the draft back where it started, so Apply writes nothing at
// all rather than persisting whatever the stale text had parsed to.
fireEvent.click(screen.getByText(en.apply))
await waitFor(() => { expect(screen.getByText(en.apply)).toBeTruthy() })
expect(mutate).not.toHaveBeenCalled()
})
it('edits an output cap per model and carries its text across a removal', async () => {
const { mutate } = await mountSection({
mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
})
fireEvent.click(screen.getByText(en.customized))
expandRow(1)
expandRow(2)
// The profile's own cap is the placeholder both rows inherit.
expect(capacityInputs(en.maxTokens).map(input => input.placeholder)).toEqual(['256K', '256K'])
fireEvent.change(screen.getByLabelText(`${en.maxTokens} 2`), { target: { value: '64K' } })
fireEvent.blur(screen.getByLabelText(`${en.maxTokens} 2`))
expect(screen.getByLabelText<HTMLInputElement>(`${en.maxTokens} 2`).value).toBe('64K')
// Dropping the row above carries the cap text down with its own row.
fireEvent.click(screen.getAllByLabelText(new RegExp(en.removeModel))[0] as HTMLElement)
expect(screen.getByLabelText<HTMLInputElement>(`${en.maxTokens} 1`).value).toBe('64K')
// The disclosure closes on a second press.
expandRow(1)
expect(screen.queryByLabelText(`${en.maxTokens} 1`)).toBeNull()
fireEvent.click(screen.getByText(en.apply))
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-deepseek',
ops: [{
op: 'set',
path: ['models'],
value: [{ ...DEFAULT_DEEPSEEK_MODELS[1], maxTokens: 64_000 }],
}],
expectedRevision: 0,
})
})
it('settles a pasted id and refuses whitespace that would never match', async () => {
await mountSection()
fireEvent.click(screen.getByText(en.customized))
const ids = screen.getAllByLabelText<HTMLInputElement>(new RegExp(en.modelId))
fireEvent.change(ids[0] as HTMLInputElement, { target: { value: ' deepseek-v4-flash ' } })
fireEvent.blur(ids[0] as HTMLInputElement)
expect((ids[0] as HTMLInputElement).value).toBe('deepseek-v4-flash')
// A settled id needs no second trim.
fireEvent.blur(ids[0] as HTMLInputElement)
expect((ids[0] as HTMLInputElement).value).toBe('deepseek-v4-flash')
// An id that is only whitespace is as absent as an empty one, and a padded
// id is a duplicate of its trimmed twin.
expect(validateDeepSeekModels([{ id: ' ' }])).toEqual({ index: 0, key: 'modelIdRequired' })
expect(validateDeepSeekModels([{ id: 'model' }, { id: 'model ' }]))
.toEqual({ index: 1, key: 'modelIdDuplicate' })
})
it('renders malformed draft fallbacks without inventing catalog values', () => {
render(<DeepSeekModelsEditor
models={[{}]}
overridden={false}
defaultContextWindow={undefined}
defaultMaxTokens={undefined}
t={t}
disabled={true}
onChange={vi.fn()}
onReset={vi.fn()}
/>)
expect(screen.getByLabelText<HTMLInputElement>(`${en.modelId} 1`).value).toBe('')
expandRow(1)
expect(screen.getByLabelText<HTMLInputElement>(`${en.contextWindow} 1`).placeholder)
.toBe(en.contextWindowPlaceholder)
expect(screen.getByLabelText<HTMLInputElement>(`${en.maxTokens} 1`).placeholder)
.toBe(en.maxTokensPlaceholder)
})
it('can empty and reset the model override, then clear optional fields without dropping hidden data', async () => {
const { mutate } = await mountSection({
mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
})
fireEvent.click(screen.getByText(en.customized))
fireEvent.click(screen.getAllByLabelText(new RegExp(en.removeModel))[0] as HTMLElement)
fireEvent.click(screen.getByLabelText(new RegExp(en.removeModel)))
expect(screen.getByText(en.modelsEmpty)).toBeTruthy()
fireEvent.click(screen.getByText(en.resetModels))
expect(screen.getByText(en.modelsInherited)).toBeTruthy()
const names = screen.getAllByLabelText(new RegExp(en.modelName))
expandRow(1)
const windows = capacityInputs(en.contextWindow)
fireEvent.change(names[0] as HTMLInputElement, { target: { value: '' } })
fireEvent.change(windows[0] as HTMLInputElement, { target: { value: '' } })
fireEvent.click(screen.getByText(en.apply))
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-deepseek',
ops: [{
op: 'set',
path: ['models'],
value: [
{ id: 'deepseek-v4-flash', description: 'Preserved hidden detail' },
DEFAULT_DEEPSEEK_MODELS[1],
],
}],
expectedRevision: 0,
})
})
it('clears an inherited override with an unset op, never a whole-section replace', async () => {
// A whole-section replace would clobber sibling overrides to clear one field.
const { replace, update, mutate } = await mountSection()
fireEvent.click(screen.getByText(en.customized))
const url = screen.getByLabelText<HTMLInputElement>(en.baseUrl)
expect(url.value).toBe('https://base')
fireEvent.change(url, { target: { value: '' } })
fireEvent.click(screen.getByText(en.apply))
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
expect(replace).not.toHaveBeenCalled()
expect(update).not.toHaveBeenCalled()
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-deepseek',
ops: [{ op: 'unset', path: ['baseURL'] }],
expectedRevision: 0,
})
})
it('pins the deepseek placeholder and clears typed input back to inherited', async () => {
const { face } = scriptedFace()
const bare: SettingsNamespaceView = {
ns: 'llm-deepseek',
schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as unknown,
value: {},
applies: 'live',
secrets: [],
revision: 0,
}
const { ProviderEditor } = await import('../src/client/ProviderEditor.tsx')
render(<ProviderEditor
provider="deepseek-official"
displayName="DeepSeek"
namespace={bare}
settingsPath={[]}
api={face as never}
t={t}
readOnly={false}
onClose={() => {}}
/>)
fireEvent.click(screen.getByText(en.customized))
const baseURL = screen.getByLabelText<HTMLInputElement>(en.baseUrl)
expect(baseURL.placeholder).toBe('https://api.deepseek.com')
fireEvent.change(baseURL, { target: { value: 'https://x' } })
expect(baseURL.value).toBe('https://x')
fireEvent.change(baseURL, { target: { value: '' } })
expect(baseURL.value).toBe('')
})
it('rejects an invalid draft before writing', async () => {
const { update } = await mountSection()
fireEvent.click(screen.getByText(en.customized))
fireEvent.change(screen.getByLabelText(en.baseUrl), { target: { value: 'not-a-url' } })
fireEvent.click(screen.getByText(en.apply))
await screen.findByText(/baseURL/)
expect(update).not.toHaveBeenCalled()
})
it('edits a pi-ai profile with the curated fields only', async () => {
const { mutate } = await mountSection()
fireEvent.click(screen.getAllByText(en.edit)[0] as HTMLElement)
// The configured credential shows as the stored placeholder.
const keys = await screen.findAllByLabelText<HTMLInputElement>(en.keyInput)
const editorKey = keys[keys.length - 1] as HTMLInputElement
await waitFor(() => { expect(editorKey.placeholder).toBe(en.keyStored) })
// pi-ai carries Base URL too: the stored override shows as the value and
// the effective profile endpoint as its placeholder source.
fireEvent.click(screen.getAllByText(en.customized)[1] as HTMLElement)
const urls = screen.getAllByLabelText<HTMLInputElement>(en.baseUrl)
expect(urls).toHaveLength(2)
expect((urls[1] as HTMLInputElement).value).toBe('https://proxy')
fireEvent.change(urls[1] as HTMLInputElement, { target: { value: 'https://proxy/v2' } })
fireEvent.click(screen.getAllByText(en.apply)[1] as HTMLElement)
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
// Only the edited field travels: apiKeyEnv and headers were already stored
// with these values, so no op restates them.
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-pi-ai',
ops: [{ op: 'set', path: ['providers', 'openai', 'baseURL'], value: 'https://proxy/v2' }],
expectedRevision: 0,
})
})
it('adds a dormant provider with a derived reference and stores its key', async () => {
const { mutate, set } = await mountSection()
fireEvent.click(screen.getByText(en.add))
const pick = await screen.findByLabelText<HTMLSelectElement>(en.provider)
expect([...pick.options].map(option => option.value)).toEqual(['anthropic', 'broken', 'plain'])
expect(pick.value).toBe('anthropic')
// A dormant profile has no endpoint anywhere: the pi-ai placeholder
// falls back to the provider-default wording.
fireEvent.click(screen.getAllByText(en.customized)[1] as HTMLElement)
const urls = screen.getAllByLabelText<HTMLInputElement>(en.baseUrl)
expect((urls[1] as HTMLInputElement).placeholder).toBe(en.baseUrlDefault)
const keys = screen.getAllByLabelText<HTMLInputElement>(en.keyInput)
const addKey = keys[keys.length - 1] as HTMLInputElement
expect(addKey.placeholder).toBe(en.keyPlaceholderNative)
fireEvent.change(addKey, { target: { value: 'sk-ant' } })
fireEvent.click(screen.getAllByText(en.apply)[1] as HTMLElement)
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-pi-ai',
ops: [{ op: 'set', path: ['providers', 'anthropic', 'apiKeyEnv'], value: 'ANTHROPIC_API_KEY' }],
expectedRevision: 0,
})
await waitFor(() => { expect(set).toHaveBeenCalledWith({ ref: 'ANTHROPIC_API_KEY', value: 'sk-ant' }) })
})
it('keeps pi-ai provider-native authentication when no key is entered', async () => {
const { mutate, set } = await mountSection()
fireEvent.click(screen.getByText(en.add))
await screen.findByLabelText(en.provider)
fireEvent.click(screen.getAllByText(en.apply)[1] as HTMLElement)
await waitFor(() => { expect(mutate).toHaveBeenCalledOnce() })
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-pi-ai',
ops: [{ op: 'set', path: ['providers', 'anthropic'], value: {} }],
expectedRevision: 0,
})
expect(set).not.toHaveBeenCalled()
})
it('retries only the credential after refreshed settings already committed', async () => {
const committed = wireNamespaces()[2]!
const afterSettings: SettingsNamespaceView = {
...committed,
value: { providers: {
...(committed.value as { providers: object }).providers,
anthropic: { apiKeyEnv: 'ANTHROPIC_API_KEY' },
} },
user: { providers: {
...(committed.user as { providers: object }).providers,
anthropic: { apiKeyEnv: 'ANTHROPIC_API_KEY' },
} },
revision: 1,
}
const mutate = vi.fn(() => Promise.resolve(ok(afterSettings)))
const set = vi.fn()
.mockResolvedValueOnce(fail('credential store unavailable', 'credential-rejected'))
.mockResolvedValueOnce(ok({}))
const { face, controller } = await mountSection({ mutate, set })
fireEvent.click(screen.getByText(en.add))
await screen.findByLabelText(en.provider)
const keys = screen.getAllByLabelText<HTMLInputElement>(en.keyInput)
fireEvent.change(keys[keys.length - 1] as HTMLInputElement, { target: { value: 'sk-ant' } })
fireEvent.click(screen.getAllByText(en.apply)[1] as HTMLElement)
await screen.findByText('credential store unavailable')
expect(mutate).toHaveBeenCalledOnce()
face.settings.describe.mockResolvedValue(ok({
writable: true,
hasDocument: false,
namespaces: wireNamespaces().map(namespace => namespace.ns === 'llm-pi-ai' ? afterSettings : namespace),
}))
await act(async () => { await controller.load() })
expect(controller.store.getSnapshot().namespaces.get('llm-pi-ai')?.revision).toBe(1)
fireEvent.click(screen.getAllByText(en.apply)[1] as HTMLElement)
await waitFor(() => { expect(set).toHaveBeenCalledTimes(2) })
expect(mutate).toHaveBeenCalledOnce()
expect(set).toHaveBeenLastCalledWith({ ref: 'ANTHROPIC_API_KEY', value: 'sk-ant' })
})
it('switches the add card target and degrades unknown or broken targets loudly', async () => {
await mountSection()
fireEvent.click(screen.getByText(en.add))
const pick = await screen.findByLabelText<HTMLSelectElement>(en.provider)
fireEvent.change(pick, { target: { value: 'broken' } })
await screen.findByText(/unresolvable settings path/)
fireEvent.change(pick, { target: { value: 'plain' } })
await waitFor(() => {
expect(screen.getAllByText(content => content.includes(en.advancedHint)).length).toBeGreaterThan(0)
})
// The hint-only card cannot apply anything.
const applies = screen.getAllByText<HTMLButtonElement>(en.apply)
expect((applies[applies.length - 1] as HTMLButtonElement).disabled).toBe(true)
expect(screen.getAllByLabelText(en.keyInput)).toHaveLength(1)
})
it('surfaces a rejected settings write and never stores the key after it', async () => {
const { set } = await mountSection({
mutate: vi.fn(() => Promise.resolve(fail('llm-pi-ai: unknown pi-ai provider "bogus"'))),
})
fireEvent.click(screen.getByText(en.add))
await screen.findByLabelText(en.provider)
const keys = screen.getAllByLabelText<HTMLInputElement>(en.keyInput)
fireEvent.change(keys[keys.length - 1] as HTMLInputElement, { target: { value: 'sk-x' } })
fireEvent.click(screen.getAllByText(en.apply)[1] as HTMLElement)
await screen.findByText(/unknown pi-ai provider/)
expect(set).not.toHaveBeenCalled()
})
it('renders the card without the stored-key hint when the credential probe rejects', async () => {
// The probe is a placeholder hint, not a precondition: an escaping
// rejection would surface in the browser as an unhandled rejection.
const { face } = scriptedFace()
face.credentials.describe = vi.fn(() => Promise.reject(new Error('connection lost')))
const unhandled = vi.fn()
process.on('unhandledRejection', unhandled)
try {
const controller = new ModelsSettingsStore(face as unknown as WireFace)
await controller.load()
render(<ModelsSection
controller={controller}
useSnapshot={bindSnapshotSelector(controller.store)}
api={face as never}
t={t}
/>)
const key = await screen.findByLabelText<HTMLInputElement>(en.keyInput)
expect(key.placeholder).toBe(en.keyPlaceholder)
await new Promise(resolve => setTimeout(resolve, 10))
expect(unhandled).not.toHaveBeenCalled()
} finally {
process.off('unhandledRejection', unhandled)
}
})
it('tells the user to reopen when another writer moved the namespace first', async () => {
// The stale-draft overwrite: two tabs open the same card, the other saves,
// and this one must be refused rather than replay its opening snapshot.
const { set } = await mountSection({
mutate: vi.fn(() => Promise.resolve(fail('changed since it was read', 'settings-conflict'))),
})
fireEvent.click(screen.getByText(en.customized))
fireEvent.change(screen.getByLabelText<HTMLInputElement>(en.baseUrl), { target: { value: 'https://mine' } })
fireEvent.click(screen.getByText(en.apply))
await screen.findByText(en.conflict)
expect(set).not.toHaveBeenCalled()
})
it('keeps the card usable when the write rejects instead of answering', async () => {
// A transport failure (disconnect, or the 403 a non-loopback browser now
// gets on the whole configuration plane) rejects rather than returning a
// failed envelope: without a catch the card would stay busy forever.
await mountSection({ mutate: vi.fn(() => Promise.reject(new Error('connection lost'))) })
fireEvent.click(screen.getByText(en.customized))
fireEvent.change(screen.getByLabelText<HTMLInputElement>(en.baseUrl), { target: { value: 'https://next' } })
fireEvent.click(screen.getByText(en.apply))
await screen.findByText('connection lost')
// Not stuck in `applying…`: the finally cleared busy, so Apply is live again.
expect(screen.getByText(en.apply)).toBeTruthy()
})
it('surfaces a shadowed credential write on the card', async () => {
await mountSection({
set: vi.fn(() => Promise.resolve(fail('credentials: DEEPSEEK_API_KEY is shadowed by the read-only environment', 'credential-rejected'))),
})
const key = screen.getByLabelText<HTMLInputElement>(en.keyInput)
fireEvent.change(key, { target: { value: 'sk-live' } })
fireEvent.click(screen.getByText(en.apply))
await screen.findByText(/shadowed by the read-only environment/)
expect(screen.queryByRole('status')).toBeNull()
})
it('locks the key input when the launch environment provides the credential', async () => {
const { face } = await mountSection()
face.credentials.describe.mockImplementation((payload: { refs: string[] }) => Promise.resolve(ok({
credentials: Object.fromEntries(payload.refs.map(ref => [ref, {
configured: ref === 'OPENAI_API_KEY', source: 'env', writable: false,
}])),
})))
fireEvent.click(screen.getAllByText(en.edit)[0] as HTMLElement)
const keys = await screen.findAllByLabelText<HTMLInputElement>(en.keyInput)
const editorKey = keys[keys.length - 1] as HTMLInputElement
await waitFor(() => { expect(editorKey.placeholder).toBe(en.keyEnvLocked) })
expect(editorKey.disabled).toBe(true)
})
it('keeps a failed credential describe silent and the input usable', async () => {
const { face, set } = await mountSection()
face.credentials.describe.mockImplementation(() => Promise.resolve(fail('down', 'internal')) as never)
fireEvent.click(screen.getAllByText(en.edit)[0] as HTMLElement)
const keys = await screen.findAllByLabelText<HTMLInputElement>(en.keyInput)
const editorKey = keys[keys.length - 1] as HTMLInputElement
expect(editorKey.placeholder).toBe(en.keyPlaceholderNative)
fireEvent.change(editorKey, { target: { value: 'sk-live' } })
fireEvent.click(screen.getAllByText(en.apply)[1] as HTMLElement)
await waitFor(() => { expect(set).toHaveBeenCalledTimes(1) })
})
it('requires confirmation before removing a user-added provider', async () => {
const { replace, mutate, unset } = await mountSection()
fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.removeProvider) }))
const dialog = screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) })
expect(dialog.textContent).toContain(openaiCopy(en.deleteDescriptionWithCredential))
expect(document.activeElement).toBe(within(dialog).getByRole('button', { name: en.cancel }))
expect(unset).not.toHaveBeenCalled()
expect(mutate).not.toHaveBeenCalled()
fireEvent.click(within(dialog).getByRole('button', { name: en.cancel }))
expect(screen.queryByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBeNull()
expect(mutate).not.toHaveBeenCalled()
fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.removeProvider) }))
fireEvent.click(within(screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) }))
.getByRole('button', { name: en.close }))
expect(screen.queryByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBeNull()
expect(mutate).not.toHaveBeenCalled()
fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.removeProvider) }))
fireEvent.click(within(screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) }))
.getByRole('button', { name: openaiCopy(en.deleteConfirm) }))
await waitFor(() => { expect(unset).toHaveBeenCalledWith({ ref: 'OPENAI_API_KEY' }) })
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
expect(unset.mock.invocationCallOrder[0]).toBeLessThan(mutate.mock.invocationCallOrder[0] as number)
expect(screen.queryByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBeNull()
expect(replace).not.toHaveBeenCalled()
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-pi-ai',
ops: [{ op: 'unset', path: ['providers', 'openai'] }],
})
})
it('blocks duplicate deletion while the confirmed removal is pending', async () => {
let resolveRemoval!: (response: RpcResponse<SettingsNamespaceView>) => void
const mutate = vi.fn(() => new Promise<RpcResponse<SettingsNamespaceView>>((resolve) => {
resolveRemoval = resolve
}))
await mountSection({ mutate })
fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.removeProvider) }))
const dialog = screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) })
const confirm = within(dialog).getByRole<HTMLButtonElement>('button', { name: openaiCopy(en.deleteConfirm) })
fireEvent.click(confirm)
fireEvent.click(confirm)
await waitFor(() => { expect(mutate).toHaveBeenCalledOnce() })
expect(confirm.disabled).toBe(true)
expect(within(dialog).getByRole<HTMLButtonElement>('button', { name: en.cancel }).disabled).toBe(true)
expect(within(dialog).getByRole('button', { name: openaiCopy(en.deleting) })).toBe(confirm)
fireEvent.click(within(dialog).getByRole('button', { name: en.close }))
expect(screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBe(dialog)
expect(mutate).toHaveBeenCalledOnce()
await act(async () => { resolveRemoval(ok(wireNamespaces()[2]!)) })
await waitFor(() => {
expect(screen.queryByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBeNull()
})
})
it('renders the load failure with a retry control', async () => {
const face = scriptedFace()
face.face.llm.providers = vi.fn(() => Promise.resolve(fail('directory down', 'internal'))) as never
const controller = new ModelsSettingsStore(face.face as unknown as WireFace)
await controller.load()
render(<ModelsSection
controller={controller}
useSnapshot={bindSnapshotSelector(controller.store)}
api={face.face as never}
t={t}
/>)
expect(screen.getByText(/directory down/)).toBeTruthy()
fireEvent.click(screen.getByText(en.retry))
await waitFor(() => { expect(screen.queryByText(/directory down/)).toBeNull() })
})
it('shows the read-only notice and disables mutations for a read-only provider', async () => {
const { face } = await mountSection()
face.settings.describe.mockImplementation(() => Promise.resolve(ok({
writable: false,
hasDocument: false,
namespaces: wireNamespaces(),
})))
const controller = new ModelsSettingsStore(face as unknown as WireFace)
await controller.load()
cleanup()
render(<ModelsSection
controller={controller}
useSnapshot={bindSnapshotSelector(controller.store)}
api={face as never}
t={t}
/>)
expect(screen.getByText(en.readOnly)).toBeTruthy()
expect(screen.getAllByText<HTMLButtonElement>(en.remove).every(button => button.disabled)).toBe(true)
expect(screen.getByText<HTMLButtonElement>(en.add).disabled).toBe(true)
})
it('toggles the row editor closed on a second edit click and on cancel', async () => {
const { update } = await mountSection()
const edit = screen.getAllByText(en.edit)[0] as HTMLElement
fireEvent.click(edit)
await waitFor(() => { expect(screen.getAllByLabelText(en.keyInput).length).toBe(2) })
fireEvent.click(edit)
expect(screen.getAllByLabelText(en.keyInput)).toHaveLength(1)
fireEvent.click(edit)
await waitFor(() => { expect(screen.getAllByLabelText(en.keyInput).length).toBe(2) })
fireEvent.click(screen.getAllByText(en.cancel)[1] as HTMLElement)
expect(screen.getAllByLabelText(en.keyInput)).toHaveLength(1)
expect(update).not.toHaveBeenCalled()
})
it('cancels the add card back to the add button', async () => {
await mountSection()
fireEvent.click(screen.getByText(en.add))
await screen.findByLabelText(en.provider)
fireEvent.click(screen.getAllByText(en.cancel)[1] as HTMLElement)
await screen.findByText(en.add)
expect(screen.queryByLabelText(en.provider)).toBeNull()
})
it('loads on first render of an idle controller', async () => {
const { face } = scriptedFace()
const controller = new ModelsSettingsStore(face as unknown as WireFace)
render(<ModelsSection
controller={controller}
useSnapshot={bindSnapshotSelector(controller.store)}
api={face as never}
t={t}
/>)
await screen.findByText('DeepSeek')
})
it('removes by unsetting the profile path, never by rebuilding the section', async () => {
// The page only needs to name the profile path; rebuilding the section
// would widen the write for no benefit.
const { face, mutate, replace, controller } = await mountSection()
await removeProviderProfile(
face as unknown as Parameters<typeof removeProviderProfile>[0],
controller,
{ settingsNs: 'llm-plain', settingsPath: ['ghost-profile'] },
)
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-plain',
ops: [{ op: 'unset', path: ['ghost-profile'] }],
})
expect(replace).not.toHaveBeenCalled()
})
it('keeps the snapshot untouched and reports the message when a removal write is refused', async () => {
const { face, controller } = await mountSection({
mutate: vi.fn(() => Promise.resolve(fail('read-only'))),
})
const before = controller.store.getSnapshot().rows
const failure = await removeProviderProfile(
face as unknown as Parameters<typeof removeProviderProfile>[0],
controller,
{ settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'openai'] },
)
expect(failure).toBe('read-only')
expect(controller.store.getSnapshot().rows).toBe(before)
})
it('keeps a failed identified deletion recoverable in its confirmation dialog', async () => {
const mutate = vi.fn()
.mockResolvedValueOnce(fail('the host refused'))
.mockResolvedValueOnce(ok(wireNamespaces()[2]!))
const { unset } = await mountSection({ mutate })
fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.removeProvider) }))
const dialog = screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) })
const confirm = within(dialog).getByRole('button', { name: openaiCopy(en.deleteConfirm) })
fireEvent.click(confirm)
await within(dialog).findByText('the host refused')
expect(screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBe(dialog)
expect(unset).toHaveBeenCalledOnce()
expect(mutate).toHaveBeenCalledOnce()
fireEvent.click(confirm)
await waitFor(() => { expect(unset).toHaveBeenCalledTimes(2) })
await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(2) })
await waitFor(() => {
expect(screen.queryByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBeNull()
})
})
it('retains credentials that are not identified as page-managed', async () => {
const { unset, mutate } = await mountSection()
const target = { provider: 'zombie', displayName: 'zombie' }
fireEvent.click(screen.getByRole('button', { name: providerCopy(en.removeProvider, target) }))
const dialog = screen.getByRole('dialog', { name: providerCopy(en.deleteTitle, target) })
expect(dialog.textContent).toContain(providerCopy(en.deleteDescription, target))
fireEvent.click(within(dialog).getByRole('button', { name: providerCopy(en.deleteConfirm, target) }))
await waitFor(() => { expect(mutate).toHaveBeenCalledOnce() })
expect(unset).not.toHaveBeenCalled()
expect(mutate.mock.calls[0]?.[0]).toEqual({
ns: 'llm-pi-ai',
ops: [{ op: 'unset', path: ['providers', 'zombie'] }],
})
})
it('does not remove provider settings when its managed credential removal is refused', async () => {
const { face, controller, mutate } = await mountSection({
unset: vi.fn(() => Promise.resolve(fail('credential is read-only', 'credential-rejected'))),
})
const failure = await removeProviderProfile(
face as unknown as Parameters<typeof removeProviderProfile>[0],
controller,
{
settingsNs: 'llm-pi-ai',
settingsPath: ['providers', 'openai'],
credentialRef: 'OPENAI_API_KEY',
},
)
expect(failure).toBe('credential is read-only')
expect(mutate).not.toHaveBeenCalled()
})
it('reports a transport rejection instead of failing the removal silently', async () => {
const { face, controller } = await mountSection({
mutate: vi.fn(() => Promise.reject(new Error('connection lost'))),
})
const failure = await removeProviderProfile(
face as unknown as Parameters<typeof removeProviderProfile>[0],
controller,
{ settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'openai'] },
)
expect(failure).toBe('connection lost')
})
})
describe('apiKeyFailure', () => {
it('treats a blank field as no failure — it means keep the stored key', () => {
expect(apiKeyFailure('')).toBeUndefined()
})
it.each([
['a printable-ASCII key', 'sk-0123456789'],
['a padded key, which the caller trims', ' sk-abc '],
['the printable-ASCII boundary characters', '!~'],
['a hyphenated key carrying an equals sign', 'sk-ABC=xyz'],
['an all-upper-case key ending in base64 padding', 'ABCD=='],
['an all-upper-case key ending in one padding character', 'MNOPQRST='],
])('accepts %s', (_label, draft) => {
expect(apiKeyFailure(draft)).toBeUndefined()
})
it.each([
['spaces', ' '],
['a tab', '\t'],
])('fails a field holding only %s instead of silently dropping it', (_label, draft) => {
expect(apiKeyFailure(draft)).toBe('keyBlank')
})
it.each([
['an emoji', 'sk-\u{1F600}'],
['CJK text', 'sk-你好'],
['full-width punctuation', 'sk-abc'],
['an interior space', 'sk-abc def'],
['a C0 control character', 'sk-abc\x01'],
['a latin-1 character', 'sk-café'],
])('fails %s as illegal characters', (_label, draft) => {
expect(apiKeyFailure(draft)).toBe('keyIllegalCharacters')
})
it.each([
['a pasted environment line', 'DEEPSEEK_API_KEY=sk-abc'],
['double quotes', '"sk-abc"'],
['single quotes', '\'sk-abc\''],
['backticks', '`sk-abc`'],
])('fails %s as a format failure', (_label, draft) => {
expect(apiKeyFailure(draft)).toBe('keyIllegalCharacters')
})
it('needs a matching closing quote before it calls a value wrapped', () => {
// A lone quote and an unbalanced one are legal printable ASCII, so the
// heuristic leaves them alone rather than guessing at a paste error.
expect(apiKeyFailure('"')).toBeUndefined()
expect(apiKeyFailure('"a')).toBeUndefined()
})
})