ai-tests 2.0.2 → 2.1.1
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/.turbo/turbo-build.log +4 -5
- package/.turbo/turbo-test.log +4 -0
- package/CHANGELOG.md +9 -0
- package/package.json +2 -3
- package/src/assertions.js +383 -0
- package/src/cli.js +76 -0
- package/src/index.js +18 -0
- package/src/local.js +62 -0
- package/src/runner.js +214 -0
- package/src/types.js +4 -0
- package/src/worker.js +91 -0
- package/test/assertions.test.js +493 -0
- package/test/index.test.js +42 -0
- package/test/local.test.js +27 -0
- package/test/runner.test.js +315 -0
- package/test/worker.test.js +162 -0
- package/vitest.config.js +10 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
|
|
2
|
+
> ai-tests@2.0.3 build /Users/nathanclevenger/projects/primitives.org.ai/packages/ai-tests
|
|
3
|
+
> tsc -p tsconfig.json
|
|
4
|
+
|
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-tests",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Test utilities worker - expect, should, assert, describe, it via RPC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,8 +32,7 @@
|
|
|
32
32
|
"deploy": "wrangler deploy"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"chai": "^5.1.0"
|
|
36
|
-
"rpc.do": "^0.1.0"
|
|
35
|
+
"chai": "^5.1.0"
|
|
37
36
|
},
|
|
38
37
|
"devDependencies": {
|
|
39
38
|
"@cloudflare/vitest-pool-workers": "^0.8.0",
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assertion utilities powered by Chai
|
|
3
|
+
*
|
|
4
|
+
* Exposes expect, should, and assert APIs via RPC.
|
|
5
|
+
* Uses Chai under the hood for battle-tested assertions.
|
|
6
|
+
*/
|
|
7
|
+
import * as chai from 'chai';
|
|
8
|
+
import { RpcTarget } from 'cloudflare:workers';
|
|
9
|
+
// Initialize chai's should
|
|
10
|
+
chai.should();
|
|
11
|
+
/**
|
|
12
|
+
* Wrapper around Chai's expect that extends RpcTarget
|
|
13
|
+
* This allows the assertion chain to work over RPC with promise pipelining
|
|
14
|
+
*/
|
|
15
|
+
export class Assertion extends RpcTarget {
|
|
16
|
+
// Using 'any' to avoid complex type gymnastics with Chai's chainable types
|
|
17
|
+
// (Deep, Nested, etc. don't match Chai.Assertion directly)
|
|
18
|
+
assertion;
|
|
19
|
+
constructor(value, message) {
|
|
20
|
+
super();
|
|
21
|
+
this.assertion = chai.expect(value, message);
|
|
22
|
+
}
|
|
23
|
+
// Chainable language chains
|
|
24
|
+
get to() { return this; }
|
|
25
|
+
get be() { return this; }
|
|
26
|
+
get been() { return this; }
|
|
27
|
+
get is() { return this; }
|
|
28
|
+
get that() { return this; }
|
|
29
|
+
get which() { return this; }
|
|
30
|
+
get and() { return this; }
|
|
31
|
+
get has() { return this; }
|
|
32
|
+
get have() { return this; }
|
|
33
|
+
get with() { return this; }
|
|
34
|
+
get at() { return this; }
|
|
35
|
+
get of() { return this; }
|
|
36
|
+
get same() { return this; }
|
|
37
|
+
get but() { return this; }
|
|
38
|
+
get does() { return this; }
|
|
39
|
+
get still() { return this; }
|
|
40
|
+
get also() { return this; }
|
|
41
|
+
// Negation
|
|
42
|
+
get not() {
|
|
43
|
+
this.assertion = this.assertion.not;
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
// Deep flag
|
|
47
|
+
get deep() {
|
|
48
|
+
this.assertion = this.assertion.deep;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
// Nested flag
|
|
52
|
+
get nested() {
|
|
53
|
+
this.assertion = this.assertion.nested;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
// Own flag
|
|
57
|
+
get own() {
|
|
58
|
+
this.assertion = this.assertion.own;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
// Ordered flag
|
|
62
|
+
get ordered() {
|
|
63
|
+
this.assertion = this.assertion.ordered;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
// Any flag
|
|
67
|
+
get any() {
|
|
68
|
+
this.assertion = this.assertion.any;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
// All flag
|
|
72
|
+
get all() {
|
|
73
|
+
this.assertion = this.assertion.all;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
// Length chain
|
|
77
|
+
get length() {
|
|
78
|
+
this.assertion = this.assertion.length;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
// Type assertions
|
|
82
|
+
get ok() { this.assertion.ok; return this; }
|
|
83
|
+
get true() { this.assertion.true; return this; }
|
|
84
|
+
get false() { this.assertion.false; return this; }
|
|
85
|
+
get null() { this.assertion.null; return this; }
|
|
86
|
+
get undefined() { this.assertion.undefined; return this; }
|
|
87
|
+
get NaN() { this.assertion.NaN; return this; }
|
|
88
|
+
get exist() { this.assertion.exist; return this; }
|
|
89
|
+
get empty() { this.assertion.empty; return this; }
|
|
90
|
+
get arguments() { this.assertion.arguments; return this; }
|
|
91
|
+
// Value assertions
|
|
92
|
+
equal(value, message) {
|
|
93
|
+
this.assertion.equal(value, message);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
equals(value, message) {
|
|
97
|
+
return this.equal(value, message);
|
|
98
|
+
}
|
|
99
|
+
eq(value, message) {
|
|
100
|
+
return this.equal(value, message);
|
|
101
|
+
}
|
|
102
|
+
eql(value, message) {
|
|
103
|
+
this.assertion.eql(value, message);
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
eqls(value, message) {
|
|
107
|
+
return this.eql(value, message);
|
|
108
|
+
}
|
|
109
|
+
above(value, message) {
|
|
110
|
+
this.assertion.above(value, message);
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
gt(value, message) {
|
|
114
|
+
return this.above(value, message);
|
|
115
|
+
}
|
|
116
|
+
greaterThan(value, message) {
|
|
117
|
+
return this.above(value, message);
|
|
118
|
+
}
|
|
119
|
+
least(value, message) {
|
|
120
|
+
this.assertion.least(value, message);
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
gte(value, message) {
|
|
124
|
+
return this.least(value, message);
|
|
125
|
+
}
|
|
126
|
+
greaterThanOrEqual(value, message) {
|
|
127
|
+
return this.least(value, message);
|
|
128
|
+
}
|
|
129
|
+
below(value, message) {
|
|
130
|
+
this.assertion.below(value, message);
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
lt(value, message) {
|
|
134
|
+
return this.below(value, message);
|
|
135
|
+
}
|
|
136
|
+
lessThan(value, message) {
|
|
137
|
+
return this.below(value, message);
|
|
138
|
+
}
|
|
139
|
+
most(value, message) {
|
|
140
|
+
this.assertion.most(value, message);
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
lte(value, message) {
|
|
144
|
+
return this.most(value, message);
|
|
145
|
+
}
|
|
146
|
+
lessThanOrEqual(value, message) {
|
|
147
|
+
return this.most(value, message);
|
|
148
|
+
}
|
|
149
|
+
within(start, finish, message) {
|
|
150
|
+
this.assertion.within(start, finish, message);
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
instanceof(constructor, message) {
|
|
154
|
+
this.assertion.instanceof(constructor, message);
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
instanceOf(constructor, message) {
|
|
158
|
+
return this.instanceof(constructor, message);
|
|
159
|
+
}
|
|
160
|
+
property(name, value, message) {
|
|
161
|
+
if (arguments.length > 1) {
|
|
162
|
+
this.assertion.property(name, value, message);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this.assertion.property(name);
|
|
166
|
+
}
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
ownProperty(name, message) {
|
|
170
|
+
this.assertion.ownProperty(name, message);
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
haveOwnProperty(name, message) {
|
|
174
|
+
return this.ownProperty(name, message);
|
|
175
|
+
}
|
|
176
|
+
ownPropertyDescriptor(name, descriptor, message) {
|
|
177
|
+
this.assertion.ownPropertyDescriptor(name, descriptor, message);
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
lengthOf(n, message) {
|
|
181
|
+
this.assertion.lengthOf(n, message);
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
match(re, message) {
|
|
185
|
+
this.assertion.match(re, message);
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
matches(re, message) {
|
|
189
|
+
return this.match(re, message);
|
|
190
|
+
}
|
|
191
|
+
string(str, message) {
|
|
192
|
+
this.assertion.string(str, message);
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
keys(...keys) {
|
|
196
|
+
this.assertion.keys(...keys);
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
key(...keys) {
|
|
200
|
+
return this.keys(...keys);
|
|
201
|
+
}
|
|
202
|
+
throw(errorLike, errMsgMatcher, message) {
|
|
203
|
+
this.assertion.throw(errorLike, errMsgMatcher, message);
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
throws(errorLike, errMsgMatcher, message) {
|
|
207
|
+
return this.throw(errorLike, errMsgMatcher, message);
|
|
208
|
+
}
|
|
209
|
+
Throw(errorLike, errMsgMatcher, message) {
|
|
210
|
+
return this.throw(errorLike, errMsgMatcher, message);
|
|
211
|
+
}
|
|
212
|
+
respondTo(method, message) {
|
|
213
|
+
this.assertion.respondTo(method, message);
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
respondsTo(method, message) {
|
|
217
|
+
return this.respondTo(method, message);
|
|
218
|
+
}
|
|
219
|
+
satisfy(matcher, message) {
|
|
220
|
+
this.assertion.satisfy(matcher, message);
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
satisfies(matcher, message) {
|
|
224
|
+
return this.satisfy(matcher, message);
|
|
225
|
+
}
|
|
226
|
+
closeTo(expected, delta, message) {
|
|
227
|
+
this.assertion.closeTo(expected, delta, message);
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
approximately(expected, delta, message) {
|
|
231
|
+
return this.closeTo(expected, delta, message);
|
|
232
|
+
}
|
|
233
|
+
members(set, message) {
|
|
234
|
+
this.assertion.members(set, message);
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
oneOf(list, message) {
|
|
238
|
+
this.assertion.oneOf(list, message);
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
include(value, message) {
|
|
242
|
+
this.assertion.include(value, message);
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
includes(value, message) {
|
|
246
|
+
return this.include(value, message);
|
|
247
|
+
}
|
|
248
|
+
contain(value, message) {
|
|
249
|
+
return this.include(value, message);
|
|
250
|
+
}
|
|
251
|
+
contains(value, message) {
|
|
252
|
+
return this.include(value, message);
|
|
253
|
+
}
|
|
254
|
+
a(type, message) {
|
|
255
|
+
this.assertion.a(type, message);
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
258
|
+
an(type, message) {
|
|
259
|
+
return this.a(type, message);
|
|
260
|
+
}
|
|
261
|
+
// Vitest-compatible aliases
|
|
262
|
+
toBe(value) {
|
|
263
|
+
this.assertion.equal(value);
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
toEqual(value) {
|
|
267
|
+
this.assertion.deep.equal(value);
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
270
|
+
toStrictEqual(value) {
|
|
271
|
+
this.assertion.deep.equal(value);
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
toBeTruthy() {
|
|
275
|
+
this.assertion.ok;
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
toBeFalsy() {
|
|
279
|
+
this.assertion.not.ok;
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
toBeNull() {
|
|
283
|
+
this.assertion.null;
|
|
284
|
+
return this;
|
|
285
|
+
}
|
|
286
|
+
toBeUndefined() {
|
|
287
|
+
this.assertion.undefined;
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
toBeDefined() {
|
|
291
|
+
this.assertion.not.undefined;
|
|
292
|
+
return this;
|
|
293
|
+
}
|
|
294
|
+
toBeNaN() {
|
|
295
|
+
this.assertion.NaN;
|
|
296
|
+
return this;
|
|
297
|
+
}
|
|
298
|
+
toContain(value) {
|
|
299
|
+
this.assertion.include(value);
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
toHaveLength(length) {
|
|
303
|
+
this.assertion.lengthOf(length);
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
toHaveProperty(path, value) {
|
|
307
|
+
if (arguments.length > 1) {
|
|
308
|
+
this.assertion.nested.property(path, value);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
this.assertion.nested.property(path);
|
|
312
|
+
}
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
toMatch(pattern) {
|
|
316
|
+
if (typeof pattern === 'string') {
|
|
317
|
+
this.assertion.include(pattern);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
this.assertion.match(pattern);
|
|
321
|
+
}
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
toMatchObject(obj) {
|
|
325
|
+
this.assertion.deep.include(obj);
|
|
326
|
+
return this;
|
|
327
|
+
}
|
|
328
|
+
toThrow(expected) {
|
|
329
|
+
if (expected) {
|
|
330
|
+
this.assertion.throw(expected);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
this.assertion.throw();
|
|
334
|
+
}
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
toBeGreaterThan(n) {
|
|
338
|
+
this.assertion.above(n);
|
|
339
|
+
return this;
|
|
340
|
+
}
|
|
341
|
+
toBeLessThan(n) {
|
|
342
|
+
this.assertion.below(n);
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
toBeGreaterThanOrEqual(n) {
|
|
346
|
+
this.assertion.least(n);
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
toBeLessThanOrEqual(n) {
|
|
350
|
+
this.assertion.most(n);
|
|
351
|
+
return this;
|
|
352
|
+
}
|
|
353
|
+
toBeCloseTo(n, digits = 2) {
|
|
354
|
+
const delta = Math.pow(10, -digits) / 2;
|
|
355
|
+
this.assertion.closeTo(n, delta);
|
|
356
|
+
return this;
|
|
357
|
+
}
|
|
358
|
+
toBeInstanceOf(cls) {
|
|
359
|
+
this.assertion.instanceof(cls);
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
toBeTypeOf(type) {
|
|
363
|
+
this.assertion.a(type);
|
|
364
|
+
return this;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Assert API - TDD style assertions
|
|
369
|
+
*/
|
|
370
|
+
export const assert = chai.assert;
|
|
371
|
+
/**
|
|
372
|
+
* Create an expect assertion
|
|
373
|
+
*/
|
|
374
|
+
export function expect(value, message) {
|
|
375
|
+
return new Assertion(value, message);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Create a should-style assertion
|
|
379
|
+
* Since we can't modify Object.prototype over RPC, this takes a value
|
|
380
|
+
*/
|
|
381
|
+
export function should(value) {
|
|
382
|
+
return new Assertion(value);
|
|
383
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ai-tests CLI
|
|
4
|
+
*
|
|
5
|
+
* Simple CLI for deploying and managing the ai-tests worker.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx ai-tests deploy Deploy to Cloudflare Workers
|
|
9
|
+
* npx ai-tests dev Start local dev server
|
|
10
|
+
* npx ai-tests --help Show help
|
|
11
|
+
*/
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const packageRoot = join(__dirname, '..');
|
|
17
|
+
const commands = {
|
|
18
|
+
deploy: {
|
|
19
|
+
description: 'Deploy ai-tests worker to Cloudflare',
|
|
20
|
+
args: ['wrangler', 'deploy']
|
|
21
|
+
},
|
|
22
|
+
dev: {
|
|
23
|
+
description: 'Start local development server',
|
|
24
|
+
args: ['wrangler', 'dev']
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function showHelp() {
|
|
28
|
+
console.log(`
|
|
29
|
+
ai-tests - Test utilities worker for Cloudflare Workers
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
npx ai-tests <command>
|
|
33
|
+
|
|
34
|
+
Commands:
|
|
35
|
+
deploy Deploy to Cloudflare Workers
|
|
36
|
+
dev Start local development server
|
|
37
|
+
help Show this help message
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
npx ai-tests deploy
|
|
41
|
+
npx ai-tests dev
|
|
42
|
+
|
|
43
|
+
After deploying, bind the worker to your sandbox worker:
|
|
44
|
+
# In your wrangler.toml:
|
|
45
|
+
[[services]]
|
|
46
|
+
binding = "TEST"
|
|
47
|
+
service = "ai-tests"
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
async function run() {
|
|
51
|
+
const args = process.argv.slice(2);
|
|
52
|
+
const command = args[0];
|
|
53
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
54
|
+
showHelp();
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
const cmd = commands[command];
|
|
58
|
+
if (!cmd) {
|
|
59
|
+
console.error(`Unknown command: ${command}`);
|
|
60
|
+
showHelp();
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
console.log(`Running: ${cmd.args.join(' ')}`);
|
|
64
|
+
const child = spawn('npx', cmd.args, {
|
|
65
|
+
cwd: packageRoot,
|
|
66
|
+
stdio: 'inherit',
|
|
67
|
+
shell: true
|
|
68
|
+
});
|
|
69
|
+
child.on('close', (code) => {
|
|
70
|
+
process.exit(code ?? 0);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
run().catch((err) => {
|
|
74
|
+
console.error(err);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
});
|
package/src/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-tests - Test utilities via RPC
|
|
3
|
+
*
|
|
4
|
+
* Provides expect, should, assert, and a test runner that can be:
|
|
5
|
+
* - Deployed as a Cloudflare Worker
|
|
6
|
+
* - Run locally via Miniflare
|
|
7
|
+
* - Called via Workers RPC from other workers (like ai-sandbox)
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
// Export assertion utilities
|
|
12
|
+
export { Assertion, expect, should, assert } from './assertions.js';
|
|
13
|
+
// Export test runner
|
|
14
|
+
export { TestRunner, createRunner } from './runner.js';
|
|
15
|
+
// Export worker service (WorkerEntrypoint and RpcTarget)
|
|
16
|
+
export { TestService, TestService as TestWorker, TestServiceCore } from './worker.js';
|
|
17
|
+
// Export the default WorkerEntrypoint class
|
|
18
|
+
export { default } from './worker.js';
|
package/src/local.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local development helpers for ai-tests
|
|
3
|
+
*
|
|
4
|
+
* Run the test worker via Miniflare for local development and Node.js usage.
|
|
5
|
+
*/
|
|
6
|
+
import { TestServiceCore } from './worker.js';
|
|
7
|
+
let miniflareInstance = null;
|
|
8
|
+
let localService = null;
|
|
9
|
+
/**
|
|
10
|
+
* Get a local TestServiceCore instance
|
|
11
|
+
*
|
|
12
|
+
* For local development, this creates a direct instance.
|
|
13
|
+
* The RPC serialization happens when called via worker bindings.
|
|
14
|
+
*/
|
|
15
|
+
export function getLocalTestService() {
|
|
16
|
+
if (!localService) {
|
|
17
|
+
localService = new TestServiceCore();
|
|
18
|
+
}
|
|
19
|
+
return localService;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Start a Miniflare instance running the test worker
|
|
23
|
+
*
|
|
24
|
+
* This is useful for testing the RPC interface locally.
|
|
25
|
+
*/
|
|
26
|
+
export async function startTestWorker(options) {
|
|
27
|
+
const { Miniflare } = await import('miniflare');
|
|
28
|
+
// Build worker script inline
|
|
29
|
+
const workerScript = `
|
|
30
|
+
import { TestService } from './index.js';
|
|
31
|
+
export { TestService };
|
|
32
|
+
export default {
|
|
33
|
+
async fetch(request) {
|
|
34
|
+
return new Response('ai-tests worker running');
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
`;
|
|
38
|
+
miniflareInstance = new Miniflare({
|
|
39
|
+
modules: true,
|
|
40
|
+
script: workerScript,
|
|
41
|
+
port: options?.port,
|
|
42
|
+
compatibilityDate: '2024-12-01',
|
|
43
|
+
});
|
|
44
|
+
const url = await miniflareInstance.ready;
|
|
45
|
+
return {
|
|
46
|
+
url: url.toString(),
|
|
47
|
+
stop: async () => {
|
|
48
|
+
if (miniflareInstance) {
|
|
49
|
+
await miniflareInstance.dispose();
|
|
50
|
+
miniflareInstance = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a test service that can be used as a service binding
|
|
57
|
+
*
|
|
58
|
+
* For use in Miniflare configurations when testing sandbox workers.
|
|
59
|
+
*/
|
|
60
|
+
export function createTestServiceBinding() {
|
|
61
|
+
return new TestServiceCore();
|
|
62
|
+
}
|