@fluojs/testing 2.0.0 → 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 +94 -12
- package/README.md +96 -12
- 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/mock.js +1 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +45 -73
- 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 +24 -1
- package/dist/portability/http-adapter-portability.d.ts.map +1 -1
- package/dist/portability/http-adapter-portability.js +441 -68
- 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 +23 -1
- 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 +5 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +19 -15
|
@@ -3,8 +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 { Controller, Get, Post, SseResponse } from '@fluojs/http';
|
|
6
|
+
import { Controller, Get, Head, Post, Produces, Query, Route, SseResponse } from '@fluojs/http';
|
|
7
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';
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Options for configuring the HTTP adapter portability harness.
|
|
@@ -61,6 +64,41 @@ async function requestHttps(url) {
|
|
|
61
64
|
request.end();
|
|
62
65
|
});
|
|
63
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
|
+
}
|
|
64
102
|
function createLogCaptureLogger() {
|
|
65
103
|
const messages = [];
|
|
66
104
|
const capture = (...args) => messages.push(args.map(arg => String(arg)).join(' '));
|
|
@@ -145,28 +183,289 @@ export class HttpAdapterPortabilityHarness {
|
|
|
145
183
|
this.options = options;
|
|
146
184
|
}
|
|
147
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
|
+
|
|
148
447
|
/**
|
|
149
448
|
* Asserts that the adapter preserves malformed cookie values without crashing
|
|
150
449
|
* or incorrectly normalizing them.
|
|
151
450
|
*/
|
|
152
451
|
async assertPreservesMalformedCookieValues() {
|
|
153
|
-
let
|
|
452
|
+
let _initProto3, _initClass3;
|
|
154
453
|
let _CookieController;
|
|
155
454
|
class CookieController {
|
|
156
455
|
static {
|
|
157
456
|
({
|
|
158
|
-
e: [
|
|
159
|
-
c: [_CookieController,
|
|
457
|
+
e: [_initProto3],
|
|
458
|
+
c: [_CookieController, _initClass3]
|
|
160
459
|
} = _applyDecs(this, [Controller('/cookies')], [[Get('/'), 2, "readCookies"]]));
|
|
161
460
|
}
|
|
162
461
|
constructor() {
|
|
163
|
-
|
|
462
|
+
_initProto3(this);
|
|
164
463
|
}
|
|
165
464
|
readCookies(_input, context) {
|
|
166
465
|
return context.request.cookies;
|
|
167
466
|
}
|
|
168
467
|
static {
|
|
169
|
-
|
|
468
|
+
_initClass3();
|
|
170
469
|
}
|
|
171
470
|
}
|
|
172
471
|
class AppModule {}
|
|
@@ -193,18 +492,78 @@ export class HttpAdapterPortabilityHarness {
|
|
|
193
492
|
}
|
|
194
493
|
});
|
|
195
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
|
+
}
|
|
196
555
|
async assertPreservesRawBodyForJsonAndText() {
|
|
197
|
-
let
|
|
556
|
+
let _initProto5, _initClass5;
|
|
198
557
|
let _WebhookController;
|
|
199
558
|
class WebhookController {
|
|
200
559
|
static {
|
|
201
560
|
({
|
|
202
|
-
e: [
|
|
203
|
-
c: [_WebhookController,
|
|
561
|
+
e: [_initProto5],
|
|
562
|
+
c: [_WebhookController, _initClass5]
|
|
204
563
|
} = _applyDecs(this, [Controller('/webhooks')], [[Post('/json'), 2, "handleJson"], [Post('/text'), 2, "handleText"]]));
|
|
205
564
|
}
|
|
206
565
|
constructor() {
|
|
207
|
-
|
|
566
|
+
_initProto5(this);
|
|
208
567
|
}
|
|
209
568
|
handleJson(_input, context) {
|
|
210
569
|
return {
|
|
@@ -219,7 +578,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
219
578
|
};
|
|
220
579
|
}
|
|
221
580
|
static {
|
|
222
|
-
|
|
581
|
+
_initClass5();
|
|
223
582
|
}
|
|
224
583
|
}
|
|
225
584
|
class AppModule {}
|
|
@@ -269,17 +628,17 @@ export class HttpAdapterPortabilityHarness {
|
|
|
269
628
|
});
|
|
270
629
|
}
|
|
271
630
|
async assertPreservesExactRawBodyBytesForByteSensitivePayloads() {
|
|
272
|
-
let
|
|
631
|
+
let _initProto6, _initClass6;
|
|
273
632
|
let _WebhookController2;
|
|
274
633
|
class WebhookController {
|
|
275
634
|
static {
|
|
276
635
|
({
|
|
277
|
-
e: [
|
|
278
|
-
c: [_WebhookController2,
|
|
636
|
+
e: [_initProto6],
|
|
637
|
+
c: [_WebhookController2, _initClass6]
|
|
279
638
|
} = _applyDecs(this, [Controller('/webhooks')], [[Post('/bytes'), 2, "handleBytes"]]));
|
|
280
639
|
}
|
|
281
640
|
constructor() {
|
|
282
|
-
|
|
641
|
+
_initProto6(this);
|
|
283
642
|
}
|
|
284
643
|
handleBytes(_input, context) {
|
|
285
644
|
return {
|
|
@@ -287,7 +646,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
287
646
|
};
|
|
288
647
|
}
|
|
289
648
|
static {
|
|
290
|
-
|
|
649
|
+
_initClass6();
|
|
291
650
|
}
|
|
292
651
|
}
|
|
293
652
|
class AppModule {}
|
|
@@ -324,17 +683,17 @@ export class HttpAdapterPortabilityHarness {
|
|
|
324
683
|
});
|
|
325
684
|
}
|
|
326
685
|
async assertExcludesRawBodyForMultipart() {
|
|
327
|
-
let
|
|
686
|
+
let _initProto7, _initClass7;
|
|
328
687
|
let _UploadController;
|
|
329
688
|
class UploadController {
|
|
330
689
|
static {
|
|
331
690
|
({
|
|
332
|
-
e: [
|
|
333
|
-
c: [_UploadController,
|
|
691
|
+
e: [_initProto7],
|
|
692
|
+
c: [_UploadController, _initClass7]
|
|
334
693
|
} = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
|
|
335
694
|
}
|
|
336
695
|
constructor() {
|
|
337
|
-
|
|
696
|
+
_initProto7(this);
|
|
338
697
|
}
|
|
339
698
|
upload(_input, context) {
|
|
340
699
|
return {
|
|
@@ -344,7 +703,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
344
703
|
};
|
|
345
704
|
}
|
|
346
705
|
static {
|
|
347
|
-
|
|
706
|
+
_initClass7();
|
|
348
707
|
}
|
|
349
708
|
}
|
|
350
709
|
class AppModule {}
|
|
@@ -383,17 +742,17 @@ export class HttpAdapterPortabilityHarness {
|
|
|
383
742
|
});
|
|
384
743
|
}
|
|
385
744
|
async assertDefaultsMultipartTotalLimitToMaxBodySize() {
|
|
386
|
-
let
|
|
745
|
+
let _initProto8, _initClass8;
|
|
387
746
|
let _UploadController2;
|
|
388
747
|
class UploadController {
|
|
389
748
|
static {
|
|
390
749
|
({
|
|
391
|
-
e: [
|
|
392
|
-
c: [_UploadController2,
|
|
750
|
+
e: [_initProto8],
|
|
751
|
+
c: [_UploadController2, _initClass8]
|
|
393
752
|
} = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
|
|
394
753
|
}
|
|
395
754
|
constructor() {
|
|
396
|
-
|
|
755
|
+
_initProto8(this);
|
|
397
756
|
}
|
|
398
757
|
upload(_input, context) {
|
|
399
758
|
return {
|
|
@@ -402,7 +761,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
402
761
|
};
|
|
403
762
|
}
|
|
404
763
|
static {
|
|
405
|
-
|
|
764
|
+
_initClass8();
|
|
406
765
|
}
|
|
407
766
|
}
|
|
408
767
|
class AppModule {}
|
|
@@ -438,17 +797,21 @@ export class HttpAdapterPortabilityHarness {
|
|
|
438
797
|
});
|
|
439
798
|
}
|
|
440
799
|
async assertSupportsSseStreaming() {
|
|
441
|
-
let
|
|
800
|
+
let _initProto9, _initClass9;
|
|
801
|
+
let resolveHandlerReady;
|
|
802
|
+
const handlerReady = new Promise(resolve => {
|
|
803
|
+
resolveHandlerReady = resolve;
|
|
804
|
+
});
|
|
442
805
|
let _EventsController;
|
|
443
806
|
class EventsController {
|
|
444
807
|
static {
|
|
445
808
|
({
|
|
446
|
-
e: [
|
|
447
|
-
c: [_EventsController,
|
|
809
|
+
e: [_initProto9],
|
|
810
|
+
c: [_EventsController, _initClass9]
|
|
448
811
|
} = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
|
|
449
812
|
}
|
|
450
813
|
constructor() {
|
|
451
|
-
|
|
814
|
+
_initProto9(this);
|
|
452
815
|
}
|
|
453
816
|
stream(_input, context) {
|
|
454
817
|
const stream = new SseResponse(context);
|
|
@@ -459,13 +822,11 @@ export class HttpAdapterPortabilityHarness {
|
|
|
459
822
|
event: 'ready',
|
|
460
823
|
id: 'evt-1'
|
|
461
824
|
});
|
|
462
|
-
|
|
463
|
-
stream.close();
|
|
464
|
-
});
|
|
825
|
+
resolveHandlerReady(stream);
|
|
465
826
|
return stream;
|
|
466
827
|
}
|
|
467
828
|
static {
|
|
468
|
-
|
|
829
|
+
_initClass9();
|
|
469
830
|
}
|
|
470
831
|
}
|
|
471
832
|
class AppModule {}
|
|
@@ -478,21 +839,30 @@ export class HttpAdapterPortabilityHarness {
|
|
|
478
839
|
});
|
|
479
840
|
await prepareAndListenWithCleanup(app, this.options.name);
|
|
480
841
|
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
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.`);
|
|
484
856
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (!body.includes('event: ready') || !body.includes('data: {"ready":true}')) {
|
|
495
|
-
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();
|
|
496
866
|
}
|
|
497
867
|
});
|
|
498
868
|
}
|
|
@@ -502,7 +872,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
502
872
|
* closes before a `drain` event is emitted.
|
|
503
873
|
*/
|
|
504
874
|
async assertSettlesStreamDrainWaitOnClose() {
|
|
505
|
-
let
|
|
875
|
+
let _initProto0, _initClass0;
|
|
506
876
|
const adapterName = this.options.name;
|
|
507
877
|
let resolveDrainWait;
|
|
508
878
|
const drainWaitSettled = new Promise(resolve => {
|
|
@@ -512,12 +882,12 @@ export class HttpAdapterPortabilityHarness {
|
|
|
512
882
|
class EventsController {
|
|
513
883
|
static {
|
|
514
884
|
({
|
|
515
|
-
e: [
|
|
516
|
-
c: [_EventsController2,
|
|
885
|
+
e: [_initProto0],
|
|
886
|
+
c: [_EventsController2, _initClass0]
|
|
517
887
|
} = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
|
|
518
888
|
}
|
|
519
889
|
constructor() {
|
|
520
|
-
|
|
890
|
+
_initProto0(this);
|
|
521
891
|
}
|
|
522
892
|
async stream(_input, context) {
|
|
523
893
|
const stream = new SseResponse(context);
|
|
@@ -532,7 +902,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
532
902
|
return stream;
|
|
533
903
|
}
|
|
534
904
|
static {
|
|
535
|
-
|
|
905
|
+
_initClass0();
|
|
536
906
|
}
|
|
537
907
|
}
|
|
538
908
|
class AppModule {}
|
|
@@ -558,18 +928,18 @@ export class HttpAdapterPortabilityHarness {
|
|
|
558
928
|
});
|
|
559
929
|
}
|
|
560
930
|
async assertReportsConfiguredHostInStartupLogs() {
|
|
561
|
-
let
|
|
931
|
+
let _initProto1, _initClass1;
|
|
562
932
|
const logger = createLogCaptureLogger();
|
|
563
933
|
let _HealthController;
|
|
564
934
|
class HealthController {
|
|
565
935
|
static {
|
|
566
936
|
({
|
|
567
|
-
e: [
|
|
568
|
-
c: [_HealthController,
|
|
937
|
+
e: [_initProto1],
|
|
938
|
+
c: [_HealthController, _initClass1]
|
|
569
939
|
} = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
|
|
570
940
|
}
|
|
571
941
|
constructor() {
|
|
572
|
-
|
|
942
|
+
_initProto1(this);
|
|
573
943
|
}
|
|
574
944
|
getHealth() {
|
|
575
945
|
return {
|
|
@@ -577,7 +947,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
577
947
|
};
|
|
578
948
|
}
|
|
579
949
|
static {
|
|
580
|
-
|
|
950
|
+
_initClass1();
|
|
581
951
|
}
|
|
582
952
|
}
|
|
583
953
|
class AppModule {}
|
|
@@ -607,18 +977,18 @@ export class HttpAdapterPortabilityHarness {
|
|
|
607
977
|
});
|
|
608
978
|
}
|
|
609
979
|
async assertReportsHttpsStartupUrl(https) {
|
|
610
|
-
let
|
|
980
|
+
let _initProto10, _initClass10;
|
|
611
981
|
const logger = createLogCaptureLogger();
|
|
612
982
|
let _HealthController2;
|
|
613
983
|
class HealthController {
|
|
614
984
|
static {
|
|
615
985
|
({
|
|
616
|
-
e: [
|
|
617
|
-
c: [_HealthController2,
|
|
986
|
+
e: [_initProto10],
|
|
987
|
+
c: [_HealthController2, _initClass10]
|
|
618
988
|
} = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
|
|
619
989
|
}
|
|
620
990
|
constructor() {
|
|
621
|
-
|
|
991
|
+
_initProto10(this);
|
|
622
992
|
}
|
|
623
993
|
getHealth() {
|
|
624
994
|
return {
|
|
@@ -626,7 +996,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
626
996
|
};
|
|
627
997
|
}
|
|
628
998
|
static {
|
|
629
|
-
|
|
999
|
+
_initClass10();
|
|
630
1000
|
}
|
|
631
1001
|
}
|
|
632
1002
|
class AppModule {}
|
|
@@ -656,7 +1026,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
656
1026
|
});
|
|
657
1027
|
}
|
|
658
1028
|
async assertRemovesShutdownSignalListenersAfterClose() {
|
|
659
|
-
let
|
|
1029
|
+
let _initProto11, _initClass11;
|
|
660
1030
|
const logger = {
|
|
661
1031
|
debug() {},
|
|
662
1032
|
error() {},
|
|
@@ -667,12 +1037,12 @@ export class HttpAdapterPortabilityHarness {
|
|
|
667
1037
|
class HealthController {
|
|
668
1038
|
static {
|
|
669
1039
|
({
|
|
670
|
-
e: [
|
|
671
|
-
c: [_HealthController3,
|
|
1040
|
+
e: [_initProto11],
|
|
1041
|
+
c: [_HealthController3, _initClass11]
|
|
672
1042
|
} = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
|
|
673
1043
|
}
|
|
674
1044
|
constructor() {
|
|
675
|
-
|
|
1045
|
+
_initProto11(this);
|
|
676
1046
|
}
|
|
677
1047
|
getHealth() {
|
|
678
1048
|
return {
|
|
@@ -680,7 +1050,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
680
1050
|
};
|
|
681
1051
|
}
|
|
682
1052
|
static {
|
|
683
|
-
|
|
1053
|
+
_initClass11();
|
|
684
1054
|
}
|
|
685
1055
|
}
|
|
686
1056
|
class AppModule {}
|
|
@@ -721,6 +1091,9 @@ export class HttpAdapterPortabilityHarness {
|
|
|
721
1091
|
export function createHttpAdapterPortabilityHarness(options) {
|
|
722
1092
|
return new HttpAdapterPortabilityHarness(options);
|
|
723
1093
|
}
|
|
1094
|
+
function equalByteArrays(left, right) {
|
|
1095
|
+
return left.byteLength === right.byteLength && left.every((value, index) => value === right[index]);
|
|
1096
|
+
}
|
|
724
1097
|
async function withTimeout(promise, timeoutMs, message) {
|
|
725
1098
|
let timeout;
|
|
726
1099
|
const timeoutPromise = new Promise((_resolve, reject) => {
|