@forwardimpact/libmock 0.1.2
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/LICENSE +201 -0
- package/README.md +122 -0
- package/package.json +44 -0
- package/src/fixture/assertions.js +42 -0
- package/src/fixture/cache.js +50 -0
- package/src/fixture/eval.js +146 -0
- package/src/fixture/index.js +9 -0
- package/src/fixture/pathway.js +451 -0
- package/src/fixture/services.js +56 -0
- package/src/index.js +3 -0
- package/src/mock/clients.js +264 -0
- package/src/mock/clock.js +62 -0
- package/src/mock/config.js +45 -0
- package/src/mock/data.js +46 -0
- package/src/mock/environments.js +80 -0
- package/src/mock/finder.js +83 -0
- package/src/mock/fs.js +301 -0
- package/src/mock/gh-client.js +28 -0
- package/src/mock/git-client.js +56 -0
- package/src/mock/grpc.js +94 -0
- package/src/mock/http.js +60 -0
- package/src/mock/index.js +49 -0
- package/src/mock/infra.js +297 -0
- package/src/mock/logger.js +42 -0
- package/src/mock/observer.js +78 -0
- package/src/mock/resource-index.js +95 -0
- package/src/mock/service-callbacks.js +39 -0
- package/src/mock/services.js +79 -0
- package/src/mock/spy.js +44 -0
- package/src/mock/storage.js +118 -0
- package/src/mock/subprocess.js +92 -0
- package/src/runtime.js +29 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Additional infrastructure mocks for services/products tests. Centralizes
|
|
3
|
+
* variants that consumers previously inlined.
|
|
4
|
+
*/
|
|
5
|
+
import { Writable } from "node:stream";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A capturing `Writable` used as the mock `proc.stdout`/`stderr`. It retains
|
|
9
|
+
* the `chunks` accessor existing assertions read AND is a real `Writable`, so
|
|
10
|
+
* it accepts piped input (e.g. a `pipeline()` from a read stream).
|
|
11
|
+
*/
|
|
12
|
+
class CaptureWritable extends Writable {
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.chunks = [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {Buffer|string} chunk - The written chunk.
|
|
20
|
+
* @param {string} _encoding - Chunk encoding (unused).
|
|
21
|
+
* @param {Function} callback - Completion callback.
|
|
22
|
+
*/
|
|
23
|
+
_write(chunk, _encoding, callback) {
|
|
24
|
+
this.chunks.push(String(chunk));
|
|
25
|
+
callback();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a mock Supabase-style client with configurable table and storage
|
|
31
|
+
* behaviour. Covers the patterns found across products/map/test/activity/*.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} [options]
|
|
34
|
+
* @param {Record<string, object>} [options.tables] - Map of table name to
|
|
35
|
+
* override. Each override may expose `select`, `insert`, `upsert`, `delete`
|
|
36
|
+
* as async functions. Unspecified methods return `{ data: [], error: null }`.
|
|
37
|
+
* @param {Record<string, string>} [options.files] - Files exposed via
|
|
38
|
+
* `storage.from(...).list(prefix)` / `.download(path)`.
|
|
39
|
+
* @returns {object} Mock client and call-tracking arrays.
|
|
40
|
+
*/
|
|
41
|
+
export function createMockSupabaseClient({ tables = {}, files = {} } = {}) {
|
|
42
|
+
const calls = {
|
|
43
|
+
select: [],
|
|
44
|
+
insert: [],
|
|
45
|
+
upsert: [],
|
|
46
|
+
delete: [],
|
|
47
|
+
download: [],
|
|
48
|
+
list: [],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function record(kind, entry) {
|
|
52
|
+
calls[kind].push(entry);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
calls,
|
|
57
|
+
from(table) {
|
|
58
|
+
const override = tables[table] ?? {};
|
|
59
|
+
return {
|
|
60
|
+
async select(...args) {
|
|
61
|
+
record("select", { table, args });
|
|
62
|
+
if (override.select) return override.select(...args);
|
|
63
|
+
return { data: [], error: null };
|
|
64
|
+
},
|
|
65
|
+
async insert(rows, opts) {
|
|
66
|
+
record("insert", { table, rows, options: opts });
|
|
67
|
+
if (override.insert) return override.insert(rows, opts);
|
|
68
|
+
return { data: rows, error: null };
|
|
69
|
+
},
|
|
70
|
+
async upsert(rows, opts) {
|
|
71
|
+
record("upsert", {
|
|
72
|
+
table,
|
|
73
|
+
rows,
|
|
74
|
+
onConflict: opts?.onConflict,
|
|
75
|
+
options: opts,
|
|
76
|
+
});
|
|
77
|
+
if (override.upsert) return override.upsert(rows, opts);
|
|
78
|
+
return { data: rows, error: null };
|
|
79
|
+
},
|
|
80
|
+
async delete(...args) {
|
|
81
|
+
record("delete", { table, args });
|
|
82
|
+
if (override.delete) return override.delete(...args);
|
|
83
|
+
return { error: null };
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
storage: {
|
|
88
|
+
from() {
|
|
89
|
+
return {
|
|
90
|
+
async list(prefix) {
|
|
91
|
+
record("list", { prefix });
|
|
92
|
+
const names = Object.keys(files)
|
|
93
|
+
.filter((k) => k.startsWith(prefix))
|
|
94
|
+
.map((k) => ({
|
|
95
|
+
name: k.slice(prefix.length),
|
|
96
|
+
created_at: "z",
|
|
97
|
+
}));
|
|
98
|
+
return { data: names, error: null };
|
|
99
|
+
},
|
|
100
|
+
async download(path) {
|
|
101
|
+
record("download", { path });
|
|
102
|
+
const content = files[path];
|
|
103
|
+
if (content === undefined) {
|
|
104
|
+
return { data: null, error: { message: "not found" } };
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
data: { text: async () => content },
|
|
108
|
+
error: null,
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Creates Turtle parsing helpers bound to an injected n3 Parser. Keeps
|
|
119
|
+
* libmock free of an n3 dependency while allowing services to share the
|
|
120
|
+
* parseQuads / findAll / findOne idiom.
|
|
121
|
+
*
|
|
122
|
+
* @param {import("n3").Parser | Function} ParserOrInstance - n3 Parser class
|
|
123
|
+
* or a pre-built parser instance.
|
|
124
|
+
* @param {object} [options]
|
|
125
|
+
* @param {string} [options.format="Turtle"] - Parser format.
|
|
126
|
+
* @returns {object} { parseQuads, findAll, findOne }
|
|
127
|
+
*/
|
|
128
|
+
export function createTurtleHelpers(
|
|
129
|
+
ParserOrInstance,
|
|
130
|
+
{ format = "Turtle" } = {},
|
|
131
|
+
) {
|
|
132
|
+
const isClass =
|
|
133
|
+
typeof ParserOrInstance === "function" &&
|
|
134
|
+
ParserOrInstance.prototype &&
|
|
135
|
+
typeof ParserOrInstance.prototype.parse === "function";
|
|
136
|
+
const parser = isClass ? new ParserOrInstance({ format }) : ParserOrInstance;
|
|
137
|
+
|
|
138
|
+
function parseQuads(turtle) {
|
|
139
|
+
return parser.parse(turtle);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function findAll(quads, { subject, predicate, object } = {}) {
|
|
143
|
+
return quads.filter(
|
|
144
|
+
(q) =>
|
|
145
|
+
(!subject || q.subject.value === subject) &&
|
|
146
|
+
(!predicate || q.predicate.value === predicate) &&
|
|
147
|
+
(!object || q.object.value === object),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function findOne(quads, pattern) {
|
|
152
|
+
return findAll(quads, pattern)[0];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { parseQuads, findAll, findOne };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Build an `AsyncIterable<string>` over a fixed list of input chunks, used as
|
|
160
|
+
* the mock `proc.stdin`.
|
|
161
|
+
* @param {string[]} chunks - Lines/chunks the iterator yields in order.
|
|
162
|
+
* @returns {AsyncIterable<string>}
|
|
163
|
+
*/
|
|
164
|
+
export function createMockStdin(chunks = []) {
|
|
165
|
+
return {
|
|
166
|
+
async *[Symbol.asyncIterator]() {
|
|
167
|
+
for (const chunk of chunks) yield chunk;
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Creates a mock `process`-like object matching the `Runtime.proc` surface:
|
|
174
|
+
* `cwd()`, `env`, `argv`, `stdin`, `stdout`/`stderr` (capturing `Writable`s),
|
|
175
|
+
* `exit(code)`, `kill(pid, signal)`, `pid`, `platform`, `on(event, handler)`,
|
|
176
|
+
* and a settable `exitCode`. Writes are captured on `stdout.chunks` /
|
|
177
|
+
* `stderr.chunks` (and the streams accept piped input); kill calls on `kills`;
|
|
178
|
+
* event handlers on `handlers` (fire them via `emit(event, ...args)` to
|
|
179
|
+
* simulate a signal).
|
|
180
|
+
*
|
|
181
|
+
* @param {object} [options]
|
|
182
|
+
* @param {Record<string, string>} [options.env] - Initial env map.
|
|
183
|
+
* @param {string} [options.cwd] - Working directory `cwd()` returns.
|
|
184
|
+
* @param {string[]} [options.argv] - The frozen `argv` array.
|
|
185
|
+
* @param {string[]} [options.stdin] - Chunks the `stdin` iterator yields.
|
|
186
|
+
* @param {(pid: number, signal: string|number) => any} [options.kill] - Optional
|
|
187
|
+
* `kill` implementation (e.g. to model a liveness probe); calls are always
|
|
188
|
+
* recorded on the returned `kills` array regardless.
|
|
189
|
+
* @param {number} [options.pid] - The fake's `pid` (default 1234).
|
|
190
|
+
* @param {string} [options.platform] - The fake's `platform` string
|
|
191
|
+
* (default `"linux"`; set `"darwin"`/`"win32"` to exercise per-platform code).
|
|
192
|
+
* @returns {object}
|
|
193
|
+
*/
|
|
194
|
+
export function createMockProcess({
|
|
195
|
+
env = {},
|
|
196
|
+
cwd,
|
|
197
|
+
argv,
|
|
198
|
+
stdin,
|
|
199
|
+
kill,
|
|
200
|
+
pid = 1234,
|
|
201
|
+
platform = "linux",
|
|
202
|
+
} = {}) {
|
|
203
|
+
const stdout = new CaptureWritable();
|
|
204
|
+
const stderr = new CaptureWritable();
|
|
205
|
+
const kills = [];
|
|
206
|
+
// Registered event handlers (e.g. "SIGTERM"/"SIGINT"); a test can fire them
|
|
207
|
+
// via `emit(event, ...args)` to simulate a signal without a real process.
|
|
208
|
+
const handlers = {};
|
|
209
|
+
return {
|
|
210
|
+
env: { ...env },
|
|
211
|
+
cwd: () => cwd ?? "/work",
|
|
212
|
+
argv: Object.freeze([...(argv ?? ["/usr/bin/node", "/tmp/test-bin.js"])]),
|
|
213
|
+
stdin: createMockStdin(stdin ?? []),
|
|
214
|
+
stdout,
|
|
215
|
+
stderr,
|
|
216
|
+
pid,
|
|
217
|
+
platform,
|
|
218
|
+
exitCode: 0,
|
|
219
|
+
exit(code = 0) {
|
|
220
|
+
this.exitCode = code;
|
|
221
|
+
},
|
|
222
|
+
kills,
|
|
223
|
+
kill(pid, signal) {
|
|
224
|
+
kills.push({ pid, signal });
|
|
225
|
+
return kill?.(pid, signal);
|
|
226
|
+
},
|
|
227
|
+
handlers,
|
|
228
|
+
on(event, handler) {
|
|
229
|
+
(handlers[event] ??= []).push(handler);
|
|
230
|
+
},
|
|
231
|
+
emit(event, ...args) {
|
|
232
|
+
for (const handler of handlers[event] ?? []) handler(...args);
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Runs `fn` with `console.log`, `console.info`, and `console.warn` suppressed,
|
|
239
|
+
* returning whatever `fn` returns. Errors still propagate.
|
|
240
|
+
*
|
|
241
|
+
* @template T
|
|
242
|
+
* @param {() => T | Promise<T>} fn
|
|
243
|
+
* @returns {Promise<T>}
|
|
244
|
+
*/
|
|
245
|
+
export async function withSilentConsole(fn) {
|
|
246
|
+
const originals = {
|
|
247
|
+
log: console.log,
|
|
248
|
+
info: console.info,
|
|
249
|
+
warn: console.warn,
|
|
250
|
+
};
|
|
251
|
+
console.log = () => {};
|
|
252
|
+
console.info = () => {};
|
|
253
|
+
console.warn = () => {};
|
|
254
|
+
try {
|
|
255
|
+
return await fn();
|
|
256
|
+
} finally {
|
|
257
|
+
console.log = originals.log;
|
|
258
|
+
console.info = originals.info;
|
|
259
|
+
console.warn = originals.warn;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Creates a bag of async query stubs from a plain values object. A function
|
|
265
|
+
* value is passed through untouched; anything else becomes an async function
|
|
266
|
+
* returning that value. Collapses landmark-style `stubQueries` boilerplate.
|
|
267
|
+
*
|
|
268
|
+
* @param {Record<string, unknown>} values
|
|
269
|
+
* @returns {Record<string, Function>}
|
|
270
|
+
*/
|
|
271
|
+
export function createMockQueries(values = {}) {
|
|
272
|
+
const out = {};
|
|
273
|
+
for (const [key, val] of Object.entries(values)) {
|
|
274
|
+
out[key] = typeof val === "function" ? val : async () => val;
|
|
275
|
+
}
|
|
276
|
+
return out;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Creates a minimal mock S3 client that records command sends and returns a
|
|
281
|
+
* configurable response.
|
|
282
|
+
*
|
|
283
|
+
* @param {object} [options]
|
|
284
|
+
* @param {(command: object) => unknown} [options.sendFn] - Custom send handler.
|
|
285
|
+
* @returns {object} { client, sends }
|
|
286
|
+
*/
|
|
287
|
+
export function createMockS3Client({ sendFn } = {}) {
|
|
288
|
+
const sends = [];
|
|
289
|
+
return {
|
|
290
|
+
sends,
|
|
291
|
+
async send(command) {
|
|
292
|
+
sends.push(command);
|
|
293
|
+
if (sendFn) return sendFn(command);
|
|
294
|
+
return {};
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { spy } from "./spy.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a mock logger with call tracking
|
|
4
|
+
* @param {object} options - Logger options
|
|
5
|
+
* @param {boolean} options.captureOutput - Whether to capture log output
|
|
6
|
+
* @returns {object} Mock logger
|
|
7
|
+
*/
|
|
8
|
+
export function createMockLogger(options = {}) {
|
|
9
|
+
const logs = [];
|
|
10
|
+
const capture = options.captureOutput ?? false;
|
|
11
|
+
|
|
12
|
+
const createMethod = (level) =>
|
|
13
|
+
spy((appId, msg, attributes) => {
|
|
14
|
+
if (capture) {
|
|
15
|
+
logs.push({ level, appId, msg, attributes });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
logs,
|
|
21
|
+
debug: createMethod("debug"),
|
|
22
|
+
info: createMethod("info"),
|
|
23
|
+
warn: createMethod("warn"),
|
|
24
|
+
error: createMethod("error"),
|
|
25
|
+
exception: createMethod("exception"),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a silent logger that does nothing
|
|
31
|
+
* @returns {object} Silent logger
|
|
32
|
+
*/
|
|
33
|
+
export function createSilentLogger() {
|
|
34
|
+
const noop = () => {};
|
|
35
|
+
return {
|
|
36
|
+
debug: noop,
|
|
37
|
+
info: noop,
|
|
38
|
+
warn: noop,
|
|
39
|
+
error: noop,
|
|
40
|
+
exception: noop,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { spy } from "./spy.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a mock observer factory
|
|
4
|
+
* @param {object} logger - Logger to use (optional)
|
|
5
|
+
* @returns {Function} Mock observer factory
|
|
6
|
+
*/
|
|
7
|
+
export function createMockObserverFn(logger = null) {
|
|
8
|
+
const mockLogger = logger || {
|
|
9
|
+
debug: spy(),
|
|
10
|
+
info: spy(),
|
|
11
|
+
error: spy(),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return () => ({
|
|
15
|
+
observeServerUnaryCall: async (_method, handler, call, callback) => {
|
|
16
|
+
return await handler(call, callback);
|
|
17
|
+
},
|
|
18
|
+
observeClientUnaryCall: async (_method, _request, fn) => {
|
|
19
|
+
return await fn();
|
|
20
|
+
},
|
|
21
|
+
observeClientStreamingCall: (_method, _request, fn) => {
|
|
22
|
+
return fn();
|
|
23
|
+
},
|
|
24
|
+
logger: () => mockLogger,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a mock tracer
|
|
30
|
+
* @param {object} overrides - Method overrides
|
|
31
|
+
* @returns {object} Mock tracer
|
|
32
|
+
*/
|
|
33
|
+
export function createMockTracer(overrides = {}) {
|
|
34
|
+
return {
|
|
35
|
+
startSpan: spy((name, options = {}) => ({
|
|
36
|
+
span_id: `span-${Date.now()}`,
|
|
37
|
+
trace_id: `trace-${Date.now()}`,
|
|
38
|
+
name,
|
|
39
|
+
...options,
|
|
40
|
+
addEvent: spy(),
|
|
41
|
+
setOk: spy(),
|
|
42
|
+
setError: spy(),
|
|
43
|
+
end: spy(async () => {}),
|
|
44
|
+
})),
|
|
45
|
+
startClientSpan: spy((_service, _method) => ({
|
|
46
|
+
span: {
|
|
47
|
+
span_id: `client-span-${Date.now()}`,
|
|
48
|
+
trace_id: `trace-${Date.now()}`,
|
|
49
|
+
},
|
|
50
|
+
metadata: { get: () => [], set: () => {} },
|
|
51
|
+
})),
|
|
52
|
+
startServerSpan: spy((_service, _method, _request, metadata) => ({
|
|
53
|
+
span_id: `server-span-${Date.now()}`,
|
|
54
|
+
trace_id: metadata?.get?.("x-trace-id")?.[0] || `trace-${Date.now()}`,
|
|
55
|
+
})),
|
|
56
|
+
getSpanContext: () => ({
|
|
57
|
+
run: (span, fn) => fn(),
|
|
58
|
+
getStore: () => null,
|
|
59
|
+
}),
|
|
60
|
+
endSpan: spy(),
|
|
61
|
+
recordError: spy(),
|
|
62
|
+
...overrides,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates a mock auth factory
|
|
68
|
+
* @param {object} options - Auth options
|
|
69
|
+
* @returns {Function} Mock auth factory
|
|
70
|
+
*/
|
|
71
|
+
export function createMockAuthFn(options = {}) {
|
|
72
|
+
const { isValid = true, serviceId = "test" } = options;
|
|
73
|
+
|
|
74
|
+
return () => ({
|
|
75
|
+
createClientInterceptor: () => () => {},
|
|
76
|
+
validateCall: () => ({ isValid, serviceId }),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { common, tool } from "@forwardimpact/libtype";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a mock resource index with common test data
|
|
5
|
+
* @param {object} options - Setup options
|
|
6
|
+
* @returns {object} Mock resource index
|
|
7
|
+
*/
|
|
8
|
+
export function createMockResourceIndex(options = {}) {
|
|
9
|
+
const resources = new Map();
|
|
10
|
+
|
|
11
|
+
const index = {
|
|
12
|
+
resources,
|
|
13
|
+
|
|
14
|
+
async get(identifiers, _actor) {
|
|
15
|
+
if (!identifiers || identifiers.length === 0) return [];
|
|
16
|
+
return identifiers
|
|
17
|
+
.map((id) => {
|
|
18
|
+
const key = typeof id === "string" ? id : id.toString?.() || id.name;
|
|
19
|
+
return resources.get(key);
|
|
20
|
+
})
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
put(resource) {
|
|
25
|
+
const key = resource.id?.toString?.() || resource.id?.name;
|
|
26
|
+
if (key) resources.set(key, resource);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async has(id) {
|
|
30
|
+
const key = typeof id === "string" ? id : id.toString?.();
|
|
31
|
+
return resources.has(key);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sets up default test resources
|
|
36
|
+
* @param {object} setupOptions - Setup options
|
|
37
|
+
* @param {string[]} [setupOptions.tools] - Tool names to seed
|
|
38
|
+
* @param {string} [setupOptions.conversationId] - Conversation ID
|
|
39
|
+
*/
|
|
40
|
+
setupDefaults(setupOptions = {}) {
|
|
41
|
+
const { tools = [], conversationId = "test-conversation" } = setupOptions;
|
|
42
|
+
|
|
43
|
+
resources.set(
|
|
44
|
+
conversationId,
|
|
45
|
+
common.Conversation.fromObject({
|
|
46
|
+
id: { name: conversationId },
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
for (const name of tools) {
|
|
51
|
+
resources.set(
|
|
52
|
+
`tool.ToolFunction.${name}`,
|
|
53
|
+
tool.ToolFunction.fromObject({
|
|
54
|
+
id: { name, tokens: 20 },
|
|
55
|
+
name,
|
|
56
|
+
description: `${name} tool`,
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Adds a message resource
|
|
64
|
+
* @param {object} msg - Message to add
|
|
65
|
+
*/
|
|
66
|
+
addMessage(msg) {
|
|
67
|
+
const id =
|
|
68
|
+
msg.id?.type && msg.id?.toString
|
|
69
|
+
? msg.id.toString()
|
|
70
|
+
: msg.id?.name || String(msg.id);
|
|
71
|
+
resources.set(id, msg);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Find resources by prefix
|
|
76
|
+
* @param {string} prefix - Prefix to search for
|
|
77
|
+
* @returns {Promise<string[]>} List of matching keys
|
|
78
|
+
*/
|
|
79
|
+
async findByPrefix(prefix) {
|
|
80
|
+
const keys = [];
|
|
81
|
+
for (const key of resources.keys()) {
|
|
82
|
+
if (key.startsWith(prefix)) {
|
|
83
|
+
keys.push(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return keys;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (options.tools || options.conversationId) {
|
|
91
|
+
index.setupDefaults(options);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return index;
|
|
95
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a mock service callbacks object for agent testing
|
|
3
|
+
* @param {object} overrides - Method overrides per service
|
|
4
|
+
* @returns {object} Service callbacks object
|
|
5
|
+
*/
|
|
6
|
+
export function createMockServiceCallbacks(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
memory: {
|
|
9
|
+
append: async () => ({}),
|
|
10
|
+
get: async () => ({
|
|
11
|
+
messages: [{ role: "system", content: "You are an assistant" }],
|
|
12
|
+
tools: [],
|
|
13
|
+
}),
|
|
14
|
+
...overrides.memory,
|
|
15
|
+
},
|
|
16
|
+
llm: {
|
|
17
|
+
createCompletions: async () => ({
|
|
18
|
+
choices: [
|
|
19
|
+
{
|
|
20
|
+
message: {
|
|
21
|
+
role: "assistant",
|
|
22
|
+
content: "Test response",
|
|
23
|
+
tool_calls: [],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
}),
|
|
28
|
+
...overrides.llm,
|
|
29
|
+
},
|
|
30
|
+
tool: {
|
|
31
|
+
call: async () => ({
|
|
32
|
+
role: "tool",
|
|
33
|
+
content: "Tool result",
|
|
34
|
+
}),
|
|
35
|
+
...overrides.tool,
|
|
36
|
+
},
|
|
37
|
+
...overrides,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock service utilities for testing
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a mock vector service
|
|
7
|
+
* @param {object} overrides - Properties to override in the mock
|
|
8
|
+
* @returns {object} Mock vector service
|
|
9
|
+
*/
|
|
10
|
+
export function mockVectorService(overrides = {}) {
|
|
11
|
+
return {
|
|
12
|
+
QueryItems: async (request, callback) => {
|
|
13
|
+
callback(null, { results: [], total: 0 });
|
|
14
|
+
},
|
|
15
|
+
close: () => {},
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a mock history service
|
|
22
|
+
* @param {object} overrides - Properties to override in the mock
|
|
23
|
+
* @returns {object} Mock history service
|
|
24
|
+
*/
|
|
25
|
+
export function mockHistoryService(overrides = {}) {
|
|
26
|
+
return {
|
|
27
|
+
GetHistory: async (request, callback) => {
|
|
28
|
+
callback(null, { messages: [] });
|
|
29
|
+
},
|
|
30
|
+
UpdateHistory: async (request, callback) => {
|
|
31
|
+
callback(null, {});
|
|
32
|
+
},
|
|
33
|
+
close: () => {},
|
|
34
|
+
...overrides,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a mock LLM service
|
|
40
|
+
* @param {object} overrides - Properties to override in the mock
|
|
41
|
+
* @returns {object} Mock LLM service
|
|
42
|
+
*/
|
|
43
|
+
export function mockLlmService(overrides = {}) {
|
|
44
|
+
return {
|
|
45
|
+
CreateCompletions: async (request, callback) => {
|
|
46
|
+
callback(null, {
|
|
47
|
+
choices: [
|
|
48
|
+
{
|
|
49
|
+
index: 0,
|
|
50
|
+
message: { role: "assistant", content: "Test response" },
|
|
51
|
+
finish_reason: "stop",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
CreateEmbeddings: async (request, callback) => {
|
|
57
|
+
callback(null, {
|
|
58
|
+
data: [{ index: 0, embedding: [0.1, 0.2, 0.3] }],
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
close: () => {},
|
|
62
|
+
...overrides,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates a mock text service
|
|
68
|
+
* @param {object} overrides - Properties to override in the mock
|
|
69
|
+
* @returns {object} Mock text service
|
|
70
|
+
*/
|
|
71
|
+
export function mockTextService(overrides = {}) {
|
|
72
|
+
return {
|
|
73
|
+
GetChunks: async (request, callback) => {
|
|
74
|
+
callback(null, { chunks: {} });
|
|
75
|
+
},
|
|
76
|
+
close: () => {},
|
|
77
|
+
...overrides,
|
|
78
|
+
};
|
|
79
|
+
}
|
package/src/mock/spy.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable mock function helper. Replaces `mock.fn` from `node:test` so the
|
|
3
|
+
* test suite can run under either node:test or bun:test.
|
|
4
|
+
*
|
|
5
|
+
* Shape matches node:test's `mock.fn` to keep call-inspection sites
|
|
6
|
+
* (`fn.mock.calls[0].arguments`, `fn.mock.callCount()`, `fn.mock.resetCalls()`)
|
|
7
|
+
* unchanged across the codebase.
|
|
8
|
+
*
|
|
9
|
+
* @template T
|
|
10
|
+
* @param {(...args: any[]) => T} [impl] - Initial implementation.
|
|
11
|
+
* @returns {((...args: any[]) => T) & { mock: { calls: Array<{arguments: any[], result?: T, error?: unknown, this: unknown}>, callCount: () => number, resetCalls: () => void, mockImplementation: (newImpl: (...args: any[]) => T) => void } }}
|
|
12
|
+
*/
|
|
13
|
+
export function spy(impl) {
|
|
14
|
+
let _impl = impl;
|
|
15
|
+
const calls = [];
|
|
16
|
+
const fn = function (...args) {
|
|
17
|
+
const rec = { arguments: args, this: this };
|
|
18
|
+
if (!_impl) {
|
|
19
|
+
calls.push(rec);
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const result = _impl.apply(this, args);
|
|
24
|
+
rec.result = result;
|
|
25
|
+
calls.push(rec);
|
|
26
|
+
return result;
|
|
27
|
+
} catch (err) {
|
|
28
|
+
rec.error = err;
|
|
29
|
+
calls.push(rec);
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
fn.mock = {
|
|
34
|
+
calls,
|
|
35
|
+
callCount: () => calls.length,
|
|
36
|
+
resetCalls: () => {
|
|
37
|
+
calls.length = 0;
|
|
38
|
+
},
|
|
39
|
+
mockImplementation: (newImpl) => {
|
|
40
|
+
_impl = newImpl;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
return fn;
|
|
44
|
+
}
|