Files
deepseek-harness/packages/client/ui-agent-preset/tests/settings-store.spec.ts
Yichen Jiang e265c6038f test: cover the agent-preset wire routes and both restore edges
master moved the RPC surface onto the fetch transport, so the two agentPreset
methods arrived there with nothing driving them. Adds the round trip, plus the
two `recompose` edges that had none: an agent that never composed a preset, so
there is nothing to restore, and a restore that fails because the composition
it reaches for is gone — the roster is a live directory.

Marks the deliberate non-Error rejections in the store specs, which is the
branch they exist to cover.
2026-08-07 16:39:44 +08:00

429 lines
17 KiB
TypeScript

/**
* The agent-preset settings controller: it derives both the options and the
* current default from one roster call, writes only the `default` field, and
* treats an empty roster as "this deployment composes no presets" rather than
* as a failure.
*/
import { describe, expect, it } from 'vitest'
import type { IApiClient } from '@deepseek-ai/dsh-client-connection/client'
import {
AGENT_PRESET_SETTINGS_NS, AgentPresetSettingsController,
} from '../src/client/settings-store.ts'
import { AgentPresetSeatController } from '../src/client/seat-store.ts'
interface Recorded { ns: string; patch: unknown }
/** A client whose roster and write outcome the test controls. */
function fakeApi(
presets: { id: string; trust: 'system' | 'user'; isDefault: boolean }[],
options: { writes?: Recorded[]; failWrite?: string; failList?: string; readOnly?: boolean } = {},
): IApiClient {
return {
agentPresets: {
list: () => Promise.resolve(options.failList === undefined
? { rpcId: 'r', result: { ok: true as const, value: { presets } } }
: { rpcId: 'r', result: { ok: false as const, error: { code: 'internal', message: options.failList, details: {} } } }),
},
settings: {
// Loopback-only in production; a read-only provider answers writable:false
// and the row disables its control instead of offering a refused write.
describe: () => Promise.resolve({
rpcId: 'r',
result: {
ok: true as const,
value: { writable: options.readOnly !== true, hasDocument: true, namespaces: [] },
},
}),
update: (payload: { ns: string; patch: unknown }) => {
options.writes?.push({ ns: payload.ns, patch: payload.patch })
if (options.failWrite !== undefined) {
return Promise.resolve({ rpcId: 'r', result: { ok: false as const, error: { code: 'internal', message: options.failWrite, details: {} } } })
}
// A committed write moves the roster's default, exactly as the host does.
for (const preset of presets) {
preset.isDefault = preset.id === (payload.patch as { default?: string }).default
}
return Promise.resolve({ rpcId: 'r', result: { ok: true as const, value: {} } })
},
},
} as unknown as IApiClient
}
describe('the agent-preset settings controller', () => {
it('disables the control when this browser may not write settings', async () => {
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
], { readOnly: true }))
await controller.load()
// `settings.describe` is loopback-only and reports a read-only provider;
// offering a control whose write answers `settings-not-exposed` would
// promise a switch the host refuses.
expect(controller.store.getSnapshot().writable).toBe(false)
expect(controller.store.getSnapshot().currentValue).toBe('standard')
})
it('derives options and the current default from one roster call', async () => {
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'mine', trust: 'user', isDefault: false },
]))
await controller.load()
const state = controller.store.getSnapshot()
expect(state.status).toBe('ready')
expect(state.currentValue).toBe('standard')
expect(state.options).toEqual([
{ id: 'standard', trust: 'system' },
{ id: 'mine', trust: 'user' },
])
})
it('reports an empty roster as unavailable, not as an error', async () => {
const controller = new AgentPresetSettingsController(fakeApi([]))
await controller.load()
// A deployment composing no presets is valid: every session shares the
// host composition and the row renders nothing.
expect(controller.store.getSnapshot().status).toBe('unavailable')
expect(controller.store.getSnapshot().error).toBeNull()
})
it('writes only the default field, into the agent-presets namespace', async () => {
const writes: Recorded[] = []
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
], { writes }))
await controller.load()
await controller.select('core-web')
expect(writes).toEqual([{ ns: AGENT_PRESET_SETTINGS_NS, patch: { default: 'core-web' } }])
expect(controller.store.getSnapshot().currentValue).toBe('core-web')
})
it('restores the previous value and surfaces the message when the write fails', async () => {
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
], { failWrite: 'read-only settings' }))
await controller.load()
await controller.select('core-web')
const state = controller.store.getSnapshot()
expect(state.currentValue).toBe('standard')
expect(state.error).toBe('read-only settings')
expect(state.status).toBe('ready')
})
it('ignores a pick that is already the default', async () => {
const writes: Recorded[] = []
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
], { writes }))
await controller.load()
await controller.select('standard')
expect(writes).toEqual([])
})
it('surfaces a roster failure without claiming the deployment has no presets', async () => {
const controller = new AgentPresetSettingsController(fakeApi([], { failList: 'host down' }))
await controller.load()
const state = controller.store.getSnapshot()
expect(state.status).toBe('error')
expect(state.error).toBe('host down')
})
it('falls back to the first preset when the roster names no default', async () => {
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: false },
{ id: 'core-web', trust: 'system', isDefault: false },
]))
await controller.load()
// The row has to show something the menu can select; the first row of the
// roster is the deployment's own order.
expect(controller.store.getSnapshot().currentValue).toBe('standard')
})
it('reads a rejection that is not an Error', async () => {
const rejecting = (value: unknown): IApiClient => ({
agentPresets: {
list: () => Promise.resolve({
rpcId: 'r',
result: { ok: true as const, value: { presets: [{ id: 'standard', trust: 'system', isDefault: true }] } },
}),
},
settings: {
describe: () => Promise.resolve({
rpcId: 'r', result: { ok: true as const, value: { writable: true, hasDocument: true, namespaces: [] } },
}),
// oxlint-disable-next-line typescript/prefer-promise-reject-errors -- the non-Error branch is what this covers
update: () => Promise.reject(value),
},
} as unknown as IApiClient)
const controller = new AgentPresetSettingsController(rejecting('socket closed'))
await controller.load()
await controller.select('core-web')
// A transport may reject with anything; the row still has to say something.
expect(controller.store.getSnapshot().error).toBe('socket closed')
const failing = new AgentPresetSettingsController({
// oxlint-disable-next-line typescript/prefer-promise-reject-errors -- the non-Error branch is what this covers
agentPresets: { list: () => Promise.reject('offline') },
settings: {
describe: () => Promise.resolve({
rpcId: 'r', result: { ok: true as const, value: { writable: true, hasDocument: true, namespaces: [] } },
}),
},
} as unknown as IApiClient)
await failing.load()
expect(failing.store.getSnapshot().error).toBe('offline')
})
it('lets one roster call in flight answer for every caller', async () => {
let answer = (): void => {}
const pending = new Promise<void>((resolve) => { answer = resolve })
let calls = 0
const api = {
agentPresets: {
list: async () => {
calls += 1
await pending
return { rpcId: 'r', result: { ok: true as const, value: { presets: [] } } }
},
},
settings: {
describe: () => Promise.resolve({
rpcId: 'r', result: { ok: true as const, value: { writable: true, hasDocument: true, namespaces: [] } },
}),
},
} as unknown as IApiClient
const controller = new AgentPresetSettingsController(api)
// Both the settings surface and a reconnect can ask at once; a second
// request must not race a roster the first is already reading.
const first = controller.load()
await controller.load()
answer()
await first
expect(calls).toBe(1)
})
it('reports a transport failure rather than throwing at the row', async () => {
const api = {
agentPresets: { list: () => Promise.reject(new Error('offline')) },
settings: {
describe: () => Promise.resolve({
rpcId: 'r', result: { ok: true as const, value: { writable: true, hasDocument: true, namespaces: [] } },
}),
update: () => Promise.reject(new Error('socket closed')),
},
} as unknown as IApiClient
const controller = new AgentPresetSettingsController(api)
await controller.load()
expect(controller.store.getSnapshot().status).toBe('error')
expect(controller.store.getSnapshot().error).toBe('offline')
})
it('restores the previous default when the write never reached the host', async () => {
const presets = [
{ id: 'standard', trust: 'system' as const, isDefault: true },
{ id: 'core-web', trust: 'system' as const, isDefault: false },
]
const api = {
agentPresets: {
list: () => Promise.resolve({ rpcId: 'r', result: { ok: true as const, value: { presets } } }),
},
settings: {
describe: () => Promise.resolve({
rpcId: 'r', result: { ok: true as const, value: { writable: true, hasDocument: true, namespaces: [] } },
}),
update: () => Promise.reject(new Error('socket closed')),
},
} as unknown as IApiClient
const controller = new AgentPresetSettingsController(api)
await controller.load()
await controller.select('core-web')
expect(controller.store.getSnapshot().currentValue).toBe('standard')
expect(controller.store.getSnapshot().status).toBe('ready')
expect(controller.store.getSnapshot().error).toBe('socket closed')
})
})
describe('the composer seat controller', () => {
/** A seat over a fixed session summary. */
function seat(
presets: { id: string; trust: 'system' | 'user'; isDefault: boolean }[],
summary: { blank: boolean; agentPreset?: string } | undefined,
options: { writes?: Recorded[]; failSelect?: string } = {},
): AgentPresetSeatController {
const api = {
agentPresets: {
list: () => Promise.resolve({ rpcId: 'r', result: { ok: true as const, value: { presets } } }),
select: (payload: { agentPreset: string }) => {
options.writes?.push({ ns: 'select', patch: payload.agentPreset })
return Promise.resolve(options.failSelect === undefined
? { rpcId: 'r', result: { ok: true as const, value: { agentPreset: payload.agentPreset } } }
: { rpcId: 'r', result: { ok: false as const, error: { code: 'agent-preset-locked', message: options.failSelect, details: {} } } })
},
},
} as unknown as IApiClient
return new AgentPresetSeatController(api, 's1' as never, () => summary)
}
const ROSTER: { id: string; trust: 'system' | 'user'; isDefault: boolean }[] = [
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
]
it('shows what the session runs, not the deployment default', async () => {
const controller = seat(ROSTER, { blank: true, agentPreset: 'core-web' })
await controller.load()
// A resumed session runs what it was created with; showing `standard`
// because it is the current default would be a lie about this session.
expect(controller.store.getSnapshot().current).toBe('core-web')
expect(controller.store.getSnapshot().switchable).toBe(true)
})
it('falls back to the roster default when the session records none', async () => {
const controller = seat(ROSTER, { blank: true })
await controller.load()
expect(controller.store.getSnapshot().current).toBe('standard')
})
it('is not switchable once the conversation has started', async () => {
const controller = seat(ROSTER, { blank: false, agentPreset: 'standard' })
await controller.load()
expect(controller.store.getSnapshot().switchable).toBe(false)
})
it('refuses to switch a session that already started', async () => {
const writes: Recorded[] = []
const controller = seat(ROSTER, { blank: false, agentPreset: 'standard' }, { writes })
await controller.load()
await controller.select('core-web')
// The host enforces the same rule; the seat simply never asks.
expect(writes).toEqual([])
expect(controller.store.getSnapshot().current).toBe('standard')
})
it('switches a blank session and keeps the host\'s answer', async () => {
const writes: Recorded[] = []
const controller = seat(ROSTER, { blank: true, agentPreset: 'standard' }, { writes })
await controller.load()
await controller.select('core-web')
expect(writes).toEqual([{ ns: 'select', patch: 'core-web' }])
expect(controller.store.getSnapshot().current).toBe('core-web')
})
it('restores the previous value when the host rejects the switch', async () => {
const controller = seat(ROSTER, { blank: true, agentPreset: 'standard' }, { failSelect: 'already started' })
await controller.load()
await controller.select('core-web')
const state = controller.store.getSnapshot()
expect(state.current).toBe('standard')
expect(state.error).toBe('already started')
})
it('reports no options when the session is unknown to the list yet', async () => {
const controller = seat([], undefined)
await controller.load()
expect(controller.store.getSnapshot().options).toEqual([])
expect(controller.store.getSnapshot().switchable).toBe(false)
})
it('reads a rejection that is not an Error', async () => {
const api = {
agentPresets: {
// oxlint-disable-next-line typescript/prefer-promise-reject-errors -- the non-Error branch is what this covers
list: () => Promise.reject('offline'),
// oxlint-disable-next-line typescript/prefer-promise-reject-errors -- the non-Error branch is what this covers
select: () => Promise.reject('socket closed'),
},
} as unknown as IApiClient
const controller = new AgentPresetSeatController(api, 's1' as never, () => ({ blank: true }))
await controller.load()
expect(controller.store.getSnapshot().error).toBe('offline')
controller.store.set({ ...controller.store.getSnapshot(), current: 'standard', switchable: true })
await controller.select('core-web')
// A transport may reject with anything; the seat still has to say something.
expect(controller.store.getSnapshot().error).toBe('socket closed')
})
it('surfaces a roster failure and keeps the seat unswitchable', async () => {
const api = {
agentPresets: {
list: () => Promise.resolve({
rpcId: 'r',
result: { ok: false as const, error: { code: 'internal', message: 'roster down', details: {} } },
}),
},
} as unknown as IApiClient
const controller = new AgentPresetSeatController(api, 's1' as never, () => ({ blank: true }))
await controller.load()
expect(controller.store.getSnapshot().error).toBe('roster down')
expect(controller.store.getSnapshot().switchable).toBe(false)
})
it('reports a transport failure on either call rather than throwing at the seat', async () => {
const api = {
agentPresets: {
list: () => Promise.reject(new Error('offline')),
select: () => Promise.reject(new Error('socket closed')),
},
} as unknown as IApiClient
const controller = new AgentPresetSeatController(api, 's1' as never, () => ({ blank: true }))
await controller.load()
expect(controller.store.getSnapshot().error).toBe('offline')
// A switch that never reached the host leaves the session on what it ran.
controller.store.set({ ...controller.store.getSnapshot(), current: 'standard', switchable: true })
await controller.select('core-web')
expect(controller.store.getSnapshot().current).toBe('standard')
expect(controller.store.getSnapshot().busy).toBe(false)
expect(controller.store.getSnapshot().error).toBe('socket closed')
})
})