test: finish the Remote-result and picker-split test migrations
Every generated Remote method resolves to `RemoteResult<T>`, so the Gateway client spec asserts the ok and error branches instead of the unwrapped value and a throw, and the generator fixtures declare the wrapper in the consumer face they typecheck. The RPC-failure test splits into the Host error carried verbatim in the error branch plus a transport throw folded into it. The runtime client, ui-command and ui-plan benches answer the generated commands Remote through its result branches and provide the `remote.commands` namespace their plugins now inject; the ui-command bench also serves the `$on` the service subscribes on construction. The directory-picker chooser mounts a backend and its surface as a pair, so the real-Loader composition serves both surface packages and asserts each entry arrives and leaves with its backend.
This commit is contained in:
@@ -14,7 +14,7 @@ import type { ConversationNodeDefinition } from '../src/client/contract/conversa
|
||||
import { Session } from '../src/client/sessions/session.ts'
|
||||
import type { SessionsService } from '../src/client/sessions/service.ts'
|
||||
import type { WorkspacesService } from '../src/client/workspaces/service.ts'
|
||||
import { FakeApiClient, ok } from './fake-api.ts'
|
||||
import { FakeApiClient, fakeRemote, ok } from './fake-api.ts'
|
||||
|
||||
interface Bench {
|
||||
ctx: Context
|
||||
@@ -45,6 +45,7 @@ async function mount(): Promise<Bench> {
|
||||
}
|
||||
ctx.reflect.provide('connection', handle)
|
||||
ctx.reflect.provide('remote', {})
|
||||
ctx.reflect.provide('remote.commands', fakeRemote().commands)
|
||||
await ctx.plugin(RuntimeClient).await()
|
||||
return bench
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import TypertRegistry from '@deepseek-ai/dsh-typert-registry'
|
||||
// key face and per-event listener signatures.
|
||||
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import * as RuntimeClient from '../src/client/index.ts'
|
||||
import { FakeApiClient } from './fake-api.ts'
|
||||
import { FakeApiClient, fakeRemote } from './fake-api.ts'
|
||||
|
||||
/**
|
||||
* Compile-time face of `ctx.remote.$on`, asserted by type-checking this file
|
||||
@@ -74,6 +74,7 @@ async function mount(): Promise<Bench> {
|
||||
},
|
||||
}
|
||||
ctx.reflect.provide('connection', handle)
|
||||
ctx.reflect.provide('remote.commands', fakeRemote().commands)
|
||||
await ctx.plugin(RuntimeClient).await()
|
||||
return bench
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ async function bench() {
|
||||
scopeOf: (c: Context) => scopeOf(c),
|
||||
})
|
||||
const commandsRemote = { list: () => Promise.resolve([]) }
|
||||
ctx.provide('remote', { commands: commandsRemote })
|
||||
// The service subscribes its cache-invalidation events on construction, so
|
||||
// the Remote face needs `$on` even where this spec dispatches none.
|
||||
ctx.provide('remote', { commands: commandsRemote, $on: () => () => {} })
|
||||
ctx.provide('remote.commands', commandsRemote)
|
||||
await ctx.plugin(SlotsService).await()
|
||||
ctx.slots.register({
|
||||
|
||||
@@ -40,6 +40,28 @@ interface BenchOptions {
|
||||
addressed?: SessionId
|
||||
}
|
||||
|
||||
/**
|
||||
* Fold one programmed answer into the generated Remote face's outcome: a
|
||||
* resolved value is the ok branch, a rejection is the transport failure the
|
||||
* carrier reports in the error branch instead of throwing at the caller.
|
||||
* @param produce - the scripted answer for one Remote method.
|
||||
* @returns the carried result the service reads.
|
||||
*/
|
||||
async function carried<T>(produce: () => Promise<T>) {
|
||||
try {
|
||||
return { ok: true as const, value: await produce() }
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false as const,
|
||||
error: {
|
||||
code: 'internal',
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
details: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bench(opts: BenchOptions = {}) {
|
||||
const ctx = new Context()
|
||||
const registered = new Map<string, SlashSource>()
|
||||
@@ -50,21 +72,22 @@ async function bench(opts: BenchOptions = {}) {
|
||||
const commandsRemote = {
|
||||
list: async (sessionId: SessionId) => {
|
||||
listCalls.push({ sessionId })
|
||||
const value = await (opts.commands ?? (p => Promise.resolve({
|
||||
commands: p.sessionId === sid('s2') ? S2_CMDS : S1_CMDS,
|
||||
})))({ sessionId })
|
||||
return { ok: true as const, value: value.commands }
|
||||
return await carried(async () => {
|
||||
const value = await (opts.commands ?? (p => Promise.resolve({
|
||||
commands: p.sessionId === sid('s2') ? S2_CMDS : S1_CMDS,
|
||||
})))({ sessionId })
|
||||
return value.commands
|
||||
})
|
||||
},
|
||||
execute: async (sessionId: SessionId, line: string) => {
|
||||
executeCalls.push({ sessionId, line })
|
||||
const fallback = (): Promise<ExecuteValue> => Promise.resolve({ matched: true })
|
||||
const value = await (opts.execute ?? fallback)({ sessionId, line })
|
||||
return {
|
||||
ok: true as const,
|
||||
value: value.matched
|
||||
return await carried(async () => {
|
||||
const fallback = (): Promise<ExecuteValue> => Promise.resolve({ matched: true })
|
||||
const value = await (opts.execute ?? fallback)({ sessionId, line })
|
||||
return value.matched
|
||||
? { commandId: value.commandId ?? 'fake-command', result: { kind: 'success' as const } }
|
||||
: undefined,
|
||||
}
|
||||
: undefined
|
||||
})
|
||||
},
|
||||
}
|
||||
ctx.provide('slash', {
|
||||
|
||||
@@ -26,7 +26,7 @@ async function bench() {
|
||||
children: { 'conversation.input.plan': { kind: 'single', scope: 'session' } },
|
||||
} as never, () => null)
|
||||
const execute = vi.fn((_sessionId: SessionId, _line: string) =>
|
||||
Promise.resolve({ commandId: 'c1', result: { kind: 'success' as const } }))
|
||||
Promise.resolve({ ok: true, value: { commandId: 'c1', result: { kind: 'success' as const } } }))
|
||||
const commandsRemote = { execute }
|
||||
ctx.provide('remote', { commands: commandsRemote })
|
||||
ctx.provide('remote.commands', commandsRemote)
|
||||
@@ -71,14 +71,15 @@ describe('ui-plan browser apply', () => {
|
||||
expect(b.execute).toHaveBeenLastCalledWith(SID, '/plan off')
|
||||
|
||||
// Business failure folds to the composer-visible line: the generated method
|
||||
// throws with the RPC failure as its cause.
|
||||
b.execute.mockRejectedValueOnce(new Error('client api: commands/execute failed', {
|
||||
cause: { code: 'session-not-found', message: 'gone', details: {} },
|
||||
}))
|
||||
// reports the RPC failure in its error branch.
|
||||
b.execute.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
error: { code: 'session-not-found', message: 'gone', details: {} },
|
||||
} as never)
|
||||
await expect(injected.exitPlanMode()).resolves.toBe('gone (session-not-found)')
|
||||
|
||||
// Unmatched admission (plan-mode not composed host-side) is also a failure line.
|
||||
b.execute.mockResolvedValueOnce(undefined as never)
|
||||
b.execute.mockResolvedValueOnce({ ok: true, value: undefined } as never)
|
||||
await expect(injected.exitPlanMode()).resolves.toBe('unknown command: /plan off')
|
||||
|
||||
await fiber.dispose()
|
||||
|
||||
Reference in New Issue
Block a user