mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-01 04:00:45 +00:00
76 lines
3.2 KiB
TypeScript
76 lines
3.2 KiB
TypeScript
/** Test-owned Remote face: `$on` subscriptions with an explicit test event driver. */
|
|
import type { Context } from '@deepseek-ai/cordis'
|
|
|
|
/**
|
|
* Remote service test double for the forwarded-event path. Feature specs need
|
|
* `ctx.remote.$on` to exist (their plugins inject `remote`) and need forwarded
|
|
* Host events to reach those subscribers, but not the wire — so this double
|
|
* implements subscription plus an explicit `emit` driver available only on the
|
|
* concrete test object. A spec that also calls one namespace scripts it through
|
|
* the constructor rather than reaching the real Client Remote service.
|
|
*
|
|
* `$mount` rejects: a spec that needs a real generated contribution installed —
|
|
* codecs, descriptors, and the wire — has outgrown this double and needs the
|
|
* real Client Remote service.
|
|
*
|
|
* One deliberate asymmetry with production: a throwing listener propagates out
|
|
* of the emit instead of being contained and logged, so a spec cannot lean on
|
|
* this double for the containment guarantee `$on` documents — assert that
|
|
* against the real service.
|
|
*/
|
|
export class TestRemote {
|
|
private readonly subscriptions = new Map<string, Set<(...args: never[]) => void>>()
|
|
|
|
/**
|
|
* Register the double as `ctx.remote`, plus one service per scripted
|
|
* namespace so a plugin injecting `remote.<name>` also unparks.
|
|
* @param ctx - the spec's root Context.
|
|
* @param namespaces - scripted namespace faces reached as `ctx.remote.<name>`.
|
|
*/
|
|
constructor(ctx: Context, namespaces: Readonly<Record<string, object>> = {}) {
|
|
for (const name of Object.keys(namespaces)) {
|
|
// A namespace named after one of the double's own members would replace
|
|
// it, and `$mount`'s rejection is the contract a spec relies on.
|
|
if (name in TestRemote.prototype || name === 'subscriptions') {
|
|
throw new TypeError(`TestRemote: scripted namespace "${name}" would shadow the double's own member`)
|
|
}
|
|
}
|
|
Object.assign(this, namespaces)
|
|
ctx.provide('remote', this)
|
|
for (const [name, face] of Object.entries(namespaces)) ctx.provide(`remote.${name}`, face)
|
|
}
|
|
|
|
/**
|
|
* Deliver one forwarded host event to its subscribers, standing in for the
|
|
* carrier that owns the frame sink.
|
|
* @param event - forwarded host event name.
|
|
* @param args - the Host argument list, verbatim.
|
|
*/
|
|
emit(event: string, args: readonly unknown[]): void {
|
|
const listeners = this.subscriptions.get(event)
|
|
if (listeners === undefined) return
|
|
for (const listener of [...listeners]) listener(...args as never[])
|
|
}
|
|
|
|
/**
|
|
* Subscribe to one forwarded host event.
|
|
* @param event - forwarded host event name.
|
|
* @param listener - receives the Host argument list verbatim.
|
|
* @returns disposer removing this subscription.
|
|
*/
|
|
$on(event: string, listener: (...args: never[]) => void): () => void {
|
|
const listeners = this.subscriptions.get(event) ?? new Set()
|
|
this.subscriptions.set(event, listeners)
|
|
listeners.add(listener)
|
|
return () => { listeners.delete(listener) }
|
|
}
|
|
|
|
/**
|
|
* Generated-namespace mount, unsupported by this double.
|
|
* @returns never; always rejects.
|
|
*/
|
|
$mount(): Promise<() => Promise<void>> {
|
|
return Promise.reject(new Error('TestRemote: $mount needs the real Client Remote service'))
|
|
}
|
|
}
|