@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
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
|
|
2
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
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
|
+
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
|
+
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, Head, Header, HttpCode, NotFoundException } from '@fluojs/http';
|
|
7
|
+
import { defineModule } from '@fluojs/runtime';
|
|
8
|
+
function hasErrorCode(value, code) {
|
|
9
|
+
if (typeof value !== 'object' || value === null) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const error = Reflect.get(value, 'error');
|
|
13
|
+
return typeof error === 'object' && error !== null && Reflect.get(error, 'code') === code;
|
|
14
|
+
}
|
|
15
|
+
function assertStatus(response, expected, name, scenario) {
|
|
16
|
+
if (response.status !== expected) {
|
|
17
|
+
throw new Error(`${name} changed ${scenario} status: expected ${String(expected)}, received ${String(response.status)}.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function assertHeadRepresentationParity(name, pair) {
|
|
21
|
+
const contentType = pair.response.headers.get('content-type');
|
|
22
|
+
const headContentType = pair.headResponse.headers.get('content-type');
|
|
23
|
+
if (contentType === null || headContentType !== contentType) {
|
|
24
|
+
throw new Error(`${name} changed ${pair.scenario} HEAD content type parity: GET=${String(contentType)}, HEAD=${String(headContentType)}.`);
|
|
25
|
+
}
|
|
26
|
+
if ((await pair.response.arrayBuffer().then(body => body.byteLength)) === 0 || (await pair.headResponse.text()) !== '') {
|
|
27
|
+
throw new Error(`${name} changed ${pair.scenario} GET body or HEAD body suppression semantics.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Verifies provider-less canonical JSON metadata and body suppression for a HEAD response.
|
|
33
|
+
*
|
|
34
|
+
* @param name Adapter name included in assertion failures.
|
|
35
|
+
* @param response Provider-less HEAD response returned by the adapter.
|
|
36
|
+
* @returns A promise that resolves when the response preserves the portable contract.
|
|
37
|
+
*/
|
|
38
|
+
export async function assertProviderlessHeadResponse(name, response) {
|
|
39
|
+
assertStatus(response, 404, name, 'provider-less canonical JSON HEAD error representation');
|
|
40
|
+
if (response.headers.get('content-type') !== 'application/json; charset=utf-8' || (await response.text()) !== '') {
|
|
41
|
+
throw new Error(`${name} changed provider-less canonical JSON HEAD representation metadata or body suppression.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Verifies negotiated error and successful HEAD responses returned by an adapter.
|
|
47
|
+
*
|
|
48
|
+
* @param name Adapter name included in assertion failures.
|
|
49
|
+
* @param responses Responses collected from the shared representation fixture.
|
|
50
|
+
* @returns A promise that resolves when every response preserves the portable contract.
|
|
51
|
+
*/
|
|
52
|
+
export async function assertRepresentationResponses(name, responses) {
|
|
53
|
+
assertStatus(responses.html, 404, name, 'HTML error representation');
|
|
54
|
+
assertStatus(responses.json, 404, name, 'JSON error representation');
|
|
55
|
+
assertStatus(responses.head, 404, name, 'HEAD error representation');
|
|
56
|
+
assertStatus(responses.jsonHead, 404, name, 'canonical JSON HEAD error representation');
|
|
57
|
+
assertStatus(responses.success, 202, name, 'successful JSON response');
|
|
58
|
+
assertStatus(responses.successHead, 202, name, 'successful HEAD response');
|
|
59
|
+
assertStatus(responses.plain, 200, name, 'successful plain text response');
|
|
60
|
+
assertStatus(responses.plainHead, 200, name, 'successful plain text HEAD response');
|
|
61
|
+
assertStatus(responses.binary, 200, name, 'successful binary response');
|
|
62
|
+
assertStatus(responses.binaryHead, 200, name, 'successful binary HEAD response');
|
|
63
|
+
assertStatus(responses.unsupported, 406, name, 'unsupported error representation');
|
|
64
|
+
assertStatus(responses.unsupportedHead, 406, name, 'unsupported HEAD error representation');
|
|
65
|
+
assertStatus(responses.committed, 202, name, 'already-committed response');
|
|
66
|
+
const [html, json, head, jsonHead, unsupported, unsupportedHead, committed] = await Promise.all([responses.html.text(), responses.json.json(), responses.head.text(), responses.jsonHead.text(), responses.unsupported.json(), responses.unsupportedHead.text(), responses.committed.text()]);
|
|
67
|
+
if (!responses.html.headers.get('content-type')?.includes('text/html') || !html.includes('404:NOT_FOUND')) {
|
|
68
|
+
throw new Error(`${name} changed negotiated HTML error representation semantics.`);
|
|
69
|
+
}
|
|
70
|
+
if (!hasErrorCode(json, 'NOT_FOUND')) {
|
|
71
|
+
throw new Error(`${name} changed canonical JSON error representation semantics.`);
|
|
72
|
+
}
|
|
73
|
+
if (!responses.head.headers.get('content-type')?.includes('text/html') || head !== '') {
|
|
74
|
+
throw new Error(`${name} changed HEAD error representation body suppression.`);
|
|
75
|
+
}
|
|
76
|
+
if (responses.jsonHead.headers.get('content-type') !== 'application/json; charset=utf-8' || jsonHead !== '') {
|
|
77
|
+
throw new Error(`${name} changed canonical JSON HEAD error representation metadata or body suppression.`);
|
|
78
|
+
}
|
|
79
|
+
if (responses.successHead.headers.get('x-head-contract') !== 'preserved') {
|
|
80
|
+
throw new Error(`${name} changed successful HEAD explicit header semantics.`);
|
|
81
|
+
}
|
|
82
|
+
await assertHeadRepresentationParity(name, {
|
|
83
|
+
headResponse: responses.successHead,
|
|
84
|
+
response: responses.success,
|
|
85
|
+
scenario: 'successful JSON'
|
|
86
|
+
});
|
|
87
|
+
await assertHeadRepresentationParity(name, {
|
|
88
|
+
headResponse: responses.plainHead,
|
|
89
|
+
response: responses.plain,
|
|
90
|
+
scenario: 'successful plain text'
|
|
91
|
+
});
|
|
92
|
+
await assertHeadRepresentationParity(name, {
|
|
93
|
+
headResponse: responses.binaryHead,
|
|
94
|
+
response: responses.binary,
|
|
95
|
+
scenario: 'successful binary'
|
|
96
|
+
});
|
|
97
|
+
if (!hasErrorCode(unsupported, 'NOT_ACCEPTABLE')) {
|
|
98
|
+
throw new Error(`${name} changed unsupported error representation fallback semantics.`);
|
|
99
|
+
}
|
|
100
|
+
if (responses.unsupportedHead.headers.get('content-type') !== 'application/json; charset=utf-8' || unsupportedHead !== '') {
|
|
101
|
+
throw new Error(`${name} changed unsupported HEAD error representation metadata or body suppression.`);
|
|
102
|
+
}
|
|
103
|
+
if (committed !== 'handler-owned') {
|
|
104
|
+
throw new Error(`${name} rewrote an already-committed response through the error provider.`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Creates the shared application module used by error-representation portability checks.
|
|
110
|
+
*
|
|
111
|
+
* @returns A module containing success, error, HEAD, and committed-response routes.
|
|
112
|
+
*/
|
|
113
|
+
export function createRepresentationFixture() {
|
|
114
|
+
let _initProto, _initClass;
|
|
115
|
+
let _ErrorRepresentationC;
|
|
116
|
+
class ErrorRepresentationController {
|
|
117
|
+
static {
|
|
118
|
+
({
|
|
119
|
+
e: [_initProto],
|
|
120
|
+
c: [_ErrorRepresentationC, _initClass]
|
|
121
|
+
} = _applyDecs(this, [Controller('/error-representations')], [[Get('/json'), 2, "json"], [Head('/head'), 2, "head"], [[Head('/success-head'), Header('x-head-contract', 'preserved'), HttpCode(202)], 2, "successHead"], [[Get('/success-head'), HttpCode(202)], 2, "success"], [Head('/plain-head'), 2, "plainHead"], [Get('/plain-head'), 2, "plain"], [Head('/binary-head'), 2, "binaryHead"], [Get('/binary-head'), 2, "binary"], [Get('/committed'), 2, "committed"]]));
|
|
122
|
+
}
|
|
123
|
+
constructor() {
|
|
124
|
+
_initProto(this);
|
|
125
|
+
}
|
|
126
|
+
json() {
|
|
127
|
+
throw new NotFoundException('Matched resource missing.');
|
|
128
|
+
}
|
|
129
|
+
head() {
|
|
130
|
+
throw new NotFoundException('HEAD resource missing.');
|
|
131
|
+
}
|
|
132
|
+
successHead() {
|
|
133
|
+
return {
|
|
134
|
+
ok: true
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
success() {
|
|
138
|
+
return {
|
|
139
|
+
ok: true
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
plainHead() {
|
|
143
|
+
return 'plain response';
|
|
144
|
+
}
|
|
145
|
+
plain() {
|
|
146
|
+
return 'plain response';
|
|
147
|
+
}
|
|
148
|
+
binaryHead() {
|
|
149
|
+
return new Uint8Array([1, 2, 3]);
|
|
150
|
+
}
|
|
151
|
+
binary() {
|
|
152
|
+
return new Uint8Array([1, 2, 3]);
|
|
153
|
+
}
|
|
154
|
+
async committed(_input, context) {
|
|
155
|
+
context.response.setStatus(202);
|
|
156
|
+
await context.response.send('handler-owned');
|
|
157
|
+
throw new NotFoundException('Committed response must not be replaced.');
|
|
158
|
+
}
|
|
159
|
+
static {
|
|
160
|
+
_initClass();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
class AppModule {}
|
|
164
|
+
defineModule(AppModule, {
|
|
165
|
+
controllers: [_ErrorRepresentationC]
|
|
166
|
+
});
|
|
167
|
+
return AppModule;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Creates bootstrap options with the shared HTML error-representation provider enabled.
|
|
172
|
+
*
|
|
173
|
+
* @returns Common adapter bootstrap options for negotiated representation checks.
|
|
174
|
+
*/
|
|
175
|
+
export function createErrorRepresentationOptions() {
|
|
176
|
+
return {
|
|
177
|
+
cors: false,
|
|
178
|
+
errorRepresentation: {
|
|
179
|
+
html: {
|
|
180
|
+
render({
|
|
181
|
+
json
|
|
182
|
+
}) {
|
|
183
|
+
return `<html><body>${String(json.error.status)}:${json.error.code}</body></html>`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
middleware: []
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Creates bootstrap options without an HTML error-representation provider.
|
|
193
|
+
*
|
|
194
|
+
* @returns Common adapter bootstrap options for canonical JSON fallback checks.
|
|
195
|
+
*/
|
|
196
|
+
export function createProviderlessErrorRepresentationOptions() {
|
|
197
|
+
return {
|
|
198
|
+
cors: false,
|
|
199
|
+
errorRepresentation: undefined,
|
|
200
|
+
middleware: []
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { HttpErrorRepresentationOptions, Middleware, RequestObserver } from '@fluojs/http';
|
|
2
|
+
import type { ModuleType } from '@fluojs/runtime';
|
|
3
|
+
type NetworkApp = {
|
|
4
|
+
close(): Promise<void>;
|
|
5
|
+
listen(): Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
type NetworkHarnessOptions<TBootstrapOptions extends object, TApp extends NetworkApp> = {
|
|
8
|
+
readonly bootstrap: (rootModule: ModuleType, options: TBootstrapOptions) => Promise<TApp>;
|
|
9
|
+
readonly createBootstrapOptions: (options: NetworkHttpErrorRepresentationBootstrapOptions) => TBootstrapOptions;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
};
|
|
12
|
+
type WebApp = {
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
dispatch(request: Request): Promise<Response>;
|
|
15
|
+
};
|
|
16
|
+
type WebHarnessOptions<TBootstrapOptions extends object, TApp extends WebApp> = {
|
|
17
|
+
readonly bootstrap: (rootModule: ModuleType, options: TBootstrapOptions) => Promise<TApp>;
|
|
18
|
+
readonly createBootstrapOptions: (options: WebHttpErrorRepresentationBootstrapOptions) => TBootstrapOptions;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
};
|
|
21
|
+
/** Adapter bootstrap fields required by the network error-representation portability scenario. */
|
|
22
|
+
export type NetworkHttpErrorRepresentationBootstrapOptions = {
|
|
23
|
+
readonly cors: false;
|
|
24
|
+
readonly errorRepresentation: HttpErrorRepresentationOptions | undefined;
|
|
25
|
+
readonly middleware: Middleware[];
|
|
26
|
+
readonly observers: RequestObserver[];
|
|
27
|
+
readonly port: 0;
|
|
28
|
+
};
|
|
29
|
+
/** Adapter bootstrap fields required by the Web error-representation portability scenario. */
|
|
30
|
+
export type WebHttpErrorRepresentationBootstrapOptions = {
|
|
31
|
+
readonly cors: false;
|
|
32
|
+
readonly errorRepresentation: HttpErrorRepresentationOptions | undefined;
|
|
33
|
+
readonly middleware: Middleware[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Verifies negotiated HTTP error representations through a listening adapter.
|
|
37
|
+
*
|
|
38
|
+
* @param options Adapter bootstrap and identity callbacks.
|
|
39
|
+
* @returns A promise that resolves after JSON, HTML, HEAD, 406, and commit-guard checks pass.
|
|
40
|
+
*/
|
|
41
|
+
export declare function assertNetworkHttpErrorRepresentationPortability<TBootstrapOptions extends object, TApp extends NetworkApp>(options: NetworkHarnessOptions<TBootstrapOptions, TApp>): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Verifies negotiated HTTP error representations through a fetch-style adapter.
|
|
44
|
+
*
|
|
45
|
+
* @param options Adapter bootstrap and identity callbacks.
|
|
46
|
+
* @returns A promise that resolves after JSON, HTML, HEAD, 406, and commit-guard checks pass.
|
|
47
|
+
*/
|
|
48
|
+
export declare function assertWebHttpErrorRepresentationPortability<TBootstrapOptions extends object, TApp extends WebApp>(options: WebHarnessOptions<TBootstrapOptions, TApp>): Promise<void>;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=error-representation-portability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-representation-portability.d.ts","sourceRoot":"","sources":["../../src/portability/error-representation-portability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAChG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AASlD,KAAK,UAAU,GAAG;IAChB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAEF,KAAK,qBAAqB,CAAC,iBAAiB,SAAS,MAAM,EAAE,IAAI,SAAS,UAAU,IAAI;IACtF,QAAQ,CAAC,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,QAAQ,CAAC,sBAAsB,EAAE,CAC/B,OAAO,EAAE,8CAA8C,KACpD,iBAAiB,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,KAAK,MAAM,GAAG;IACZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC/C,CAAC;AAEF,KAAK,iBAAiB,CAAC,iBAAiB,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,IAAI;IAC9E,QAAQ,CAAC,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,QAAQ,CAAC,sBAAsB,EAAE,CAC/B,OAAO,EAAE,0CAA0C,KAChD,iBAAiB,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,kGAAkG;AAClG,MAAM,MAAM,8CAA8C,GAAG;IAC3D,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,mBAAmB,EAAE,8BAA8B,GAAG,SAAS,CAAC;IACzE,QAAQ,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,eAAe,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;CAClB,CAAC;AAEF,8FAA8F;AAC9F,MAAM,MAAM,0CAA0C,GAAG;IACvD,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,mBAAmB,EAAE,8BAA8B,GAAG,SAAS,CAAC;IACzE,QAAQ,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC;CACnC,CAAC;AAyCF;;;;;GAKG;AACH,wBAAsB,+CAA+C,CACnE,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,UAAU,EACvB,OAAO,EAAE,qBAAqB,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CxE;AAED;;;;;GAKG;AACH,wBAAsB,2CAA2C,CAC/D,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,MAAM,EACnB,OAAO,EAAE,iBAAiB,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCpE"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { assertProviderlessHeadResponse, assertRepresentationResponses, createErrorRepresentationOptions, createProviderlessErrorRepresentationOptions, createRepresentationFixture } from './error-representation-portability-fixture.js';
|
|
2
|
+
|
|
3
|
+
/** Adapter bootstrap fields required by the network error-representation portability scenario. */
|
|
4
|
+
|
|
5
|
+
/** Adapter bootstrap fields required by the Web error-representation portability scenario. */
|
|
6
|
+
|
|
7
|
+
function hasListenTarget(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null && 'getListenTarget' in value && typeof value.getListenTarget === 'function';
|
|
9
|
+
}
|
|
10
|
+
function resolveListeningUrl(app, name) {
|
|
11
|
+
const adapter = Reflect.get(app, 'adapter');
|
|
12
|
+
if (!hasListenTarget(adapter)) {
|
|
13
|
+
throw new Error(`${name} error representation portability check could not resolve its listener URL.`);
|
|
14
|
+
}
|
|
15
|
+
return adapter.getListenTarget().url;
|
|
16
|
+
}
|
|
17
|
+
async function closeAfterAssertion(app, name, assertion) {
|
|
18
|
+
let assertionError;
|
|
19
|
+
try {
|
|
20
|
+
await assertion();
|
|
21
|
+
} catch (error) {
|
|
22
|
+
assertionError = error;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
await app.close();
|
|
26
|
+
} catch (cleanupError) {
|
|
27
|
+
throw assertionError === undefined ? cleanupError : new AggregateError([assertionError, cleanupError], `${name} representation assertion and cleanup both failed.`);
|
|
28
|
+
}
|
|
29
|
+
if (assertionError !== undefined) {
|
|
30
|
+
throw assertionError;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Verifies negotiated HTTP error representations through a listening adapter.
|
|
36
|
+
*
|
|
37
|
+
* @param options Adapter bootstrap and identity callbacks.
|
|
38
|
+
* @returns A promise that resolves after JSON, HTML, HEAD, 406, and commit-guard checks pass.
|
|
39
|
+
*/
|
|
40
|
+
export async function assertNetworkHttpErrorRepresentationPortability(options) {
|
|
41
|
+
const app = await options.bootstrap(createRepresentationFixture(), options.createBootstrapOptions({
|
|
42
|
+
...createErrorRepresentationOptions(),
|
|
43
|
+
observers: [],
|
|
44
|
+
port: 0
|
|
45
|
+
}));
|
|
46
|
+
await closeAfterAssertion(app, options.name, async () => {
|
|
47
|
+
await app.listen();
|
|
48
|
+
const baseUrl = resolveListeningUrl(app, options.name);
|
|
49
|
+
await assertRepresentationResponses(options.name, {
|
|
50
|
+
binary: await fetch(`${baseUrl}/error-representations/binary-head`),
|
|
51
|
+
binaryHead: await fetch(`${baseUrl}/error-representations/binary-head`, {
|
|
52
|
+
method: 'HEAD'
|
|
53
|
+
}),
|
|
54
|
+
committed: await fetch(`${baseUrl}/error-representations/committed`, {
|
|
55
|
+
headers: {
|
|
56
|
+
accept: 'text/html'
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
head: await fetch(`${baseUrl}/error-representations/head`, {
|
|
60
|
+
headers: {
|
|
61
|
+
accept: 'text/html'
|
|
62
|
+
},
|
|
63
|
+
method: 'HEAD'
|
|
64
|
+
}),
|
|
65
|
+
html: await fetch(`${baseUrl}/not-registered`, {
|
|
66
|
+
headers: {
|
|
67
|
+
accept: 'text/html'
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
70
|
+
json: await fetch(`${baseUrl}/error-representations/json`, {
|
|
71
|
+
headers: {
|
|
72
|
+
accept: 'application/json'
|
|
73
|
+
}
|
|
74
|
+
}),
|
|
75
|
+
jsonHead: await fetch(`${baseUrl}/error-representations/head`, {
|
|
76
|
+
headers: {
|
|
77
|
+
accept: 'application/json'
|
|
78
|
+
},
|
|
79
|
+
method: 'HEAD'
|
|
80
|
+
}),
|
|
81
|
+
plain: await fetch(`${baseUrl}/error-representations/plain-head`),
|
|
82
|
+
plainHead: await fetch(`${baseUrl}/error-representations/plain-head`, {
|
|
83
|
+
method: 'HEAD'
|
|
84
|
+
}),
|
|
85
|
+
success: await fetch(`${baseUrl}/error-representations/success-head`),
|
|
86
|
+
successHead: await fetch(`${baseUrl}/error-representations/success-head`, {
|
|
87
|
+
method: 'HEAD'
|
|
88
|
+
}),
|
|
89
|
+
unsupported: await fetch(`${baseUrl}/not-registered`, {
|
|
90
|
+
headers: {
|
|
91
|
+
accept: 'image/avif'
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
94
|
+
unsupportedHead: await fetch(`${baseUrl}/not-registered`, {
|
|
95
|
+
headers: {
|
|
96
|
+
accept: 'image/avif'
|
|
97
|
+
},
|
|
98
|
+
method: 'HEAD'
|
|
99
|
+
})
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
const providerlessApp = await options.bootstrap(createRepresentationFixture(), options.createBootstrapOptions({
|
|
103
|
+
...createProviderlessErrorRepresentationOptions(),
|
|
104
|
+
observers: [],
|
|
105
|
+
port: 0
|
|
106
|
+
}));
|
|
107
|
+
await closeAfterAssertion(providerlessApp, options.name, async () => {
|
|
108
|
+
await providerlessApp.listen();
|
|
109
|
+
const baseUrl = resolveListeningUrl(providerlessApp, options.name);
|
|
110
|
+
await assertProviderlessHeadResponse(options.name, await fetch(`${baseUrl}/not-registered`, {
|
|
111
|
+
method: 'HEAD'
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Verifies negotiated HTTP error representations through a fetch-style adapter.
|
|
118
|
+
*
|
|
119
|
+
* @param options Adapter bootstrap and identity callbacks.
|
|
120
|
+
* @returns A promise that resolves after JSON, HTML, HEAD, 406, and commit-guard checks pass.
|
|
121
|
+
*/
|
|
122
|
+
export async function assertWebHttpErrorRepresentationPortability(options) {
|
|
123
|
+
const app = await options.bootstrap(createRepresentationFixture(), options.createBootstrapOptions(createErrorRepresentationOptions()));
|
|
124
|
+
await closeAfterAssertion(app, options.name, async () => {
|
|
125
|
+
const request = (path, accept, method = 'GET') => new Request(`https://runtime.test${path}`, {
|
|
126
|
+
headers: {
|
|
127
|
+
accept
|
|
128
|
+
},
|
|
129
|
+
method
|
|
130
|
+
});
|
|
131
|
+
await assertRepresentationResponses(options.name, {
|
|
132
|
+
binary: await app.dispatch(request('/error-representations/binary-head', '*/*')),
|
|
133
|
+
binaryHead: await app.dispatch(request('/error-representations/binary-head', '*/*', 'HEAD')),
|
|
134
|
+
committed: await app.dispatch(request('/error-representations/committed', 'text/html')),
|
|
135
|
+
head: await app.dispatch(request('/error-representations/head', 'text/html', 'HEAD')),
|
|
136
|
+
html: await app.dispatch(request('/not-registered', 'text/html')),
|
|
137
|
+
json: await app.dispatch(request('/error-representations/json', 'application/json')),
|
|
138
|
+
jsonHead: await app.dispatch(request('/error-representations/head', 'application/json', 'HEAD')),
|
|
139
|
+
plain: await app.dispatch(request('/error-representations/plain-head', '*/*')),
|
|
140
|
+
plainHead: await app.dispatch(request('/error-representations/plain-head', '*/*', 'HEAD')),
|
|
141
|
+
success: await app.dispatch(request('/error-representations/success-head', '*/*')),
|
|
142
|
+
successHead: await app.dispatch(request('/error-representations/success-head', '*/*', 'HEAD')),
|
|
143
|
+
unsupported: await app.dispatch(request('/not-registered', 'image/avif')),
|
|
144
|
+
unsupportedHead: await app.dispatch(request('/not-registered', 'image/avif', 'HEAD'))
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
const providerlessApp = await options.bootstrap(createRepresentationFixture(), options.createBootstrapOptions(createProviderlessErrorRepresentationOptions()));
|
|
148
|
+
await closeAfterAssertion(providerlessApp, options.name, async () => {
|
|
149
|
+
await assertProviderlessHeadResponse(options.name, await providerlessApp.dispatch(new Request('https://runtime.test/not-registered', {
|
|
150
|
+
method: 'HEAD'
|
|
151
|
+
})));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
rawBody?: Uint8Array;
|
|
6
|
-
}
|
|
7
|
-
}
|
|
1
|
+
import { type ContentNegotiationOptions, type ConditionalRequestOptions } from '@fluojs/http';
|
|
2
|
+
import { type ModuleType } from '@fluojs/runtime';
|
|
3
|
+
import { type NetworkHttpErrorRepresentationBootstrapOptions } from './error-representation-portability.js';
|
|
4
|
+
export type { NetworkHttpErrorRepresentationBootstrapOptions } from './error-representation-portability.js';
|
|
8
5
|
type AppLike = {
|
|
9
6
|
close(): Promise<void>;
|
|
10
7
|
listen(): Promise<void>;
|
|
@@ -25,6 +22,15 @@ export interface HttpAdapterPortabilityHarnessOptions<TBootstrapOptions extends
|
|
|
25
22
|
* @returns A promise that resolves to the application instance.
|
|
26
23
|
*/
|
|
27
24
|
bootstrap: (rootModule: ModuleType, options: TBootstrapOptions) => Promise<TApp>;
|
|
25
|
+
/** Adapts the shared error-representation fixture fields to this adapter's bootstrap options. */
|
|
26
|
+
createErrorRepresentationBootstrapOptions?: (options: NetworkHttpErrorRepresentationBootstrapOptions) => TBootstrapOptions;
|
|
27
|
+
/** Adapts shared conditional-request policy options to one listener bootstrap API. */
|
|
28
|
+
createConditionalRequestBootstrapOptions?: (options: {
|
|
29
|
+
readonly conditionalRequest: ConditionalRequestOptions;
|
|
30
|
+
readonly contentNegotiation: ContentNegotiationOptions;
|
|
31
|
+
readonly cors: false;
|
|
32
|
+
readonly port: 0;
|
|
33
|
+
}) => TBootstrapOptions;
|
|
28
34
|
/**
|
|
29
35
|
* Optional adapter-specific content type used by the exact-byte raw-body portability assertion.
|
|
30
36
|
*/
|
|
@@ -62,11 +68,23 @@ export declare class HttpAdapterPortabilityHarness<TBootstrapOptions extends obj
|
|
|
62
68
|
* @param options - Configuration options for the harness.
|
|
63
69
|
*/
|
|
64
70
|
constructor(options: HttpAdapterPortabilityHarnessOptions<TBootstrapOptions, TRunOptions, TApp>);
|
|
71
|
+
/** Verifies JSON, HTML, HEAD, 406, and committed error-response portability. */
|
|
72
|
+
assertSupportsHttpErrorRepresentations(): Promise<void>;
|
|
73
|
+
/** Verifies client-disconnect abort propagation without an HTML or JSON fallback commit. */
|
|
74
|
+
assertDoesNotCommitAbortedHttpErrorRepresentations(): Promise<void>;
|
|
75
|
+
/** Verifies ordered, non-folded portable response cookies over a real listener. */
|
|
76
|
+
assertSupportsPortableResponseCookies(): Promise<void>;
|
|
77
|
+
/** Verifies 304/412 metadata and body suppression through a real network listener. */
|
|
78
|
+
assertSupportsConditionalRequests(): Promise<void>;
|
|
79
|
+
/** Verifies single-byte-range metadata and payload slicing through a real network listener. */
|
|
80
|
+
assertSupportsSingleByteRanges(): Promise<void>;
|
|
65
81
|
/**
|
|
66
82
|
* Asserts that the adapter preserves malformed cookie values without crashing
|
|
67
83
|
* or incorrectly normalizing them.
|
|
68
84
|
*/
|
|
69
85
|
assertPreservesMalformedCookieValues(): Promise<void>;
|
|
86
|
+
/** Verifies `QUERY` and extension-method execution through the adapter's real listener fallback. */
|
|
87
|
+
assertSupportsCustomHttpRouteMethods(): Promise<void>;
|
|
70
88
|
assertPreservesRawBodyForJsonAndText(): Promise<void>;
|
|
71
89
|
assertPreservesExactRawBodyBytesForByteSensitivePayloads(): Promise<void>;
|
|
72
90
|
assertExcludesRawBodyForMultipart(): Promise<void>;
|
|
@@ -94,5 +112,4 @@ export declare class HttpAdapterPortabilityHarness<TBootstrapOptions extends obj
|
|
|
94
112
|
* @returns A new portability harness instance.
|
|
95
113
|
*/
|
|
96
114
|
export declare function createHttpAdapterPortabilityHarness<TBootstrapOptions extends object, TRunOptions extends object, TApp extends AppLike = AppLike>(options: HttpAdapterPortabilityHarnessOptions<TBootstrapOptions, TRunOptions, TApp>): HttpAdapterPortabilityHarness<TBootstrapOptions, TRunOptions, TApp>;
|
|
97
|
-
export {};
|
|
98
115
|
//# sourceMappingURL=http-adapter-portability.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/http-adapter-portability.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/http-adapter-portability.ts"],"names":[],"mappings":"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"}
|