@ricsam/isolate-test-utils 0.1.10 → 0.1.12
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/dist/cjs/fetch-context.cjs +66 -0
- package/dist/cjs/fetch-context.cjs.map +10 -0
- package/dist/cjs/fs-context.cjs +73 -0
- package/dist/cjs/fs-context.cjs.map +10 -0
- package/dist/cjs/index.cjs +121 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/{src/mock-fs.ts → dist/cjs/mock-fs.cjs} +63 -97
- package/dist/cjs/mock-fs.cjs.map +10 -0
- package/dist/cjs/native-input-test.cjs +347 -0
- package/dist/cjs/native-input-test.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/runtime-context.cjs +97 -0
- package/dist/cjs/runtime-context.cjs.map +10 -0
- package/dist/cjs/server.cjs +109 -0
- package/dist/cjs/server.cjs.map +10 -0
- package/dist/mjs/fetch-context.mjs +23 -0
- package/dist/mjs/fetch-context.mjs.map +10 -0
- package/dist/mjs/fs-context.mjs +30 -0
- package/dist/mjs/fs-context.mjs.map +10 -0
- package/dist/mjs/index.mjs +78 -0
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/mock-fs.mjs +181 -0
- package/dist/mjs/mock-fs.mjs.map +10 -0
- package/{src/native-input-test.ts → dist/mjs/native-input-test.mjs} +60 -169
- package/dist/mjs/native-input-test.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/mjs/runtime-context.mjs +67 -0
- package/dist/mjs/runtime-context.mjs.map +10 -0
- package/dist/mjs/server.mjs +79 -0
- package/dist/mjs/server.mjs.map +10 -0
- package/dist/types/fetch-context.d.ts +7 -0
- package/dist/types/fs-context.d.ts +30 -0
- package/dist/types/index.d.ts +88 -0
- package/dist/types/mock-fs.d.ts +59 -0
- package/dist/types/native-input-test.d.ts +29 -0
- package/dist/types/runtime-context.d.ts +73 -0
- package/dist/types/server.d.ts +53 -0
- package/package.json +45 -18
- package/CHANGELOG.md +0 -119
- package/src/fetch-context.ts +0 -33
- package/src/fs-context.ts +0 -65
- package/src/index.test.ts +0 -472
- package/src/index.ts +0 -177
- package/src/runtime-context.ts +0 -148
- package/src/server.ts +0 -150
package/src/index.test.ts
DELETED
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
import { test, describe, afterEach } from "node:test";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import {
|
|
4
|
-
createTestContext,
|
|
5
|
-
createCoreTestContext,
|
|
6
|
-
evalCode,
|
|
7
|
-
evalCodeAsync,
|
|
8
|
-
evalCodeJson,
|
|
9
|
-
evalCodeJsonAsync,
|
|
10
|
-
injectGlobals,
|
|
11
|
-
MockFileSystem,
|
|
12
|
-
createFsTestContext,
|
|
13
|
-
createRuntimeTestContext,
|
|
14
|
-
startIntegrationServer,
|
|
15
|
-
type TestContext,
|
|
16
|
-
type FsTestContext,
|
|
17
|
-
type RuntimeTestContext,
|
|
18
|
-
type IntegrationServer,
|
|
19
|
-
} from "./index.ts";
|
|
20
|
-
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// createTestContext tests
|
|
23
|
-
// ============================================================================
|
|
24
|
-
|
|
25
|
-
describe("createTestContext", () => {
|
|
26
|
-
let ctx: TestContext | undefined;
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
ctx?.dispose();
|
|
30
|
-
ctx = undefined;
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("creates a basic context", async () => {
|
|
34
|
-
ctx = await createTestContext();
|
|
35
|
-
assert.ok(ctx.isolate);
|
|
36
|
-
assert.ok(ctx.context);
|
|
37
|
-
assert.ok(typeof ctx.dispose === "function");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("can evaluate code in context", async () => {
|
|
41
|
-
ctx = await createTestContext();
|
|
42
|
-
const result = ctx.context.evalSync("1 + 1");
|
|
43
|
-
assert.strictEqual(result, 2);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe("createCoreTestContext", () => {
|
|
48
|
-
let ctx: TestContext | undefined;
|
|
49
|
-
|
|
50
|
-
afterEach(() => {
|
|
51
|
-
ctx?.dispose();
|
|
52
|
-
ctx = undefined;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("creates a context with core APIs", async () => {
|
|
56
|
-
ctx = await createCoreTestContext();
|
|
57
|
-
assert.ok(ctx.isolate);
|
|
58
|
-
assert.ok(ctx.context);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("has Blob available", async () => {
|
|
62
|
-
ctx = await createCoreTestContext();
|
|
63
|
-
const result = ctx.context.evalSync("typeof Blob");
|
|
64
|
-
assert.strictEqual(result, "function");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("has URL available", async () => {
|
|
68
|
-
ctx = await createCoreTestContext();
|
|
69
|
-
const result = ctx.context.evalSync(
|
|
70
|
-
"new URL('https://example.com').hostname"
|
|
71
|
-
);
|
|
72
|
-
assert.strictEqual(result, "example.com");
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// ============================================================================
|
|
77
|
-
// evalCode tests
|
|
78
|
-
// ============================================================================
|
|
79
|
-
|
|
80
|
-
describe("evalCode", () => {
|
|
81
|
-
let ctx: TestContext | undefined;
|
|
82
|
-
|
|
83
|
-
afterEach(() => {
|
|
84
|
-
ctx?.dispose();
|
|
85
|
-
ctx = undefined;
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("evaluates code synchronously", async () => {
|
|
89
|
-
ctx = await createTestContext();
|
|
90
|
-
const result = evalCode<number>(ctx.context, "2 + 3");
|
|
91
|
-
assert.strictEqual(result, 5);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("returns string", async () => {
|
|
95
|
-
ctx = await createTestContext();
|
|
96
|
-
const result = evalCode<string>(ctx.context, '"hello"');
|
|
97
|
-
assert.strictEqual(result, "hello");
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe("evalCodeAsync", () => {
|
|
102
|
-
let ctx: TestContext | undefined;
|
|
103
|
-
|
|
104
|
-
afterEach(() => {
|
|
105
|
-
ctx?.dispose();
|
|
106
|
-
ctx = undefined;
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("evaluates async code", async () => {
|
|
110
|
-
ctx = await createTestContext();
|
|
111
|
-
const result = await evalCodeAsync<number>(
|
|
112
|
-
ctx.context,
|
|
113
|
-
"Promise.resolve(42)"
|
|
114
|
-
);
|
|
115
|
-
assert.strictEqual(result, 42);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("handles async IIFE", async () => {
|
|
119
|
-
ctx = await createTestContext();
|
|
120
|
-
const result = await evalCodeAsync<string>(
|
|
121
|
-
ctx.context,
|
|
122
|
-
`(async () => { return "async result"; })()`
|
|
123
|
-
);
|
|
124
|
-
assert.strictEqual(result, "async result");
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe("evalCodeJson", () => {
|
|
129
|
-
let ctx: TestContext | undefined;
|
|
130
|
-
|
|
131
|
-
afterEach(() => {
|
|
132
|
-
ctx?.dispose();
|
|
133
|
-
ctx = undefined;
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test("parses JSON result", async () => {
|
|
137
|
-
ctx = await createTestContext();
|
|
138
|
-
const result = evalCodeJson<{ name: string }>(
|
|
139
|
-
ctx.context,
|
|
140
|
-
'JSON.stringify({ name: "test" })'
|
|
141
|
-
);
|
|
142
|
-
assert.deepStrictEqual(result, { name: "test" });
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe("evalCodeJsonAsync", () => {
|
|
147
|
-
let ctx: TestContext | undefined;
|
|
148
|
-
|
|
149
|
-
afterEach(() => {
|
|
150
|
-
ctx?.dispose();
|
|
151
|
-
ctx = undefined;
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("parses async JSON result", async () => {
|
|
155
|
-
ctx = await createTestContext();
|
|
156
|
-
const result = await evalCodeJsonAsync<{ value: number }>(
|
|
157
|
-
ctx.context,
|
|
158
|
-
`(async () => JSON.stringify({ value: 123 }))()`
|
|
159
|
-
);
|
|
160
|
-
assert.deepStrictEqual(result, { value: 123 });
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe("injectGlobals", () => {
|
|
165
|
-
let ctx: TestContext | undefined;
|
|
166
|
-
|
|
167
|
-
afterEach(() => {
|
|
168
|
-
ctx?.dispose();
|
|
169
|
-
ctx = undefined;
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("injects primitive values", async () => {
|
|
173
|
-
ctx = await createTestContext();
|
|
174
|
-
await injectGlobals(ctx.context, {
|
|
175
|
-
testString: "hello",
|
|
176
|
-
testNumber: 42,
|
|
177
|
-
testBool: true,
|
|
178
|
-
});
|
|
179
|
-
assert.strictEqual(evalCode<string>(ctx.context, "testString"), "hello");
|
|
180
|
-
assert.strictEqual(evalCode<number>(ctx.context, "testNumber"), 42);
|
|
181
|
-
assert.strictEqual(evalCode<boolean>(ctx.context, "testBool"), true);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("injects objects", async () => {
|
|
185
|
-
ctx = await createTestContext();
|
|
186
|
-
await injectGlobals(ctx.context, {
|
|
187
|
-
testConfig: { debug: true, level: 3 },
|
|
188
|
-
});
|
|
189
|
-
const result = evalCodeJson<{ debug: boolean; level: number }>(
|
|
190
|
-
ctx.context,
|
|
191
|
-
"JSON.stringify(testConfig)"
|
|
192
|
-
);
|
|
193
|
-
assert.deepStrictEqual(result, { debug: true, level: 3 });
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// ============================================================================
|
|
198
|
-
// MockFileSystem tests
|
|
199
|
-
// ============================================================================
|
|
200
|
-
|
|
201
|
-
describe("MockFileSystem", () => {
|
|
202
|
-
test("creates empty file system with root directory", () => {
|
|
203
|
-
const fs = new MockFileSystem();
|
|
204
|
-
assert.strictEqual(fs.directories.has("/"), true);
|
|
205
|
-
assert.strictEqual(fs.files.size, 0);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test("setFile creates file with content", async () => {
|
|
209
|
-
const fs = new MockFileSystem();
|
|
210
|
-
fs.setFile("/test.txt", "Hello, World!");
|
|
211
|
-
|
|
212
|
-
const file = await fs.readFile("/test.txt");
|
|
213
|
-
assert.strictEqual(new TextDecoder().decode(file.data), "Hello, World!");
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("getFile retrieves file content", () => {
|
|
217
|
-
const fs = new MockFileSystem();
|
|
218
|
-
fs.setFile("/test.txt", "content");
|
|
219
|
-
|
|
220
|
-
const data = fs.getFile("/test.txt");
|
|
221
|
-
assert.ok(data);
|
|
222
|
-
assert.strictEqual(new TextDecoder().decode(data), "content");
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
test("getFileAsString retrieves file as string", () => {
|
|
226
|
-
const fs = new MockFileSystem();
|
|
227
|
-
fs.setFile("/test.txt", "string content");
|
|
228
|
-
|
|
229
|
-
const content = fs.getFileAsString("/test.txt");
|
|
230
|
-
assert.strictEqual(content, "string content");
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test("createDirectory creates nested directories", () => {
|
|
234
|
-
const fs = new MockFileSystem();
|
|
235
|
-
fs.createDirectory("/a/b/c");
|
|
236
|
-
|
|
237
|
-
assert.strictEqual(fs.directories.has("/a"), true);
|
|
238
|
-
assert.strictEqual(fs.directories.has("/a/b"), true);
|
|
239
|
-
assert.strictEqual(fs.directories.has("/a/b/c"), true);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test("reset clears all files and directories", async () => {
|
|
243
|
-
const fs = new MockFileSystem();
|
|
244
|
-
fs.setFile("/test.txt", "content");
|
|
245
|
-
fs.createDirectory("/dir");
|
|
246
|
-
|
|
247
|
-
fs.reset();
|
|
248
|
-
|
|
249
|
-
assert.strictEqual(fs.files.size, 0);
|
|
250
|
-
assert.strictEqual(fs.directories.size, 1); // Only root
|
|
251
|
-
assert.strictEqual(fs.directories.has("/"), true);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test("getFileHandle throws NotFoundError for missing file", async () => {
|
|
255
|
-
const fs = new MockFileSystem();
|
|
256
|
-
await assert.rejects(
|
|
257
|
-
fs.getFileHandle("/missing.txt"),
|
|
258
|
-
/NotFoundError/
|
|
259
|
-
);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
test("getFileHandle creates file with create option", async () => {
|
|
263
|
-
const fs = new MockFileSystem();
|
|
264
|
-
await fs.getFileHandle("/new.txt", { create: true });
|
|
265
|
-
assert.strictEqual(fs.files.has("/new.txt"), true);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
test("readDirectory lists files and directories", async () => {
|
|
269
|
-
const fs = new MockFileSystem();
|
|
270
|
-
fs.setFile("/file1.txt", "content");
|
|
271
|
-
fs.setFile("/file2.txt", "content");
|
|
272
|
-
fs.createDirectory("/subdir");
|
|
273
|
-
|
|
274
|
-
const entries = await fs.readDirectory("/");
|
|
275
|
-
assert.strictEqual(entries.length, 3);
|
|
276
|
-
|
|
277
|
-
const names = entries.map((e) => e.name).sort();
|
|
278
|
-
assert.deepStrictEqual(names, ["file1.txt", "file2.txt", "subdir"]);
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// ============================================================================
|
|
283
|
-
// createFsTestContext tests
|
|
284
|
-
// ============================================================================
|
|
285
|
-
|
|
286
|
-
describe("createFsTestContext", () => {
|
|
287
|
-
let ctx: FsTestContext | undefined;
|
|
288
|
-
|
|
289
|
-
afterEach(() => {
|
|
290
|
-
ctx?.dispose();
|
|
291
|
-
ctx = undefined;
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test("creates context with file system APIs", async () => {
|
|
295
|
-
ctx = await createFsTestContext();
|
|
296
|
-
assert.ok(ctx.isolate);
|
|
297
|
-
assert.ok(ctx.context);
|
|
298
|
-
assert.ok(ctx.mockFs);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test("mockFs is connected to context", async () => {
|
|
302
|
-
ctx = await createFsTestContext();
|
|
303
|
-
|
|
304
|
-
// Create a file in mockFs
|
|
305
|
-
ctx.mockFs.setFile("/test.txt", "Hello from test!");
|
|
306
|
-
|
|
307
|
-
// Read it from the isolate
|
|
308
|
-
const result = await ctx.context.eval(
|
|
309
|
-
`
|
|
310
|
-
(async () => {
|
|
311
|
-
const root = await getDirectory('/');
|
|
312
|
-
const fileHandle = await root.getFileHandle("test.txt");
|
|
313
|
-
const file = await fileHandle.getFile();
|
|
314
|
-
return await file.text();
|
|
315
|
-
})()
|
|
316
|
-
`,
|
|
317
|
-
{ promise: true }
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
assert.strictEqual(result, "Hello from test!");
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
// ============================================================================
|
|
325
|
-
// createRuntimeTestContext tests
|
|
326
|
-
// ============================================================================
|
|
327
|
-
|
|
328
|
-
describe("createRuntimeTestContext", () => {
|
|
329
|
-
let ctx: RuntimeTestContext | undefined;
|
|
330
|
-
|
|
331
|
-
afterEach(async () => {
|
|
332
|
-
await ctx?.dispose();
|
|
333
|
-
ctx = undefined;
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
test("creates full runtime context", async () => {
|
|
337
|
-
ctx = await createRuntimeTestContext();
|
|
338
|
-
assert.ok(typeof ctx.eval === "function");
|
|
339
|
-
assert.ok(typeof ctx.clearTimers === "function");
|
|
340
|
-
assert.ok(typeof ctx.dispatchRequest === "function");
|
|
341
|
-
assert.ok(typeof ctx.dispose === "function");
|
|
342
|
-
assert.ok(Array.isArray(ctx.logs));
|
|
343
|
-
assert.ok(Array.isArray(ctx.fetchCalls));
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
test("captures console logs", async () => {
|
|
347
|
-
ctx = await createRuntimeTestContext();
|
|
348
|
-
|
|
349
|
-
await ctx.eval('console.log("test message")');
|
|
350
|
-
await ctx.eval('console.warn("warning message")');
|
|
351
|
-
|
|
352
|
-
assert.strictEqual(ctx.logs.length, 2);
|
|
353
|
-
assert.strictEqual(ctx.logs[0]!.level, "log");
|
|
354
|
-
assert.strictEqual(ctx.logs[0]!.stdout, "test message");
|
|
355
|
-
assert.strictEqual(ctx.logs[1]!.level, "warn");
|
|
356
|
-
assert.strictEqual(ctx.logs[1]!.stdout, "warning message");
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
test("captures and mocks fetch calls", async () => {
|
|
360
|
-
ctx = await createRuntimeTestContext();
|
|
361
|
-
|
|
362
|
-
ctx.setMockResponse({
|
|
363
|
-
status: 200,
|
|
364
|
-
body: '{"data": "test"}',
|
|
365
|
-
headers: { "Content-Type": "application/json" },
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
await ctx.eval(`
|
|
369
|
-
const response = await fetch("https://api.example.com/data");
|
|
370
|
-
const json = await response.json();
|
|
371
|
-
setResult({ status: response.status, data: json });
|
|
372
|
-
`);
|
|
373
|
-
|
|
374
|
-
const result = ctx.getResult<{ status: number; data: { data: string } }>();
|
|
375
|
-
assert.strictEqual(result?.status, 200);
|
|
376
|
-
assert.deepStrictEqual(result?.data, { data: "test" });
|
|
377
|
-
|
|
378
|
-
assert.strictEqual(ctx.fetchCalls.length, 1);
|
|
379
|
-
assert.strictEqual(ctx.fetchCalls[0]!.url, "https://api.example.com/data");
|
|
380
|
-
assert.strictEqual(ctx.fetchCalls[0]!.method, "GET");
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
test("timers fire automatically with real time", async () => {
|
|
384
|
-
ctx = await createRuntimeTestContext();
|
|
385
|
-
|
|
386
|
-
await ctx.eval(`
|
|
387
|
-
globalThis.timerFired = false;
|
|
388
|
-
setTimeout(() => { globalThis.timerFired = true; }, 20);
|
|
389
|
-
`);
|
|
390
|
-
|
|
391
|
-
// Timer should not have fired yet - check via setResult
|
|
392
|
-
await ctx.eval('setResult(globalThis.timerFired)');
|
|
393
|
-
assert.strictEqual(ctx.getResult<boolean>(), false);
|
|
394
|
-
ctx.clearResult();
|
|
395
|
-
|
|
396
|
-
// Wait for real time to pass
|
|
397
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
398
|
-
|
|
399
|
-
// Timer should have fired
|
|
400
|
-
await ctx.eval('setResult(globalThis.timerFired)');
|
|
401
|
-
assert.strictEqual(ctx.getResult<boolean>(), true);
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
// ============================================================================
|
|
406
|
-
// startIntegrationServer tests
|
|
407
|
-
// ============================================================================
|
|
408
|
-
|
|
409
|
-
describe("startIntegrationServer", () => {
|
|
410
|
-
let server: IntegrationServer | undefined;
|
|
411
|
-
|
|
412
|
-
afterEach(async () => {
|
|
413
|
-
await server?.close();
|
|
414
|
-
server = undefined;
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
test("starts server on available port", async () => {
|
|
418
|
-
server = await startIntegrationServer();
|
|
419
|
-
assert.ok(server.port > 0);
|
|
420
|
-
assert.ok(server.url.startsWith("http://localhost:"));
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
test("responds with configured response", async () => {
|
|
424
|
-
server = await startIntegrationServer();
|
|
425
|
-
server.setResponse("/api/test", {
|
|
426
|
-
status: 200,
|
|
427
|
-
body: '{"message": "hello"}',
|
|
428
|
-
headers: { "Content-Type": "application/json" },
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
const response = await fetch(`${server.url}/api/test`);
|
|
432
|
-
assert.strictEqual(response.status, 200);
|
|
433
|
-
const data = await response.json();
|
|
434
|
-
assert.deepStrictEqual(data, { message: "hello" });
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
test("records requests", async () => {
|
|
438
|
-
server = await startIntegrationServer();
|
|
439
|
-
server.setDefaultResponse({ status: 200, body: "OK" });
|
|
440
|
-
|
|
441
|
-
await fetch(`${server.url}/api/endpoint`, {
|
|
442
|
-
method: "POST",
|
|
443
|
-
headers: { "X-Custom": "value" },
|
|
444
|
-
body: "request body",
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
const requests = server.getRequests();
|
|
448
|
-
assert.strictEqual(requests.length, 1);
|
|
449
|
-
assert.strictEqual(requests[0]!.method, "POST");
|
|
450
|
-
assert.strictEqual(requests[0]!.path, "/api/endpoint");
|
|
451
|
-
assert.strictEqual(requests[0]!.headers["x-custom"], "value");
|
|
452
|
-
assert.strictEqual(requests[0]!.body, "request body");
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
test("clears requests", async () => {
|
|
456
|
-
server = await startIntegrationServer();
|
|
457
|
-
server.setDefaultResponse({ status: 200 });
|
|
458
|
-
|
|
459
|
-
await fetch(`${server.url}/test`);
|
|
460
|
-
assert.strictEqual(server.getRequests().length, 1);
|
|
461
|
-
|
|
462
|
-
server.clearRequests();
|
|
463
|
-
assert.strictEqual(server.getRequests().length, 0);
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
test("returns 404 for unmatched paths", async () => {
|
|
467
|
-
server = await startIntegrationServer();
|
|
468
|
-
|
|
469
|
-
const response = await fetch(`${server.url}/unknown`);
|
|
470
|
-
assert.strictEqual(response.status, 404);
|
|
471
|
-
});
|
|
472
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import type ivm from "isolated-vm";
|
|
2
|
-
|
|
3
|
-
// ============================================================================
|
|
4
|
-
// Types
|
|
5
|
-
// ============================================================================
|
|
6
|
-
|
|
7
|
-
export interface TestContext {
|
|
8
|
-
isolate: ivm.Isolate;
|
|
9
|
-
context: ivm.Context;
|
|
10
|
-
dispose(): void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface TestResult<T> {
|
|
14
|
-
result: T;
|
|
15
|
-
logs: Array<{ level: string; args: unknown[] }>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// ============================================================================
|
|
19
|
-
// Context Creation
|
|
20
|
-
// ============================================================================
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Create a basic test context for isolated-vm tests.
|
|
24
|
-
* This creates a bare context without any APIs set up.
|
|
25
|
-
*/
|
|
26
|
-
export async function createTestContext(): Promise<TestContext> {
|
|
27
|
-
const ivm = await import("isolated-vm");
|
|
28
|
-
const isolate = new ivm.default.Isolate();
|
|
29
|
-
const context = await isolate.createContext();
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
isolate,
|
|
33
|
-
context,
|
|
34
|
-
dispose() {
|
|
35
|
-
context.release();
|
|
36
|
-
isolate.dispose();
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Create a test context with core APIs set up (Blob, File, URL, streams, etc.)
|
|
43
|
-
*/
|
|
44
|
-
export async function createCoreTestContext(): Promise<TestContext> {
|
|
45
|
-
const ivm = await import("isolated-vm");
|
|
46
|
-
const { setupCore } = await import("@ricsam/isolate-core");
|
|
47
|
-
|
|
48
|
-
const isolate = new ivm.default.Isolate();
|
|
49
|
-
const context = await isolate.createContext();
|
|
50
|
-
const coreHandle = await setupCore(context);
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
isolate,
|
|
54
|
-
context,
|
|
55
|
-
dispose() {
|
|
56
|
-
coreHandle.dispose();
|
|
57
|
-
context.release();
|
|
58
|
-
isolate.dispose();
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ============================================================================
|
|
64
|
-
// Code Evaluation Helpers
|
|
65
|
-
// ============================================================================
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Synchronously evaluate code and return typed result.
|
|
69
|
-
* Use this for simple expressions that don't involve promises.
|
|
70
|
-
*
|
|
71
|
-
* @example
|
|
72
|
-
* const result = evalCode<number>(ctx.context, "1 + 1");
|
|
73
|
-
* // result === 2
|
|
74
|
-
*/
|
|
75
|
-
export function evalCode<T = unknown>(context: ivm.Context, code: string): T {
|
|
76
|
-
return context.evalSync(code) as T;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Asynchronously evaluate code that may return promises.
|
|
81
|
-
* Automatically wraps code to handle promise resolution.
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* const result = await evalCodeAsync<string>(ctx.context, `
|
|
85
|
-
* (async () => {
|
|
86
|
-
* return "hello";
|
|
87
|
-
* })()
|
|
88
|
-
* `);
|
|
89
|
-
*/
|
|
90
|
-
export async function evalCodeAsync<T = unknown>(
|
|
91
|
-
context: ivm.Context,
|
|
92
|
-
code: string
|
|
93
|
-
): Promise<T> {
|
|
94
|
-
return (await context.eval(code, { promise: true })) as T;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Evaluate code and return the result as JSON (for complex objects).
|
|
99
|
-
* Useful when you need to extract structured data from the isolate.
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* const data = evalCodeJson<{ name: string }>(ctx.context, `
|
|
103
|
-
* JSON.stringify({ name: "test" })
|
|
104
|
-
* `);
|
|
105
|
-
*/
|
|
106
|
-
export function evalCodeJson<T = unknown>(context: ivm.Context, code: string): T {
|
|
107
|
-
const jsonString = context.evalSync(code) as string;
|
|
108
|
-
return JSON.parse(jsonString) as T;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Evaluate async code and return the result as JSON (for complex objects).
|
|
113
|
-
*
|
|
114
|
-
* @example
|
|
115
|
-
* const data = await evalCodeJsonAsync<{ status: number }>(ctx.context, `
|
|
116
|
-
* (async () => {
|
|
117
|
-
* const response = await fetch("...");
|
|
118
|
-
* return JSON.stringify({ status: response.status });
|
|
119
|
-
* })()
|
|
120
|
-
* `);
|
|
121
|
-
*/
|
|
122
|
-
export async function evalCodeJsonAsync<T = unknown>(
|
|
123
|
-
context: ivm.Context,
|
|
124
|
-
code: string
|
|
125
|
-
): Promise<T> {
|
|
126
|
-
const jsonString = (await context.eval(code, { promise: true })) as string;
|
|
127
|
-
return JSON.parse(jsonString) as T;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Inject values into the isolate's global scope before running code.
|
|
132
|
-
*
|
|
133
|
-
* @example
|
|
134
|
-
* await injectGlobals(ctx.context, {
|
|
135
|
-
* testInput: "hello",
|
|
136
|
-
* testConfig: { debug: true }
|
|
137
|
-
* });
|
|
138
|
-
* const result = evalCode<string>(ctx.context, "testInput");
|
|
139
|
-
*/
|
|
140
|
-
export async function injectGlobals(
|
|
141
|
-
context: ivm.Context,
|
|
142
|
-
values: Record<string, unknown>
|
|
143
|
-
): Promise<void> {
|
|
144
|
-
const global = context.global;
|
|
145
|
-
|
|
146
|
-
for (const [key, value] of Object.entries(values)) {
|
|
147
|
-
if (typeof value === "function") {
|
|
148
|
-
const ivm = await import("isolated-vm");
|
|
149
|
-
global.setSync(key, new ivm.default.Callback(value as (...args: unknown[]) => unknown));
|
|
150
|
-
} else if (typeof value === "object" && value !== null) {
|
|
151
|
-
// For objects, serialize as JSON and inject
|
|
152
|
-
context.evalSync(`globalThis.${key} = ${JSON.stringify(value)}`);
|
|
153
|
-
} else {
|
|
154
|
-
// For primitives, set directly
|
|
155
|
-
global.setSync(key, value);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ============================================================================
|
|
161
|
-
// Exports from other modules
|
|
162
|
-
// ============================================================================
|
|
163
|
-
|
|
164
|
-
export { MockFileSystem } from "./mock-fs.ts";
|
|
165
|
-
export { createFsTestContext } from "./fs-context.ts";
|
|
166
|
-
export type { FsTestContext } from "./fs-context.ts";
|
|
167
|
-
export { createRuntimeTestContext } from "./runtime-context.ts";
|
|
168
|
-
export type { RuntimeTestContext } from "./runtime-context.ts";
|
|
169
|
-
export { startIntegrationServer } from "./server.ts";
|
|
170
|
-
export type { IntegrationServer } from "./server.ts";
|
|
171
|
-
export { runTestCode } from "./native-input-test.ts";
|
|
172
|
-
export type { TestRunner, TestRuntime } from "./native-input-test.ts";
|
|
173
|
-
export { createFetchTestContext } from "./fetch-context.ts";
|
|
174
|
-
export type { FetchTestContext } from "./fetch-context.ts";
|
|
175
|
-
|
|
176
|
-
// Re-export useful types
|
|
177
|
-
export type { FileSystemHandler } from "@ricsam/isolate-fs";
|