@kb-labs/shared-testing 1.0.0
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/README.md +430 -0
- package/dist/index.d.ts +545 -0
- package/dist/index.js +916 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
import { resetPlatform, platform } from '@kb-labs/core-runtime';
|
|
2
|
+
import { vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// src/setup-platform.ts
|
|
5
|
+
function setupTestPlatform(options = {}) {
|
|
6
|
+
resetPlatform();
|
|
7
|
+
const adapterMap = [
|
|
8
|
+
["llm", options.llm],
|
|
9
|
+
["cache", options.cache],
|
|
10
|
+
["embeddings", options.embeddings],
|
|
11
|
+
["vectorStore", options.vectorStore],
|
|
12
|
+
["storage", options.storage],
|
|
13
|
+
["analytics", options.analytics],
|
|
14
|
+
["logger", options.logger],
|
|
15
|
+
["eventBus", options.eventBus]
|
|
16
|
+
];
|
|
17
|
+
for (const [key, instance] of adapterMap) {
|
|
18
|
+
if (instance !== void 0) {
|
|
19
|
+
platform.setAdapter(key, instance);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
platform,
|
|
24
|
+
cleanup: () => resetPlatform()
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
var MockLLMBuilder = class {
|
|
28
|
+
rules = [];
|
|
29
|
+
defaultResponse = "mock response";
|
|
30
|
+
streamChunks = ["mock"];
|
|
31
|
+
errorToThrow = null;
|
|
32
|
+
toolCallsToReturn = [];
|
|
33
|
+
toolCallResponseContent = "";
|
|
34
|
+
/**
|
|
35
|
+
* Match a specific prompt (exact string or regex).
|
|
36
|
+
* Returns a handler to set the response.
|
|
37
|
+
*/
|
|
38
|
+
onComplete(matcher) {
|
|
39
|
+
return {
|
|
40
|
+
respondWith: (response) => {
|
|
41
|
+
this.rules.push({ matcher, response });
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Set the default response for any prompt that doesn't match a rule.
|
|
48
|
+
*/
|
|
49
|
+
onAnyComplete() {
|
|
50
|
+
return {
|
|
51
|
+
respondWith: (response) => {
|
|
52
|
+
this.defaultResponse = response;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Configure streaming to yield specific chunks.
|
|
59
|
+
*/
|
|
60
|
+
streaming(chunks) {
|
|
61
|
+
this.streamChunks = chunks;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Make the LLM throw an error on every call.
|
|
66
|
+
*/
|
|
67
|
+
failing(error) {
|
|
68
|
+
this.errorToThrow = error;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Configure chatWithTools() to return specific tool calls.
|
|
73
|
+
*/
|
|
74
|
+
withToolCalls(calls, content = "") {
|
|
75
|
+
this.toolCallsToReturn = calls;
|
|
76
|
+
this.toolCallResponseContent = content;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build the mock ILLM instance. Called automatically by mockLLM().
|
|
81
|
+
*/
|
|
82
|
+
build() {
|
|
83
|
+
const calls = [];
|
|
84
|
+
const toolCallRecords = [];
|
|
85
|
+
const rules = this.rules;
|
|
86
|
+
const defaultResponse = this.defaultResponse;
|
|
87
|
+
const streamChunks = this.streamChunks;
|
|
88
|
+
const errorToThrow = this.errorToThrow;
|
|
89
|
+
const toolCallsToReturn = this.toolCallsToReturn;
|
|
90
|
+
const toolCallResponseContent = this.toolCallResponseContent;
|
|
91
|
+
function resolveResponse(prompt) {
|
|
92
|
+
for (const rule of rules) {
|
|
93
|
+
if (matchesPrompt(rule.matcher, prompt)) {
|
|
94
|
+
return toResponse(rule.response, prompt);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return toResponse(defaultResponse, prompt);
|
|
98
|
+
}
|
|
99
|
+
const completeFn = vi.fn(async (prompt, options) => {
|
|
100
|
+
if (errorToThrow) {
|
|
101
|
+
throw errorToThrow;
|
|
102
|
+
}
|
|
103
|
+
const response = resolveResponse(prompt);
|
|
104
|
+
calls.push({ prompt, options, response });
|
|
105
|
+
return response;
|
|
106
|
+
});
|
|
107
|
+
const streamFn = vi.fn(async function* (_prompt, _options) {
|
|
108
|
+
if (errorToThrow) {
|
|
109
|
+
throw errorToThrow;
|
|
110
|
+
}
|
|
111
|
+
for (const chunk of streamChunks) {
|
|
112
|
+
yield chunk;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
const chatWithToolsFn = vi.fn(async (messages, options) => {
|
|
116
|
+
if (errorToThrow) {
|
|
117
|
+
throw errorToThrow;
|
|
118
|
+
}
|
|
119
|
+
const lastUserMsg = messages.filter((m) => m.role === "user").pop();
|
|
120
|
+
const prompt = lastUserMsg?.content ?? "";
|
|
121
|
+
const baseResponse = resolveResponse(prompt);
|
|
122
|
+
const response = {
|
|
123
|
+
...baseResponse,
|
|
124
|
+
content: toolCallsToReturn.length > 0 ? toolCallResponseContent : baseResponse.content,
|
|
125
|
+
toolCalls: toolCallsToReturn.length > 0 ? toolCallsToReturn : void 0
|
|
126
|
+
};
|
|
127
|
+
toolCallRecords.push({ messages, options, response });
|
|
128
|
+
return response;
|
|
129
|
+
});
|
|
130
|
+
const instance = {
|
|
131
|
+
complete: completeFn,
|
|
132
|
+
stream: streamFn,
|
|
133
|
+
chatWithTools: chatWithToolsFn,
|
|
134
|
+
calls,
|
|
135
|
+
toolCalls: toolCallRecords,
|
|
136
|
+
get lastCall() {
|
|
137
|
+
return calls[calls.length - 1];
|
|
138
|
+
},
|
|
139
|
+
resetCalls: () => {
|
|
140
|
+
calls.length = 0;
|
|
141
|
+
toolCallRecords.length = 0;
|
|
142
|
+
completeFn.mockClear();
|
|
143
|
+
streamFn.mockClear();
|
|
144
|
+
chatWithToolsFn.mockClear();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return instance;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
function matchesPrompt(matcher, prompt) {
|
|
151
|
+
if (typeof matcher === "string") {
|
|
152
|
+
return prompt.includes(matcher);
|
|
153
|
+
}
|
|
154
|
+
if (matcher instanceof RegExp) {
|
|
155
|
+
return matcher.test(prompt);
|
|
156
|
+
}
|
|
157
|
+
return matcher(prompt);
|
|
158
|
+
}
|
|
159
|
+
function toResponse(value, prompt) {
|
|
160
|
+
const resolved = typeof value === "function" ? value(prompt) : value;
|
|
161
|
+
if (typeof resolved === "string") {
|
|
162
|
+
return {
|
|
163
|
+
content: resolved,
|
|
164
|
+
usage: { promptTokens: 0, completionTokens: 0 },
|
|
165
|
+
model: "mock"
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return resolved;
|
|
169
|
+
}
|
|
170
|
+
function mockLLM() {
|
|
171
|
+
const builder = new MockLLMBuilder();
|
|
172
|
+
let built = null;
|
|
173
|
+
function ensureBuilt() {
|
|
174
|
+
if (!built) {
|
|
175
|
+
built = builder.build();
|
|
176
|
+
}
|
|
177
|
+
return built;
|
|
178
|
+
}
|
|
179
|
+
const proxy = new Proxy(builder, {
|
|
180
|
+
get(target, prop, _receiver) {
|
|
181
|
+
if (prop in target && typeof target[prop] === "function") {
|
|
182
|
+
const method = target[prop].bind(target);
|
|
183
|
+
return (...args) => {
|
|
184
|
+
built = null;
|
|
185
|
+
const result = method(...args);
|
|
186
|
+
if (result === target || result && typeof result === "object" && "respondWith" in result) {
|
|
187
|
+
if (result === target) {
|
|
188
|
+
return proxy;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
respondWith: (...rArgs) => {
|
|
192
|
+
built = null;
|
|
193
|
+
result.respondWith(...rArgs);
|
|
194
|
+
return proxy;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const instance = ensureBuilt();
|
|
202
|
+
return instance[prop];
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
return proxy;
|
|
206
|
+
}
|
|
207
|
+
function mockCache(initial) {
|
|
208
|
+
const store = /* @__PURE__ */ new Map();
|
|
209
|
+
const sortedSets = /* @__PURE__ */ new Map();
|
|
210
|
+
if (initial) {
|
|
211
|
+
for (const [key, value] of Object.entries(initial)) {
|
|
212
|
+
store.set(key, { value, expiresAt: null });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function isExpired(entry) {
|
|
216
|
+
return entry.expiresAt !== null && Date.now() > entry.expiresAt;
|
|
217
|
+
}
|
|
218
|
+
const spies = [];
|
|
219
|
+
function spy(fn) {
|
|
220
|
+
const s = vi.fn(fn);
|
|
221
|
+
spies.push(s);
|
|
222
|
+
return s;
|
|
223
|
+
}
|
|
224
|
+
const getFn = spy(async (key) => {
|
|
225
|
+
const entry = store.get(key);
|
|
226
|
+
if (!entry || isExpired(entry)) {
|
|
227
|
+
if (entry) {
|
|
228
|
+
store.delete(key);
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
return entry.value;
|
|
233
|
+
});
|
|
234
|
+
const setFn = spy(async (key, value, ttl) => {
|
|
235
|
+
store.set(key, {
|
|
236
|
+
value,
|
|
237
|
+
expiresAt: ttl ? Date.now() + ttl : null
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
const deleteFn = spy(async (key) => {
|
|
241
|
+
store.delete(key);
|
|
242
|
+
});
|
|
243
|
+
const clearFn = spy(async (pattern) => {
|
|
244
|
+
if (!pattern) {
|
|
245
|
+
store.clear();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const prefix = pattern.replace(/\*$/, "");
|
|
249
|
+
for (const key of store.keys()) {
|
|
250
|
+
if (key.startsWith(prefix)) {
|
|
251
|
+
store.delete(key);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
const zaddFn = spy(async (key, score, member) => {
|
|
256
|
+
if (!sortedSets.has(key)) {
|
|
257
|
+
sortedSets.set(key, []);
|
|
258
|
+
}
|
|
259
|
+
const set = sortedSets.get(key);
|
|
260
|
+
const idx = set.findIndex((m) => m.member === member);
|
|
261
|
+
if (idx >= 0) {
|
|
262
|
+
set.splice(idx, 1);
|
|
263
|
+
}
|
|
264
|
+
set.push({ score, member });
|
|
265
|
+
set.sort((a, b) => a.score - b.score);
|
|
266
|
+
});
|
|
267
|
+
const zrangebyscoreFn = spy(async (key, min, max) => {
|
|
268
|
+
const set = sortedSets.get(key);
|
|
269
|
+
if (!set) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
return set.filter((m) => m.score >= min && m.score <= max).map((m) => m.member);
|
|
273
|
+
});
|
|
274
|
+
const zremFn = spy(async (key, member) => {
|
|
275
|
+
const set = sortedSets.get(key);
|
|
276
|
+
if (!set) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const idx = set.findIndex((m) => m.member === member);
|
|
280
|
+
if (idx >= 0) {
|
|
281
|
+
set.splice(idx, 1);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
const setIfNotExistsFn = spy(async (key, value, ttl) => {
|
|
285
|
+
const existing = store.get(key);
|
|
286
|
+
if (existing && !isExpired(existing)) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
store.set(key, {
|
|
290
|
+
value,
|
|
291
|
+
expiresAt: ttl ? Date.now() + ttl : null
|
|
292
|
+
});
|
|
293
|
+
return true;
|
|
294
|
+
});
|
|
295
|
+
const instance = {
|
|
296
|
+
get: getFn,
|
|
297
|
+
set: setFn,
|
|
298
|
+
delete: deleteFn,
|
|
299
|
+
clear: clearFn,
|
|
300
|
+
zadd: zaddFn,
|
|
301
|
+
zrangebyscore: zrangebyscoreFn,
|
|
302
|
+
zrem: zremFn,
|
|
303
|
+
setIfNotExists: setIfNotExistsFn,
|
|
304
|
+
get store() {
|
|
305
|
+
return store;
|
|
306
|
+
},
|
|
307
|
+
get sortedSets() {
|
|
308
|
+
return sortedSets;
|
|
309
|
+
},
|
|
310
|
+
reset: () => {
|
|
311
|
+
store.clear();
|
|
312
|
+
sortedSets.clear();
|
|
313
|
+
for (const s of spies) {
|
|
314
|
+
s.mockClear();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
return instance;
|
|
319
|
+
}
|
|
320
|
+
function mockStorage(initial) {
|
|
321
|
+
const files = /* @__PURE__ */ new Map();
|
|
322
|
+
if (initial) {
|
|
323
|
+
for (const [path, content] of Object.entries(initial)) {
|
|
324
|
+
files.set(path, typeof content === "string" ? Buffer.from(content) : content);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const readFn = vi.fn(async (path) => {
|
|
328
|
+
return files.get(path) ?? null;
|
|
329
|
+
});
|
|
330
|
+
const writeFn = vi.fn(async (path, data) => {
|
|
331
|
+
files.set(path, data);
|
|
332
|
+
});
|
|
333
|
+
const deleteFn = vi.fn(async (path) => {
|
|
334
|
+
files.delete(path);
|
|
335
|
+
});
|
|
336
|
+
const listFn = vi.fn(async (prefix) => {
|
|
337
|
+
const result = [];
|
|
338
|
+
for (const path of files.keys()) {
|
|
339
|
+
if (path.startsWith(prefix)) {
|
|
340
|
+
result.push(path);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return result.sort();
|
|
344
|
+
});
|
|
345
|
+
const existsFn = vi.fn(async (path) => {
|
|
346
|
+
return files.has(path);
|
|
347
|
+
});
|
|
348
|
+
const instance = {
|
|
349
|
+
read: readFn,
|
|
350
|
+
write: writeFn,
|
|
351
|
+
delete: deleteFn,
|
|
352
|
+
list: listFn,
|
|
353
|
+
exists: existsFn,
|
|
354
|
+
get files() {
|
|
355
|
+
return files;
|
|
356
|
+
},
|
|
357
|
+
reset: () => {
|
|
358
|
+
files.clear();
|
|
359
|
+
readFn.mockClear();
|
|
360
|
+
writeFn.mockClear();
|
|
361
|
+
deleteFn.mockClear();
|
|
362
|
+
listFn.mockClear();
|
|
363
|
+
existsFn.mockClear();
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
return instance;
|
|
367
|
+
}
|
|
368
|
+
function mockLogger(sharedMessages) {
|
|
369
|
+
const messages = sharedMessages ?? [];
|
|
370
|
+
function createSimpleLogMethod(level) {
|
|
371
|
+
return vi.fn((msg, meta) => {
|
|
372
|
+
messages.push({ level, msg, meta });
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function createErrorLogMethod(level) {
|
|
376
|
+
return vi.fn((msg, error, meta) => {
|
|
377
|
+
messages.push({ level, msg, error, meta });
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
const instance = {
|
|
381
|
+
trace: createSimpleLogMethod("trace"),
|
|
382
|
+
debug: createSimpleLogMethod("debug"),
|
|
383
|
+
info: createSimpleLogMethod("info"),
|
|
384
|
+
warn: createSimpleLogMethod("warn"),
|
|
385
|
+
error: createErrorLogMethod("error"),
|
|
386
|
+
fatal: createErrorLogMethod("fatal"),
|
|
387
|
+
child: (_bindings) => mockLogger(messages),
|
|
388
|
+
get messages() {
|
|
389
|
+
return messages;
|
|
390
|
+
},
|
|
391
|
+
reset: () => {
|
|
392
|
+
messages.length = 0;
|
|
393
|
+
instance.trace.mockClear();
|
|
394
|
+
instance.debug.mockClear();
|
|
395
|
+
instance.info.mockClear();
|
|
396
|
+
instance.warn.mockClear();
|
|
397
|
+
instance.error.mockClear();
|
|
398
|
+
instance.fatal.mockClear();
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
return instance;
|
|
402
|
+
}
|
|
403
|
+
function createMockTrace() {
|
|
404
|
+
return {
|
|
405
|
+
traceId: "test-trace-id",
|
|
406
|
+
spanId: "test-span-id",
|
|
407
|
+
parentSpanId: void 0,
|
|
408
|
+
addEvent: () => {
|
|
409
|
+
},
|
|
410
|
+
setAttribute: () => {
|
|
411
|
+
},
|
|
412
|
+
recordError: () => {
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function createMockUI() {
|
|
417
|
+
const messages = [];
|
|
418
|
+
const mockColor = (text) => text;
|
|
419
|
+
const mockColors = {
|
|
420
|
+
success: mockColor,
|
|
421
|
+
error: mockColor,
|
|
422
|
+
warning: mockColor,
|
|
423
|
+
info: mockColor,
|
|
424
|
+
primary: mockColor,
|
|
425
|
+
accent: mockColor,
|
|
426
|
+
highlight: mockColor,
|
|
427
|
+
secondary: mockColor,
|
|
428
|
+
emphasis: mockColor,
|
|
429
|
+
muted: mockColor,
|
|
430
|
+
foreground: mockColor,
|
|
431
|
+
dim: mockColor,
|
|
432
|
+
bold: mockColor,
|
|
433
|
+
underline: mockColor,
|
|
434
|
+
inverse: mockColor
|
|
435
|
+
};
|
|
436
|
+
return {
|
|
437
|
+
colors: mockColors,
|
|
438
|
+
symbols: {
|
|
439
|
+
success: "+",
|
|
440
|
+
error: "x",
|
|
441
|
+
warning: "!",
|
|
442
|
+
info: "i",
|
|
443
|
+
bullet: "-",
|
|
444
|
+
clock: "T",
|
|
445
|
+
folder: "D",
|
|
446
|
+
package: "P",
|
|
447
|
+
pointer: ">",
|
|
448
|
+
section: "#",
|
|
449
|
+
separator: "-",
|
|
450
|
+
border: "|",
|
|
451
|
+
topLeft: "+",
|
|
452
|
+
topRight: "+",
|
|
453
|
+
bottomLeft: "+",
|
|
454
|
+
bottomRight: "+",
|
|
455
|
+
leftT: "+",
|
|
456
|
+
rightT: "+"
|
|
457
|
+
},
|
|
458
|
+
write: vi.fn((text) => messages.push(`WRITE: ${text}`)),
|
|
459
|
+
info: vi.fn((msg) => messages.push(`INFO: ${msg}`)),
|
|
460
|
+
success: vi.fn((msg) => messages.push(`SUCCESS: ${msg}`)),
|
|
461
|
+
warn: vi.fn((msg) => messages.push(`WARN: ${msg}`)),
|
|
462
|
+
error: vi.fn((err) => messages.push(`ERROR: ${err instanceof Error ? err.message : err}`)),
|
|
463
|
+
debug: vi.fn((msg) => messages.push(`DEBUG: ${msg}`)),
|
|
464
|
+
spinner: vi.fn((msg) => {
|
|
465
|
+
messages.push(`SPINNER: ${msg}`);
|
|
466
|
+
return {
|
|
467
|
+
update: vi.fn((m) => messages.push(`SPINNER UPDATE: ${m}`)),
|
|
468
|
+
succeed: vi.fn((m) => messages.push(`SPINNER SUCCEED: ${m ?? msg}`)),
|
|
469
|
+
fail: vi.fn((m) => messages.push(`SPINNER FAIL: ${m ?? msg}`)),
|
|
470
|
+
stop: vi.fn()
|
|
471
|
+
};
|
|
472
|
+
}),
|
|
473
|
+
table: vi.fn((data) => messages.push(`TABLE: ${JSON.stringify(data)}`)),
|
|
474
|
+
json: vi.fn((data) => messages.push(`JSON: ${JSON.stringify(data)}`)),
|
|
475
|
+
newline: vi.fn(() => messages.push("")),
|
|
476
|
+
divider: vi.fn(() => messages.push("-".repeat(40))),
|
|
477
|
+
box: vi.fn((content, title) => {
|
|
478
|
+
if (title) {
|
|
479
|
+
messages.push(`+- ${title} -+`);
|
|
480
|
+
}
|
|
481
|
+
messages.push(content);
|
|
482
|
+
if (title) {
|
|
483
|
+
messages.push(`+${"-".repeat(title.length + 4)}+`);
|
|
484
|
+
}
|
|
485
|
+
}),
|
|
486
|
+
sideBox: vi.fn((options) => {
|
|
487
|
+
messages.push(`+- ${options.title} -+`);
|
|
488
|
+
if (options.summary) {
|
|
489
|
+
Object.entries(options.summary).forEach(([key, value]) => {
|
|
490
|
+
messages.push(` ${key}: ${value}`);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
if (options.sections) {
|
|
494
|
+
options.sections.forEach((section) => {
|
|
495
|
+
if (section.header) {
|
|
496
|
+
messages.push(` ${section.header}:`);
|
|
497
|
+
}
|
|
498
|
+
section.items.forEach((item) => messages.push(` ${item}`));
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
if (options.timing) {
|
|
502
|
+
messages.push(` Timing: ${options.timing}ms`);
|
|
503
|
+
}
|
|
504
|
+
messages.push("+" + "-".repeat(options.title.length + 4) + "+");
|
|
505
|
+
}),
|
|
506
|
+
confirm: vi.fn(async () => true),
|
|
507
|
+
prompt: vi.fn(async () => "")
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function createMockRuntime() {
|
|
511
|
+
const mockFS = {
|
|
512
|
+
readFile: vi.fn(async () => ""),
|
|
513
|
+
readFileBuffer: vi.fn(async () => new Uint8Array()),
|
|
514
|
+
writeFile: vi.fn(async () => {
|
|
515
|
+
}),
|
|
516
|
+
readdir: vi.fn(async () => []),
|
|
517
|
+
readdirWithStats: vi.fn(async () => []),
|
|
518
|
+
mkdir: vi.fn(async () => {
|
|
519
|
+
}),
|
|
520
|
+
rm: vi.fn(async () => {
|
|
521
|
+
}),
|
|
522
|
+
copy: vi.fn(async () => {
|
|
523
|
+
}),
|
|
524
|
+
move: vi.fn(async () => {
|
|
525
|
+
}),
|
|
526
|
+
glob: vi.fn(async () => []),
|
|
527
|
+
stat: vi.fn(async () => ({
|
|
528
|
+
isFile: () => false,
|
|
529
|
+
isDirectory: () => false,
|
|
530
|
+
size: 0,
|
|
531
|
+
mtime: Date.now(),
|
|
532
|
+
ctime: Date.now()
|
|
533
|
+
})),
|
|
534
|
+
exists: vi.fn(async () => false),
|
|
535
|
+
resolve: (path) => path,
|
|
536
|
+
relative: (path) => path,
|
|
537
|
+
join: (...segments) => segments.join("/"),
|
|
538
|
+
dirname: (path) => path.split("/").slice(0, -1).join("/"),
|
|
539
|
+
basename: (path) => path.split("/").pop() ?? "",
|
|
540
|
+
extname: (path) => {
|
|
541
|
+
const base = path.split("/").pop() ?? "";
|
|
542
|
+
const idx = base.lastIndexOf(".");
|
|
543
|
+
return idx > 0 ? base.slice(idx) : "";
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
const mockFetch = vi.fn(async () => new Response("mock"));
|
|
547
|
+
const mockEnv = vi.fn((_key) => void 0);
|
|
548
|
+
return {
|
|
549
|
+
fs: mockFS,
|
|
550
|
+
fetch: mockFetch,
|
|
551
|
+
env: mockEnv
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function createMockEnvironmentAPI() {
|
|
555
|
+
return {
|
|
556
|
+
create: vi.fn(async () => ({
|
|
557
|
+
environmentId: "env_mock_1",
|
|
558
|
+
provider: "mock",
|
|
559
|
+
status: "ready",
|
|
560
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
561
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
562
|
+
})),
|
|
563
|
+
status: vi.fn(async (environmentId) => ({
|
|
564
|
+
environmentId,
|
|
565
|
+
status: "ready",
|
|
566
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
567
|
+
})),
|
|
568
|
+
destroy: vi.fn(async () => {
|
|
569
|
+
}),
|
|
570
|
+
renewLease: vi.fn(async () => ({
|
|
571
|
+
leaseId: "lease_mock_1",
|
|
572
|
+
acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
573
|
+
expiresAt: new Date(Date.now() + 10 * 60 * 1e3).toISOString()
|
|
574
|
+
}))
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function createMockWorkspaceAPI() {
|
|
578
|
+
return {
|
|
579
|
+
materialize: vi.fn(async () => ({
|
|
580
|
+
workspaceId: "ws_mock_1",
|
|
581
|
+
provider: "mock",
|
|
582
|
+
status: "ready",
|
|
583
|
+
rootPath: "/tmp/ws_mock_1",
|
|
584
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
585
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
586
|
+
})),
|
|
587
|
+
attach: vi.fn(async (request) => ({
|
|
588
|
+
workspaceId: request.workspaceId,
|
|
589
|
+
environmentId: request.environmentId,
|
|
590
|
+
mountPath: request.mountPath ?? "/workspace",
|
|
591
|
+
attachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
592
|
+
})),
|
|
593
|
+
release: vi.fn(async () => {
|
|
594
|
+
}),
|
|
595
|
+
status: vi.fn(async (workspaceId) => ({
|
|
596
|
+
workspaceId,
|
|
597
|
+
status: "ready",
|
|
598
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
599
|
+
}))
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function createMockSnapshotAPI() {
|
|
603
|
+
return {
|
|
604
|
+
capture: vi.fn(async (request) => ({
|
|
605
|
+
snapshotId: request.snapshotId ?? "snap_mock_1",
|
|
606
|
+
provider: "mock",
|
|
607
|
+
status: "ready",
|
|
608
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
609
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
610
|
+
workspaceId: request.workspaceId,
|
|
611
|
+
environmentId: request.environmentId
|
|
612
|
+
})),
|
|
613
|
+
restore: vi.fn(async (request) => ({
|
|
614
|
+
snapshotId: request.snapshotId,
|
|
615
|
+
restoredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
616
|
+
workspaceId: request.workspaceId,
|
|
617
|
+
environmentId: request.environmentId,
|
|
618
|
+
targetPath: request.targetPath
|
|
619
|
+
})),
|
|
620
|
+
status: vi.fn(async (snapshotId) => ({
|
|
621
|
+
snapshotId,
|
|
622
|
+
status: "ready",
|
|
623
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
624
|
+
})),
|
|
625
|
+
delete: vi.fn(async () => {
|
|
626
|
+
}),
|
|
627
|
+
gc: vi.fn(async (request) => ({
|
|
628
|
+
scanned: 0,
|
|
629
|
+
deleted: 0,
|
|
630
|
+
dryRun: request?.dryRun ?? false
|
|
631
|
+
}))
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function createInfraApiMocks() {
|
|
635
|
+
return {
|
|
636
|
+
environment: createMockEnvironmentAPI(),
|
|
637
|
+
workspace: createMockWorkspaceAPI(),
|
|
638
|
+
snapshot: createMockSnapshotAPI()
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function createMockPluginAPI() {
|
|
642
|
+
const infra = createInfraApiMocks();
|
|
643
|
+
return {
|
|
644
|
+
lifecycle: {
|
|
645
|
+
onCleanup: vi.fn()
|
|
646
|
+
},
|
|
647
|
+
state: {
|
|
648
|
+
get: vi.fn(async () => void 0),
|
|
649
|
+
set: vi.fn(async () => {
|
|
650
|
+
}),
|
|
651
|
+
delete: vi.fn(async () => {
|
|
652
|
+
}),
|
|
653
|
+
has: vi.fn(async () => false),
|
|
654
|
+
getMany: vi.fn(async () => /* @__PURE__ */ new Map()),
|
|
655
|
+
setMany: vi.fn(async () => {
|
|
656
|
+
})
|
|
657
|
+
},
|
|
658
|
+
artifacts: {
|
|
659
|
+
write: vi.fn(async () => "/mock/path"),
|
|
660
|
+
list: vi.fn(async () => []),
|
|
661
|
+
read: vi.fn(async () => ""),
|
|
662
|
+
readBuffer: vi.fn(async () => new Uint8Array()),
|
|
663
|
+
exists: vi.fn(async () => false),
|
|
664
|
+
path: vi.fn(() => "/mock/path")
|
|
665
|
+
},
|
|
666
|
+
shell: {
|
|
667
|
+
exec: vi.fn(async () => ({ code: 0, stdout: "", stderr: "", ok: true }))
|
|
668
|
+
},
|
|
669
|
+
events: {
|
|
670
|
+
emit: vi.fn(async () => {
|
|
671
|
+
})
|
|
672
|
+
},
|
|
673
|
+
invoke: {
|
|
674
|
+
call: vi.fn(async (_pluginId, _input, _options) => void 0)
|
|
675
|
+
},
|
|
676
|
+
workflows: {
|
|
677
|
+
run: vi.fn(async () => "mock-run-id"),
|
|
678
|
+
wait: vi.fn(async () => void 0),
|
|
679
|
+
status: vi.fn(async () => null),
|
|
680
|
+
cancel: vi.fn(async () => {
|
|
681
|
+
}),
|
|
682
|
+
list: vi.fn(async () => [])
|
|
683
|
+
},
|
|
684
|
+
jobs: {
|
|
685
|
+
submit: vi.fn(async () => "mock-job-id"),
|
|
686
|
+
schedule: vi.fn(async () => "mock-scheduled-job-id"),
|
|
687
|
+
wait: vi.fn(async () => void 0),
|
|
688
|
+
status: vi.fn(async () => null),
|
|
689
|
+
cancel: vi.fn(async () => false),
|
|
690
|
+
list: vi.fn(async () => [])
|
|
691
|
+
},
|
|
692
|
+
cron: {
|
|
693
|
+
register: vi.fn(async () => {
|
|
694
|
+
}),
|
|
695
|
+
unregister: vi.fn(async () => {
|
|
696
|
+
}),
|
|
697
|
+
list: vi.fn(async () => []),
|
|
698
|
+
pause: vi.fn(async () => {
|
|
699
|
+
}),
|
|
700
|
+
resume: vi.fn(async () => {
|
|
701
|
+
}),
|
|
702
|
+
trigger: vi.fn(async () => {
|
|
703
|
+
})
|
|
704
|
+
},
|
|
705
|
+
environment: infra.environment,
|
|
706
|
+
workspace: infra.workspace,
|
|
707
|
+
snapshot: infra.snapshot
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
var createMockPlatformApi = createMockPluginAPI;
|
|
711
|
+
function createMockPluginContextV3(options = {}) {
|
|
712
|
+
return createTestContext(options);
|
|
713
|
+
}
|
|
714
|
+
function createTestContext(options = {}) {
|
|
715
|
+
const {
|
|
716
|
+
pluginId = "test-plugin",
|
|
717
|
+
pluginVersion = "0.0.0",
|
|
718
|
+
host = "cli",
|
|
719
|
+
hostContext,
|
|
720
|
+
config,
|
|
721
|
+
cwd = process.cwd(),
|
|
722
|
+
outdir,
|
|
723
|
+
tenantId,
|
|
724
|
+
signal,
|
|
725
|
+
platform: platformOverrides,
|
|
726
|
+
ui: uiOverrides,
|
|
727
|
+
syncSingleton = true
|
|
728
|
+
} = options;
|
|
729
|
+
const resolvedOutdir = outdir ?? `${cwd}/.kb/output`;
|
|
730
|
+
const defaultHostContext = hostContext ?? (() => {
|
|
731
|
+
switch (host) {
|
|
732
|
+
case "cli":
|
|
733
|
+
return { host: "cli", argv: ["test"], flags: {} };
|
|
734
|
+
case "rest":
|
|
735
|
+
return {
|
|
736
|
+
host: "rest",
|
|
737
|
+
method: "GET",
|
|
738
|
+
path: "/test",
|
|
739
|
+
requestId: "test-req-001",
|
|
740
|
+
traceId: "test-trace-001"
|
|
741
|
+
};
|
|
742
|
+
case "workflow":
|
|
743
|
+
return { host: "workflow", workflowId: "test-wf", runId: "test-run", stepId: "test-step" };
|
|
744
|
+
case "webhook":
|
|
745
|
+
return { host: "webhook", event: "test:event" };
|
|
746
|
+
}
|
|
747
|
+
})();
|
|
748
|
+
const defaultLogger = mockLogger();
|
|
749
|
+
const defaultLLM = mockLLM();
|
|
750
|
+
const defaultCache = mockCache();
|
|
751
|
+
const defaultStorage = mockStorage();
|
|
752
|
+
const defaultPlatform = {
|
|
753
|
+
logger: defaultLogger,
|
|
754
|
+
llm: defaultLLM,
|
|
755
|
+
embeddings: {
|
|
756
|
+
embed: vi.fn(async () => []),
|
|
757
|
+
embedBatch: vi.fn(async () => [[]]),
|
|
758
|
+
dimensions: 1536,
|
|
759
|
+
getDimensions: vi.fn(async () => 1536)
|
|
760
|
+
},
|
|
761
|
+
vectorStore: {
|
|
762
|
+
search: vi.fn(async () => []),
|
|
763
|
+
upsert: vi.fn(async () => {
|
|
764
|
+
}),
|
|
765
|
+
delete: vi.fn(async () => {
|
|
766
|
+
}),
|
|
767
|
+
count: vi.fn(async () => 0)
|
|
768
|
+
},
|
|
769
|
+
cache: defaultCache,
|
|
770
|
+
storage: defaultStorage,
|
|
771
|
+
analytics: {
|
|
772
|
+
track: vi.fn(async () => {
|
|
773
|
+
}),
|
|
774
|
+
identify: vi.fn(async () => {
|
|
775
|
+
}),
|
|
776
|
+
flush: vi.fn(async () => {
|
|
777
|
+
})
|
|
778
|
+
},
|
|
779
|
+
eventBus: {
|
|
780
|
+
publish: vi.fn(async () => {
|
|
781
|
+
}),
|
|
782
|
+
subscribe: vi.fn(() => () => {
|
|
783
|
+
})
|
|
784
|
+
},
|
|
785
|
+
logs: {
|
|
786
|
+
query: vi.fn(async () => ({ logs: [], total: 0, hasMore: false, source: "buffer" })),
|
|
787
|
+
getById: vi.fn(async () => null),
|
|
788
|
+
search: vi.fn(async () => ({ logs: [], total: 0, hasMore: false })),
|
|
789
|
+
subscribe: vi.fn(() => () => {
|
|
790
|
+
}),
|
|
791
|
+
getStats: vi.fn(async () => ({})),
|
|
792
|
+
getCapabilities: vi.fn(() => ({ hasBuffer: false, hasPersistence: false, hasSearch: false, hasStreaming: false }))
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
const finalPlatform = {
|
|
796
|
+
...defaultPlatform,
|
|
797
|
+
...platformOverrides
|
|
798
|
+
};
|
|
799
|
+
let cleanupFn = () => {
|
|
800
|
+
};
|
|
801
|
+
if (syncSingleton) {
|
|
802
|
+
const result = setupTestPlatform({
|
|
803
|
+
llm: finalPlatform.llm,
|
|
804
|
+
cache: finalPlatform.cache,
|
|
805
|
+
storage: finalPlatform.storage,
|
|
806
|
+
logger: finalPlatform.logger,
|
|
807
|
+
analytics: finalPlatform.analytics,
|
|
808
|
+
embeddings: finalPlatform.embeddings,
|
|
809
|
+
vectorStore: finalPlatform.vectorStore,
|
|
810
|
+
eventBus: finalPlatform.eventBus
|
|
811
|
+
});
|
|
812
|
+
cleanupFn = result.cleanup;
|
|
813
|
+
}
|
|
814
|
+
const defaultUI = createMockUI();
|
|
815
|
+
const finalUI = {
|
|
816
|
+
...defaultUI,
|
|
817
|
+
...uiOverrides
|
|
818
|
+
};
|
|
819
|
+
const ctx = {
|
|
820
|
+
host,
|
|
821
|
+
requestId: "test-trace:test-span",
|
|
822
|
+
pluginId,
|
|
823
|
+
pluginVersion,
|
|
824
|
+
tenantId,
|
|
825
|
+
cwd,
|
|
826
|
+
outdir: resolvedOutdir,
|
|
827
|
+
config,
|
|
828
|
+
signal,
|
|
829
|
+
trace: createMockTrace(),
|
|
830
|
+
hostContext: defaultHostContext,
|
|
831
|
+
ui: finalUI,
|
|
832
|
+
platform: finalPlatform,
|
|
833
|
+
runtime: createMockRuntime(),
|
|
834
|
+
api: createMockPluginAPI()
|
|
835
|
+
};
|
|
836
|
+
return { ctx, cleanup: cleanupFn };
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/test-command.ts
|
|
840
|
+
async function testCommand(handler, options = {}) {
|
|
841
|
+
const {
|
|
842
|
+
flags,
|
|
843
|
+
argv = [],
|
|
844
|
+
query,
|
|
845
|
+
body,
|
|
846
|
+
params,
|
|
847
|
+
input: rawInput,
|
|
848
|
+
host = "cli",
|
|
849
|
+
config,
|
|
850
|
+
cwd,
|
|
851
|
+
tenantId,
|
|
852
|
+
signal,
|
|
853
|
+
platform: platform2,
|
|
854
|
+
ui,
|
|
855
|
+
syncSingleton = true
|
|
856
|
+
} = options;
|
|
857
|
+
const ctxOptions = {
|
|
858
|
+
host,
|
|
859
|
+
config,
|
|
860
|
+
cwd,
|
|
861
|
+
tenantId,
|
|
862
|
+
signal,
|
|
863
|
+
platform: platform2,
|
|
864
|
+
ui,
|
|
865
|
+
syncSingleton
|
|
866
|
+
};
|
|
867
|
+
const { ctx, cleanup } = createTestContext(ctxOptions);
|
|
868
|
+
let input;
|
|
869
|
+
if (rawInput !== void 0) {
|
|
870
|
+
input = rawInput;
|
|
871
|
+
} else if (host === "rest") {
|
|
872
|
+
input = {
|
|
873
|
+
...query !== void 0 ? { query } : {},
|
|
874
|
+
...body !== void 0 ? { body } : {},
|
|
875
|
+
...params !== void 0 ? { params } : {}
|
|
876
|
+
};
|
|
877
|
+
} else {
|
|
878
|
+
input = { flags: flags ?? {}, argv };
|
|
879
|
+
}
|
|
880
|
+
let raw;
|
|
881
|
+
try {
|
|
882
|
+
raw = await handler.execute(ctx, input);
|
|
883
|
+
} finally {
|
|
884
|
+
await handler.cleanup?.();
|
|
885
|
+
}
|
|
886
|
+
let exitCode = 0;
|
|
887
|
+
let result;
|
|
888
|
+
let meta;
|
|
889
|
+
if (raw != null && typeof raw === "object" && "exitCode" in raw) {
|
|
890
|
+
const cmdResult = raw;
|
|
891
|
+
exitCode = cmdResult.exitCode;
|
|
892
|
+
result = cmdResult.result;
|
|
893
|
+
meta = cmdResult.meta;
|
|
894
|
+
} else if (raw === void 0 || raw === null) {
|
|
895
|
+
exitCode = 0;
|
|
896
|
+
result = void 0;
|
|
897
|
+
meta = void 0;
|
|
898
|
+
} else {
|
|
899
|
+
exitCode = 0;
|
|
900
|
+
result = raw;
|
|
901
|
+
meta = void 0;
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
exitCode,
|
|
905
|
+
result,
|
|
906
|
+
meta,
|
|
907
|
+
raw,
|
|
908
|
+
ui: ctx.ui,
|
|
909
|
+
ctx,
|
|
910
|
+
cleanup
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
export { createInfraApiMocks, createMockEnvironmentAPI, createMockPlatformApi, createMockPluginAPI, createMockPluginContextV3, createMockRuntime, createMockSnapshotAPI, createMockTrace, createMockUI, createMockWorkspaceAPI, createTestContext, mockCache, mockLLM, mockLogger, mockStorage, setupTestPlatform, testCommand };
|
|
915
|
+
//# sourceMappingURL=index.js.map
|
|
916
|
+
//# sourceMappingURL=index.js.map
|