@fluojs/testing 1.0.6 → 3.0.0
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.ko.md +98 -16
- package/README.md +100 -16
- package/dist/babel-decorators-plugin.d.ts +1 -0
- package/dist/babel-decorators-plugin.d.ts.map +1 -1
- package/dist/babel-decorators-plugin.js +1 -0
- package/dist/byte-range-portability-consumer.test-fixture.d.ts +2 -0
- package/dist/byte-range-portability-consumer.test-fixture.d.ts.map +1 -0
- package/dist/byte-range-portability-consumer.test-fixture.js +11 -0
- package/dist/conformance/fetch-style-websocket-conformance.d.ts +1 -0
- package/dist/conformance/fetch-style-websocket-conformance.d.ts.map +1 -1
- package/dist/conformance/fetch-style-websocket-conformance.js +1 -1
- package/dist/conformance/platform-shell-lifecycle-conformance.d.ts +30 -0
- package/dist/conformance/platform-shell-lifecycle-conformance.d.ts.map +1 -0
- package/dist/conformance/platform-shell-lifecycle-conformance.js +259 -0
- package/dist/http.d.ts +2 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +8 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/mock-types.d.ts +2 -0
- package/dist/mock-types.d.ts.map +1 -0
- package/dist/mock-types.js +1 -0
- package/dist/mock.d.ts +3 -2
- package/dist/mock.d.ts.map +1 -1
- package/dist/mock.js +1 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +103 -113
- package/dist/portability/error-representation-abort-portability.d.ts +36 -0
- package/dist/portability/error-representation-abort-portability.d.ts.map +1 -0
- package/dist/portability/error-representation-abort-portability.js +190 -0
- package/dist/portability/error-representation-portability-fixture.d.ts +58 -0
- package/dist/portability/error-representation-portability-fixture.d.ts.map +1 -0
- package/dist/portability/error-representation-portability-fixture.js +202 -0
- package/dist/portability/error-representation-portability.d.ts +50 -0
- package/dist/portability/error-representation-portability.d.ts.map +1 -0
- package/dist/portability/error-representation-portability.js +153 -0
- package/dist/portability/http-adapter-portability.d.ts +25 -8
- package/dist/portability/http-adapter-portability.d.ts.map +1 -1
- package/dist/portability/http-adapter-portability.js +446 -69
- package/dist/portability/response-cookie-portability.d.ts +16 -0
- package/dist/portability/response-cookie-portability.d.ts.map +1 -0
- package/dist/portability/response-cookie-portability.js +81 -0
- package/dist/portability/web-runtime-adapter-portability.d.ts +24 -8
- package/dist/portability/web-runtime-adapter-portability.d.ts.map +1 -1
- package/dist/portability/web-runtime-adapter-portability.js +356 -31
- package/dist/types.d.ts +68 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +19 -15
|
@@ -3,9 +3,11 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
|
|
3
3
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
4
4
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
5
5
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
6
|
-
import {
|
|
7
|
-
import { Controller, Get, Post, SseResponse } from '@fluojs/http';
|
|
6
|
+
import { Controller, Get, Head, Post, Produces, Query, Route, SseResponse } from '@fluojs/http';
|
|
8
7
|
import { defineModule } from '@fluojs/runtime';
|
|
8
|
+
import { assertNetworkHttpErrorRepresentationAbortPortability } from './error-representation-abort-portability.js';
|
|
9
|
+
import { assertNetworkHttpErrorRepresentationPortability } from './error-representation-portability.js';
|
|
10
|
+
import { assertPortableResponseCookies, createResponseCookiePortabilityModule } from './response-cookie-portability.js';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Options for configuring the HTTP adapter portability harness.
|
|
@@ -37,6 +39,11 @@ function resolveListeningUrl(app, adapterName) {
|
|
|
37
39
|
return target.url;
|
|
38
40
|
}
|
|
39
41
|
async function requestHttps(url) {
|
|
42
|
+
const [{
|
|
43
|
+
Buffer
|
|
44
|
+
}, {
|
|
45
|
+
request: httpsRequest
|
|
46
|
+
}] = await Promise.all([import('node:buffer'), import('node:https')]);
|
|
40
47
|
return await new Promise((resolve, reject) => {
|
|
41
48
|
const request = httpsRequest(url, {
|
|
42
49
|
rejectUnauthorized: false
|
|
@@ -57,6 +64,41 @@ async function requestHttps(url) {
|
|
|
57
64
|
request.end();
|
|
58
65
|
});
|
|
59
66
|
}
|
|
67
|
+
async function requestCustomHttpMethod(url, method, body) {
|
|
68
|
+
const [{
|
|
69
|
+
Buffer
|
|
70
|
+
}, {
|
|
71
|
+
request: httpRequest
|
|
72
|
+
}] = await Promise.all([import('node:buffer'), import('node:http')]);
|
|
73
|
+
const target = new URL(url);
|
|
74
|
+
const hostname = target.hostname.startsWith('[') && target.hostname.endsWith(']') ? target.hostname.slice(1, -1) : target.hostname;
|
|
75
|
+
return await new Promise((resolve, reject) => {
|
|
76
|
+
const request = httpRequest({
|
|
77
|
+
headers: {
|
|
78
|
+
'content-length': String(Buffer.byteLength(body)),
|
|
79
|
+
'content-type': 'application/json'
|
|
80
|
+
},
|
|
81
|
+
hostname,
|
|
82
|
+
method,
|
|
83
|
+
path: `${target.pathname}${target.search}`,
|
|
84
|
+
port: target.port
|
|
85
|
+
}, response => {
|
|
86
|
+
const chunks = [];
|
|
87
|
+
response.on('data', chunk => {
|
|
88
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
89
|
+
});
|
|
90
|
+
response.on('end', () => {
|
|
91
|
+
resolve({
|
|
92
|
+
body: Buffer.concat(chunks).toString('utf8'),
|
|
93
|
+
statusCode: response.statusCode ?? 0
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
response.on('error', reject);
|
|
97
|
+
});
|
|
98
|
+
request.on('error', reject);
|
|
99
|
+
request.end(body);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
60
102
|
function createLogCaptureLogger() {
|
|
61
103
|
const messages = [];
|
|
62
104
|
const capture = (...args) => messages.push(args.map(arg => String(arg)).join(' '));
|
|
@@ -141,28 +183,289 @@ export class HttpAdapterPortabilityHarness {
|
|
|
141
183
|
this.options = options;
|
|
142
184
|
}
|
|
143
185
|
|
|
186
|
+
/** Verifies JSON, HTML, HEAD, 406, and committed error-response portability. */
|
|
187
|
+
async assertSupportsHttpErrorRepresentations() {
|
|
188
|
+
const createBootstrapOptions = this.options.createErrorRepresentationBootstrapOptions;
|
|
189
|
+
if (createBootstrapOptions === undefined) {
|
|
190
|
+
throw new Error(`${this.options.name} adapter portability harness requires createErrorRepresentationBootstrapOptions.`);
|
|
191
|
+
}
|
|
192
|
+
await assertNetworkHttpErrorRepresentationPortability({
|
|
193
|
+
bootstrap: this.options.bootstrap,
|
|
194
|
+
createBootstrapOptions,
|
|
195
|
+
name: this.options.name
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Verifies client-disconnect abort propagation without an HTML or JSON fallback commit. */
|
|
200
|
+
async assertDoesNotCommitAbortedHttpErrorRepresentations() {
|
|
201
|
+
const createBootstrapOptions = this.options.createErrorRepresentationBootstrapOptions;
|
|
202
|
+
if (createBootstrapOptions === undefined) {
|
|
203
|
+
throw new Error(`${this.options.name} adapter portability harness requires createErrorRepresentationBootstrapOptions.`);
|
|
204
|
+
}
|
|
205
|
+
await assertNetworkHttpErrorRepresentationAbortPortability({
|
|
206
|
+
bootstrap: this.options.bootstrap,
|
|
207
|
+
createBootstrapOptions,
|
|
208
|
+
name: this.options.name
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Verifies ordered, non-folded portable response cookies over a real listener. */
|
|
213
|
+
async assertSupportsPortableResponseCookies() {
|
|
214
|
+
const app = await this.options.bootstrap(createResponseCookiePortabilityModule(), {
|
|
215
|
+
cors: false,
|
|
216
|
+
port: 0
|
|
217
|
+
});
|
|
218
|
+
await prepareAndListenWithCleanup(app, this.options.name);
|
|
219
|
+
await runWithListeningUrlCleanup(app, this.options.name, async url => {
|
|
220
|
+
assertPortableResponseCookies(await fetch(`${url}/response-cookies`), this.options.name);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Verifies 304/412 metadata and body suppression through a real network listener. */
|
|
225
|
+
async assertSupportsConditionalRequests() {
|
|
226
|
+
let _initProto, _initClass;
|
|
227
|
+
const createBootstrapOptions = this.options.createConditionalRequestBootstrapOptions;
|
|
228
|
+
if (createBootstrapOptions === undefined) {
|
|
229
|
+
throw new Error(`${this.options.name} adapter portability harness requires createConditionalRequestBootstrapOptions.`);
|
|
230
|
+
}
|
|
231
|
+
let _ValidatorsController;
|
|
232
|
+
class ValidatorsController {
|
|
233
|
+
static {
|
|
234
|
+
({
|
|
235
|
+
e: [_initProto],
|
|
236
|
+
c: [_ValidatorsController, _initClass]
|
|
237
|
+
} = _applyDecs(this, [Controller('/validators')], [[[Produces('application/json'), Get('/resource')], 2, "getResource"], [[Produces('application/json'), Head('/resource')], 2, "headResource"], [Post('/resource'), 2, "updateResource"]]));
|
|
238
|
+
}
|
|
239
|
+
constructor() {
|
|
240
|
+
_initProto(this);
|
|
241
|
+
}
|
|
242
|
+
getResource() {
|
|
243
|
+
return {
|
|
244
|
+
id: 'resource'
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
headResource() {
|
|
248
|
+
return {
|
|
249
|
+
id: 'resource'
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
updateResource() {
|
|
253
|
+
return {
|
|
254
|
+
id: 'resource'
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
static {
|
|
258
|
+
_initClass();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
class AppModule {}
|
|
262
|
+
defineModule(AppModule, {
|
|
263
|
+
controllers: [_ValidatorsController]
|
|
264
|
+
});
|
|
265
|
+
const app = await this.options.bootstrap(AppModule, createBootstrapOptions({
|
|
266
|
+
conditionalRequest: {
|
|
267
|
+
resolve() {
|
|
268
|
+
return {
|
|
269
|
+
exists: true,
|
|
270
|
+
validators: {
|
|
271
|
+
etag: {
|
|
272
|
+
opaqueValue: 'resource-v1',
|
|
273
|
+
strength: 'strong'
|
|
274
|
+
},
|
|
275
|
+
lastModified: new Date('2026-01-01T00:00:00.750Z')
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
contentNegotiation: {
|
|
281
|
+
formatters: [{
|
|
282
|
+
format(body) {
|
|
283
|
+
return JSON.stringify(body);
|
|
284
|
+
},
|
|
285
|
+
mediaType: 'application/json'
|
|
286
|
+
}]
|
|
287
|
+
},
|
|
288
|
+
cors: false,
|
|
289
|
+
port: 0
|
|
290
|
+
}));
|
|
291
|
+
await prepareAndListenWithCleanup(app, this.options.name);
|
|
292
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
293
|
+
const [notModified, preconditionFailed, head] = await Promise.all([fetch(`${baseUrl}/validators/resource`, {
|
|
294
|
+
headers: {
|
|
295
|
+
'if-none-match': '"resource-v1"'
|
|
296
|
+
}
|
|
297
|
+
}), fetch(`${baseUrl}/validators/resource`, {
|
|
298
|
+
headers: {
|
|
299
|
+
'if-match': '"different-resource"'
|
|
300
|
+
},
|
|
301
|
+
method: 'POST'
|
|
302
|
+
}), fetch(`${baseUrl}/validators/resource`, {
|
|
303
|
+
headers: {
|
|
304
|
+
'if-none-match': '"resource-v1"'
|
|
305
|
+
},
|
|
306
|
+
method: 'HEAD'
|
|
307
|
+
})]);
|
|
308
|
+
if (notModified.status !== 304 || preconditionFailed.status !== 412 || head.status !== 304 || (await notModified.text()) !== '' || (await preconditionFailed.text()) !== '' || (await head.text()) !== '' || notModified.headers.get('etag') !== '"resource-v1"' || notModified.headers.get('last-modified') !== 'Thu, 01 Jan 2026 00:00:00 GMT' || notModified.headers.get('vary') !== 'Accept' || preconditionFailed.headers.get('etag') !== '"resource-v1"' || preconditionFailed.headers.get('last-modified') !== 'Thu, 01 Jan 2026 00:00:00 GMT' || head.headers.get('etag') !== '"resource-v1"' || head.headers.get('last-modified') !== 'Thu, 01 Jan 2026 00:00:00 GMT' || head.headers.get('vary') !== 'Accept') {
|
|
309
|
+
throw new Error(`${this.options.name} adapter changed conditional request response semantics.`);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Verifies single-byte-range metadata and payload slicing through a real network listener. */
|
|
315
|
+
async assertSupportsSingleByteRanges() {
|
|
316
|
+
let _initProto2, _initClass2;
|
|
317
|
+
const createBootstrapOptions = this.options.createConditionalRequestBootstrapOptions;
|
|
318
|
+
if (createBootstrapOptions === undefined) {
|
|
319
|
+
throw new Error(`${this.options.name} adapter portability harness requires createConditionalRequestBootstrapOptions.`);
|
|
320
|
+
}
|
|
321
|
+
let _AssetController;
|
|
322
|
+
class AssetController {
|
|
323
|
+
static {
|
|
324
|
+
({
|
|
325
|
+
e: [_initProto2],
|
|
326
|
+
c: [_AssetController, _initClass2]
|
|
327
|
+
} = _applyDecs(this, [Controller('/assets')], [[[Produces('application/octet-stream'), Get('/logo')], 2, "getLogo"], [[Produces('application/octet-stream'), Head('/logo')], 2, "headLogo"], [[Produces('application/octet-stream'), Post('/logo')], 2, "postLogo"]]));
|
|
328
|
+
}
|
|
329
|
+
constructor() {
|
|
330
|
+
_initProto2(this);
|
|
331
|
+
}
|
|
332
|
+
getLogo() {
|
|
333
|
+
return Uint8Array.from([0, 1, 2, 3, 4, 5]);
|
|
334
|
+
}
|
|
335
|
+
headLogo() {
|
|
336
|
+
return Uint8Array.from([0, 1, 2, 3, 4, 5]);
|
|
337
|
+
}
|
|
338
|
+
postLogo() {
|
|
339
|
+
return Uint8Array.from([0, 1, 2, 3, 4, 5]);
|
|
340
|
+
}
|
|
341
|
+
static {
|
|
342
|
+
_initClass2();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
class AppModule {}
|
|
346
|
+
defineModule(AppModule, {
|
|
347
|
+
controllers: [_AssetController]
|
|
348
|
+
});
|
|
349
|
+
const app = await this.options.bootstrap(AppModule, createBootstrapOptions({
|
|
350
|
+
conditionalRequest: {
|
|
351
|
+
resolve() {
|
|
352
|
+
return {
|
|
353
|
+
exists: true,
|
|
354
|
+
validators: {
|
|
355
|
+
etag: {
|
|
356
|
+
opaqueValue: 'asset-v1',
|
|
357
|
+
strength: 'strong'
|
|
358
|
+
},
|
|
359
|
+
lastModified: new Date('2026-01-01T00:00:00.750Z')
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
contentNegotiation: {
|
|
365
|
+
formatters: [{
|
|
366
|
+
format(body) {
|
|
367
|
+
return JSON.stringify(body);
|
|
368
|
+
},
|
|
369
|
+
mediaType: 'application/json'
|
|
370
|
+
}, {
|
|
371
|
+
format(body) {
|
|
372
|
+
if (!(body instanceof Uint8Array)) {
|
|
373
|
+
throw new Error('Expected byte-range formatter to receive a Uint8Array.');
|
|
374
|
+
}
|
|
375
|
+
return body;
|
|
376
|
+
},
|
|
377
|
+
mediaType: 'application/octet-stream'
|
|
378
|
+
}]
|
|
379
|
+
},
|
|
380
|
+
cors: false,
|
|
381
|
+
port: 0
|
|
382
|
+
}));
|
|
383
|
+
await prepareAndListenWithCleanup(app, this.options.name);
|
|
384
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
385
|
+
const [bounded, suffix, openEnded, malformed, multiple, unsatisfiable, head, post, matchingEtag, nonmatchingEtag, matchingDate, nonmatchingDate] = await Promise.all([fetch(`${baseUrl}/assets/logo`, {
|
|
386
|
+
headers: {
|
|
387
|
+
range: 'bytes=2-4'
|
|
388
|
+
}
|
|
389
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
390
|
+
headers: {
|
|
391
|
+
range: 'bytes=-2'
|
|
392
|
+
}
|
|
393
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
394
|
+
headers: {
|
|
395
|
+
range: 'bytes=3-'
|
|
396
|
+
}
|
|
397
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
398
|
+
headers: {
|
|
399
|
+
range: 'items=2-4'
|
|
400
|
+
}
|
|
401
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
402
|
+
headers: {
|
|
403
|
+
range: 'bytes=0-1,3-4'
|
|
404
|
+
}
|
|
405
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
406
|
+
headers: {
|
|
407
|
+
range: 'bytes=9-'
|
|
408
|
+
}
|
|
409
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
410
|
+
headers: {
|
|
411
|
+
range: 'bytes=2-4'
|
|
412
|
+
},
|
|
413
|
+
method: 'HEAD'
|
|
414
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
415
|
+
headers: {
|
|
416
|
+
range: 'bytes=2-4'
|
|
417
|
+
},
|
|
418
|
+
method: 'POST'
|
|
419
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
420
|
+
headers: {
|
|
421
|
+
'if-range': '"asset-v1"',
|
|
422
|
+
range: 'bytes=2-4'
|
|
423
|
+
}
|
|
424
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
425
|
+
headers: {
|
|
426
|
+
'if-range': '"different-asset"',
|
|
427
|
+
range: 'bytes=2-4'
|
|
428
|
+
}
|
|
429
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
430
|
+
headers: {
|
|
431
|
+
'if-range': 'Thu, 01 Jan 2026 00:00:00 GMT',
|
|
432
|
+
range: 'bytes=2-4'
|
|
433
|
+
}
|
|
434
|
+
}), fetch(`${baseUrl}/assets/logo`, {
|
|
435
|
+
headers: {
|
|
436
|
+
'if-range': 'Wed, 31 Dec 2025 23:59:59 GMT',
|
|
437
|
+
range: 'bytes=2-4'
|
|
438
|
+
}
|
|
439
|
+
})]);
|
|
440
|
+
const [boundedBytes, suffixBytes, openEndedBytes, malformedBytes, multipleBytes, unsatisfiableBody, headBody, postBytes, matchingEtagBytes, nonmatchingEtagBytes, matchingDateBytes, nonmatchingDateBytes] = await Promise.all([bounded.bytes(), suffix.bytes(), openEnded.bytes(), malformed.bytes(), multiple.bytes(), unsatisfiable.text(), head.text(), post.bytes(), matchingEtag.bytes(), nonmatchingEtag.bytes(), matchingDate.bytes(), nonmatchingDate.bytes()]);
|
|
441
|
+
if (bounded.status !== 206 || suffix.status !== 206 || openEnded.status !== 206 || malformed.status !== 200 || multiple.status !== 200 || unsatisfiable.status !== 416 || head.status !== 206 || post.status !== 201 || matchingEtag.status !== 206 || nonmatchingEtag.status !== 200 || matchingDate.status !== 206 || nonmatchingDate.status !== 200 || bounded.headers.get('accept-ranges') !== 'bytes' || bounded.headers.get('content-range') !== 'bytes 2-4/6' || bounded.headers.get('content-length') !== '3' || suffix.headers.get('content-range') !== 'bytes 4-5/6' || openEnded.headers.get('content-range') !== 'bytes 3-5/6' || openEnded.headers.get('content-length') !== '3' || unsatisfiable.headers.get('accept-ranges') !== 'bytes' || unsatisfiable.headers.get('content-range') !== 'bytes */6' || unsatisfiable.headers.get('content-length') !== '0' || unsatisfiableBody !== '' || head.headers.get('content-range') !== bounded.headers.get('content-range') || head.headers.get('content-length') !== bounded.headers.get('content-length') || headBody !== '' || matchingEtag.headers.get('content-range') !== 'bytes 2-4/6' || matchingEtag.headers.get('etag') !== '"asset-v1"' || matchingDate.headers.get('content-range') !== 'bytes 2-4/6' || matchingDate.headers.get('last-modified') !== 'Thu, 01 Jan 2026 00:00:00 GMT' || !equalByteArrays(boundedBytes, Uint8Array.from([2, 3, 4])) || !equalByteArrays(suffixBytes, Uint8Array.from([4, 5])) || !equalByteArrays(openEndedBytes, Uint8Array.from([3, 4, 5])) || !equalByteArrays(malformedBytes, Uint8Array.from([0, 1, 2, 3, 4, 5])) || !equalByteArrays(multipleBytes, Uint8Array.from([0, 1, 2, 3, 4, 5])) || !equalByteArrays(postBytes, Uint8Array.from([0, 1, 2, 3, 4, 5])) || !equalByteArrays(matchingEtagBytes, Uint8Array.from([2, 3, 4])) || !equalByteArrays(nonmatchingEtagBytes, Uint8Array.from([0, 1, 2, 3, 4, 5])) || !equalByteArrays(matchingDateBytes, Uint8Array.from([2, 3, 4])) || !equalByteArrays(nonmatchingDateBytes, Uint8Array.from([0, 1, 2, 3, 4, 5]))) {
|
|
442
|
+
throw new Error(`${this.options.name} adapter changed single byte-range or If-Range response semantics.`);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
144
447
|
/**
|
|
145
448
|
* Asserts that the adapter preserves malformed cookie values without crashing
|
|
146
449
|
* or incorrectly normalizing them.
|
|
147
450
|
*/
|
|
148
451
|
async assertPreservesMalformedCookieValues() {
|
|
149
|
-
let
|
|
452
|
+
let _initProto3, _initClass3;
|
|
150
453
|
let _CookieController;
|
|
151
454
|
class CookieController {
|
|
152
455
|
static {
|
|
153
456
|
({
|
|
154
|
-
e: [
|
|
155
|
-
c: [_CookieController,
|
|
457
|
+
e: [_initProto3],
|
|
458
|
+
c: [_CookieController, _initClass3]
|
|
156
459
|
} = _applyDecs(this, [Controller('/cookies')], [[Get('/'), 2, "readCookies"]]));
|
|
157
460
|
}
|
|
158
461
|
constructor() {
|
|
159
|
-
|
|
462
|
+
_initProto3(this);
|
|
160
463
|
}
|
|
161
464
|
readCookies(_input, context) {
|
|
162
465
|
return context.request.cookies;
|
|
163
466
|
}
|
|
164
467
|
static {
|
|
165
|
-
|
|
468
|
+
_initClass3();
|
|
166
469
|
}
|
|
167
470
|
}
|
|
168
471
|
class AppModule {}
|
|
@@ -189,18 +492,78 @@ export class HttpAdapterPortabilityHarness {
|
|
|
189
492
|
}
|
|
190
493
|
});
|
|
191
494
|
}
|
|
495
|
+
|
|
496
|
+
/** Verifies `QUERY` and extension-method execution through the adapter's real listener fallback. */
|
|
497
|
+
async assertSupportsCustomHttpRouteMethods() {
|
|
498
|
+
let _initProto4, _initClass4;
|
|
499
|
+
let _CustomMethodControll;
|
|
500
|
+
class CustomMethodController {
|
|
501
|
+
static {
|
|
502
|
+
({
|
|
503
|
+
e: [_initProto4],
|
|
504
|
+
c: [_CustomMethodControll, _initClass4]
|
|
505
|
+
} = _applyDecs(this, [Controller('/custom-methods')], [[Query('/query'), 2, "query"], [Route('PURGE', '/purge'), 2, "purge"]]));
|
|
506
|
+
}
|
|
507
|
+
constructor() {
|
|
508
|
+
_initProto4(this);
|
|
509
|
+
}
|
|
510
|
+
query(_input, context) {
|
|
511
|
+
return {
|
|
512
|
+
body: context.request.body,
|
|
513
|
+
method: context.request.method
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
purge(_input, context) {
|
|
517
|
+
return {
|
|
518
|
+
body: context.request.body,
|
|
519
|
+
method: context.request.method
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
static {
|
|
523
|
+
_initClass4();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
class AppModule {}
|
|
527
|
+
defineModule(AppModule, {
|
|
528
|
+
controllers: [_CustomMethodControll]
|
|
529
|
+
});
|
|
530
|
+
const app = await this.options.bootstrap(AppModule, {
|
|
531
|
+
cors: false,
|
|
532
|
+
port: 0
|
|
533
|
+
});
|
|
534
|
+
await prepareAndListenWithCleanup(app, this.options.name);
|
|
535
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
536
|
+
for (const method of ['QUERY', 'PURGE']) {
|
|
537
|
+
const body = JSON.stringify({
|
|
538
|
+
value: method.toLowerCase()
|
|
539
|
+
});
|
|
540
|
+
const response = await requestCustomHttpMethod(`${baseUrl}/custom-methods/${method.toLowerCase()}`, method, body);
|
|
541
|
+
if (response.statusCode !== 200) {
|
|
542
|
+
throw new Error(`${this.options.name} adapter changed ${method} response status semantics: received ${String(response.statusCode)}.`);
|
|
543
|
+
}
|
|
544
|
+
if (JSON.stringify(JSON.parse(response.body)) !== JSON.stringify({
|
|
545
|
+
body: {
|
|
546
|
+
value: method.toLowerCase()
|
|
547
|
+
},
|
|
548
|
+
method
|
|
549
|
+
})) {
|
|
550
|
+
throw new Error(`${this.options.name} adapter changed ${method} method or body semantics.`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
192
555
|
async assertPreservesRawBodyForJsonAndText() {
|
|
193
|
-
let
|
|
556
|
+
let _initProto5, _initClass5;
|
|
194
557
|
let _WebhookController;
|
|
195
558
|
class WebhookController {
|
|
196
559
|
static {
|
|
197
560
|
({
|
|
198
|
-
e: [
|
|
199
|
-
c: [_WebhookController,
|
|
561
|
+
e: [_initProto5],
|
|
562
|
+
c: [_WebhookController, _initClass5]
|
|
200
563
|
} = _applyDecs(this, [Controller('/webhooks')], [[Post('/json'), 2, "handleJson"], [Post('/text'), 2, "handleText"]]));
|
|
201
564
|
}
|
|
202
565
|
constructor() {
|
|
203
|
-
|
|
566
|
+
_initProto5(this);
|
|
204
567
|
}
|
|
205
568
|
handleJson(_input, context) {
|
|
206
569
|
return {
|
|
@@ -215,7 +578,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
215
578
|
};
|
|
216
579
|
}
|
|
217
580
|
static {
|
|
218
|
-
|
|
581
|
+
_initClass5();
|
|
219
582
|
}
|
|
220
583
|
}
|
|
221
584
|
class AppModule {}
|
|
@@ -265,17 +628,17 @@ export class HttpAdapterPortabilityHarness {
|
|
|
265
628
|
});
|
|
266
629
|
}
|
|
267
630
|
async assertPreservesExactRawBodyBytesForByteSensitivePayloads() {
|
|
268
|
-
let
|
|
631
|
+
let _initProto6, _initClass6;
|
|
269
632
|
let _WebhookController2;
|
|
270
633
|
class WebhookController {
|
|
271
634
|
static {
|
|
272
635
|
({
|
|
273
|
-
e: [
|
|
274
|
-
c: [_WebhookController2,
|
|
636
|
+
e: [_initProto6],
|
|
637
|
+
c: [_WebhookController2, _initClass6]
|
|
275
638
|
} = _applyDecs(this, [Controller('/webhooks')], [[Post('/bytes'), 2, "handleBytes"]]));
|
|
276
639
|
}
|
|
277
640
|
constructor() {
|
|
278
|
-
|
|
641
|
+
_initProto6(this);
|
|
279
642
|
}
|
|
280
643
|
handleBytes(_input, context) {
|
|
281
644
|
return {
|
|
@@ -283,7 +646,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
283
646
|
};
|
|
284
647
|
}
|
|
285
648
|
static {
|
|
286
|
-
|
|
649
|
+
_initClass6();
|
|
287
650
|
}
|
|
288
651
|
}
|
|
289
652
|
class AppModule {}
|
|
@@ -320,17 +683,17 @@ export class HttpAdapterPortabilityHarness {
|
|
|
320
683
|
});
|
|
321
684
|
}
|
|
322
685
|
async assertExcludesRawBodyForMultipart() {
|
|
323
|
-
let
|
|
686
|
+
let _initProto7, _initClass7;
|
|
324
687
|
let _UploadController;
|
|
325
688
|
class UploadController {
|
|
326
689
|
static {
|
|
327
690
|
({
|
|
328
|
-
e: [
|
|
329
|
-
c: [_UploadController,
|
|
691
|
+
e: [_initProto7],
|
|
692
|
+
c: [_UploadController, _initClass7]
|
|
330
693
|
} = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
|
|
331
694
|
}
|
|
332
695
|
constructor() {
|
|
333
|
-
|
|
696
|
+
_initProto7(this);
|
|
334
697
|
}
|
|
335
698
|
upload(_input, context) {
|
|
336
699
|
return {
|
|
@@ -340,7 +703,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
340
703
|
};
|
|
341
704
|
}
|
|
342
705
|
static {
|
|
343
|
-
|
|
706
|
+
_initClass7();
|
|
344
707
|
}
|
|
345
708
|
}
|
|
346
709
|
class AppModule {}
|
|
@@ -379,17 +742,17 @@ export class HttpAdapterPortabilityHarness {
|
|
|
379
742
|
});
|
|
380
743
|
}
|
|
381
744
|
async assertDefaultsMultipartTotalLimitToMaxBodySize() {
|
|
382
|
-
let
|
|
745
|
+
let _initProto8, _initClass8;
|
|
383
746
|
let _UploadController2;
|
|
384
747
|
class UploadController {
|
|
385
748
|
static {
|
|
386
749
|
({
|
|
387
|
-
e: [
|
|
388
|
-
c: [_UploadController2,
|
|
750
|
+
e: [_initProto8],
|
|
751
|
+
c: [_UploadController2, _initClass8]
|
|
389
752
|
} = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
|
|
390
753
|
}
|
|
391
754
|
constructor() {
|
|
392
|
-
|
|
755
|
+
_initProto8(this);
|
|
393
756
|
}
|
|
394
757
|
upload(_input, context) {
|
|
395
758
|
return {
|
|
@@ -398,7 +761,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
398
761
|
};
|
|
399
762
|
}
|
|
400
763
|
static {
|
|
401
|
-
|
|
764
|
+
_initClass8();
|
|
402
765
|
}
|
|
403
766
|
}
|
|
404
767
|
class AppModule {}
|
|
@@ -434,17 +797,21 @@ export class HttpAdapterPortabilityHarness {
|
|
|
434
797
|
});
|
|
435
798
|
}
|
|
436
799
|
async assertSupportsSseStreaming() {
|
|
437
|
-
let
|
|
800
|
+
let _initProto9, _initClass9;
|
|
801
|
+
let resolveHandlerReady;
|
|
802
|
+
const handlerReady = new Promise(resolve => {
|
|
803
|
+
resolveHandlerReady = resolve;
|
|
804
|
+
});
|
|
438
805
|
let _EventsController;
|
|
439
806
|
class EventsController {
|
|
440
807
|
static {
|
|
441
808
|
({
|
|
442
|
-
e: [
|
|
443
|
-
c: [_EventsController,
|
|
809
|
+
e: [_initProto9],
|
|
810
|
+
c: [_EventsController, _initClass9]
|
|
444
811
|
} = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
|
|
445
812
|
}
|
|
446
813
|
constructor() {
|
|
447
|
-
|
|
814
|
+
_initProto9(this);
|
|
448
815
|
}
|
|
449
816
|
stream(_input, context) {
|
|
450
817
|
const stream = new SseResponse(context);
|
|
@@ -455,13 +822,11 @@ export class HttpAdapterPortabilityHarness {
|
|
|
455
822
|
event: 'ready',
|
|
456
823
|
id: 'evt-1'
|
|
457
824
|
});
|
|
458
|
-
|
|
459
|
-
stream.close();
|
|
460
|
-
});
|
|
825
|
+
resolveHandlerReady(stream);
|
|
461
826
|
return stream;
|
|
462
827
|
}
|
|
463
828
|
static {
|
|
464
|
-
|
|
829
|
+
_initClass9();
|
|
465
830
|
}
|
|
466
831
|
}
|
|
467
832
|
class AppModule {}
|
|
@@ -474,21 +839,30 @@ export class HttpAdapterPortabilityHarness {
|
|
|
474
839
|
});
|
|
475
840
|
await prepareAndListenWithCleanup(app, this.options.name);
|
|
476
841
|
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
842
|
+
const client = new AbortController();
|
|
843
|
+
try {
|
|
844
|
+
const responsePromise = fetch(`${baseUrl}/events`, {
|
|
845
|
+
headers: {
|
|
846
|
+
accept: 'text/event-stream'
|
|
847
|
+
},
|
|
848
|
+
signal: client.signal
|
|
849
|
+
});
|
|
850
|
+
const stream = await withTimeout(handlerReady, 2_000, `${this.options.name} adapter did not enter the SSE handler.`);
|
|
851
|
+
stream.close();
|
|
852
|
+
const response = await responsePromise;
|
|
853
|
+
const body = await withTimeout(response.text(), 2_000, `${this.options.name} adapter did not close the SSE response stream.`);
|
|
854
|
+
if (response.status !== 200) {
|
|
855
|
+
throw new Error(`${this.options.name} adapter changed SSE response status semantics.`);
|
|
480
856
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (!body.includes('event: ready') || !body.includes('data: {"ready":true}')) {
|
|
491
|
-
throw new Error(`${this.options.name} adapter changed SSE body framing.`);
|
|
857
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
858
|
+
if (!contentType.includes('text/event-stream')) {
|
|
859
|
+
throw new Error(`${this.options.name} adapter does not expose text/event-stream content-type.`);
|
|
860
|
+
}
|
|
861
|
+
if (!body.includes('event: ready') || !body.includes('data: {"ready":true}')) {
|
|
862
|
+
throw new Error(`${this.options.name} adapter changed SSE body framing.`);
|
|
863
|
+
}
|
|
864
|
+
} finally {
|
|
865
|
+
client.abort();
|
|
492
866
|
}
|
|
493
867
|
});
|
|
494
868
|
}
|
|
@@ -498,7 +872,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
498
872
|
* closes before a `drain` event is emitted.
|
|
499
873
|
*/
|
|
500
874
|
async assertSettlesStreamDrainWaitOnClose() {
|
|
501
|
-
let
|
|
875
|
+
let _initProto0, _initClass0;
|
|
502
876
|
const adapterName = this.options.name;
|
|
503
877
|
let resolveDrainWait;
|
|
504
878
|
const drainWaitSettled = new Promise(resolve => {
|
|
@@ -508,12 +882,12 @@ export class HttpAdapterPortabilityHarness {
|
|
|
508
882
|
class EventsController {
|
|
509
883
|
static {
|
|
510
884
|
({
|
|
511
|
-
e: [
|
|
512
|
-
c: [_EventsController2,
|
|
885
|
+
e: [_initProto0],
|
|
886
|
+
c: [_EventsController2, _initClass0]
|
|
513
887
|
} = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
|
|
514
888
|
}
|
|
515
889
|
constructor() {
|
|
516
|
-
|
|
890
|
+
_initProto0(this);
|
|
517
891
|
}
|
|
518
892
|
async stream(_input, context) {
|
|
519
893
|
const stream = new SseResponse(context);
|
|
@@ -528,7 +902,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
528
902
|
return stream;
|
|
529
903
|
}
|
|
530
904
|
static {
|
|
531
|
-
|
|
905
|
+
_initClass0();
|
|
532
906
|
}
|
|
533
907
|
}
|
|
534
908
|
class AppModule {}
|
|
@@ -554,18 +928,18 @@ export class HttpAdapterPortabilityHarness {
|
|
|
554
928
|
});
|
|
555
929
|
}
|
|
556
930
|
async assertReportsConfiguredHostInStartupLogs() {
|
|
557
|
-
let
|
|
931
|
+
let _initProto1, _initClass1;
|
|
558
932
|
const logger = createLogCaptureLogger();
|
|
559
933
|
let _HealthController;
|
|
560
934
|
class HealthController {
|
|
561
935
|
static {
|
|
562
936
|
({
|
|
563
|
-
e: [
|
|
564
|
-
c: [_HealthController,
|
|
937
|
+
e: [_initProto1],
|
|
938
|
+
c: [_HealthController, _initClass1]
|
|
565
939
|
} = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
|
|
566
940
|
}
|
|
567
941
|
constructor() {
|
|
568
|
-
|
|
942
|
+
_initProto1(this);
|
|
569
943
|
}
|
|
570
944
|
getHealth() {
|
|
571
945
|
return {
|
|
@@ -573,7 +947,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
573
947
|
};
|
|
574
948
|
}
|
|
575
949
|
static {
|
|
576
|
-
|
|
950
|
+
_initClass1();
|
|
577
951
|
}
|
|
578
952
|
}
|
|
579
953
|
class AppModule {}
|
|
@@ -603,18 +977,18 @@ export class HttpAdapterPortabilityHarness {
|
|
|
603
977
|
});
|
|
604
978
|
}
|
|
605
979
|
async assertReportsHttpsStartupUrl(https) {
|
|
606
|
-
let
|
|
980
|
+
let _initProto10, _initClass10;
|
|
607
981
|
const logger = createLogCaptureLogger();
|
|
608
982
|
let _HealthController2;
|
|
609
983
|
class HealthController {
|
|
610
984
|
static {
|
|
611
985
|
({
|
|
612
|
-
e: [
|
|
613
|
-
c: [_HealthController2,
|
|
986
|
+
e: [_initProto10],
|
|
987
|
+
c: [_HealthController2, _initClass10]
|
|
614
988
|
} = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
|
|
615
989
|
}
|
|
616
990
|
constructor() {
|
|
617
|
-
|
|
991
|
+
_initProto10(this);
|
|
618
992
|
}
|
|
619
993
|
getHealth() {
|
|
620
994
|
return {
|
|
@@ -622,7 +996,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
622
996
|
};
|
|
623
997
|
}
|
|
624
998
|
static {
|
|
625
|
-
|
|
999
|
+
_initClass10();
|
|
626
1000
|
}
|
|
627
1001
|
}
|
|
628
1002
|
class AppModule {}
|
|
@@ -652,7 +1026,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
652
1026
|
});
|
|
653
1027
|
}
|
|
654
1028
|
async assertRemovesShutdownSignalListenersAfterClose() {
|
|
655
|
-
let
|
|
1029
|
+
let _initProto11, _initClass11;
|
|
656
1030
|
const logger = {
|
|
657
1031
|
debug() {},
|
|
658
1032
|
error() {},
|
|
@@ -663,12 +1037,12 @@ export class HttpAdapterPortabilityHarness {
|
|
|
663
1037
|
class HealthController {
|
|
664
1038
|
static {
|
|
665
1039
|
({
|
|
666
|
-
e: [
|
|
667
|
-
c: [_HealthController3,
|
|
1040
|
+
e: [_initProto11],
|
|
1041
|
+
c: [_HealthController3, _initClass11]
|
|
668
1042
|
} = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
|
|
669
1043
|
}
|
|
670
1044
|
constructor() {
|
|
671
|
-
|
|
1045
|
+
_initProto11(this);
|
|
672
1046
|
}
|
|
673
1047
|
getHealth() {
|
|
674
1048
|
return {
|
|
@@ -676,7 +1050,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
676
1050
|
};
|
|
677
1051
|
}
|
|
678
1052
|
static {
|
|
679
|
-
|
|
1053
|
+
_initClass11();
|
|
680
1054
|
}
|
|
681
1055
|
}
|
|
682
1056
|
class AppModule {}
|
|
@@ -717,6 +1091,9 @@ export class HttpAdapterPortabilityHarness {
|
|
|
717
1091
|
export function createHttpAdapterPortabilityHarness(options) {
|
|
718
1092
|
return new HttpAdapterPortabilityHarness(options);
|
|
719
1093
|
}
|
|
1094
|
+
function equalByteArrays(left, right) {
|
|
1095
|
+
return left.byteLength === right.byteLength && left.every((value, index) => value === right[index]);
|
|
1096
|
+
}
|
|
720
1097
|
async function withTimeout(promise, timeoutMs, message) {
|
|
721
1098
|
let timeout;
|
|
722
1099
|
const timeoutPromise = new Promise((_resolve, reject) => {
|