@zhin.js/testing 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fake-hosts.d.ts +3 -1
- package/lib/fake-hosts.d.ts.map +1 -1
- package/lib/fake-hosts.js +2 -2
- package/lib/fake-hosts.js.map +1 -1
- package/lib/fake-plugin.d.ts +3 -1
- package/lib/fake-plugin.d.ts.map +1 -1
- package/lib/fake-plugin.js +13 -2
- package/lib/fake-plugin.js.map +1 -1
- package/lib/fake-runners.d.ts +45 -3
- package/lib/fake-runners.d.ts.map +1 -1
- package/lib/fake-runners.js +177 -3
- package/lib/fake-runners.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +51 -25
- package/lib/index.js.map +1 -1
- package/lib/test-module-runtime.d.ts.map +1 -1
- package/lib/test-module-runtime.js +17 -1
- package/lib/test-module-runtime.js.map +1 -1
- package/package.json +12 -12
- package/src/fake-hosts.ts +2 -2
- package/src/fake-plugin.ts +11 -3
- package/src/fake-runners.ts +239 -7
- package/src/index.ts +53 -24
- package/src/test-module-runtime.ts +16 -1
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { realpath } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { defineAdapter, type AdapterDefinition, type AdapterSegmentPolicy,
|
|
3
|
+
import { Adapter, createEndpointSpecification, endpointSpec, defineAdapter, type Endpoint, type AdapterDefinition, type AdapterSegmentPolicy,
|
|
4
4
|
type EndpointIdentity, type EndpointEventSink, type EndpointIncomingMessage, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
5
5
|
import { ImRuntime, type ConversationAddress, type MessageDispatchResult,
|
|
6
6
|
type RuntimeMessageDiagnosticsReader, type SendContent } from 'zhin.js/core/runtime';
|
|
@@ -16,9 +16,10 @@ export { FakeDatabaseRootHost, FakeOutboundRootHost, type FakeOutboundCall } fro
|
|
|
16
16
|
export { FakePlugin, createFakePlugin, runPluginSetup, resolvePluginConfig, PluginConfigValidationError, fakeResource,
|
|
17
17
|
type FakePluginOptions, type RunPluginSetupOptions, type FakeResource, type FakeRegisteredFeature } from './fake-plugin.js';
|
|
18
18
|
export { fakeOf } from './partial.js';
|
|
19
|
-
export { createFakeMessage, createCommandContext, runCommand, runMiddleware, runHandler,
|
|
19
|
+
export { createFakeMessage, createFakeInteraction, createCommandContext, runCommand, runMiddleware, runHandler,
|
|
20
20
|
createToolContext, runTool, toolOriginFromMessage,
|
|
21
|
-
type FakeMessage, type
|
|
21
|
+
type FakeMessage, type FakeSentDelivery, type FakeInteraction, type FakeInteractionOptions,
|
|
22
|
+
type RunToolOptions, type FakeMessageInit, type RunCommandOptions, type RunMiddlewareResult } from './fake-runners.js';
|
|
22
23
|
|
|
23
24
|
export interface PluginTestKitOptions {
|
|
24
25
|
/** A real, disposable test app with its own package.json, plugin entry, dependencies and mounts. */
|
|
@@ -80,27 +81,55 @@ export class PluginTestKit {
|
|
|
80
81
|
|| names.some(name => !/^[a-z][a-z0-9-]*$/.test(name))) {
|
|
81
82
|
throw new TypeError('Test endpoints must have unique lowercase names');
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
84
|
+
const [firstEndpoint = 'test'] = names;
|
|
85
|
+
this.#defaultEndpoint = firstEndpoint;
|
|
86
|
+
const recordAttempt = (endpoint: string, request: EndpointSendRequest): string => {
|
|
87
|
+
const id = `test-${this.#nextDelivery++}`;
|
|
88
|
+
this.#attempts.push(Object.freeze({
|
|
89
|
+
endpoint,
|
|
90
|
+
id,
|
|
91
|
+
conversation: captureDeliveryValue(request.conversation),
|
|
92
|
+
payload: captureDeliveryValue(request.payload),
|
|
93
|
+
}));
|
|
94
|
+
return id;
|
|
95
|
+
};
|
|
96
|
+
const registerOpen = (endpoint: string, spec: Endpoint[typeof endpointSpec]): void => {
|
|
97
|
+
this.#identities.set(endpoint, spec.identity);
|
|
98
|
+
this.#sinks.set(endpoint, spec.events);
|
|
99
|
+
};
|
|
100
|
+
const releaseEndpoint = (endpoint: string, spec: Endpoint[typeof endpointSpec]): void => {
|
|
101
|
+
if (this.#sinks.get(endpoint) !== spec.events) return;
|
|
102
|
+
this.#sinks.delete(endpoint);
|
|
103
|
+
this.#identities.delete(endpoint);
|
|
104
|
+
};
|
|
105
|
+
const definitions = new Map<string, AdapterDefinition>(names.map(name => {
|
|
106
|
+
class SyntheticClient {
|
|
107
|
+
readonly synthetic = true;
|
|
108
|
+
send(request: EndpointSendRequest): string {
|
|
109
|
+
return recordAttempt(name, request);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
class SyntheticEndpoint extends SyntheticClient implements Endpoint {
|
|
113
|
+
readonly [endpointSpec] = createEndpointSpecification(this, {
|
|
114
|
+
start: signal => signal.throwIfAborted(),
|
|
115
|
+
open: () => {
|
|
116
|
+
registerOpen(name, this[endpointSpec]);
|
|
117
|
+
},
|
|
118
|
+
close: () => this.#release(), stop: () => this.#release(),
|
|
119
|
+
send: request => this.send(request),
|
|
120
|
+
});
|
|
121
|
+
#release(): void {
|
|
122
|
+
releaseEndpoint(name, this[endpointSpec]);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
class SyntheticAdapter extends Adapter<SyntheticEndpoint> {
|
|
126
|
+
protected createEndpoint(): SyntheticEndpoint { return new SyntheticEndpoint(); }
|
|
127
|
+
}
|
|
128
|
+
return [name, defineAdapter({ capabilities: ['inbound', 'outbound'],
|
|
129
|
+
segments: options.segments ?? { html: 'text', interactive: 'text', markdown: 'text' },
|
|
130
|
+
createAdapter: () => new SyntheticAdapter(),
|
|
131
|
+
})];
|
|
132
|
+
}));
|
|
104
133
|
const modules = options.modules ?? new NativeDevelopmentModuleRuntime({ projectRoot, watch: false });
|
|
105
134
|
this.#root = new RootRuntime({ projectRoot,
|
|
106
135
|
modules: withTestEndpoints(modules, rootEntry, definitions),
|
|
@@ -24,7 +24,22 @@ export function withTestEndpoints(
|
|
|
24
24
|
});
|
|
25
25
|
return { ...loaded, default: definition } as T;
|
|
26
26
|
},
|
|
27
|
-
|
|
27
|
+
loadClientModule: delegate.loadClientModule
|
|
28
|
+
? delegate.loadClientModule.bind(delegate)
|
|
29
|
+
: async <T = unknown>(source: string): Promise<T> => {
|
|
30
|
+
let metadata: unknown;
|
|
31
|
+
try {
|
|
32
|
+
const loaded = await delegate.load<{ meta?: unknown }>(source);
|
|
33
|
+
metadata = loaded?.meta;
|
|
34
|
+
} catch {
|
|
35
|
+
// Browser-only client modules may not load in headless Node test mode; fall back to default metadata.
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
module: source,
|
|
39
|
+
hash: 'test',
|
|
40
|
+
...(metadata !== undefined ? { metadata } : {}),
|
|
41
|
+
}) as T;
|
|
42
|
+
},
|
|
28
43
|
...(delegate.invalidate ? { invalidate: delegate.invalidate.bind(delegate) } : {}),
|
|
29
44
|
...(delegate.affectedSources ? { affectedSources: delegate.affectedSources.bind(delegate) } : {}),
|
|
30
45
|
...(delegate.requiresProcessRestart ? { requiresProcessRestart: delegate.requiresProcessRestart.bind(delegate) } : {}),
|