@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.
- package/README.md +93 -0
- package/dist/cjs/index.cjs +1800 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/stream-state.cjs +230 -0
- package/dist/cjs/stream-state.cjs.map +10 -0
- package/{src/index.ts → dist/mjs/index.mjs} +357 -923
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/mjs/stream-state.mjs +199 -0
- package/dist/mjs/stream-state.mjs.map +10 -0
- package/dist/types/index.d.ts +63 -0
- package/dist/types/isolate.d.ts +267 -0
- package/dist/types/stream-state.d.ts +61 -0
- package/package.json +41 -13
- package/CHANGELOG.md +0 -9
- package/src/debug-delayed.test.ts +0 -89
- package/src/debug-streaming.test.ts +0 -81
- package/src/download-streaming-simple.test.ts +0 -167
- package/src/download-streaming.test.ts +0 -286
- package/src/form-data.test.ts +0 -824
- package/src/formdata.test.ts +0 -212
- package/src/headers.test.ts +0 -582
- package/src/host-backed-stream.test.ts +0 -363
- package/src/index.test.ts +0 -274
- package/src/integration.test.ts +0 -665
- package/src/request.test.ts +0 -482
- package/src/response.test.ts +0 -520
- package/src/serve.test.ts +0 -425
- package/src/stream-state.test.ts +0 -338
- package/src/stream-state.ts +0 -337
- package/src/upload-streaming.test.ts +0 -373
- package/src/websocket.test.ts +0 -627
- package/tsconfig.json +0 -8
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
import { test, describe, beforeEach, afterEach, it } from "node:test";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import ivm from "isolated-vm";
|
|
4
|
-
import {
|
|
5
|
-
setupFetch,
|
|
6
|
-
clearAllInstanceState,
|
|
7
|
-
type FetchHandle,
|
|
8
|
-
} from "./index.ts";
|
|
9
|
-
import { setupTimers, type TimersHandle } from "@ricsam/isolate-timers";
|
|
10
|
-
import { clearStreamRegistryForContext } from "./stream-state.ts";
|
|
11
|
-
|
|
12
|
-
describe("HostBackedReadableStream", () => {
|
|
13
|
-
let isolate: ivm.Isolate;
|
|
14
|
-
let context: ivm.Context;
|
|
15
|
-
let fetchHandle: FetchHandle;
|
|
16
|
-
let timersHandle: TimersHandle;
|
|
17
|
-
|
|
18
|
-
beforeEach(async () => {
|
|
19
|
-
isolate = new ivm.Isolate();
|
|
20
|
-
context = await isolate.createContext();
|
|
21
|
-
clearAllInstanceState();
|
|
22
|
-
timersHandle = await setupTimers(context);
|
|
23
|
-
fetchHandle = await setupFetch(context);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach(async () => {
|
|
27
|
-
fetchHandle.dispose();
|
|
28
|
-
timersHandle.dispose();
|
|
29
|
-
clearStreamRegistryForContext(context);
|
|
30
|
-
context.release();
|
|
31
|
-
isolate.dispose();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("creation", () => {
|
|
35
|
-
it("creates a stream with a valid stream ID", () => {
|
|
36
|
-
const result = context.evalSync(`
|
|
37
|
-
const stream = new HostBackedReadableStream();
|
|
38
|
-
const streamId = stream._getStreamId();
|
|
39
|
-
JSON.stringify({
|
|
40
|
-
hasStreamId: typeof streamId === "number",
|
|
41
|
-
streamIdPositive: streamId > 0
|
|
42
|
-
})
|
|
43
|
-
`);
|
|
44
|
-
const data = JSON.parse(result as string) as {
|
|
45
|
-
hasStreamId: boolean;
|
|
46
|
-
streamIdPositive: boolean;
|
|
47
|
-
};
|
|
48
|
-
assert.strictEqual(data.hasStreamId, true);
|
|
49
|
-
assert.strictEqual(data.streamIdPositive, true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("each stream gets a unique ID", () => {
|
|
53
|
-
const result = context.evalSync(`
|
|
54
|
-
const stream1 = new HostBackedReadableStream();
|
|
55
|
-
const stream2 = new HostBackedReadableStream();
|
|
56
|
-
const stream3 = new HostBackedReadableStream();
|
|
57
|
-
JSON.stringify({
|
|
58
|
-
id1: stream1._getStreamId(),
|
|
59
|
-
id2: stream2._getStreamId(),
|
|
60
|
-
id3: stream3._getStreamId()
|
|
61
|
-
})
|
|
62
|
-
`);
|
|
63
|
-
const data = JSON.parse(result as string) as {
|
|
64
|
-
id1: number;
|
|
65
|
-
id2: number;
|
|
66
|
-
id3: number;
|
|
67
|
-
};
|
|
68
|
-
assert.notStrictEqual(data.id1, data.id2);
|
|
69
|
-
assert.notStrictEqual(data.id2, data.id3);
|
|
70
|
-
assert.notStrictEqual(data.id1, data.id3);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe("getReader", () => {
|
|
75
|
-
it("returns a reader object", () => {
|
|
76
|
-
const result = context.evalSync(`
|
|
77
|
-
const stream = new HostBackedReadableStream();
|
|
78
|
-
const reader = stream.getReader();
|
|
79
|
-
JSON.stringify({
|
|
80
|
-
hasReader: reader != null,
|
|
81
|
-
hasReadMethod: typeof reader.read === "function",
|
|
82
|
-
hasCancelMethod: typeof reader.cancel === "function",
|
|
83
|
-
hasReleaseLockMethod: typeof reader.releaseLock === "function"
|
|
84
|
-
})
|
|
85
|
-
`);
|
|
86
|
-
const data = JSON.parse(result as string) as {
|
|
87
|
-
hasReader: boolean;
|
|
88
|
-
hasReadMethod: boolean;
|
|
89
|
-
hasCancelMethod: boolean;
|
|
90
|
-
hasReleaseLockMethod: boolean;
|
|
91
|
-
};
|
|
92
|
-
assert.strictEqual(data.hasReader, true);
|
|
93
|
-
assert.strictEqual(data.hasReadMethod, true);
|
|
94
|
-
assert.strictEqual(data.hasCancelMethod, true);
|
|
95
|
-
assert.strictEqual(data.hasReleaseLockMethod, true);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("has locked property (always false in current implementation)", () => {
|
|
99
|
-
const result = context.evalSync(`
|
|
100
|
-
const stream = new HostBackedReadableStream();
|
|
101
|
-
const before = stream.locked;
|
|
102
|
-
const reader = stream.getReader();
|
|
103
|
-
const after = stream.locked;
|
|
104
|
-
JSON.stringify({ before, after, hasLocked: "locked" in stream })
|
|
105
|
-
`);
|
|
106
|
-
const data = JSON.parse(result as string) as {
|
|
107
|
-
before: boolean;
|
|
108
|
-
after: boolean;
|
|
109
|
-
hasLocked: boolean;
|
|
110
|
-
};
|
|
111
|
-
assert.strictEqual(data.hasLocked, true);
|
|
112
|
-
// Note: Current implementation always returns false for locked
|
|
113
|
-
assert.strictEqual(data.before, false);
|
|
114
|
-
assert.strictEqual(data.after, false);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("allows getting multiple readers (current implementation)", () => {
|
|
118
|
-
// Note: This differs from WHATWG spec but matches current implementation
|
|
119
|
-
const result = context.evalSync(`
|
|
120
|
-
const stream = new HostBackedReadableStream();
|
|
121
|
-
const reader1 = stream.getReader();
|
|
122
|
-
const reader2 = stream.getReader();
|
|
123
|
-
JSON.stringify({
|
|
124
|
-
hasReader1: reader1 != null,
|
|
125
|
-
hasReader2: reader2 != null,
|
|
126
|
-
areDifferent: reader1 !== reader2
|
|
127
|
-
})
|
|
128
|
-
`);
|
|
129
|
-
const data = JSON.parse(result as string) as {
|
|
130
|
-
hasReader1: boolean;
|
|
131
|
-
hasReader2: boolean;
|
|
132
|
-
areDifferent: boolean;
|
|
133
|
-
};
|
|
134
|
-
assert.strictEqual(data.hasReader1, true);
|
|
135
|
-
assert.strictEqual(data.hasReader2, true);
|
|
136
|
-
assert.strictEqual(data.areDifferent, true);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe("read", () => {
|
|
141
|
-
it("returns chunks pushed to stream", async () => {
|
|
142
|
-
const result = await context.eval(
|
|
143
|
-
`
|
|
144
|
-
(async () => {
|
|
145
|
-
const stream = new HostBackedReadableStream();
|
|
146
|
-
const streamId = stream._getStreamId();
|
|
147
|
-
|
|
148
|
-
// Push data to the stream
|
|
149
|
-
__Stream_push(streamId, Array.from(new TextEncoder().encode("hello")));
|
|
150
|
-
__Stream_close(streamId);
|
|
151
|
-
|
|
152
|
-
const reader = stream.getReader();
|
|
153
|
-
const { value, done } = await reader.read();
|
|
154
|
-
|
|
155
|
-
return JSON.stringify({
|
|
156
|
-
text: new TextDecoder().decode(value),
|
|
157
|
-
done
|
|
158
|
-
});
|
|
159
|
-
})()
|
|
160
|
-
`,
|
|
161
|
-
{ promise: true }
|
|
162
|
-
);
|
|
163
|
-
const data = JSON.parse(result as string) as { text: string; done: boolean };
|
|
164
|
-
assert.strictEqual(data.text, "hello");
|
|
165
|
-
assert.strictEqual(data.done, false);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("returns done when stream closes", async () => {
|
|
169
|
-
const result = await context.eval(
|
|
170
|
-
`
|
|
171
|
-
(async () => {
|
|
172
|
-
const stream = new HostBackedReadableStream();
|
|
173
|
-
const streamId = stream._getStreamId();
|
|
174
|
-
|
|
175
|
-
__Stream_close(streamId);
|
|
176
|
-
|
|
177
|
-
const reader = stream.getReader();
|
|
178
|
-
const { done } = await reader.read();
|
|
179
|
-
|
|
180
|
-
return JSON.stringify({ done });
|
|
181
|
-
})()
|
|
182
|
-
`,
|
|
183
|
-
{ promise: true }
|
|
184
|
-
);
|
|
185
|
-
const data = JSON.parse(result as string) as { done: boolean };
|
|
186
|
-
assert.strictEqual(data.done, true);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("returns all queued chunks before done", async () => {
|
|
190
|
-
const result = await context.eval(
|
|
191
|
-
`
|
|
192
|
-
(async () => {
|
|
193
|
-
const stream = new HostBackedReadableStream();
|
|
194
|
-
const streamId = stream._getStreamId();
|
|
195
|
-
|
|
196
|
-
// Push multiple chunks
|
|
197
|
-
__Stream_push(streamId, Array.from(new TextEncoder().encode("a")));
|
|
198
|
-
__Stream_push(streamId, Array.from(new TextEncoder().encode("b")));
|
|
199
|
-
__Stream_push(streamId, Array.from(new TextEncoder().encode("c")));
|
|
200
|
-
__Stream_close(streamId);
|
|
201
|
-
|
|
202
|
-
const reader = stream.getReader();
|
|
203
|
-
const chunks = [];
|
|
204
|
-
while (true) {
|
|
205
|
-
const { value, done } = await reader.read();
|
|
206
|
-
if (done) break;
|
|
207
|
-
chunks.push(new TextDecoder().decode(value));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return JSON.stringify({ chunks });
|
|
211
|
-
})()
|
|
212
|
-
`,
|
|
213
|
-
{ promise: true }
|
|
214
|
-
);
|
|
215
|
-
const data = JSON.parse(result as string) as { chunks: string[] };
|
|
216
|
-
assert.deepStrictEqual(data.chunks, ["a", "b", "c"]);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it("throws on errored stream", async () => {
|
|
220
|
-
await assert.rejects(
|
|
221
|
-
context.eval(
|
|
222
|
-
`
|
|
223
|
-
(async () => {
|
|
224
|
-
const stream = new HostBackedReadableStream();
|
|
225
|
-
const streamId = stream._getStreamId();
|
|
226
|
-
|
|
227
|
-
__Stream_error(streamId, "Stream failed");
|
|
228
|
-
|
|
229
|
-
const reader = stream.getReader();
|
|
230
|
-
await reader.read();
|
|
231
|
-
})()
|
|
232
|
-
`,
|
|
233
|
-
{ promise: true }
|
|
234
|
-
)
|
|
235
|
-
);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
describe("cancel", () => {
|
|
241
|
-
it("cancel returns a promise", async () => {
|
|
242
|
-
const result = await context.eval(
|
|
243
|
-
`
|
|
244
|
-
(async () => {
|
|
245
|
-
const stream = new HostBackedReadableStream();
|
|
246
|
-
const reader = stream.getReader();
|
|
247
|
-
const cancelResult = await reader.cancel();
|
|
248
|
-
return JSON.stringify({ cancelResult: cancelResult === undefined });
|
|
249
|
-
})()
|
|
250
|
-
`,
|
|
251
|
-
{ promise: true }
|
|
252
|
-
);
|
|
253
|
-
const data = JSON.parse(result as string) as { cancelResult: boolean };
|
|
254
|
-
assert.strictEqual(data.cancelResult, true);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
describe("releaseLock", () => {
|
|
259
|
-
it("releaseLock prevents further reads from that reader", async () => {
|
|
260
|
-
await assert.rejects(
|
|
261
|
-
context.eval(
|
|
262
|
-
`
|
|
263
|
-
(async () => {
|
|
264
|
-
const stream = new HostBackedReadableStream();
|
|
265
|
-
const streamId = stream._getStreamId();
|
|
266
|
-
__Stream_push(streamId, Array.from(new TextEncoder().encode("test")));
|
|
267
|
-
__Stream_close(streamId);
|
|
268
|
-
|
|
269
|
-
const reader = stream.getReader();
|
|
270
|
-
reader.releaseLock();
|
|
271
|
-
|
|
272
|
-
// Should throw after releaseLock
|
|
273
|
-
await reader.read();
|
|
274
|
-
})()
|
|
275
|
-
`,
|
|
276
|
-
{ promise: true }
|
|
277
|
-
),
|
|
278
|
-
{ message: "Reader has been released" }
|
|
279
|
-
);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it("can get new reader after releaseLock", async () => {
|
|
283
|
-
const result = await context.eval(
|
|
284
|
-
`
|
|
285
|
-
(async () => {
|
|
286
|
-
const stream = new HostBackedReadableStream();
|
|
287
|
-
const streamId = stream._getStreamId();
|
|
288
|
-
__Stream_push(streamId, Array.from(new TextEncoder().encode("test")));
|
|
289
|
-
__Stream_close(streamId);
|
|
290
|
-
|
|
291
|
-
const reader1 = stream.getReader();
|
|
292
|
-
reader1.releaseLock();
|
|
293
|
-
|
|
294
|
-
const reader2 = stream.getReader();
|
|
295
|
-
const { value, done } = await reader2.read();
|
|
296
|
-
|
|
297
|
-
return JSON.stringify({
|
|
298
|
-
text: new TextDecoder().decode(value),
|
|
299
|
-
done,
|
|
300
|
-
areDifferentReaders: reader1 !== reader2
|
|
301
|
-
});
|
|
302
|
-
})()
|
|
303
|
-
`,
|
|
304
|
-
{ promise: true }
|
|
305
|
-
);
|
|
306
|
-
const data = JSON.parse(result as string) as {
|
|
307
|
-
text: string;
|
|
308
|
-
done: boolean;
|
|
309
|
-
areDifferentReaders: boolean;
|
|
310
|
-
};
|
|
311
|
-
assert.strictEqual(data.text, "test");
|
|
312
|
-
assert.strictEqual(data.done, false);
|
|
313
|
-
assert.strictEqual(data.areDifferentReaders, true);
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
describe("integration with Request", () => {
|
|
318
|
-
it("Request.body is a HostBackedReadableStream", () => {
|
|
319
|
-
const result = context.evalSync(`
|
|
320
|
-
const request = new Request("http://test/", {
|
|
321
|
-
method: "POST",
|
|
322
|
-
body: "test body"
|
|
323
|
-
});
|
|
324
|
-
JSON.stringify({
|
|
325
|
-
isHostBackedStream: request.body instanceof HostBackedReadableStream,
|
|
326
|
-
hasGetReader: typeof request.body.getReader === "function"
|
|
327
|
-
})
|
|
328
|
-
`);
|
|
329
|
-
const data = JSON.parse(result as string) as {
|
|
330
|
-
isHostBackedStream: boolean;
|
|
331
|
-
hasGetReader: boolean;
|
|
332
|
-
};
|
|
333
|
-
assert.strictEqual(data.isHostBackedStream, true);
|
|
334
|
-
assert.strictEqual(data.hasGetReader, true);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it("can read Request.body via stream reader", async () => {
|
|
338
|
-
const result = await context.eval(
|
|
339
|
-
`
|
|
340
|
-
(async () => {
|
|
341
|
-
const request = new Request("http://test/", {
|
|
342
|
-
method: "POST",
|
|
343
|
-
body: "hello world"
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
const reader = request.body.getReader();
|
|
347
|
-
const chunks = [];
|
|
348
|
-
while (true) {
|
|
349
|
-
const { value, done } = await reader.read();
|
|
350
|
-
if (done) break;
|
|
351
|
-
chunks.push(new TextDecoder().decode(value));
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return JSON.stringify({ text: chunks.join("") });
|
|
355
|
-
})()
|
|
356
|
-
`,
|
|
357
|
-
{ promise: true }
|
|
358
|
-
);
|
|
359
|
-
const data = JSON.parse(result as string) as { text: string };
|
|
360
|
-
assert.strictEqual(data.text, "hello world");
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
});
|
package/src/index.test.ts
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { test, describe, beforeEach, afterEach } from "node:test";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import ivm from "isolated-vm";
|
|
4
|
-
import { setupFetch, clearAllInstanceState } from "./index.ts";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Integration tests for @ricsam/isolate-fetch
|
|
8
|
-
*
|
|
9
|
-
* Note: Comprehensive tests for Headers, Request, Response, and FormData
|
|
10
|
-
* are in their respective test files (headers.test.ts, request.test.ts, etc.)
|
|
11
|
-
* These tests focus on integration scenarios like fetch function and AbortController.
|
|
12
|
-
*/
|
|
13
|
-
describe("@ricsam/isolate-fetch Integration", () => {
|
|
14
|
-
let isolate: ivm.Isolate;
|
|
15
|
-
let context: ivm.Context;
|
|
16
|
-
|
|
17
|
-
beforeEach(async () => {
|
|
18
|
-
isolate = new ivm.Isolate();
|
|
19
|
-
context = await isolate.createContext();
|
|
20
|
-
clearAllInstanceState();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
context.release();
|
|
25
|
-
isolate.dispose();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe("AbortController", () => {
|
|
29
|
-
test("creates AbortController", async () => {
|
|
30
|
-
await setupFetch(context);
|
|
31
|
-
const result = context.evalSync(`
|
|
32
|
-
const controller = new AbortController();
|
|
33
|
-
typeof controller.signal;
|
|
34
|
-
`);
|
|
35
|
-
assert.strictEqual(result, "object");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("signal starts not aborted", async () => {
|
|
39
|
-
await setupFetch(context);
|
|
40
|
-
const result = context.evalSync(`
|
|
41
|
-
const controller = new AbortController();
|
|
42
|
-
controller.signal.aborted;
|
|
43
|
-
`);
|
|
44
|
-
assert.strictEqual(result, false);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("abort() sets signal.aborted to true", async () => {
|
|
48
|
-
await setupFetch(context);
|
|
49
|
-
const result = context.evalSync(`
|
|
50
|
-
const controller = new AbortController();
|
|
51
|
-
controller.abort();
|
|
52
|
-
controller.signal.aborted;
|
|
53
|
-
`);
|
|
54
|
-
assert.strictEqual(result, true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("abort() with reason", async () => {
|
|
58
|
-
await setupFetch(context);
|
|
59
|
-
const result = context.evalSync(`
|
|
60
|
-
const controller = new AbortController();
|
|
61
|
-
controller.abort("custom reason");
|
|
62
|
-
JSON.stringify({
|
|
63
|
-
aborted: controller.signal.aborted,
|
|
64
|
-
reason: controller.signal.reason
|
|
65
|
-
});
|
|
66
|
-
`);
|
|
67
|
-
const data = JSON.parse(result as string);
|
|
68
|
-
assert.strictEqual(data.aborted, true);
|
|
69
|
-
assert.strictEqual(data.reason, "custom reason");
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe("fetch function", () => {
|
|
74
|
-
test("calls onFetch handler", async () => {
|
|
75
|
-
let requestReceived: Request | null = null;
|
|
76
|
-
|
|
77
|
-
await setupFetch(context, {
|
|
78
|
-
onFetch: async (request) => {
|
|
79
|
-
requestReceived = request;
|
|
80
|
-
return new Response("OK");
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
await context.eval(
|
|
85
|
-
`
|
|
86
|
-
(async () => {
|
|
87
|
-
await fetch('https://example.com/api');
|
|
88
|
-
})();
|
|
89
|
-
`,
|
|
90
|
-
{ promise: true }
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
assert.notStrictEqual(requestReceived, null);
|
|
94
|
-
assert.strictEqual(requestReceived!.url, "https://example.com/api");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("returns Response from handler", async () => {
|
|
98
|
-
await setupFetch(context, {
|
|
99
|
-
onFetch: async () => {
|
|
100
|
-
return new Response(JSON.stringify({ success: true }), {
|
|
101
|
-
status: 200,
|
|
102
|
-
headers: { "Content-Type": "application/json" },
|
|
103
|
-
});
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const result = await context.eval(
|
|
108
|
-
`
|
|
109
|
-
(async () => {
|
|
110
|
-
const response = await fetch('https://example.com/api');
|
|
111
|
-
const data = await response.json();
|
|
112
|
-
return JSON.stringify({
|
|
113
|
-
status: response.status,
|
|
114
|
-
data: data
|
|
115
|
-
});
|
|
116
|
-
})();
|
|
117
|
-
`,
|
|
118
|
-
{ promise: true }
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
const data = JSON.parse(result as string);
|
|
122
|
-
assert.strictEqual(data.status, 200);
|
|
123
|
-
assert.deepStrictEqual(data.data, { success: true });
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("passes request method and headers to handler", async () => {
|
|
127
|
-
let receivedMethod = "";
|
|
128
|
-
let receivedHeaders: Headers | null = null;
|
|
129
|
-
|
|
130
|
-
await setupFetch(context, {
|
|
131
|
-
onFetch: async (request) => {
|
|
132
|
-
receivedMethod = request.method;
|
|
133
|
-
receivedHeaders = request.headers;
|
|
134
|
-
return new Response("OK");
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
await context.eval(
|
|
139
|
-
`
|
|
140
|
-
(async () => {
|
|
141
|
-
await fetch('https://example.com/api', {
|
|
142
|
-
method: 'POST',
|
|
143
|
-
headers: { 'Content-Type': 'application/json', 'X-Custom': 'value' }
|
|
144
|
-
});
|
|
145
|
-
})();
|
|
146
|
-
`,
|
|
147
|
-
{ promise: true }
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
assert.strictEqual(receivedMethod, "POST");
|
|
151
|
-
assert.ok(receivedHeaders !== null);
|
|
152
|
-
assert.strictEqual(receivedHeaders!.get("content-type"), "application/json");
|
|
153
|
-
assert.strictEqual(receivedHeaders!.get("x-custom"), "value");
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("supports abort signal", async () => {
|
|
157
|
-
await setupFetch(context, {
|
|
158
|
-
onFetch: async () => {
|
|
159
|
-
return new Response("OK");
|
|
160
|
-
},
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const result = await context.eval(
|
|
164
|
-
`
|
|
165
|
-
(async () => {
|
|
166
|
-
const controller = new AbortController();
|
|
167
|
-
controller.abort();
|
|
168
|
-
try {
|
|
169
|
-
await fetch('https://example.com', { signal: controller.signal });
|
|
170
|
-
return 'no error';
|
|
171
|
-
} catch (e) {
|
|
172
|
-
return e.name;
|
|
173
|
-
}
|
|
174
|
-
})();
|
|
175
|
-
`,
|
|
176
|
-
{ promise: true }
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
assert.strictEqual(result, "AbortError");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("abort signal works with default handler", async () => {
|
|
183
|
-
await setupFetch(context);
|
|
184
|
-
|
|
185
|
-
const result = await context.eval(
|
|
186
|
-
`
|
|
187
|
-
(async () => {
|
|
188
|
-
const controller = new AbortController();
|
|
189
|
-
controller.abort();
|
|
190
|
-
try {
|
|
191
|
-
await fetch('https://example.com', { signal: controller.signal });
|
|
192
|
-
return 'no error';
|
|
193
|
-
} catch (e) {
|
|
194
|
-
return e.name;
|
|
195
|
-
}
|
|
196
|
-
})();
|
|
197
|
-
`,
|
|
198
|
-
{ promise: true }
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
assert.strictEqual(result, "AbortError");
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("fetch with Request object", async () => {
|
|
205
|
-
let receivedUrl = "";
|
|
206
|
-
let receivedMethod = "";
|
|
207
|
-
|
|
208
|
-
await setupFetch(context, {
|
|
209
|
-
onFetch: async (request) => {
|
|
210
|
-
receivedUrl = request.url;
|
|
211
|
-
receivedMethod = request.method;
|
|
212
|
-
return new Response("OK");
|
|
213
|
-
},
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
await context.eval(
|
|
217
|
-
`
|
|
218
|
-
(async () => {
|
|
219
|
-
const request = new Request('https://example.com/api', { method: 'PUT' });
|
|
220
|
-
await fetch(request);
|
|
221
|
-
})();
|
|
222
|
-
`,
|
|
223
|
-
{ promise: true }
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
assert.strictEqual(receivedUrl, "https://example.com/api");
|
|
227
|
-
assert.strictEqual(receivedMethod, "PUT");
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("fetch response body can be read as text", async () => {
|
|
231
|
-
await setupFetch(context, {
|
|
232
|
-
onFetch: async () => {
|
|
233
|
-
return new Response("Hello from handler");
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const result = await context.eval(
|
|
238
|
-
`
|
|
239
|
-
(async () => {
|
|
240
|
-
const response = await fetch('https://example.com');
|
|
241
|
-
return await response.text();
|
|
242
|
-
})();
|
|
243
|
-
`,
|
|
244
|
-
{ promise: true }
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
assert.strictEqual(result, "Hello from handler");
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test("fetch response body can be read as JSON", async () => {
|
|
251
|
-
await setupFetch(context, {
|
|
252
|
-
onFetch: async () => {
|
|
253
|
-
return new Response(JSON.stringify({ message: "hello", count: 42 }), {
|
|
254
|
-
headers: { "Content-Type": "application/json" },
|
|
255
|
-
});
|
|
256
|
-
},
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
const result = await context.eval(
|
|
260
|
-
`
|
|
261
|
-
(async () => {
|
|
262
|
-
const response = await fetch('https://example.com');
|
|
263
|
-
const data = await response.json();
|
|
264
|
-
return JSON.stringify(data);
|
|
265
|
-
})();
|
|
266
|
-
`,
|
|
267
|
-
{ promise: true }
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
const data = JSON.parse(result as string);
|
|
271
|
-
assert.deepStrictEqual(data, { message: "hello", count: 42 });
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
});
|