@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/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { realpath } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { defineAdapter } from 'zhin.js/adapter';
|
|
3
|
+
import { Adapter, createEndpointSpecification, endpointSpec, defineAdapter } from 'zhin.js/adapter';
|
|
4
4
|
import { ImRuntime } from 'zhin.js/core/runtime';
|
|
5
5
|
import { rootPluginId } from 'zhin.js/plugin-runtime';
|
|
6
6
|
import { NativeDevelopmentModuleRuntime, NodePackageResolver, RootRuntime } from '@zhin.js/runtime';
|
|
@@ -10,7 +10,7 @@ export { FakeClock, FakeScheduleRootHost, parseCron } from './fake-clock.js';
|
|
|
10
10
|
export { FakeDatabaseRootHost, FakeOutboundRootHost } from './fake-hosts.js';
|
|
11
11
|
export { FakePlugin, createFakePlugin, runPluginSetup, resolvePluginConfig, PluginConfigValidationError, fakeResource } from './fake-plugin.js';
|
|
12
12
|
export { fakeOf } from './partial.js';
|
|
13
|
-
export { createFakeMessage, createCommandContext, runCommand, runMiddleware, runHandler, createToolContext, runTool, toolOriginFromMessage } from './fake-runners.js';
|
|
13
|
+
export { createFakeMessage, createFakeInteraction, createCommandContext, runCommand, runMiddleware, runHandler, createToolContext, runTool, toolOriginFromMessage } from './fake-runners.js';
|
|
14
14
|
/**
|
|
15
15
|
* Owns a test IM host and synthetic transports. Every operation enters the real Root Runtime.
|
|
16
16
|
* It does not install dependencies or replace providers. Discovered plugins still execute normally;
|
|
@@ -37,30 +37,56 @@ export class PluginTestKit {
|
|
|
37
37
|
|| names.some(name => !/^[a-z][a-z0-9-]*$/.test(name))) {
|
|
38
38
|
throw new TypeError('Test endpoints must have unique lowercase names');
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
40
|
+
const [firstEndpoint = 'test'] = names;
|
|
41
|
+
this.#defaultEndpoint = firstEndpoint;
|
|
42
|
+
const recordAttempt = (endpoint, request) => {
|
|
43
|
+
const id = `test-${this.#nextDelivery++}`;
|
|
44
|
+
this.#attempts.push(Object.freeze({
|
|
45
|
+
endpoint,
|
|
46
|
+
id,
|
|
47
|
+
conversation: captureDeliveryValue(request.conversation),
|
|
48
|
+
payload: captureDeliveryValue(request.payload),
|
|
49
|
+
}));
|
|
50
|
+
return id;
|
|
51
|
+
};
|
|
52
|
+
const registerOpen = (endpoint, spec) => {
|
|
53
|
+
this.#identities.set(endpoint, spec.identity);
|
|
54
|
+
this.#sinks.set(endpoint, spec.events);
|
|
55
|
+
};
|
|
56
|
+
const releaseEndpoint = (endpoint, spec) => {
|
|
57
|
+
if (this.#sinks.get(endpoint) !== spec.events)
|
|
58
|
+
return;
|
|
59
|
+
this.#sinks.delete(endpoint);
|
|
60
|
+
this.#identities.delete(endpoint);
|
|
61
|
+
};
|
|
62
|
+
const definitions = new Map(names.map(name => {
|
|
63
|
+
class SyntheticClient {
|
|
64
|
+
synthetic = true;
|
|
65
|
+
send(request) {
|
|
66
|
+
return recordAttempt(name, request);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
class SyntheticEndpoint extends SyntheticClient {
|
|
70
|
+
[endpointSpec] = createEndpointSpecification(this, {
|
|
71
|
+
start: signal => signal.throwIfAborted(),
|
|
72
|
+
open: () => {
|
|
73
|
+
registerOpen(name, this[endpointSpec]);
|
|
61
74
|
},
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
close: () => this.#release(), stop: () => this.#release(),
|
|
76
|
+
send: request => this.send(request),
|
|
77
|
+
});
|
|
78
|
+
#release() {
|
|
79
|
+
releaseEndpoint(name, this[endpointSpec]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
class SyntheticAdapter extends Adapter {
|
|
83
|
+
createEndpoint() { return new SyntheticEndpoint(); }
|
|
84
|
+
}
|
|
85
|
+
return [name, defineAdapter({ capabilities: ['inbound', 'outbound'],
|
|
86
|
+
segments: options.segments ?? { html: 'text', interactive: 'text', markdown: 'text' },
|
|
87
|
+
createAdapter: () => new SyntheticAdapter(),
|
|
88
|
+
})];
|
|
89
|
+
}));
|
|
64
90
|
const modules = options.modules ?? new NativeDevelopmentModuleRuntime({ projectRoot, watch: false });
|
|
65
91
|
this.#root = new RootRuntime({ projectRoot,
|
|
66
92
|
modules: withTestEndpoints(modules, rootEntry, definitions),
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-module-runtime.d.ts","sourceRoot":"","sources":["../src/test-module-runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,gGAAgG;AAChG,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAChD,aAAa,
|
|
1
|
+
{"version":3,"file":"test-module-runtime.d.ts","sourceRoot":"","sources":["../src/test-module-runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,gGAAgG;AAChG,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAChD,aAAa,CA0Cf"}
|
|
@@ -19,7 +19,23 @@ export function withTestEndpoints(delegate, rootEntry, endpoints) {
|
|
|
19
19
|
});
|
|
20
20
|
return { ...loaded, default: definition };
|
|
21
21
|
},
|
|
22
|
-
|
|
22
|
+
loadClientModule: delegate.loadClientModule
|
|
23
|
+
? delegate.loadClientModule.bind(delegate)
|
|
24
|
+
: async (source) => {
|
|
25
|
+
let metadata;
|
|
26
|
+
try {
|
|
27
|
+
const loaded = await delegate.load(source);
|
|
28
|
+
metadata = loaded?.meta;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Browser-only client modules may not load in headless Node test mode; fall back to default metadata.
|
|
32
|
+
}
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
module: source,
|
|
35
|
+
hash: 'test',
|
|
36
|
+
...(metadata !== undefined ? { metadata } : {}),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
23
39
|
...(delegate.invalidate ? { invalidate: delegate.invalidate.bind(delegate) } : {}),
|
|
24
40
|
...(delegate.affectedSources ? { affectedSources: delegate.affectedSources.bind(delegate) } : {}),
|
|
25
41
|
...(delegate.requiresProcessRestart ? { requiresProcessRestart: delegate.requiresProcessRestart.bind(delegate) } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-module-runtime.js","sourceRoot":"","sources":["../src/test-module-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAyB,MAAM,wBAAwB,CAAC;AAI7E,gGAAgG;AAChG,MAAM,UAAU,iBAAiB,CAC/B,QAAuB,EACvB,SAAiB,EACjB,SAAiD;IAEjD,OAAO;QACL,KAAK,CAAC,IAAI,CAAI,MAAc;YAC1B,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAI,MAAM,CAAC,CAAC;YAC9C,MAAM,SAAS,GAAG,MAAwC,CAAC;YAC3D,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,SAAS,IAAI,CAAC,SAAS,CAAC,OAAO;gBAAE,OAAO,MAAM,CAAC;YACvE,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC;YACnC,MAAM,UAAU,GAAG,YAAY,CAAC;gBAC9B,GAAG,QAAQ;gBACX,KAAK,CAAC,KAAK,CAAC,OAAO;oBACjB,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,SAAS;wBAAE,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;oBAC7E,OAAO,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC;gBACnC,CAAC;aACF,CAAC,CAAC;YACH,OAAO,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,UAAU,EAAO,CAAC;QACjD,CAAC;QACD,
|
|
1
|
+
{"version":3,"file":"test-module-runtime.js","sourceRoot":"","sources":["../src/test-module-runtime.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/testing",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Real Plugin Runtime integration fixtures with synthetic IM endpoints",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -17,22 +17,22 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"zhin.js": "^1.1.
|
|
21
|
-
"@zhin.js/runtime": "^1.1.
|
|
22
|
-
"@zhin.js/tool": "^1.1.
|
|
20
|
+
"zhin.js": "^1.1.22",
|
|
21
|
+
"@zhin.js/runtime": "^1.1.19",
|
|
22
|
+
"@zhin.js/tool": "^1.1.16"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^26.1.2",
|
|
26
26
|
"typescript": "^6.0.3",
|
|
27
27
|
"vitest": "^4.1.10",
|
|
28
|
-
"@zhin.js/adapter": "1.1.
|
|
29
|
-
"@zhin.js/command": "1.1.
|
|
30
|
-
"@zhin.js/core": "1.1.
|
|
31
|
-
"@zhin.js/middleware": "1.1.
|
|
32
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
33
|
-
"@zhin.js/runtime": "1.1.
|
|
34
|
-
"@zhin.js/tool": "1.1.
|
|
35
|
-
"zhin.js": "1.1.
|
|
28
|
+
"@zhin.js/adapter": "1.1.29",
|
|
29
|
+
"@zhin.js/command": "1.1.15",
|
|
30
|
+
"@zhin.js/core": "1.1.57",
|
|
31
|
+
"@zhin.js/middleware": "1.1.14",
|
|
32
|
+
"@zhin.js/plugin-runtime": "1.1.21",
|
|
33
|
+
"@zhin.js/runtime": "1.1.19",
|
|
34
|
+
"@zhin.js/tool": "1.1.16",
|
|
35
|
+
"zhin.js": "1.1.22"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"node": ">=22.19.0"
|
package/src/fake-hosts.ts
CHANGED
|
@@ -119,8 +119,8 @@ export class FakeOutboundRootHost implements OutboundRootHost {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
send(input: OutboundSendInput): Promise<string | null> { return this.#record('send', input); }
|
|
122
|
-
sendTarget(target: DeliveryTarget, content: unknown, requester: PluginId): Promise<string | null> {
|
|
123
|
-
return this.#record('sendTarget', { target, content }, requester);
|
|
122
|
+
sendTarget(target: DeliveryTarget, content: unknown, requester: PluginId, options?: Readonly<{ locale?: import('@zhin.js/plugin-runtime').Locale }>): Promise<string | null> {
|
|
123
|
+
return this.#record('sendTarget', { target, content, ...(options?.locale ? { locale: options.locale } : {}) }, requester);
|
|
124
124
|
}
|
|
125
125
|
addReaction(input: OutboundReactionInput): Promise<string | null> { return this.#record('addReaction', input); }
|
|
126
126
|
async removeReaction(input: unknown): Promise<void> { await this.#record('removeReaction', input, undefined, false); }
|
package/src/fake-plugin.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DisposeStack, GenerationHandoffStack, PluginDatabaseHost, PluginOutboundHost, PluginScheduleHost,
|
|
2
|
+
DisposeStack, GenerationHandoffStack, PluginDatabaseHost, PluginOutboundHost, PluginScheduleHost, createPluginEventChannel,
|
|
3
3
|
Scope, databaseHostToken, outboundHostToken, qualifyPluginResourceName, rootPluginId, scheduleHostToken,
|
|
4
4
|
type Dispose, type FeatureId, type PluginDefinition, type PluginId, type PluginNodeSnapshot,
|
|
5
|
-
type PluginSetupContext, type Token,
|
|
5
|
+
type PluginSetupContext, type Token, type PluginLifecycleEventMap,
|
|
6
6
|
} from 'zhin.js/plugin-runtime';
|
|
7
7
|
import type { CapabilityContext } from 'zhin.js';
|
|
8
8
|
import { parsePluginConfigSchema } from '@zhin.js/runtime';
|
|
@@ -66,6 +66,7 @@ export class FakePlugin<TConfig = unknown> {
|
|
|
66
66
|
readonly schedule: PluginScheduleHost;
|
|
67
67
|
readonly outbound: PluginOutboundHost;
|
|
68
68
|
readonly #abort = new AbortController();
|
|
69
|
+
readonly #events = createPluginEventChannel();
|
|
69
70
|
readonly #context: PluginSetupContext<TConfig>;
|
|
70
71
|
|
|
71
72
|
constructor(options: FakePluginOptions<TConfig> = {}) {
|
|
@@ -130,11 +131,18 @@ export class FakePlugin<TConfig = unknown> {
|
|
|
130
131
|
/** Abort `signal` and run every disposer (reverse order). */
|
|
131
132
|
async dispose(): Promise<void> {
|
|
132
133
|
this.#abort.abort();
|
|
133
|
-
await this.lifecycle.dispose();
|
|
134
|
+
try { await this.lifecycle.dispose(); }
|
|
135
|
+
finally { this.#events.dispose(); }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Deliver a Runtime lifecycle fact to this concrete test instance. */
|
|
139
|
+
dispatchLifecycle<TName extends keyof PluginLifecycleEventMap>(name: TName, event: PluginLifecycleEventMap[TName]): Promise<void> {
|
|
140
|
+
return this.#events.dispatch(name, event);
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
#createSetupContext(): PluginSetupContext<TConfig> {
|
|
137
144
|
const base = {
|
|
145
|
+
events: this.#events.events,
|
|
138
146
|
signal: this.signal,
|
|
139
147
|
plugin: Object.freeze({ id: this.id, instanceKey: this.name, root: this.id, role: 'root' as const }),
|
|
140
148
|
config: Object.freeze({ get: () => this.config as Readonly<TConfig> }),
|
package/src/fake-runners.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveLocale, type Locale } from 'zhin.js/i18n';
|
|
2
2
|
import type {
|
|
3
|
-
CommandContext, CommandDefinition, CommandMessage, CommandParameterValue,
|
|
3
|
+
CommandContext, CommandDefinition, CommandMessage, CommandParamSchema, CommandParameterValue, CommandSession,
|
|
4
4
|
} from 'zhin.js/command';
|
|
5
5
|
import type { HandlerContext, HandlerDefinition } from 'zhin.js/handler';
|
|
6
6
|
import type {
|
|
@@ -11,14 +11,41 @@ import {
|
|
|
11
11
|
type AgentToolDefinition, type ToolExecutionContext, type ToolInvocationOrigin,
|
|
12
12
|
type ToolInvocationPolicy, type ToolQuestionPort,
|
|
13
13
|
} from '@zhin.js/tool';
|
|
14
|
-
import type
|
|
14
|
+
import { readOperationSession, type OperationClientPort } from 'zhin.js';
|
|
15
15
|
import type { FakePlugin } from './fake-plugin.js';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
export interface FakeSentDelivery {
|
|
19
|
+
readonly target: {
|
|
20
|
+
readonly kind: 'private' | 'group' | 'channel';
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly parent?: { readonly kind: 'private' | 'group' | 'channel'; readonly id: string };
|
|
23
|
+
readonly threadId?: string;
|
|
24
|
+
};
|
|
25
|
+
readonly content: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Inbound message with recorded replies and targeted sends. */
|
|
19
29
|
export interface FakeMessage extends CommandMessage {
|
|
20
|
-
/** Everything passed to `$reply`, in order. */
|
|
30
|
+
/** Everything passed to `$reply`, `$sendTo`, `$replyToPrivate`, `$replyToGroup`, or `$replyToChannel`, in order. */
|
|
21
31
|
readonly replies: unknown[];
|
|
32
|
+
/** Targeted sends invoked via `$sendTo`, `$replyToPrivate`, `$replyToGroup`, or `$replyToChannel`. */
|
|
33
|
+
readonly sent: FakeSentDelivery[];
|
|
34
|
+
$sendTo(
|
|
35
|
+
conversation: FakeSentDelivery['target'],
|
|
36
|
+
content: unknown,
|
|
37
|
+
): Promise<string>;
|
|
38
|
+
$replyToPrivate(
|
|
39
|
+
content: unknown,
|
|
40
|
+
from?: boolean | { readonly kind: 'group' | 'channel'; readonly id: string },
|
|
41
|
+
): Promise<string>;
|
|
42
|
+
$replyToGroup(groupIdOrContent: string | unknown, content?: unknown): Promise<string>;
|
|
43
|
+
$replyToChannel(
|
|
44
|
+
channelIdOrContent: string | unknown,
|
|
45
|
+
guildId?: string,
|
|
46
|
+
content?: unknown,
|
|
47
|
+
threadId?: string,
|
|
48
|
+
): Promise<string>;
|
|
22
49
|
}
|
|
23
50
|
|
|
24
51
|
export interface FakeMessageInit {
|
|
@@ -33,17 +60,147 @@ export interface FakeMessageInit {
|
|
|
33
60
|
|
|
34
61
|
export function createFakeMessage(init: FakeMessageInit = {}): FakeMessage {
|
|
35
62
|
const replies: unknown[] = [];
|
|
63
|
+
const sent: FakeSentDelivery[] = [];
|
|
36
64
|
const conversation: CommandMessage['conversation'] = {
|
|
37
65
|
endpoint: { id: 'test', adapter: 'test' }, kind: 'group', id: 'test-group', ...init.conversation,
|
|
38
66
|
};
|
|
67
|
+
const sender = init.sender ?? { id: 'test-user', name: 'Tester', roles: [] };
|
|
68
|
+
const recordSend = (target: FakeSentDelivery['target'], content: unknown): string => {
|
|
69
|
+
replies.push(content);
|
|
70
|
+
sent.push({ target, content });
|
|
71
|
+
return `reply-${replies.length}`;
|
|
72
|
+
};
|
|
39
73
|
return {
|
|
40
74
|
conversation, content: init.content ?? '', locale: resolveLocale(init.locale),
|
|
41
|
-
sender
|
|
75
|
+
sender,
|
|
42
76
|
...(init.id === undefined ? {} : { id: init.id }),
|
|
43
77
|
...(init.metadata === undefined ? {} : { metadata: init.metadata }),
|
|
44
78
|
...(init.$client === undefined ? {} : { $client: init.$client }),
|
|
45
79
|
replies,
|
|
80
|
+
sent,
|
|
46
81
|
async $reply(content: unknown) { replies.push(content); return `reply-${replies.length}`; },
|
|
82
|
+
async $sendTo(target, content) {
|
|
83
|
+
return recordSend(target, content);
|
|
84
|
+
},
|
|
85
|
+
async $replyToPrivate(content, from) {
|
|
86
|
+
let parent: FakeSentDelivery['target']['parent'];
|
|
87
|
+
if (from === true && (conversation.kind === 'group' || conversation.kind === 'channel')) {
|
|
88
|
+
parent = { kind: conversation.kind, id: conversation.id };
|
|
89
|
+
} else if (from != null && typeof from === 'object') {
|
|
90
|
+
parent = from;
|
|
91
|
+
}
|
|
92
|
+
return recordSend({
|
|
93
|
+
kind: 'private',
|
|
94
|
+
id: sender.id,
|
|
95
|
+
...(parent ? { parent } : {}),
|
|
96
|
+
}, content);
|
|
97
|
+
},
|
|
98
|
+
async $replyToGroup(groupIdOrContent: string | unknown, maybeContent?: unknown) {
|
|
99
|
+
if (maybeContent === undefined) {
|
|
100
|
+
return recordSend({ kind: 'group', id: conversation.id }, groupIdOrContent);
|
|
101
|
+
}
|
|
102
|
+
return recordSend({ kind: 'group', id: String(groupIdOrContent) }, maybeContent);
|
|
103
|
+
},
|
|
104
|
+
async $replyToChannel(
|
|
105
|
+
channelIdOrContent: string | unknown,
|
|
106
|
+
guildId?: string,
|
|
107
|
+
content?: unknown,
|
|
108
|
+
threadId?: string,
|
|
109
|
+
) {
|
|
110
|
+
if (guildId === undefined && content === undefined) {
|
|
111
|
+
return recordSend({ kind: 'channel', id: conversation.id }, channelIdOrContent);
|
|
112
|
+
}
|
|
113
|
+
return recordSend({
|
|
114
|
+
kind: 'channel',
|
|
115
|
+
id: String(channelIdOrContent),
|
|
116
|
+
...(guildId ? { parent: { kind: 'channel', id: guildId } } : {}),
|
|
117
|
+
...(threadId ? { threadId } : {}),
|
|
118
|
+
}, content);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type FakeInteraction = NonNullable<CommandContext['interaction']> & {
|
|
124
|
+
/** Every interaction request presented via `ask`, `sequence`, `confirm`, or `select`, in order. */
|
|
125
|
+
readonly prompts: Parameters<NonNullable<CommandContext['interaction']>['ask']>[0][];
|
|
126
|
+
/** Append additional scripted answers to the queue. */
|
|
127
|
+
enqueue(...answers: unknown[]): void;
|
|
128
|
+
/** Convenience helper for confirm requests. */
|
|
129
|
+
confirm(title: string, defaultValue?: boolean): Promise<boolean>;
|
|
130
|
+
/** Convenience helper for select requests. */
|
|
131
|
+
select<V = string>(
|
|
132
|
+
title: string,
|
|
133
|
+
options: readonly (V | { readonly label: string; readonly value: V })[],
|
|
134
|
+
): Promise<V>;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export interface FakeInteractionOptions {
|
|
138
|
+
readonly answers?: readonly unknown[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a scripted `UserInteraction` double for unit-testing commands and handlers that use
|
|
143
|
+
* `interaction.ask()`, `interaction.sequence()`, `interaction.confirm()`, or `interaction.select()`.
|
|
144
|
+
*/
|
|
145
|
+
export function createFakeInteraction(
|
|
146
|
+
init: readonly unknown[] | FakeInteractionOptions = [],
|
|
147
|
+
): FakeInteraction {
|
|
148
|
+
type Interaction = NonNullable<CommandContext['interaction']>;
|
|
149
|
+
type Request = Parameters<Interaction['ask']>[0];
|
|
150
|
+
const queue: unknown[] = Array.isArray(init)
|
|
151
|
+
? [...init]
|
|
152
|
+
: [...(init as FakeInteractionOptions).answers ?? []];
|
|
153
|
+
const prompts: Request[] = [];
|
|
154
|
+
|
|
155
|
+
const ask: Interaction['ask'] = (async (request: Request) => {
|
|
156
|
+
prompts.push(request);
|
|
157
|
+
if (queue.length > 0) {
|
|
158
|
+
const next = queue.shift();
|
|
159
|
+
return typeof next === 'function' ? await (next as (r: Request) => unknown)(request) : next;
|
|
160
|
+
}
|
|
161
|
+
if ('default' in request && request.default !== undefined) {
|
|
162
|
+
return request.default;
|
|
163
|
+
}
|
|
164
|
+
throw new Error(`No scripted answer remaining for interaction prompt: ${request.title}`);
|
|
165
|
+
}) as Interaction['ask'];
|
|
166
|
+
|
|
167
|
+
const sequence: Interaction['sequence'] = (async (seq) => {
|
|
168
|
+
const acc: Record<string, unknown> = {};
|
|
169
|
+
for (const step of seq.steps) {
|
|
170
|
+
acc[step.id] = await ask(step);
|
|
171
|
+
}
|
|
172
|
+
return Object.freeze({ ...acc });
|
|
173
|
+
}) as Interaction['sequence'];
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
prompts,
|
|
177
|
+
enqueue(...answers: unknown[]) {
|
|
178
|
+
queue.push(...answers);
|
|
179
|
+
},
|
|
180
|
+
ask,
|
|
181
|
+
sequence,
|
|
182
|
+
confirm(title: string, defaultValue?: boolean): Promise<boolean> {
|
|
183
|
+
return ask({
|
|
184
|
+
type: 'confirm',
|
|
185
|
+
title,
|
|
186
|
+
...(defaultValue === undefined ? {} : { default: defaultValue }),
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
select<V = string>(
|
|
190
|
+
title: string,
|
|
191
|
+
options: readonly (V | { readonly label: string; readonly value: V })[],
|
|
192
|
+
): Promise<V> {
|
|
193
|
+
const normalized = options.map((opt) =>
|
|
194
|
+
opt !== null && typeof opt === 'object' && 'value' in opt && 'label' in opt
|
|
195
|
+
? opt
|
|
196
|
+
: { label: String(opt), value: opt as V },
|
|
197
|
+
);
|
|
198
|
+
return ask({
|
|
199
|
+
type: 'select',
|
|
200
|
+
title,
|
|
201
|
+
options: normalized,
|
|
202
|
+
}) as Promise<V>;
|
|
203
|
+
},
|
|
47
204
|
};
|
|
48
205
|
}
|
|
49
206
|
|
|
@@ -83,6 +240,7 @@ export function createCommandContext<TConfig, TInput extends CommandMessage = Fa
|
|
|
83
240
|
options: options.options ?? {},
|
|
84
241
|
segments: options.segments ?? [],
|
|
85
242
|
input,
|
|
243
|
+
get session() { return readOperationSession(input); },
|
|
86
244
|
$client: input.$client as never,
|
|
87
245
|
interaction: options.interaction,
|
|
88
246
|
adapter: conversation.endpoint.adapter,
|
|
@@ -103,7 +261,77 @@ export async function runCommand<TConfig, TResult, TInput extends CommandMessage
|
|
|
103
261
|
options?: RunCommandOptions<TInput>,
|
|
104
262
|
): Promise<TResult> {
|
|
105
263
|
const command = options?.source === undefined ? definition : await bindSource(definition, options.source);
|
|
106
|
-
|
|
264
|
+
const resolvedOptions = populateCommandOptionsFromDefinition(command, options);
|
|
265
|
+
return command.execute(createCommandContext(fake, resolvedOptions));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function populateCommandOptionsFromDefinition<TConfig, TResult, TInput extends CommandMessage>(
|
|
269
|
+
command: CommandDefinition<TConfig, TResult, TInput, string | undefined>,
|
|
270
|
+
options?: RunCommandOptions<TInput>,
|
|
271
|
+
): RunCommandOptions<TInput> | undefined {
|
|
272
|
+
if (!command.params && !command.options) return options;
|
|
273
|
+
const args = options?.args ?? [];
|
|
274
|
+
const input = (options?.input ?? createFakeMessage()) as TInput;
|
|
275
|
+
const session: CommandSession = {
|
|
276
|
+
adapter: input.conversation.endpoint.adapter,
|
|
277
|
+
endpoint: input.conversation.endpoint.id,
|
|
278
|
+
scene: input.scene ?? { id: input.conversation.id, type: input.conversation.kind },
|
|
279
|
+
...(input.sender ? { sender: { id: input.sender.id, name: input.sender.name, role: input.sender.roles ?? [] } } : {}),
|
|
280
|
+
};
|
|
281
|
+
const resolveDefault = (def: CommandParamSchema['default']): CommandParameterValue | undefined => {
|
|
282
|
+
if (def === undefined) return undefined;
|
|
283
|
+
return typeof def === 'function' ? def(session) : def;
|
|
284
|
+
};
|
|
285
|
+
const inferredParams: Record<string, CommandParameterValue> = {};
|
|
286
|
+
if (command.params) {
|
|
287
|
+
const entries = Object.entries(command.params);
|
|
288
|
+
for (let i = 0; i < entries.length; i++) {
|
|
289
|
+
const [name, schema] = entries[i]!;
|
|
290
|
+
if (schema.rest) {
|
|
291
|
+
const rest = args.slice(i);
|
|
292
|
+
if (rest.length > 0) {
|
|
293
|
+
inferredParams[name] = rest.map((token) => coerceCommandScalar(token, schema));
|
|
294
|
+
} else {
|
|
295
|
+
const def = resolveDefault(schema.default);
|
|
296
|
+
if (def !== undefined) inferredParams[name] = def;
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
const raw = args[i];
|
|
301
|
+
if (raw !== undefined) {
|
|
302
|
+
inferredParams[name] = coerceCommandScalar(raw, schema);
|
|
303
|
+
} else {
|
|
304
|
+
const def = resolveDefault(schema.default);
|
|
305
|
+
if (def !== undefined) inferredParams[name] = def;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const inferredOptions: Record<string, CommandParameterValue> = {};
|
|
310
|
+
if (command.options) {
|
|
311
|
+
for (const [name, opt] of Object.entries(command.options)) {
|
|
312
|
+
const def = resolveDefault(opt.default);
|
|
313
|
+
if (def !== undefined) {
|
|
314
|
+
inferredOptions[name] = def;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
...options,
|
|
320
|
+
params: { ...inferredParams, ...(options?.params ?? {}) },
|
|
321
|
+
options: { ...inferredOptions, ...(options?.options ?? {}) },
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function coerceCommandScalar(raw: string, schema: CommandParamSchema): string | number | boolean {
|
|
326
|
+
if (schema.type === 'number' || schema.type === 'integer' || schema.type === 'float' || schema.type === 'dice' || schema.type === 'rps') {
|
|
327
|
+
const parsed = Number(raw);
|
|
328
|
+
return Number.isNaN(parsed) ? raw : parsed;
|
|
329
|
+
}
|
|
330
|
+
if (schema.type === 'boolean') {
|
|
331
|
+
if (raw === 'true' || raw === '1') return true;
|
|
332
|
+
if (raw === 'false' || raw === '0') return false;
|
|
333
|
+
}
|
|
334
|
+
return raw;
|
|
107
335
|
}
|
|
108
336
|
|
|
109
337
|
async function bindSource<T extends CommandDefinition<never, unknown, never, string | undefined>>(
|
|
@@ -134,6 +362,7 @@ export async function runMiddleware<TConfig, TInput = FakeMessage>(
|
|
|
134
362
|
const context: MiddlewareContext<TInput, TConfig, string | undefined> = {
|
|
135
363
|
...fake.capabilityContext(),
|
|
136
364
|
input,
|
|
365
|
+
get session() { return readOperationSession(input); },
|
|
137
366
|
$client: (input as { $client?: unknown } | undefined)?.$client as never,
|
|
138
367
|
};
|
|
139
368
|
const next = (async () => { continued = true; return undefined; }) as unknown as MiddlewareNext;
|
|
@@ -147,7 +376,10 @@ export async function runHandler<TConfig>(
|
|
|
147
376
|
definition: HandlerDefinition,
|
|
148
377
|
...args: unknown[]
|
|
149
378
|
): Promise<void> {
|
|
150
|
-
const context: HandlerContext<TConfig> =
|
|
379
|
+
const context: HandlerContext<TConfig> = {
|
|
380
|
+
...fake.capabilityContext(),
|
|
381
|
+
get session() { return readOperationSession(args[0]); },
|
|
382
|
+
};
|
|
151
383
|
await definition.handle.call(context as HandlerContext, ...args);
|
|
152
384
|
}
|
|
153
385
|
|