@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,582 +0,0 @@
1
- import { test, describe, beforeEach, afterEach } from "node:test";
2
- import assert from "node:assert";
3
- import {
4
- createFetchTestContext,
5
- evalCode,
6
- runTestCode,
7
- type FetchTestContext,
8
- } from "@ricsam/isolate-test-utils";
9
-
10
- describe("Headers", () => {
11
- let ctx: FetchTestContext;
12
-
13
- beforeEach(async () => {
14
- ctx = await createFetchTestContext();
15
- });
16
-
17
- afterEach(() => {
18
- ctx.dispose();
19
- });
20
-
21
- test("case-insensitive get", () => {
22
- const data = evalCode<{ withCaps: string; lowercase: string; uppercase: string }>(
23
- ctx.context,
24
- `
25
- const headers = new Headers({ "Content-Type": "application/json" });
26
- JSON.stringify({
27
- withCaps: headers.get("Content-Type"),
28
- lowercase: headers.get("content-type"),
29
- uppercase: headers.get("CONTENT-TYPE"),
30
- })
31
- `
32
- );
33
- const result = JSON.parse(data as unknown as string);
34
-
35
- assert.strictEqual(result.withCaps, "application/json");
36
- assert.strictEqual(result.lowercase, "application/json");
37
- assert.strictEqual(result.uppercase, "application/json");
38
- });
39
-
40
- test("forEach callback", () => {
41
- const data = evalCode<string>(
42
- ctx.context,
43
- `
44
- const headers = new Headers({ "X-One": "1", "X-Two": "2" });
45
- const collected = [];
46
- headers.forEach((value, key) => {
47
- collected.push({ value, key: key.toLowerCase() });
48
- });
49
- JSON.stringify(collected)
50
- `
51
- );
52
- const collected = JSON.parse(data) as { value: string; key: string }[];
53
-
54
- assert.strictEqual(collected.length, 2);
55
- assert.deepStrictEqual(
56
- collected.map((d) => d.key).sort(),
57
- ["x-one", "x-two"]
58
- );
59
- assert.deepStrictEqual(
60
- collected.map((d) => d.value).sort(),
61
- ["1", "2"]
62
- );
63
- });
64
-
65
- test("getSetCookie returns array", () => {
66
- const data = evalCode<string>(
67
- ctx.context,
68
- `
69
- const headers = new Headers();
70
- headers.append("Set-Cookie", "a=1");
71
- headers.append("Set-Cookie", "b=2");
72
- JSON.stringify({
73
- cookies: headers.getSetCookie(),
74
- regular: headers.get("Set-Cookie"),
75
- })
76
- `
77
- );
78
- const result = JSON.parse(data) as { cookies: string[]; regular: string };
79
-
80
- assert.deepStrictEqual(result.cookies, ["a=1", "b=2"]);
81
- assert.strictEqual(result.regular, "a=1, b=2");
82
- });
83
-
84
- test("constructor with array of pairs", () => {
85
- const data = evalCode<string>(
86
- ctx.context,
87
- `
88
- const headers = new Headers([["X-First", "one"], ["X-Second", "two"]]);
89
- JSON.stringify({
90
- first: headers.get("X-First"),
91
- second: headers.get("X-Second"),
92
- keys: Array.from(headers.keys()).map(k => k.toLowerCase()),
93
- })
94
- `
95
- );
96
- const result = JSON.parse(data) as { first: string; second: string; keys: string[] };
97
-
98
- assert.strictEqual(result.first, "one");
99
- assert.strictEqual(result.second, "two");
100
- assert.deepStrictEqual(result.keys.sort(), ["x-first", "x-second"]);
101
- });
102
-
103
- test("for...of iteration on Headers", () => {
104
- const data = evalCode<string>(
105
- ctx.context,
106
- `
107
- const headers = new Headers({
108
- "Content-Type": "application/json",
109
- "X-Custom": "value"
110
- });
111
- const entries = [];
112
- for (const [key, value] of headers) {
113
- entries.push([key.toLowerCase(), value]);
114
- }
115
- JSON.stringify(entries)
116
- `
117
- );
118
- const entries = JSON.parse(data) as Array<[string, string]>;
119
-
120
- assert.ok(
121
- entries.some(([k, v]) => k === "content-type" && v === "application/json")
122
- );
123
- assert.ok(entries.some(([k, v]) => k === "x-custom" && v === "value"));
124
- });
125
-
126
- test("Array.from(headers)", () => {
127
- const data = evalCode<string>(
128
- ctx.context,
129
- `
130
- const headers = new Headers({
131
- "Content-Type": "application/json",
132
- "Accept": "text/html"
133
- });
134
- JSON.stringify(Array.from(headers).map(([k, v]) => [k.toLowerCase(), v]))
135
- `
136
- );
137
- const entries = JSON.parse(data) as Array<[string, string]>;
138
-
139
- assert.ok(
140
- entries.some(([k, v]) => k === "content-type" && v === "application/json")
141
- );
142
- assert.ok(entries.some(([k, v]) => k === "accept" && v === "text/html"));
143
- });
144
-
145
- test("headers should not include internal properties", () => {
146
- const data = evalCode<string>(
147
- ctx.context,
148
- `
149
- const headers = new Headers({
150
- "Content-Type": "application/octet-stream",
151
- });
152
- JSON.stringify({
153
- entries: Array.from(headers.entries()).map(([k, v]) => [k.toLowerCase(), v]),
154
- keys: Array.from(headers.keys()).map(k => k.toLowerCase()),
155
- })
156
- `
157
- );
158
- const result = JSON.parse(data) as {
159
- entries: Array<[string, string]>;
160
- keys: string[];
161
- };
162
-
163
- // Headers should only contain the actual header, not internal properties
164
- assert.deepStrictEqual(result.keys, ["content-type"]);
165
- assert.ok(!result.keys.includes("__instanceid__"));
166
- assert.ok(!result.keys.includes("__classname__"));
167
- assert.deepStrictEqual(result.entries, [
168
- ["content-type", "application/octet-stream"],
169
- ]);
170
- });
171
-
172
- test("instanceof Headers returns true", () => {
173
- const data = evalCode<string>(
174
- ctx.context,
175
- `
176
- const headers = new Headers();
177
- JSON.stringify({ instanceofHeaders: headers instanceof Headers })
178
- `
179
- );
180
- const result = JSON.parse(data) as { instanceofHeaders: boolean };
181
- assert.strictEqual(result.instanceofHeaders, true);
182
- });
183
-
184
- test("constructor.name is 'Headers'", () => {
185
- const data = evalCode<string>(
186
- ctx.context,
187
- `
188
- const headers = new Headers();
189
- JSON.stringify({ constructorName: headers.constructor.name })
190
- `
191
- );
192
- const result = JSON.parse(data) as { constructorName: string };
193
- assert.strictEqual(result.constructorName, "Headers");
194
- });
195
-
196
- test("new Headers(existingHeaders) copies headers", () => {
197
- const data = evalCode<string>(
198
- ctx.context,
199
- `
200
- const original = new Headers();
201
- original.set('cookie', 'session=abc123');
202
- original.set('content-type', 'application/json');
203
-
204
- const copied = new Headers(original);
205
-
206
- JSON.stringify({
207
- originalCookie: original.get('cookie'),
208
- copiedCookie: copied.get('cookie'),
209
- originalContentType: original.get('content-type'),
210
- copiedContentType: copied.get('content-type'),
211
- })
212
- `
213
- );
214
- const result = JSON.parse(data) as {
215
- originalCookie: string | null;
216
- copiedCookie: string | null;
217
- originalContentType: string | null;
218
- copiedContentType: string | null;
219
- };
220
- assert.strictEqual(result.originalCookie, "session=abc123");
221
- assert.strictEqual(result.copiedCookie, "session=abc123");
222
- assert.strictEqual(result.originalContentType, "application/json");
223
- assert.strictEqual(result.copiedContentType, "application/json");
224
- });
225
- });
226
-
227
- /**
228
- * Native Headers -> Isolate tests
229
- *
230
- * These tests verify that native Headers objects passed into the isolate
231
- * behave identically to Headers instances created with `new Headers()` in the isolate.
232
- *
233
- * The tests use `runTestCode()` which converts native Headers to isolate Headers
234
- * instances before executing the test code.
235
- */
236
- describe("Native Headers -> Isolate", () => {
237
- let ctx: FetchTestContext;
238
-
239
- beforeEach(async () => {
240
- ctx = await createFetchTestContext();
241
- });
242
-
243
- afterEach(() => {
244
- ctx.dispose();
245
- });
246
-
247
- test("native Headers should pass instanceof check in isolate", () => {
248
- const runtime = runTestCode(
249
- ctx.context,
250
- `
251
- const headers = testingInput.headers;
252
- log("instanceof", headers instanceof Headers);
253
- log("constructorName", headers.constructor.name);
254
- `
255
- ).input({
256
- headers: new Headers({ "Content-Type": "application/json" }),
257
- });
258
-
259
- assert.deepStrictEqual(runtime.logs, {
260
- instanceof: true,
261
- constructorName: "Headers",
262
- });
263
- });
264
-
265
- test("case-insensitive get", () => {
266
- const runtime = runTestCode(
267
- ctx.context,
268
- `
269
- const headers = testingInput.headers;
270
- log("withCaps", headers.get("Content-Type"));
271
- log("lowercase", headers.get("content-type"));
272
- log("uppercase", headers.get("CONTENT-TYPE"));
273
- `
274
- ).input({
275
- headers: new Headers({ "Content-Type": "application/json" }),
276
- });
277
-
278
- assert.deepStrictEqual(runtime.logs, {
279
- withCaps: "application/json",
280
- lowercase: "application/json",
281
- uppercase: "application/json",
282
- });
283
- });
284
-
285
- test("forEach callback", () => {
286
- const runtime = runTestCode(
287
- ctx.context,
288
- `
289
- const headers = testingInput.headers;
290
- const collected = [];
291
- headers.forEach((value, key) => {
292
- collected.push({ value, key });
293
- });
294
- log("collected", collected);
295
- `
296
- ).input({
297
- headers: new Headers({ "X-One": "1", "X-Two": "2" }),
298
- });
299
-
300
- const collected = runtime.logs.collected as { value: string; key: string }[];
301
- assert.strictEqual(collected.length, 2);
302
- assert.deepStrictEqual(
303
- collected.map((d) => d.key).sort(),
304
- ["x-one", "x-two"]
305
- );
306
- assert.deepStrictEqual(
307
- collected.map((d) => d.value).sort(),
308
- ["1", "2"]
309
- );
310
- });
311
-
312
- test("getSetCookie returns array", () => {
313
- // Native Headers with multiple Set-Cookie values
314
- const nativeHeaders = new Headers();
315
- nativeHeaders.append("Set-Cookie", "a=1");
316
- nativeHeaders.append("Set-Cookie", "b=2");
317
-
318
- const runtime = runTestCode(
319
- ctx.context,
320
- `
321
- const headers = testingInput.headers;
322
- log("cookies", headers.getSetCookie());
323
- log("regular", headers.get("Set-Cookie"));
324
- `
325
- ).input({
326
- headers: nativeHeaders,
327
- });
328
-
329
- assert.deepStrictEqual(runtime.logs.cookies, ["a=1", "b=2"]);
330
- assert.strictEqual(runtime.logs.regular, "a=1, b=2");
331
- });
332
-
333
- test("for...of iteration on Headers", () => {
334
- const runtime = runTestCode(
335
- ctx.context,
336
- `
337
- const headers = testingInput.headers;
338
- const entries = [];
339
- for (const [key, value] of headers) {
340
- entries.push([key, value]);
341
- }
342
- log("entries", entries);
343
- `
344
- ).input({
345
- headers: new Headers({
346
- "Content-Type": "application/json",
347
- "X-Custom": "value",
348
- }),
349
- });
350
-
351
- const entries = runtime.logs.entries as Array<[string, string]>;
352
- assert.ok(
353
- entries.some(([k, v]) => k === "content-type" && v === "application/json")
354
- );
355
- assert.ok(entries.some(([k, v]) => k === "x-custom" && v === "value"));
356
- });
357
-
358
- test("Array.from(headers)", () => {
359
- const runtime = runTestCode(
360
- ctx.context,
361
- `
362
- const headers = testingInput.headers;
363
- log("entries", Array.from(headers));
364
- `
365
- ).input({
366
- headers: new Headers({
367
- "Content-Type": "application/json",
368
- Accept: "text/html",
369
- }),
370
- });
371
-
372
- const entries = runtime.logs.entries as Array<[string, string]>;
373
- assert.ok(
374
- entries.some(([k, v]) => k === "content-type" && v === "application/json")
375
- );
376
- assert.ok(entries.some(([k, v]) => k === "accept" && v === "text/html"));
377
- });
378
-
379
- test("headers should not include internal properties", () => {
380
- const runtime = runTestCode(
381
- ctx.context,
382
- `
383
- const headers = testingInput.headers;
384
- log("entries", Array.from(headers.entries()));
385
- log("keys", Array.from(headers.keys()));
386
- `
387
- ).input({
388
- headers: new Headers({
389
- "Content-Type": "application/octet-stream",
390
- }),
391
- });
392
-
393
- const keys = runtime.logs.keys as string[];
394
- const entries = runtime.logs.entries as Array<[string, string]>;
395
-
396
- assert.deepStrictEqual(keys, ["content-type"]);
397
- assert.ok(!keys.includes("__instanceid__"));
398
- assert.ok(!keys.includes("__classname__"));
399
- assert.deepStrictEqual(entries, [
400
- ["content-type", "application/octet-stream"],
401
- ]);
402
- });
403
-
404
- test("methods work correctly (set, has, delete)", () => {
405
- const runtime = runTestCode(
406
- ctx.context,
407
- `
408
- const headers = testingInput.headers;
409
- log("initialGet", headers.get("content-type"));
410
- log("initialHas", headers.has("content-type"));
411
-
412
- headers.set("x-custom", "new-value");
413
- log("afterSet", headers.get("x-custom"));
414
-
415
- headers.delete("content-type");
416
- log("afterDelete", headers.has("content-type"));
417
- `
418
- ).input({
419
- headers: new Headers({ "Content-Type": "application/json" }),
420
- });
421
-
422
- assert.deepStrictEqual(runtime.logs, {
423
- initialGet: "application/json",
424
- initialHas: true,
425
- afterSet: "new-value",
426
- afterDelete: false,
427
- });
428
- });
429
-
430
- test("append adds to existing header", () => {
431
- const runtime = runTestCode(
432
- ctx.context,
433
- `
434
- const headers = testingInput.headers;
435
- headers.append("Accept", "text/html");
436
- log("afterAppend", headers.get("Accept"));
437
- `
438
- ).input({
439
- headers: new Headers({ Accept: "application/json" }),
440
- });
441
-
442
- assert.strictEqual(runtime.logs.afterAppend, "application/json, text/html");
443
- });
444
-
445
- test("keys() and values() methods", () => {
446
- const runtime = runTestCode(
447
- ctx.context,
448
- `
449
- const headers = testingInput.headers;
450
- log("keys", Array.from(headers.keys()));
451
- log("values", Array.from(headers.values()));
452
- `
453
- ).input({
454
- headers: new Headers({ "X-First": "one", "X-Second": "two" }),
455
- });
456
-
457
- const keys = runtime.logs.keys as string[];
458
- const values = runtime.logs.values as string[];
459
-
460
- assert.deepStrictEqual(keys.sort(), ["x-first", "x-second"]);
461
- assert.deepStrictEqual(values.sort(), ["one", "two"]);
462
- });
463
-
464
- describe("Bidirectional Conversion (Native->Isolate->Native)", () => {
465
- test("Headers created in isolate should return as native Headers", () => {
466
- const runtime = runTestCode(
467
- ctx.context,
468
- `
469
- const headers = new Headers({ "Content-Type": "application/json" });
470
- log("headers", headers);
471
- `
472
- ).input({});
473
-
474
- assert.ok(runtime.logs.headers instanceof Headers);
475
- assert.strictEqual(
476
- (runtime.logs.headers as Headers).get("content-type"),
477
- "application/json"
478
- );
479
- });
480
-
481
- test("native Headers passed through isolate returns as native Headers", () => {
482
- const runtime = runTestCode(
483
- ctx.context,
484
- `
485
- const headers = testingInput.headers;
486
- log("headers", headers);
487
- `
488
- ).input({
489
- headers: new Headers({ "Content-Type": "application/json" }),
490
- });
491
-
492
- assert.ok(runtime.logs.headers instanceof Headers);
493
- assert.strictEqual(
494
- (runtime.logs.headers as Headers).get("content-type"),
495
- "application/json"
496
- );
497
- });
498
-
499
- test("modifications in isolate are preserved when returning as native Headers", () => {
500
- const runtime = runTestCode(
501
- ctx.context,
502
- `
503
- const headers = testingInput.headers;
504
- headers.append("x-added", "new-value");
505
- headers.set("x-custom", "custom-value");
506
- log("headers", headers);
507
- `
508
- ).input({
509
- headers: new Headers({ "Content-Type": "application/json" }),
510
- });
511
-
512
- assert.ok(runtime.logs.headers instanceof Headers);
513
- const headers = runtime.logs.headers as Headers;
514
- assert.strictEqual(headers.get("content-type"), "application/json");
515
- assert.strictEqual(headers.get("x-added"), "new-value");
516
- assert.strictEqual(headers.get("x-custom"), "custom-value");
517
- });
518
-
519
- test("deleted headers are not present in returned native Headers", () => {
520
- const runtime = runTestCode(
521
- ctx.context,
522
- `
523
- const headers = testingInput.headers;
524
- headers.delete("x-to-delete");
525
- log("headers", headers);
526
- `
527
- ).input({
528
- headers: new Headers({
529
- "Content-Type": "application/json",
530
- "X-To-Delete": "will be deleted",
531
- }),
532
- });
533
-
534
- assert.ok(runtime.logs.headers instanceof Headers);
535
- const headers = runtime.logs.headers as Headers;
536
- assert.strictEqual(headers.get("content-type"), "application/json");
537
- assert.strictEqual(headers.has("x-to-delete"), false);
538
- });
539
-
540
- test("nested object with Headers converts properly", () => {
541
- const runtime = runTestCode(
542
- ctx.context,
543
- `
544
- const headers = testingInput.headers;
545
- headers.set("x-modified", "true");
546
- log("result", {
547
- headers: headers,
548
- metadata: { count: 3 }
549
- });
550
- `
551
- ).input({
552
- headers: new Headers({ "Content-Type": "application/json" }),
553
- });
554
-
555
- const result = runtime.logs.result as {
556
- headers: Headers;
557
- metadata: { count: number };
558
- };
559
- assert.ok(result.headers instanceof Headers);
560
- assert.strictEqual(result.headers.get("x-modified"), "true");
561
- assert.deepStrictEqual(result.metadata, { count: 3 });
562
- });
563
-
564
- test("array of Headers converts properly", () => {
565
- const runtime = runTestCode(
566
- ctx.context,
567
- `
568
- const h1 = new Headers({ "X-First": "one" });
569
- const h2 = new Headers({ "X-Second": "two" });
570
- log("headers", [h1, h2]);
571
- `
572
- ).input({});
573
-
574
- const headers = runtime.logs.headers as Headers[];
575
- assert.strictEqual(headers.length, 2);
576
- assert.ok(headers[0] instanceof Headers);
577
- assert.ok(headers[1] instanceof Headers);
578
- assert.strictEqual(headers[0].get("x-first"), "one");
579
- assert.strictEqual(headers[1].get("x-second"), "two");
580
- });
581
- });
582
- });