test(api): align migrated client contracts

This commit is contained in:
imccyu
2026-08-27 21:57:57 +08:00
parent 5f6293e67a
commit 89ee54ebb7
6 changed files with 35 additions and 33 deletions
@@ -114,11 +114,11 @@ describe('session/openWorkspacePath', () => {
}, aborted.signal)).rejects.toMatchObject({ failure: { code: 'cancelled' } })
inspect.mockRejectedValueOnce('storage offline')
await expect(controller.openWorkspacePath({
const failed = controller.openWorkspacePath({
sessionId: SessionId('inspection-failed'), path: 'result.html',
}, new AbortController().signal)).rejects.toMatchObject({
failure: { code: 'internal', message: expect.stringContaining('storage offline') },
})
}, new AbortController().signal)
await expect(failed).rejects.toMatchObject({ failure: { code: 'internal' } })
await expect(failed).rejects.toThrow('storage offline')
})
it('classifies opener cancellation and non-Error failures', async () => {
@@ -183,10 +183,9 @@ describe('SessionSkillCatalog', () => {
} as never)
const catalog = new SessionSkillCatalog(ctx)
await expect(catalog.list({ sessionId }, new AbortController().signal))
.rejects.toMatchObject({
failure: { code: 'internal', message: expect.stringContaining('skill registry is absent') },
})
const failed = catalog.list({ sessionId }, new AbortController().signal)
await expect(failed).rejects.toMatchObject({ failure: { code: 'internal' } })
await expect(failed).rejects.toThrow('skill registry is absent')
})
it('rejects observations without projections or a project cwd', async () => {
@@ -199,14 +198,12 @@ describe('SessionSkillCatalog', () => {
ctx.provide('sessionQuery', { observeSession } as never)
const catalog = new SessionSkillCatalog(ctx)
await expect(catalog.list({ sessionId }, new AbortController().signal))
.rejects.toMatchObject({
failure: { code: 'internal', message: expect.stringContaining('projected Session observation') },
})
await expect(catalog.list({ sessionId }, new AbortController().signal))
.rejects.toMatchObject({
failure: { code: 'internal', message: expect.stringContaining('has no project cwd') },
})
const unprojected = catalog.list({ sessionId }, new AbortController().signal)
await expect(unprojected).rejects.toMatchObject({ failure: { code: 'internal' } })
await expect(unprojected).rejects.toThrow('projected Session observation')
const cwdless = catalog.list({ sessionId }, new AbortController().signal)
await expect(cwdless).rejects.toMatchObject({ failure: { code: 'internal' } })
await expect(cwdless).rejects.toThrow('has no project cwd')
})
it('classifies a provider listing failure', async () => {
@@ -37,6 +37,11 @@ export type * from './types.ts'
const settingsNamespaceRequestSchema = z.object({ ns: z.string().min(1) })
/** Read abort state afresh after an awaited provider or opener call. */
function isAborted(signal: AbortSignal): boolean {
return signal.aborted
}
/** Native document-opening policy. */
export interface Config {
/** Override platform desktop-opener detection. */
@@ -185,23 +190,23 @@ export class SettingsController extends TypertRemoteService {
@Remote
async openSettingsDocument(signal: AbortSignal): Promise<SettingsDocumentOpenValue> {
const settings = this.provider()
if (signal.aborted) throw cancelled('settings document open was aborted')
if (isAborted(signal)) throw cancelled('settings document open was aborted')
let path: string | undefined
try {
path = await settings.prepareDocument()
} catch (error: unknown) {
if (signal.aborted) throw cancelled('settings document preparation was aborted')
if (isAborted(signal)) throw cancelled('settings document preparation was aborted')
throw internal(`settings document preparation failed: ${messageOf(error)}`)
}
if (path === undefined) {
throw internal('settings provider has no local document to open')
}
if (signal.aborted) throw cancelled('settings document open was aborted')
if (isAborted(signal)) throw cancelled('settings document open was aborted')
try {
await this.openTextFile(path, signal)
return { opened: true }
} catch (error: unknown) {
if (signal.aborted) throw cancelled('settings document open was aborted')
if (isAborted(signal)) throw cancelled('settings document open was aborted')
throw internal(`path open failed: ${messageOf(error)}`)
}
}
@@ -263,13 +263,15 @@ describe('the settings Remote namespace a configuration page calls', () => {
it('preserves settings-document absence, failure, and cancellation', async () => {
const absent = await boot()
await expect(absent.controller.openSettingsDocument(new AbortController().signal))
.rejects.toMatchObject({ failure: { code: 'internal', message: expect.stringContaining('no local document') } })
const missingDocument = absent.controller.openSettingsDocument(new AbortController().signal)
await expect(missingDocument).rejects.toMatchObject({ failure: { code: 'internal' } })
await expect(missingDocument).rejects.toThrow('no local document')
const failed = await boot(DocumentSettings)
vi.spyOn(failed.ctx.settings, 'prepareDocument').mockRejectedValue(new Error('read failed'))
await expect(failed.controller.openSettingsDocument(new AbortController().signal))
.rejects.toMatchObject({ failure: { code: 'internal', message: expect.stringContaining('read failed') } })
const failedRead = failed.controller.openSettingsDocument(new AbortController().signal)
await expect(failedRead).rejects.toMatchObject({ failure: { code: 'internal' } })
await expect(failedRead).rejects.toThrow('read failed')
const cancelled = new AbortController()
cancelled.abort(new Error('cancelled'))
@@ -412,7 +414,7 @@ describe('the settings Remote namespace a configuration page calls', () => {
['unexpected preset failure', 'internal'],
] as const)('maps Agent preset resolution failure %#', async (error, code) => {
const ctx = new Context()
ctx.provide('agentPresets', { resolve: () => Promise.reject(error) } as never)
ctx.provide('agentPresets', { resolve: async () => { throw error } } as never)
const controller = new SettingsController(ctx)
await expect(controller.openAgentPresetDirectory('mine', new AbortController().signal))
@@ -3657,12 +3657,10 @@ export class FixtureApiClient extends AbstractApiClient {
/** Method-key dispatch into the in-memory contract impl (a real carrier routes by URL path instead). */
private dispatch(
method: keyof RpcMethodMap,
_method: keyof RpcMethodMap,
request: RpcRequest<never>,
): Promise<RpcResponse<unknown>> {
switch (method) {
case 'host.describe': return this.api.host.describe(request)
}
return this.api.host.describe(request)
}
}
@@ -668,7 +668,7 @@ describe('SubagentModelSelectionCardController', () => {
})
.mockImplementationOnce(() => refreshed.promise)
const controller = new SubagentModelSelectionCardController(
host.scope, { modelCatalog: models } as never,
host.scope, { modelCatalog: models },
)
const face = controller.inject()
const state = () => face.hooks.subagentModelSelectionCard.getSnapshot()
@@ -747,7 +747,7 @@ describe('SubagentModelSelectionCardController', () => {
},
})
const controller = new SubagentModelSelectionCardController(
host.scope, { modelCatalog: models } as never,
host.scope, { modelCatalog: models },
)
const state = () => controller.inject().hooks.subagentModelSelectionCard.getSnapshot()
await vi.waitFor(() => { expect(state().candidates[0]?.provider).toBe('alpha') })
@@ -802,7 +802,7 @@ describe('SubagentModelSelectionCardController', () => {
const pending = deferred<never>()
const models = vi.fn(() => pending.promise)
const controller = new SubagentModelSelectionCardController(host.scope, { modelCatalog: models } as never)
const controller = new SubagentModelSelectionCardController(host.scope, { modelCatalog: models })
const face = controller.inject()
face.toggleEnabled()
face.retryCatalog()
@@ -814,7 +814,7 @@ describe('SubagentModelSelectionCardController', () => {
const pendingResolve = deferred<never>()
const resolving = new SubagentModelSelectionCardController(
host.scope,
{ modelCatalog: () => pendingResolve.promise } as never,
{ modelCatalog: () => pendingResolve.promise },
)
const resolvingFace = resolving.inject()
resolvingFace.toggleEnabled()