@fluojs/testing 1.0.0-beta.2 → 1.0.0-beta.3

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/dist/mock.js CHANGED
@@ -1,6 +1,15 @@
1
1
  import { vi } from 'vitest';
2
+
3
+ /**
4
+ * Defines the mocked methods type.
5
+ */
6
+
2
7
  /**
3
8
  * Creates a proxy mock object with optional strict missing-property checks.
9
+ *
10
+ * @param partial The partial.
11
+ * @param options The options.
12
+ * @returns The create mock result.
4
13
  */
5
14
  export function createMock(partial = {}, options = {}) {
6
15
  const autoMocks = new Map();
@@ -24,6 +33,9 @@ export function createMock(partial = {}, options = {}) {
24
33
 
25
34
  /**
26
35
  * Casts a function to a strongly typed Vitest mock.
36
+ *
37
+ * @param fn The fn.
38
+ * @returns The as mock result.
27
39
  */
28
40
  export function asMock(fn) {
29
41
  return vi.mocked(fn);
@@ -31,6 +43,9 @@ export function asMock(fn) {
31
43
 
32
44
  /**
33
45
  * Creates a deep mock by replacing prototype methods with `vi.fn()` spies.
46
+ *
47
+ * @param type The type.
48
+ * @returns The create deep mock result.
34
49
  */
35
50
  export function createDeepMock(type) {
36
51
  const spies = {};
@@ -51,6 +66,10 @@ export function createDeepMock(type) {
51
66
 
52
67
  /**
53
68
  * Creates a `useValue` provider for overriding a token in tests.
69
+ *
70
+ * @param token The token.
71
+ * @param partial The partial.
72
+ * @returns The mock token result.
54
73
  */
55
74
  export function mockToken(token, partial = {}) {
56
75
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,SAAS,EAId,KAAK,QAAQ,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAqC,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMrF,OAAO,KAAK,EAA2B,oBAAoB,EAAE,oBAAoB,EAAoB,MAAM,YAAY,CAAC;AAExH;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,QAAQ,EAAE,CAQzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,EAAE,CAQ5E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,EAAE,CAQzE;AAubD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,CAEvF;AAED;;GAEG;AACH,eAAO,MAAM,IAAI;;CAEhB,CAAC"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EAId,KAAK,QAAQ,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAqC,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMrF,OAAO,KAAK,EAA2B,oBAAoB,EAAE,oBAAoB,EAAoB,MAAM,YAAY,CAAC;AAExH;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,QAAQ,EAAE,CAQzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,EAAE,CAQ5E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,EAAE,CAQzE;AAkkBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,CAEvF;AAED;;GAEG;AACH,eAAO,MAAM,IAAI;;CAEhB,CAAC"}
package/dist/module.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getModuleMetadata } from '@fluojs/core/internal';
1
+ import { getModuleMetadata } from '@fluojs/core';
2
2
  import { isForwardRef, isOptionalToken } from '@fluojs/di';
3
3
  import { bootstrapModule, defineModule } from '@fluojs/runtime';
4
4
  import { createDispatcher, createHandlerMapping } from '@fluojs/http';
@@ -97,7 +97,7 @@ function isContainerIntrospection(value) {
97
97
  const candidate = value;
98
98
  const parentValid = candidate.parent === undefined || isContainerIntrospection(candidate.parent);
99
99
  const requestScopeValid = candidate.requestScopeEnabled === undefined || typeof candidate.requestScopeEnabled === 'boolean';
100
- return candidate.registrations instanceof Map && candidate.multiRegistrations instanceof Map && parentValid && requestScopeValid;
100
+ return candidate.registrations instanceof Map && candidate.multiRegistrations instanceof Map && candidate.singletonCache instanceof Map && parentValid && requestScopeValid;
101
101
  }
102
102
  function toContainerIntrospection(container) {
103
103
  if (!isContainerIntrospection(container)) {
@@ -108,6 +108,9 @@ function toContainerIntrospection(container) {
108
108
  function isPromiseLike(value) {
109
109
  return (typeof value === 'object' || typeof value === 'function') && value !== null && typeof value.then === 'function';
110
110
  }
111
+ function rootContainerIntrospection(target) {
112
+ return target.parent ? rootContainerIntrospection(target.parent) : target;
113
+ }
111
114
  function collectMultiProviders(target, token) {
112
115
  const fromParent = target.parent ? collectMultiProviders(target.parent, token) : [];
113
116
  const local = target.multiRegistrations.get(token) ?? [];
@@ -123,6 +126,70 @@ function lookupProvider(target, token) {
123
126
  function hasToken(state, token) {
124
127
  return lookupProvider(state.introspection, token) !== undefined || collectMultiProviders(state.introspection, token).length > 0;
125
128
  }
129
+ function dependencyToken(entry) {
130
+ if (isOptionalToken(entry)) {
131
+ return entry.token;
132
+ }
133
+ if (isForwardRef(entry)) {
134
+ return entry.forwardRef();
135
+ }
136
+ return entry;
137
+ }
138
+ function trackFactoryResolutionKind(provider, factoryResolutionKinds) {
139
+ if (provider.type !== 'factory' || !provider.useFactory) {
140
+ return;
141
+ }
142
+ const originalFactory = provider.useFactory;
143
+ provider.useFactory = (...deps) => {
144
+ const value = originalFactory(...deps);
145
+ factoryResolutionKinds.set(provider, isPromiseLike(value) ? 'async' : 'sync');
146
+ return value;
147
+ };
148
+ }
149
+ function installFactoryResolutionTracking(target, factoryResolutionKinds) {
150
+ if (target.parent) {
151
+ installFactoryResolutionTracking(target.parent, factoryResolutionKinds);
152
+ }
153
+ for (const provider of target.registrations.values()) {
154
+ trackFactoryResolutionKind(provider, factoryResolutionKinds);
155
+ }
156
+ for (const providers of target.multiRegistrations.values()) {
157
+ for (const provider of providers) {
158
+ trackFactoryResolutionKind(provider, factoryResolutionKinds);
159
+ }
160
+ }
161
+ }
162
+ function providerGraphIsSyncResolvable(state, token, visited = new Set()) {
163
+ if (visited.has(token)) {
164
+ return true;
165
+ }
166
+ visited.add(token);
167
+ try {
168
+ const provider = lookupProvider(state.introspection, token);
169
+ const multiProviders = collectMultiProviders(state.introspection, token);
170
+ const providers = provider ? [provider, ...multiProviders] : multiProviders;
171
+ return providers.every(candidate => {
172
+ if (candidate.type === 'factory') {
173
+ return state.factoryResolutionKinds.get(candidate) === 'sync';
174
+ }
175
+ if (candidate.type === 'existing') {
176
+ return candidate.useExisting !== undefined && providerGraphIsSyncResolvable(state, candidate.useExisting, visited);
177
+ }
178
+ return candidate.inject.every(entry => {
179
+ if (isOptionalToken(entry) && !hasToken(state, entry.token)) {
180
+ return true;
181
+ }
182
+ return providerGraphIsSyncResolvable(state, dependencyToken(entry), visited);
183
+ });
184
+ });
185
+ } finally {
186
+ visited.delete(token);
187
+ }
188
+ }
189
+ function canPromoteCachedSingleton(state, token) {
190
+ const provider = lookupProvider(state.introspection, token);
191
+ return provider !== undefined && provider.scope !== 'request' && providerGraphIsSyncResolvable(state, token);
192
+ }
126
193
  function resolveSyncDependency(entry, state) {
127
194
  if (isOptionalToken(entry)) {
128
195
  if (!hasToken(state, entry.token)) {
@@ -181,11 +248,15 @@ function resolveSyncProvider(provider, state) {
181
248
  if (provider.scope === 'transient') {
182
249
  return instantiateSyncProvider(provider, state);
183
250
  }
251
+ if (state.syncSingletonValues.has(provider.provide)) {
252
+ return state.syncSingletonValues.get(provider.provide);
253
+ }
184
254
  if (state.singletonCache.has(provider.provide)) {
185
- return state.singletonCache.get(provider.provide);
255
+ throw new Error(`Token ${String(provider.provide)} was already resolved asynchronously. Use resolve() instead of get() for this provider.`);
186
256
  }
187
257
  const instance = instantiateSyncProvider(provider, state);
188
- state.singletonCache.set(provider.provide, instance);
258
+ state.syncSingletonValues.set(provider.provide, instance);
259
+ state.singletonCache.set(provider.provide, Promise.resolve(instance));
189
260
  return instance;
190
261
  }
191
262
  function resolveSyncToken(token, state) {
@@ -208,12 +279,27 @@ function resolveSyncToken(token, state) {
208
279
  }
209
280
  }
210
281
  function createSyncResolver(container) {
282
+ const introspection = toContainerIntrospection(container);
283
+ const factoryResolutionKinds = new WeakMap();
284
+ installFactoryResolutionTracking(introspection, factoryResolutionKinds);
211
285
  const state = {
212
- introspection: toContainerIntrospection(container),
286
+ factoryResolutionKinds,
287
+ introspection,
213
288
  resolutionChain: new Set(),
214
- singletonCache: new Map()
289
+ singletonCache: rootContainerIntrospection(introspection).singletonCache,
290
+ syncSingletonValues: new Map()
291
+ };
292
+ return {
293
+ get: token => resolveSyncToken(token, state),
294
+ syncFromContainer: async () => {
295
+ for (const [token, promise] of state.singletonCache) {
296
+ if (!canPromoteCachedSingleton(state, token)) {
297
+ continue;
298
+ }
299
+ state.syncSingletonValues.set(token, await promise);
300
+ }
301
+ }
215
302
  };
216
- return token => resolveSyncToken(token, state);
217
303
  }
218
304
  class DefaultOverrideProviderBuilder {
219
305
  constructor(builder, token) {
@@ -327,12 +413,16 @@ class DefaultTestingModuleBuilder {
327
413
  }
328
414
  createTestingModuleRef(bootstrapped) {
329
415
  const dispatcher = createTestingDispatcher(bootstrapped);
330
- const getSync = createSyncResolver(bootstrapped.container);
416
+ const syncResolver = createSyncResolver(bootstrapped.container);
331
417
  return {
332
418
  ...bootstrapped,
333
419
  has: token => bootstrapped.container.has(token),
334
- get: token => getSync(token),
335
- resolve: token => bootstrapped.container.resolve(token),
420
+ get: token => syncResolver.get(token),
421
+ resolve: async token => {
422
+ const value = await bootstrapped.container.resolve(token);
423
+ await syncResolver.syncFromContainer();
424
+ return value;
425
+ },
336
426
  resolveAll: async tokens => {
337
427
  const results = [];
338
428
  const errors = [];
@@ -353,9 +443,14 @@ class DefaultTestingModuleBuilder {
353
443
  }) => ` - ${String(token)}: ${error instanceof Error ? error.message : String(error)}`).join('\n');
354
444
  throw new Error(`Failed to resolve ${errors.length} of ${tokens.length} tokens:\n${summary}`);
355
445
  }
446
+ await syncResolver.syncFromContainer();
356
447
  return results;
357
448
  },
358
- dispatch: request => makeRequest(dispatcher, request)
449
+ dispatch: async request => {
450
+ const response = await makeRequest(dispatcher, request);
451
+ await syncResolver.syncFromContainer();
452
+ return response;
453
+ }
359
454
  };
360
455
  }
361
456
  _applyModuleReplacements(module) {
@@ -25,6 +25,14 @@ export interface HttpAdapterPortabilityHarnessOptions<TBootstrapOptions extends
25
25
  * @returns A promise that resolves to the application instance.
26
26
  */
27
27
  bootstrap: (rootModule: ModuleType, options: TBootstrapOptions) => Promise<TApp>;
28
+ /**
29
+ * Optional adapter-specific content type used by the exact-byte raw-body portability assertion.
30
+ */
31
+ exactRawBodyByteContentType?: string;
32
+ /**
33
+ * Optional adapter-specific preparation used before the exact-byte raw-body portability assertion.
34
+ */
35
+ prepareExactRawBodyByteTest?: (app: TApp) => void | Promise<void>;
28
36
  /**
29
37
  * The name of the adapter being tested.
30
38
  */
@@ -60,6 +68,7 @@ export declare class HttpAdapterPortabilityHarness<TBootstrapOptions extends obj
60
68
  */
61
69
  assertPreservesMalformedCookieValues(): Promise<void>;
62
70
  assertPreservesRawBodyForJsonAndText(): Promise<void>;
71
+ assertPreservesExactRawBodyBytesForByteSensitivePayloads(): Promise<void>;
63
72
  assertExcludesRawBodyForMultipart(): Promise<void>;
64
73
  assertDefaultsMultipartTotalLimitToMaxBodySize(): Promise<void>;
65
74
  assertSupportsSseStreaming(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"http-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/http-adapter-portability.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED,KAAK,OAAO,GAAG;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,oCAAoC,CACnD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAE9B;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjF;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAuDD;;;;;;;GAOG;AACH,qBAAa,6BAA6B,CACxC,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAOlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC;;;;OAIG;gBAC0B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC;IAEhH;;;OAGG;IACG,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA+CrD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA8DrD,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IA8ClD,8CAA8C,IAAI,OAAO,CAAC,IAAI,CAAC;IAwD/D,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDjD;;;OAGG;IACG,mCAAmC,IAAI,OAAO,CAAC,IAAI,CAAC;IAqDpD,wCAAwC,IAAI,OAAO,CAAC,IAAI,CAAC;IAuDzD,4BAA4B,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAuDjF,8CAA8C,IAAI,OAAO,CAAC,IAAI,CAAC;CA2CtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mCAAmC,CACjD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO,EAE9B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,GAClF,6BAA6B,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,CAErE"}
1
+ {"version":3,"file":"http-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/http-adapter-portability.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED,KAAK,OAAO,GAAG;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,oCAAoC,CACnD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAE9B;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjF;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,2BAA2B,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAuDD;;;;;;;GAOG;AACH,qBAAa,6BAA6B,CACxC,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAOlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC;;;;OAIG;gBAC0B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC;IAEhH;;;OAGG;IACG,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA+CrD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA8DrD,wDAAwD,IAAI,OAAO,CAAC,IAAI,CAAC;IA6CzE,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IA8ClD,8CAA8C,IAAI,OAAO,CAAC,IAAI,CAAC;IAwD/D,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDjD;;;OAGG;IACG,mCAAmC,IAAI,OAAO,CAAC,IAAI,CAAC;IAqDpD,wCAAwC,IAAI,OAAO,CAAC,IAAI,CAAC;IAuDzD,4BAA4B,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAuDjF,8CAA8C,IAAI,OAAO,CAAC,IAAI,CAAC;CA8CtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mCAAmC,CACjD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO,EAE9B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,GAClF,6BAA6B,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,CAErE"}
@@ -210,18 +210,75 @@ export class HttpAdapterPortabilityHarness {
210
210
  await closeSilently(app);
211
211
  }
212
212
  }
213
- async assertExcludesRawBodyForMultipart() {
213
+ async assertPreservesExactRawBodyBytesForByteSensitivePayloads() {
214
214
  let _initProto3, _initClass3;
215
+ let _WebhookController2;
216
+ class WebhookController {
217
+ static {
218
+ ({
219
+ e: [_initProto3],
220
+ c: [_WebhookController2, _initClass3]
221
+ } = _applyDecs(this, [Controller('/webhooks')], [[Post('/bytes'), 2, "handleBytes"]]));
222
+ }
223
+ constructor() {
224
+ _initProto3(this);
225
+ }
226
+ handleBytes(_input, context) {
227
+ return {
228
+ rawBytes: Array.from(context.request.rawBody ?? new Uint8Array())
229
+ };
230
+ }
231
+ static {
232
+ _initClass3();
233
+ }
234
+ }
235
+ class AppModule {}
236
+ defineModule(AppModule, {
237
+ controllers: [_WebhookController2]
238
+ });
239
+ const port = await findAvailablePort();
240
+ const app = await this.options.bootstrap(AppModule, {
241
+ cors: false,
242
+ port,
243
+ rawBody: true
244
+ });
245
+ await this.options.prepareExactRawBodyByteTest?.(app);
246
+ await app.listen();
247
+ try {
248
+ const payload = Uint8Array.from([0xe9, 0x41]);
249
+ const contentType = this.options.exactRawBodyByteContentType ?? 'text/plain; charset=latin1';
250
+ const response = await fetch(`http://127.0.0.1:${String(port)}/webhooks/bytes`, {
251
+ body: payload,
252
+ headers: {
253
+ 'content-type': contentType
254
+ },
255
+ method: 'POST'
256
+ });
257
+ if (response.status !== 201) {
258
+ throw new Error(`${this.options.name} adapter changed byte-sensitive rawBody response status semantics.`);
259
+ }
260
+ const body = await response.json();
261
+ if (JSON.stringify(body) !== JSON.stringify({
262
+ rawBytes: Array.from(payload)
263
+ })) {
264
+ throw new Error(`${this.options.name} adapter changed exact-byte rawBody semantics.`);
265
+ }
266
+ } finally {
267
+ await closeSilently(app);
268
+ }
269
+ }
270
+ async assertExcludesRawBodyForMultipart() {
271
+ let _initProto4, _initClass4;
215
272
  let _UploadController;
216
273
  class UploadController {
217
274
  static {
218
275
  ({
219
- e: [_initProto3],
220
- c: [_UploadController, _initClass3]
276
+ e: [_initProto4],
277
+ c: [_UploadController, _initClass4]
221
278
  } = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
222
279
  }
223
280
  constructor() {
224
- _initProto3(this);
281
+ _initProto4(this);
225
282
  }
226
283
  upload(_input, context) {
227
284
  return {
@@ -231,7 +288,7 @@ export class HttpAdapterPortabilityHarness {
231
288
  };
232
289
  }
233
290
  static {
234
- _initClass3();
291
+ _initClass4();
235
292
  }
236
293
  }
237
294
  class AppModule {}
@@ -273,17 +330,17 @@ export class HttpAdapterPortabilityHarness {
273
330
  }
274
331
  }
275
332
  async assertDefaultsMultipartTotalLimitToMaxBodySize() {
276
- let _initProto4, _initClass4;
333
+ let _initProto5, _initClass5;
277
334
  let _UploadController2;
278
335
  class UploadController {
279
336
  static {
280
337
  ({
281
- e: [_initProto4],
282
- c: [_UploadController2, _initClass4]
338
+ e: [_initProto5],
339
+ c: [_UploadController2, _initClass5]
283
340
  } = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
284
341
  }
285
342
  constructor() {
286
- _initProto4(this);
343
+ _initProto5(this);
287
344
  }
288
345
  upload(_input, context) {
289
346
  return {
@@ -292,7 +349,7 @@ export class HttpAdapterPortabilityHarness {
292
349
  };
293
350
  }
294
351
  static {
295
- _initClass4();
352
+ _initClass5();
296
353
  }
297
354
  }
298
355
  class AppModule {}
@@ -331,17 +388,17 @@ export class HttpAdapterPortabilityHarness {
331
388
  }
332
389
  }
333
390
  async assertSupportsSseStreaming() {
334
- let _initProto5, _initClass5;
391
+ let _initProto6, _initClass6;
335
392
  let _EventsController;
336
393
  class EventsController {
337
394
  static {
338
395
  ({
339
- e: [_initProto5],
340
- c: [_EventsController, _initClass5]
396
+ e: [_initProto6],
397
+ c: [_EventsController, _initClass6]
341
398
  } = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
342
399
  }
343
400
  constructor() {
344
- _initProto5(this);
401
+ _initProto6(this);
345
402
  }
346
403
  stream(_input, context) {
347
404
  const stream = new SseResponse(context);
@@ -352,13 +409,13 @@ export class HttpAdapterPortabilityHarness {
352
409
  event: 'ready',
353
410
  id: 'evt-1'
354
411
  });
355
- setTimeout(() => {
412
+ queueMicrotask(() => {
356
413
  stream.close();
357
- }, 10);
414
+ });
358
415
  return stream;
359
416
  }
360
417
  static {
361
- _initClass5();
418
+ _initClass6();
362
419
  }
363
420
  }
364
421
  class AppModule {}
@@ -398,7 +455,7 @@ export class HttpAdapterPortabilityHarness {
398
455
  * closes before a `drain` event is emitted.
399
456
  */
400
457
  async assertSettlesStreamDrainWaitOnClose() {
401
- let _initProto6, _initClass6;
458
+ let _initProto7, _initClass7;
402
459
  const adapterName = this.options.name;
403
460
  let resolveDrainWait;
404
461
  const drainWaitSettled = new Promise(resolve => {
@@ -408,12 +465,12 @@ export class HttpAdapterPortabilityHarness {
408
465
  class EventsController {
409
466
  static {
410
467
  ({
411
- e: [_initProto6],
412
- c: [_EventsController2, _initClass6]
468
+ e: [_initProto7],
469
+ c: [_EventsController2, _initClass7]
413
470
  } = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
414
471
  }
415
472
  constructor() {
416
- _initProto6(this);
473
+ _initProto7(this);
417
474
  }
418
475
  async stream(_input, context) {
419
476
  const stream = new SseResponse(context);
@@ -428,7 +485,7 @@ export class HttpAdapterPortabilityHarness {
428
485
  return stream;
429
486
  }
430
487
  static {
431
- _initClass6();
488
+ _initClass7();
432
489
  }
433
490
  }
434
491
  class AppModule {}
@@ -457,7 +514,7 @@ export class HttpAdapterPortabilityHarness {
457
514
  }
458
515
  }
459
516
  async assertReportsConfiguredHostInStartupLogs() {
460
- let _initProto7, _initClass7;
517
+ let _initProto8, _initClass8;
461
518
  const loggerEvents = [];
462
519
  const logger = {
463
520
  debug() {},
@@ -473,12 +530,12 @@ export class HttpAdapterPortabilityHarness {
473
530
  class HealthController {
474
531
  static {
475
532
  ({
476
- e: [_initProto7],
477
- c: [_HealthController, _initClass7]
533
+ e: [_initProto8],
534
+ c: [_HealthController, _initClass8]
478
535
  } = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
479
536
  }
480
537
  constructor() {
481
- _initProto7(this);
538
+ _initProto8(this);
482
539
  }
483
540
  getHealth() {
484
541
  return {
@@ -486,7 +543,7 @@ export class HttpAdapterPortabilityHarness {
486
543
  };
487
544
  }
488
545
  static {
489
- _initClass7();
546
+ _initClass8();
490
547
  }
491
548
  }
492
549
  class AppModule {}
@@ -520,7 +577,7 @@ export class HttpAdapterPortabilityHarness {
520
577
  }
521
578
  }
522
579
  async assertReportsHttpsStartupUrl(https) {
523
- let _initProto8, _initClass8;
580
+ let _initProto9, _initClass9;
524
581
  const loggerEvents = [];
525
582
  const logger = {
526
583
  debug() {},
@@ -536,12 +593,12 @@ export class HttpAdapterPortabilityHarness {
536
593
  class HealthController {
537
594
  static {
538
595
  ({
539
- e: [_initProto8],
540
- c: [_HealthController2, _initClass8]
596
+ e: [_initProto9],
597
+ c: [_HealthController2, _initClass9]
541
598
  } = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
542
599
  }
543
600
  constructor() {
544
- _initProto8(this);
601
+ _initProto9(this);
545
602
  }
546
603
  getHealth() {
547
604
  return {
@@ -549,7 +606,7 @@ export class HttpAdapterPortabilityHarness {
549
606
  };
550
607
  }
551
608
  static {
552
- _initClass8();
609
+ _initClass9();
553
610
  }
554
611
  }
555
612
  class AppModule {}
@@ -583,7 +640,7 @@ export class HttpAdapterPortabilityHarness {
583
640
  }
584
641
  }
585
642
  async assertRemovesShutdownSignalListenersAfterClose() {
586
- let _initProto9, _initClass9;
643
+ let _initProto0, _initClass0;
587
644
  const logger = {
588
645
  debug() {},
589
646
  error() {},
@@ -594,12 +651,12 @@ export class HttpAdapterPortabilityHarness {
594
651
  class HealthController {
595
652
  static {
596
653
  ({
597
- e: [_initProto9],
598
- c: [_HealthController3, _initClass9]
654
+ e: [_initProto0],
655
+ c: [_HealthController3, _initClass0]
599
656
  } = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
600
657
  }
601
658
  constructor() {
602
- _initProto9(this);
659
+ _initProto0(this);
603
660
  }
604
661
  getHealth() {
605
662
  return {
@@ -607,7 +664,7 @@ export class HttpAdapterPortabilityHarness {
607
664
  };
608
665
  }
609
666
  static {
610
- _initClass9();
667
+ _initClass0();
611
668
  }
612
669
  }
613
670
  class AppModule {}
@@ -615,7 +672,7 @@ export class HttpAdapterPortabilityHarness {
615
672
  controllers: [_HealthController3]
616
673
  });
617
674
  const signal = 'SIGTERM';
618
- const listenersBefore = process.listeners(signal).length;
675
+ const listenersBefore = new Set(process.listeners(signal));
619
676
  const port = await findAvailablePort();
620
677
  const app = await this.options.run(AppModule, {
621
678
  cors: false,
@@ -623,14 +680,17 @@ export class HttpAdapterPortabilityHarness {
623
680
  port,
624
681
  shutdownSignals: [signal]
625
682
  });
683
+ const registeredListeners = process.listeners(signal).filter(listener => !listenersBefore.has(listener));
626
684
  try {
627
- if (process.listeners(signal).length !== listenersBefore + 1) {
685
+ if (registeredListeners.length === 0) {
628
686
  throw new Error(`${this.options.name} adapter did not register the expected shutdown listener.`);
629
687
  }
630
688
  } finally {
631
689
  await closeSilently(app);
632
690
  }
633
- if (process.listeners(signal).length !== listenersBefore) {
691
+ const remainingListeners = process.listeners(signal);
692
+ const leakedListeners = registeredListeners.filter(listener => remainingListeners.includes(listener));
693
+ if (leakedListeners.length > 0) {
634
694
  throw new Error(`${this.options.name} adapter leaked shutdown signal listeners after close().`);
635
695
  }
636
696
  }
@@ -648,14 +708,14 @@ export class HttpAdapterPortabilityHarness {
648
708
  export function createHttpAdapterPortabilityHarness(options) {
649
709
  return new HttpAdapterPortabilityHarness(options);
650
710
  }
651
- function withTimeout(promise, timeoutMs, message) {
711
+ async function withTimeout(promise, timeoutMs, message) {
652
712
  let timeout;
653
713
  const timeoutPromise = new Promise((_resolve, reject) => {
654
714
  timeout = setTimeout(() => {
655
715
  reject(new Error(message));
656
716
  }, timeoutMs);
657
717
  });
658
- return Promise.race([promise, timeoutPromise]).finally(() => {
718
+ return await Promise.race([promise, timeoutPromise]).finally(() => {
659
719
  if (timeout !== undefined) {
660
720
  clearTimeout(timeout);
661
721
  }
@@ -9,18 +9,31 @@ type WebRuntimePortabilityAppLike = {
9
9
  close(): Promise<void>;
10
10
  dispatch(request: Request): Promise<Response>;
11
11
  };
12
+ /**
13
+ * Describes the web runtime http adapter portability harness options contract.
14
+ */
12
15
  export interface WebRuntimeHttpAdapterPortabilityHarnessOptions<TBootstrapOptions extends object, TApp extends WebRuntimePortabilityAppLike = WebRuntimePortabilityAppLike> {
13
16
  bootstrap: (rootModule: ModuleType, options: TBootstrapOptions) => Promise<TApp>;
14
17
  name: string;
15
18
  }
19
+ /**
20
+ * Represents the web runtime http adapter portability harness.
21
+ */
16
22
  export declare class WebRuntimeHttpAdapterPortabilityHarness<TBootstrapOptions extends object, TApp extends WebRuntimePortabilityAppLike = WebRuntimePortabilityAppLike> {
17
23
  private readonly options;
18
24
  constructor(options: WebRuntimeHttpAdapterPortabilityHarnessOptions<TBootstrapOptions, TApp>);
25
+ assertPreservesQueryArraysAndDecoding(): Promise<void>;
19
26
  assertPreservesMalformedCookieValues(): Promise<void>;
20
27
  assertPreservesRawBodyForJsonAndText(): Promise<void>;
21
28
  assertExcludesRawBodyForMultipart(): Promise<void>;
22
29
  assertSupportsSseStreaming(): Promise<void>;
23
30
  }
31
+ /**
32
+ * Create web runtime http adapter portability harness.
33
+ *
34
+ * @param options The options.
35
+ * @returns The create web runtime http adapter portability harness result.
36
+ */
24
37
  export declare function createWebRuntimeHttpAdapterPortabilityHarness<TBootstrapOptions extends object, TApp extends WebRuntimePortabilityAppLike = WebRuntimePortabilityAppLike>(options: WebRuntimeHttpAdapterPortabilityHarnessOptions<TBootstrapOptions, TApp>): WebRuntimeHttpAdapterPortabilityHarness<TBootstrapOptions, TApp>;
25
38
  export {};
26
39
  //# sourceMappingURL=web-runtime-adapter-portability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"web-runtime-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/web-runtime-adapter-portability.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED,KAAK,4BAA4B,GAAG;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,WAAW,8CAA8C,CAC7D,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B;IAExE,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAYD,qBAAa,uCAAuC,CAClD,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B;IAE5D,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,8CAA8C,CAAC,iBAAiB,EAAE,IAAI,CAAC;IAEvG,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA4CrD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA2DrD,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IA2ClD,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;CA4ClD;AAED,wBAAgB,6CAA6C,CAC3D,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B,EAExE,OAAO,EAAE,8CAA8C,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAC/E,uCAAuC,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAElE"}
1
+ {"version":3,"file":"web-runtime-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/web-runtime-adapter-portability.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED,KAAK,4BAA4B,GAAG;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC/C,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,8CAA8C,CAC7D,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B;IAExE,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAYD;;GAEG;AACH,qBAAa,uCAAuC,CAClD,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B;IAE5D,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,8CAA8C,CAAC,iBAAiB,EAAE,IAAI,CAAC;IAEvG,qCAAqC,IAAI,OAAO,CAAC,IAAI,CAAC;IAuCtD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA4CrD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA2DrD,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IA2ClD,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;CA4ClD;AAED;;;;;GAKG;AACH,wBAAgB,6CAA6C,CAC3D,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B,EAExE,OAAO,EAAE,8CAA8C,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAC/E,uCAAuC,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAElE"}