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.
@@ -1,5 +1,4 @@
1
-
2
- 
3
- > ai-tests@2.0.1 build /Users/nathanclevenger/projects/primitives.org.ai/packages/ai-tests
4
- > tsc -p tsconfig.json
5
-
1
+
2
+ > ai-tests@2.0.3 build /Users/nathanclevenger/projects/primitives.org.ai/packages/ai-tests
3
+ > tsc -p tsconfig.json
4
+
@@ -0,0 +1,4 @@
1
+
2
+ > ai-tests@2.0.3 test /Users/nathanclevenger/projects/primitives.org.ai/packages/ai-tests
3
+ > vitest "--run"
4
+
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # ai-tests
2
2
 
3
+ ## 2.1.1
4
+
5
+ ## 2.0.3
6
+
7
+ ### Patch Changes
8
+
9
+ - Updated dependencies
10
+ - rpc.do@0.2.0
11
+
3
12
  ## 2.0.2
4
13
 
5
14
  ## 2.0.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-tests",
3
- "version": "2.0.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
+ }