@ricsam/isolate-fetch 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,520 +0,0 @@
1
- import { test, describe, beforeEach, afterEach } from "node:test";
2
- import assert from "node:assert";
3
- import {
4
- createFetchTestContext,
5
- evalCode,
6
- evalCodeAsync,
7
- runTestCode,
8
- type FetchTestContext,
9
- } from "@ricsam/isolate-test-utils";
10
-
11
- describe("Response", () => {
12
- let ctx: FetchTestContext;
13
-
14
- beforeEach(async () => {
15
- ctx = await createFetchTestContext();
16
- });
17
-
18
- afterEach(() => {
19
- ctx.dispose();
20
- });
21
-
22
- test("text() body method", async () => {
23
- const data = await evalCodeAsync<string>(
24
- ctx.context,
25
- `
26
- (async () => {
27
- const response = new Response("Hello Response");
28
- const text = await response.text();
29
- return JSON.stringify({ text });
30
- })()
31
- `
32
- );
33
- const result = JSON.parse(data);
34
-
35
- assert.strictEqual(result.text, "Hello Response");
36
- });
37
-
38
- test("json() body method", async () => {
39
- const data = await evalCodeAsync<string>(
40
- ctx.context,
41
- `
42
- (async () => {
43
- const response = new Response(JSON.stringify({ foo: "bar", num: 123 }));
44
- return JSON.stringify(await response.json());
45
- })()
46
- `
47
- );
48
- const result = JSON.parse(data) as { foo: string; num: number };
49
-
50
- assert.strictEqual(result.foo, "bar");
51
- assert.strictEqual(result.num, 123);
52
- });
53
-
54
- test("arrayBuffer() body method", async () => {
55
- const data = await evalCodeAsync<string>(
56
- ctx.context,
57
- `
58
- (async () => {
59
- const response = new Response("ABCDE");
60
- const buffer = await response.arrayBuffer();
61
- return JSON.stringify({
62
- isArrayBuffer: buffer instanceof ArrayBuffer,
63
- byteLength: buffer.byteLength,
64
- });
65
- })()
66
- `
67
- );
68
- const result = JSON.parse(data) as {
69
- isArrayBuffer: boolean;
70
- byteLength: number;
71
- };
72
-
73
- assert.strictEqual(result.isArrayBuffer, true);
74
- assert.strictEqual(result.byteLength, 5);
75
- });
76
-
77
- test("clone() creates copy", () => {
78
- const data = evalCode<string>(
79
- ctx.context,
80
- `
81
- const original = new Response("Clone me", { status: 201 });
82
- const cloned = original.clone();
83
- JSON.stringify({
84
- clonedStatus: cloned.status,
85
- sameStatus: original.status === cloned.status,
86
- originalNotUsed: !original.bodyUsed,
87
- clonedNotUsed: !cloned.bodyUsed,
88
- })
89
- `
90
- );
91
- const result = JSON.parse(data) as {
92
- clonedStatus: number;
93
- sameStatus: boolean;
94
- originalNotUsed: boolean;
95
- clonedNotUsed: boolean;
96
- };
97
-
98
- assert.strictEqual(result.clonedStatus, 201);
99
- assert.strictEqual(result.sameStatus, true);
100
- assert.strictEqual(result.originalNotUsed, true);
101
- assert.strictEqual(result.clonedNotUsed, true);
102
- });
103
-
104
- test("Response.json() with custom options", () => {
105
- const data = evalCode<string>(
106
- ctx.context,
107
- `
108
- const response = Response.json({ data: "test" }, {
109
- status: 201,
110
- headers: { "X-Custom": "value" },
111
- });
112
- JSON.stringify({
113
- status: response.status,
114
- customHeader: response.headers.get("X-Custom"),
115
- contentType: response.headers.get("Content-Type"),
116
- })
117
- `
118
- );
119
- const result = JSON.parse(data) as {
120
- status: number;
121
- customHeader: string;
122
- contentType: string;
123
- };
124
-
125
- assert.strictEqual(result.status, 201);
126
- assert.strictEqual(result.customHeader, "value");
127
- assert.ok(result.contentType.includes("application/json"));
128
- });
129
-
130
- test("body property returns HostBackedReadableStream", () => {
131
- const isStream = evalCode<boolean>(
132
- ctx.context,
133
- `
134
- const response = new Response("test body");
135
- response.body instanceof HostBackedReadableStream
136
- `
137
- );
138
- assert.strictEqual(isStream, true);
139
- });
140
-
141
- test("can read body via stream reader", async () => {
142
- const result = await evalCodeAsync<string>(
143
- ctx.context,
144
- `
145
- (async () => {
146
- const response = new Response("hello");
147
- const reader = response.body.getReader();
148
- const { value, done } = await reader.read();
149
- return JSON.stringify({
150
- hasValue: value != null,
151
- isDone: done
152
- });
153
- })()
154
- `
155
- );
156
- const data = JSON.parse(result) as { hasValue: boolean; isDone: boolean };
157
- assert.strictEqual(data.hasValue, true);
158
- assert.strictEqual(data.isDone, false);
159
- });
160
-
161
- test("Response with Headers(undefined) should be valid", async () => {
162
- const result = await evalCodeAsync<string>(
163
- ctx.context,
164
- `
165
- (async () => {
166
- const responseHeaders = new Headers(undefined);
167
- const response = new Response(JSON.stringify([]), {
168
- status: 200,
169
- headers: responseHeaders,
170
- });
171
- return JSON.stringify({
172
- status: response.status,
173
- ok: response.ok,
174
- body: await response.json(),
175
- });
176
- })()
177
- `
178
- );
179
- const data = JSON.parse(result) as {
180
- status: number;
181
- ok: boolean;
182
- body: unknown[];
183
- };
184
- assert.strictEqual(data.status, 200);
185
- assert.strictEqual(data.ok, true);
186
- assert.deepStrictEqual(data.body, []);
187
- });
188
-
189
- test("Response.json() with Headers instance should not leak internal properties", () => {
190
- // This test verifies that internal properties are non-enumerable
191
- const data = evalCode<string>(
192
- ctx.context,
193
- `
194
- const headers = new Headers();
195
- headers.set("x-custom", "value");
196
-
197
- // Check what properties exist on the Headers instance
198
- const headersOwnKeys = Object.keys(headers);
199
-
200
- // This is what Response.json does internally with Object.assign
201
- const assigned = Object.assign({ "content-type": "application/json" }, headers);
202
- const assignedKeys = Object.keys(assigned);
203
- const objectEntriesKeys = Object.entries(headers).map(e => e[0]);
204
-
205
- // When this assigned object is passed to Response
206
- const responseFromAssigned = new Response("test", { headers: assigned });
207
- const responseFromAssignedKeys = Array.from(responseFromAssigned.headers.keys());
208
-
209
- JSON.stringify({
210
- headersOwnKeys,
211
- assignedKeys,
212
- objectEntriesKeys,
213
- responseFromAssignedKeys,
214
- responseFromAssignedHasInstanceId: responseFromAssigned.headers.has("__instanceid__"),
215
- responseFromAssignedContentType: responseFromAssigned.headers.get("content-type"),
216
- })
217
- `
218
- );
219
- const result = JSON.parse(data) as {
220
- headersOwnKeys: string[];
221
- assignedKeys: string[];
222
- objectEntriesKeys: string[];
223
- responseFromAssignedKeys: string[];
224
- responseFromAssignedHasInstanceId: boolean;
225
- responseFromAssignedContentType: string | null;
226
- };
227
-
228
- // Internal properties should NOT be enumerable (not in Object.keys)
229
- assert.deepStrictEqual(result.headersOwnKeys, []);
230
- assert.deepStrictEqual(result.objectEntriesKeys, []);
231
-
232
- // Object.assign should NOT copy internal properties (since they're non-enumerable)
233
- assert.deepStrictEqual(result.assignedKeys, ["content-type"]);
234
-
235
- // Response created from plain object should only have content-type
236
- assert.deepStrictEqual(result.responseFromAssignedKeys, ["content-type"]);
237
- assert.strictEqual(result.responseFromAssignedHasInstanceId, false);
238
- assert.strictEqual(result.responseFromAssignedContentType, "application/json");
239
- });
240
- });
241
-
242
- /**
243
- * Native Response -> Isolate tests
244
- *
245
- * These tests verify that native Response objects passed into the isolate
246
- * behave identically to Response instances created with `new Response()` in the isolate.
247
- *
248
- * The tests use `runTestCode()` which converts native Response to isolate Response
249
- * instances before executing the test code.
250
- *
251
- * Note: These tests focus on synchronous properties. Body consumption methods
252
- * (text(), json(), arrayBuffer()) require async support in runTestCode which
253
- * is not yet implemented.
254
- */
255
- describe("Native Response -> Isolate", () => {
256
- let ctx: FetchTestContext;
257
-
258
- beforeEach(async () => {
259
- ctx = await createFetchTestContext();
260
- });
261
-
262
- afterEach(() => {
263
- ctx.dispose();
264
- });
265
-
266
- test("native Response should pass instanceof check in isolate", () => {
267
- const runtime = runTestCode(
268
- ctx.context,
269
- `
270
- const response = testingInput.response;
271
- log("instanceof", response instanceof Response);
272
- log("constructorName", response.constructor.name);
273
- `
274
- ).input({
275
- response: new Response("test body"),
276
- });
277
-
278
- assert.deepStrictEqual(runtime.logs, {
279
- instanceof: true,
280
- constructorName: "Response",
281
- });
282
- });
283
-
284
- test("status property is preserved", () => {
285
- const runtime = runTestCode(
286
- ctx.context,
287
- `
288
- const response = testingInput.response;
289
- log("status", response.status);
290
- `
291
- ).input({
292
- response: new Response(null, { status: 201 }),
293
- });
294
-
295
- assert.strictEqual(runtime.logs.status, 201);
296
- });
297
-
298
- test("statusText property is preserved", () => {
299
- const runtime = runTestCode(
300
- ctx.context,
301
- `
302
- const response = testingInput.response;
303
- log("statusText", response.statusText);
304
- `
305
- ).input({
306
- response: new Response(null, { status: 201, statusText: "Created" }),
307
- });
308
-
309
- assert.strictEqual(runtime.logs.statusText, "Created");
310
- });
311
-
312
- test("ok property is correct for 2xx status", () => {
313
- const runtime = runTestCode(
314
- ctx.context,
315
- `
316
- const okResponse = testingInput.okResponse;
317
- const errorResponse = testingInput.errorResponse;
318
- log("okStatus", okResponse.ok);
319
- log("errorStatus", errorResponse.ok);
320
- `
321
- ).input({
322
- okResponse: new Response(null, { status: 200 }),
323
- errorResponse: new Response(null, { status: 404 }),
324
- });
325
-
326
- assert.deepStrictEqual(runtime.logs, {
327
- okStatus: true,
328
- errorStatus: false,
329
- });
330
- });
331
-
332
- test("headers property is a Headers instance", () => {
333
- const runtime = runTestCode(
334
- ctx.context,
335
- `
336
- const response = testingInput.response;
337
- log("headersInstanceof", response.headers instanceof Headers);
338
- log("contentType", response.headers.get("content-type"));
339
- log("customHeader", response.headers.get("x-custom"));
340
- `
341
- ).input({
342
- response: new Response(null, {
343
- headers: {
344
- "Content-Type": "application/json",
345
- "X-Custom": "test-value",
346
- },
347
- }),
348
- });
349
-
350
- assert.deepStrictEqual(runtime.logs, {
351
- headersInstanceof: true,
352
- contentType: "application/json",
353
- customHeader: "test-value",
354
- });
355
- });
356
-
357
- test("bodyUsed property exists and is false initially", () => {
358
- const runtime = runTestCode(
359
- ctx.context,
360
- `
361
- const response = testingInput.response;
362
- log("bodyUsed", response.bodyUsed);
363
- log("hasBodyUsed", typeof response.bodyUsed === "boolean");
364
- `
365
- ).input({
366
- response: new Response("test"),
367
- });
368
-
369
- assert.deepStrictEqual(runtime.logs, {
370
- bodyUsed: false,
371
- hasBodyUsed: true,
372
- });
373
- });
374
-
375
- test("type property exists", () => {
376
- const runtime = runTestCode(
377
- ctx.context,
378
- `
379
- const response = testingInput.response;
380
- log("type", response.type);
381
- log("hasType", typeof response.type === "string");
382
- `
383
- ).input({
384
- response: new Response(null),
385
- });
386
-
387
- assert.strictEqual(runtime.logs.hasType, true);
388
- assert.strictEqual(typeof runtime.logs.type, "string");
389
- });
390
-
391
- test("redirected property exists", () => {
392
- const runtime = runTestCode(
393
- ctx.context,
394
- `
395
- const response = testingInput.response;
396
- log("redirected", response.redirected);
397
- log("hasRedirected", typeof response.redirected === "boolean");
398
- `
399
- ).input({
400
- response: new Response(null),
401
- });
402
-
403
- assert.deepStrictEqual(runtime.logs, {
404
- redirected: false,
405
- hasRedirected: true,
406
- });
407
- });
408
-
409
- test("all standard properties exist", () => {
410
- const runtime = runTestCode(
411
- ctx.context,
412
- `
413
- const response = testingInput.response;
414
- log("hasStatus", typeof response.status === "number");
415
- log("hasStatusText", typeof response.statusText === "string");
416
- log("hasOk", typeof response.ok === "boolean");
417
- log("hasHeaders", response.headers instanceof Headers);
418
- log("hasBodyUsed", typeof response.bodyUsed === "boolean");
419
- log("hasType", typeof response.type === "string");
420
- log("hasRedirected", typeof response.redirected === "boolean");
421
- log("hasUrl", typeof response.url === "string");
422
- `
423
- ).input({
424
- response: new Response(null),
425
- });
426
-
427
- assert.deepStrictEqual(runtime.logs, {
428
- hasStatus: true,
429
- hasStatusText: true,
430
- hasOk: true,
431
- hasHeaders: true,
432
- hasBodyUsed: true,
433
- hasType: true,
434
- hasRedirected: true,
435
- hasUrl: true,
436
- });
437
- });
438
-
439
- describe("Bidirectional Conversion (Native->Isolate->Native)", () => {
440
- test("Response created in isolate should return as native Response", () => {
441
- const runtime = runTestCode(
442
- ctx.context,
443
- `
444
- const response = new Response(null, {
445
- status: 201,
446
- statusText: "Created",
447
- headers: { "Content-Type": "application/json" }
448
- });
449
- log("response", response);
450
- `
451
- ).input({});
452
-
453
- assert.ok(runtime.logs.response instanceof Response);
454
- assert.strictEqual((runtime.logs.response as Response).status, 201);
455
- assert.strictEqual((runtime.logs.response as Response).statusText, "Created");
456
- });
457
-
458
- test("native Response passed through isolate returns as native Response", () => {
459
- const runtime = runTestCode(
460
- ctx.context,
461
- `
462
- const response = testingInput.response;
463
- log("response", response);
464
- `
465
- ).input({
466
- response: new Response(null, {
467
- status: 404,
468
- statusText: "Not Found",
469
- headers: { "X-Error": "missing" },
470
- }),
471
- });
472
-
473
- assert.ok(runtime.logs.response instanceof Response);
474
- assert.strictEqual((runtime.logs.response as Response).status, 404);
475
- assert.strictEqual((runtime.logs.response as Response).statusText, "Not Found");
476
- });
477
-
478
- test("Response headers should be native Headers after round-trip", () => {
479
- const runtime = runTestCode(
480
- ctx.context,
481
- `
482
- const response = new Response(null, {
483
- headers: { "Content-Type": "text/html" }
484
- });
485
- log("response", response);
486
- `
487
- ).input({});
488
-
489
- assert.ok(runtime.logs.response instanceof Response);
490
- assert.ok((runtime.logs.response as Response).headers instanceof Headers);
491
- assert.strictEqual(
492
- (runtime.logs.response as Response).headers.get("content-type"),
493
- "text/html"
494
- );
495
- });
496
-
497
- test("nested object with Response converts properly", () => {
498
- const runtime = runTestCode(
499
- ctx.context,
500
- `
501
- const response = testingInput.response;
502
- log("result", {
503
- response: response,
504
- metadata: { cached: true }
505
- });
506
- `
507
- ).input({
508
- response: new Response(null, { status: 200 }),
509
- });
510
-
511
- const result = runtime.logs.result as {
512
- response: Response;
513
- metadata: { cached: boolean };
514
- };
515
- assert.ok(result.response instanceof Response);
516
- assert.strictEqual(result.response.status, 200);
517
- assert.deepStrictEqual(result.metadata, { cached: true });
518
- });
519
- });
520
- });