88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { Context } from 'cordis'
|
|
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
|
|
import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
|
|
|
|
/**
|
|
* Minimal concrete runtime: records requests, "executes" by invoking every
|
|
* binding once in declaration order, and lets tests script the outcome. The
|
|
* seam package ships no implementation, so the contract is exercised through
|
|
* the smallest subclass that honors it.
|
|
*/
|
|
class StubRuntime extends CodeRuntime {
|
|
readonly language = 'typescript'
|
|
readonly isolation = 'in-process-stub'
|
|
requests: CodeRunRequest[] = []
|
|
nextResult: CodeRunResult = { logs: [] }
|
|
|
|
async run(request: CodeRunRequest): Promise<CodeRunResult> {
|
|
this.requests.push(request)
|
|
if (request.signal?.aborted) {
|
|
return { logs: [], error: { kind: 'abort', message: String(request.signal.reason) } }
|
|
}
|
|
for (const namespace of request.bindings) {
|
|
for (const fn of Object.values(namespace.functions)) {
|
|
await fn({ from: 'stub' })
|
|
}
|
|
}
|
|
return this.nextResult
|
|
}
|
|
}
|
|
|
|
async function setup() {
|
|
const ctx = new Context()
|
|
await ctx.plugin(StubRuntime)
|
|
const runtime = ctx.codeRuntime as StubRuntime
|
|
return { ctx, runtime }
|
|
}
|
|
|
|
describe('CodeRuntime service seam', () => {
|
|
it('registers as ctx.codeRuntime and serves the abstract API', async () => {
|
|
const { runtime } = await setup()
|
|
expect(runtime.language).toBe('typescript')
|
|
expect(runtime.isolation).toBe('in-process-stub')
|
|
|
|
const calls: unknown[] = []
|
|
const result = await runtime.run({
|
|
program: 'return 1',
|
|
bindings: [{ global: 'tools', functions: { probe: async args => void calls.push(args) } }],
|
|
})
|
|
expect(result).toEqual({ logs: [] })
|
|
expect(calls).toEqual([{ from: 'stub' }])
|
|
expect(runtime.requests).toHaveLength(1)
|
|
})
|
|
|
|
it('reports a failed run as an error field on a resolved result, never a rejection', async () => {
|
|
const { runtime } = await setup()
|
|
runtime.nextResult = {
|
|
logs: ['boom'],
|
|
error: { kind: 'exception', message: 'boom' },
|
|
}
|
|
const result = await runtime.run({ program: 'throw new Error("boom")', bindings: [] })
|
|
expect(result.error).toEqual({ kind: 'exception', message: 'boom' })
|
|
expect(result.value).toBeUndefined()
|
|
})
|
|
|
|
it('reports a pre-aborted signal as an abort failure', async () => {
|
|
const { runtime } = await setup()
|
|
const controller = new AbortController()
|
|
controller.abort('cancelled')
|
|
const result = await runtime.run({ program: 'return 1', bindings: [], signal: controller.signal })
|
|
expect(result.error).toEqual({ kind: 'abort', message: 'cancelled' })
|
|
})
|
|
|
|
it('is removed from the context when the providing fiber disposes (HMR safety)', async () => {
|
|
const ctx = new Context()
|
|
const fiber = await ctx.plugin(StubRuntime)
|
|
expect(ctx.get('codeRuntime')).toBeInstanceOf(StubRuntime)
|
|
|
|
await fiber.dispose()
|
|
expect(ctx.get('codeRuntime')).toBeUndefined()
|
|
})
|
|
|
|
it('rejects a second implementation in the same context (duplicate service)', async () => {
|
|
const { ctx } = await setup()
|
|
await expect(ctx.plugin(StubRuntime)).rejects.toThrow(/registered/)
|
|
})
|
|
})
|