@velajs/testing 1.22.1 → 1.23.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/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0765aaa: Use shared application finalization in testing, recalculate request scope after
8
+ provider overrides, and dispose resources on failed startup and shutdown. Await
9
+ concurrent disposal and managed test scopes. Add onClose fixture cleanup and close
10
+ Node WebSocket test servers with their owning testing module.
11
+ - 0ef063e: Add createTestHttpClient for live and injected Web-API request transports.
12
+ Validate test response bodies using shared async-aware Standard Schema and
13
+ legacy parsing, preserving inferred transformed outputs and existing parser calls.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [c6a43a6]
18
+ - Updated dependencies [bbe62d4]
19
+ - Updated dependencies [a6ef933]
20
+ - Updated dependencies [dae3654]
21
+ - Updated dependencies [77cca9e]
22
+ - Updated dependencies [b9f75f5]
23
+ - Updated dependencies [df47ea8]
24
+ - Updated dependencies [af019bf]
25
+ - Updated dependencies [6df1059]
26
+ - Updated dependencies [bdd90a1]
27
+ - Updated dependencies [8a3923f]
28
+ - Updated dependencies [c7d108b]
29
+ - Updated dependencies [1c7f635]
30
+ - Updated dependencies [636ffbc]
31
+ - Updated dependencies [54f8864]
32
+ - Updated dependencies [f49db45]
33
+ - Updated dependencies [4fde903]
34
+ - Updated dependencies [6a1b5b3]
35
+ - Updated dependencies [a95951a]
36
+ - Updated dependencies [9e82187]
37
+ - Updated dependencies [c5a3cb0]
38
+ - Updated dependencies [363fb71]
39
+ - Updated dependencies [de4e57e]
40
+ - Updated dependencies [0765aaa]
41
+ - Updated dependencies [6b7cf23]
42
+ - Updated dependencies [5205e58]
43
+ - Updated dependencies [ae45689]
44
+ - @velajs/vela@1.25.0
45
+
3
46
  ## 1.22.1
4
47
 
5
48
  ### Patch Changes
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # @velajs/testing
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@velajs/testing)](https://www.npmjs.com/package/@velajs/testing)
4
- [![License: MIT](https://img.shields.io/npm/l/@velajs/testing)](https://github.com/velajs/testing/blob/main/LICENSE)
4
+ [![License: MIT](https://img.shields.io/npm/l/@velajs/testing)](https://github.com/velajs/vela/blob/main/packages/testing/LICENSE)
5
5
 
6
- Test-module builder for [Vela](https://github.com/velajs/vela). Compose modules in isolation, override providers/guards/pipes/interceptors/filters, and exercise controllers via Hono's `app.request()`. Testing uses the same bootstrap primitive as production so request scope and framework-global providers cannot drift.
6
+ Test-module builder for [Vela](https://github.com/velajs/vela). Compose modules in isolation, override providers/guards/pipes/interceptors/filters, and exercise controllers via Hono's `app.request()`. Testing uses the production bootstrap and finalization path, including signed internal dispatch and lifecycle hooks.
7
7
 
8
8
  ## Install
9
9
 
10
10
  ```bash
11
11
  pnpm add -D @velajs/testing
12
- # Peer (already in your project): @velajs/vela >=1.21 <2, hono >=4
12
+ # Required peers: @velajs/vela ^1.22.1, hono >=4, vitest >=3
13
13
  ```
14
14
 
15
15
  No `reflect-metadata` needed — Vela ships its own polyfill.
@@ -17,9 +17,8 @@ No `reflect-metadata` needed — Vela ships its own polyfill.
17
17
  ## Quick Start
18
18
 
19
19
  ```ts
20
- import { describe, it, expect, beforeEach } from 'vitest';
20
+ import { describe, it, expect } from 'vitest';
21
21
  import { Test } from '@velajs/testing';
22
- import { MetadataRegistry } from '@velajs/vela';
23
22
  import { Injectable, Module } from '@velajs/vela';
24
23
 
25
24
  @Injectable()
@@ -30,19 +29,35 @@ class CatsService {
30
29
  @Module({ providers: [CatsService] })
31
30
  class CatsModule {}
32
31
 
33
- beforeEach(() => MetadataRegistry.clear());
34
-
35
32
  describe('CatsService', () => {
36
33
  it('returns cats', async () => {
37
34
  const moduleRef = await Test.createTestingModule({
38
35
  imports: [CatsModule],
39
36
  }).compile();
40
37
 
41
- expect(moduleRef.get(CatsService).findAll()).toEqual(['cat1', 'cat2']);
38
+ try {
39
+ expect(moduleRef.get(CatsService).findAll()).toEqual(['cat1', 'cat2']);
40
+ } finally {
41
+ await moduleRef.close();
42
+ }
42
43
  });
43
44
  });
44
45
  ```
45
46
 
47
+ Keep module decorators registered until the test completes. Clearing
48
+ `MetadataRegistry` after declaring a module removes the metadata that `compile()`
49
+ needs. Close each compiled module to run its shutdown hooks and dispose constructed
50
+ providers. Concurrent calls to \`close()\` await the same completion. Register owned
51
+ fixtures with \`moduleRef.onClose(async () => { /* cleanup */ })\`; callbacks run in
52
+ reverse order, and cleanup continues after a failure. The Node WebSocket adapter
53
+ registers its server automatically, so closing the module closes active sockets
54
+ and the listening port. A closed module rejects new requests and scopes.
55
+
56
+ \`runInRequestScope\` uses a managed invocation lifetime, seeds \`REQUEST_CONTEXT\`,
57
+ and drains deferred work before disposing the child. Module shutdown waits for
58
+ already-running scope callbacks. Always await HTTP response bodies and close
59
+ SSE connections before tearing down a test that consumes streaming responses.
60
+
46
61
  ## Overriding providers
47
62
 
48
63
  ```ts
@@ -60,11 +75,14 @@ const moduleRef = await Test.createTestingModule({ imports: [UsersModule] })
60
75
  .overrideGuard(AuthGuard).useValue({ canActivate: () => true })
61
76
  .overrideInterceptor(LogInterceptor).useClass(NoopInterceptor)
62
77
  .overrideProvider(CONFIG).useFactory({
63
- factory: (env) => ({ env: env.getEnv() }),
64
- inject: [EnvService],
78
+ factory: (env) => ({ env: env.APP_ENV }),
79
+ inject: [ENV],
65
80
  })
66
81
  ```
67
82
 
83
+ Here `ENV` is a registered `InjectionToken<{ APP_ENV: string }>` and `CONFIG`
84
+ is an `InjectionToken<{ env: string }>`.
85
+
68
86
  Inline providers (skip importing a module):
69
87
 
70
88
  ```ts
@@ -73,7 +91,25 @@ const moduleRef = await Test.createTestingModule({
73
91
  }).compile();
74
92
  ```
75
93
 
76
- Import `defineProvider` from `@velajs/vela`. Factory dependency types come from the required `inject` tuple; use `inject: []` for factories without dependencies.
94
+ Import \`defineProvider\` from \`@velajs/vela\`. Factory dependency types come from the required \`inject\` tuple; use \`inject: []\` for factories without dependencies.
95
+ Overrides recompute request-scope propagation, including dependencies introduced
96
+ or removed by a replacement factory. Compile separate testing modules to keep
97
+ application/environment-specific replacements independent.
98
+
99
+ For implementations with private state, inject a small interface token rather
100
+ than casting a partial object to the concrete class:
101
+
102
+ \`\`\`ts
103
+ interface Database { read(id: string): Promise<string>; }
104
+ const DATABASE = new InjectionToken<Database>('database');
105
+ const fake = { read: async (id: string) => id } satisfies Database;
106
+ const moduleRef = await Test.createTestingModule({
107
+ providers: [defineProvider(DATABASE, { useValue: fake })],
108
+ }).compile();
109
+ \`\`\`
110
+
111
+ Direct constructor tests remain useful when no module graph or request pipeline
112
+ is involved. No mock superclass or assertion cast is required.
77
113
 
78
114
  ## HTTP testing
79
115
 
@@ -144,6 +180,37 @@ Each scorer returns a `[0, 1]` score (auto-clamped) with an optional reason. `ev
144
180
 
145
181
  `@velajs/testing` consumes vela's framework primitives via `@velajs/vela/internal` (`MetadataRegistry`, `Container`, `RouteManager`, `ModuleLoader`, `ComponentManager`, `VelaApplication`, `bindAppProviders`). The same `bindAppProviders` that `VelaFactory.create` uses, so test-mode and run-mode app construction stay in lockstep automatically.
146
182
 
183
+ ## HTTP transports and schema validation
184
+
185
+ Use the same response assertions against an existing remote Worker or an injected
186
+ fetch handler. Each client owns its headers; there is no global environment,
187
+ automatic cookie jar, implicit authentication, or request retry.
188
+
189
+ ```ts
190
+ import { createTestHttpClient } from '@velajs/testing';
191
+
192
+ const remote = createTestHttpClient({ baseUrl: 'https://staging.example.com/' })
193
+ .withHeaders({ authorization: 'Bearer test-session' });
194
+ (await remote.get('/health').send()).assertOk();
195
+
196
+ // A native Workers test can inject SELF.fetch using an explicit closure.
197
+ const native = createTestHttpClient({
198
+ baseUrl: 'https://worker.test/',
199
+ fetch: request => SELF.fetch(request),
200
+ });
201
+ (await native.get('/health').send()).assertOk();
202
+ ```
203
+
204
+ `actingAs` needs a local `TestingModule`; remote clients use explicit headers.
205
+ `forHost` preserves the base URL scheme while replacing its host and Host header.
206
+ Relative paths resolve against `baseUrl` using standard URL rules.
207
+
208
+ `response.json(schema)` accepts Standard Schema v1, `defineDto` descriptors, and
209
+ legacy parsers (including `parseAsync`). It awaits validation and infers the
210
+ transformed output. The cached value remains the raw JSON: a later `json()` call
211
+ still returns `unknown`, and each requested schema runs against that original
212
+ value. Validator exceptions propagate; invalid payloads fail the test.
213
+
147
214
  ## License
148
215
 
149
216
  MIT
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { CanActivate, DependencyToken, ExceptionFilter, InferToken, InferTokens, ModuleOptions, NestInterceptor, PipeTransform, ProviderDefinition, SchemaParser, Token, Type, VelaApplication, defineProvider } from "@velajs/vela";
2
2
  import { Container } from "@velajs/vela/internal";
3
3
  import { ISeeder } from "@velajs/vela/seeder";
4
+ import { SchemaOutput, ValidationSchema } from "@velajs/vela/validation";
4
5
  //#region src/db/test-database.d.ts
5
6
  /**
6
7
  * TestDatabase
@@ -58,6 +59,7 @@ export declare class TestResponse {
58
59
  get headers(): Headers;
59
60
  /** Read JSON as unknown, or infer validated output from a supplied parser. */
60
61
  json(): Promise<unknown>;
62
+ json<Schema extends ValidationSchema>(parser: Schema): Promise<SchemaOutput<Schema>>;
61
63
  json<Value>(parser: SchemaParser<Value>): Promise<Value>;
62
64
  /** Read (and cache) the response body as text. */
63
65
  text(): Promise<string>;
@@ -135,7 +137,7 @@ export declare class TestHttpRequest {
135
137
  private readonly requestHeaders;
136
138
  private principal;
137
139
  private resolver;
138
- constructor(method: string, path: string, headers: Headers, module: TestingModule, host?: string | null);
140
+ constructor(method: string, path: string, headers: Headers, module: TestingModule | TestHttpTransport, host?: string | null);
139
141
  /** Set the request body (JSON-serialized on send). */
140
142
  withBody(data: unknown): this;
141
143
  /** Merge additional headers. */
@@ -156,6 +158,17 @@ export declare class TestHttpRequest {
156
158
  }
157
159
  //#endregion
158
160
  //#region src/http/test-http-client.d.ts
161
+ /** A Web-API transport for a live worker, SELF.fetch, or an in-process app. */
162
+ interface TestHttpTransport {
163
+ fetch(request: Request): Response | Promise<Response>;
164
+ readonly baseUrl?: string;
165
+ }
166
+ interface TestHttpClientOptions {
167
+ baseUrl: string;
168
+ fetch?: (request: Request) => Response | Promise<Response>;
169
+ }
170
+ /** Create an independent HTTP client. Headers are explicit; requests are never retried. */
171
+ export declare function createTestHttpClient(options: TestHttpClientOptions): TestHttpClient;
159
172
  /**
160
173
  * TestHttpClient
161
174
  *
@@ -176,7 +189,7 @@ export declare class TestHttpClient {
176
189
  private readonly module;
177
190
  private readonly host;
178
191
  private readonly defaultHeaders;
179
- constructor(module: TestingModule, host?: string | null, defaultHeaders?: Headers);
192
+ constructor(module: TestingModule | TestHttpTransport, host?: string | null, defaultHeaders?: Headers);
180
193
  /**
181
194
  * Return a new client bound to `host`. Also sets the `Host` header so domain
182
195
  * routing works even when the runtime reads the header rather than the URL.
@@ -382,11 +395,7 @@ type HonoApp = ReturnType<VelaApplication['getHonoApp']>;
382
395
  * ```
383
396
  */
384
397
  export declare class TestingModule {
385
- private readonly app;
386
- private readonly container;
387
- private _http;
388
- private honoApp;
389
- private authResolver;
398
+ #private;
390
399
  constructor(app: VelaApplication, container: Container);
391
400
  /** Resolve a provider from the root container. */
392
401
  get<const Key extends Token>(token: Key): InferToken<Key>;
@@ -428,7 +437,9 @@ export declare class TestingModule {
428
437
  assertDatabaseMissing(db: TestDatabase, table: string, where: Record<string, unknown>): Promise<void>;
429
438
  /** Assert `table` has exactly `expected` rows. */
430
439
  assertDatabaseCount(db: TestDatabase, table: string, expected: number): Promise<void>;
431
- /** Dispose the application. */
440
+ /** Register a fixture/transport cleanup, awaited in reverse registration order. */
441
+ onClose(cleanup: () => void | Promise<void>): this;
442
+ /** Fully dispose the application and owned transports. Concurrent calls share completion. */
432
443
  close(signal?: string): Promise<void>;
433
444
  private ensureHono;
434
445
  }
@@ -447,8 +458,7 @@ export declare class OverrideBy<Key extends Token> {
447
458
  }): TestingModuleBuilder;
448
459
  }
449
460
  export declare class TestingModuleBuilder {
450
- private readonly metadata;
451
- private overrides;
461
+ #private;
452
462
  constructor(metadata: ModuleOptions);
453
463
  overrideProvider<const Key extends Token>(token: OverrideToken<Key>): OverrideBy<Key>;
454
464
  overrideGuard<const Guard extends Type<CanActivate>>(guard: OverrideToken<Guard>): OverrideBy<Guard>;
@@ -477,5 +487,5 @@ export declare function getValueAtPath(obj: unknown, path: string): unknown;
477
487
  */
478
488
  export declare function hasValueAtPath(obj: unknown, path: string): boolean;
479
489
  //#endregion
480
- export type { ActingAsResolver, TestDatabase, TestPrincipal, TestSseEvent, WsConnector };
490
+ export type { ActingAsResolver, TestDatabase, TestHttpClientOptions, TestHttpTransport, TestPrincipal, TestSseEvent, WsConnector };
481
491
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { i as registerWsConnector, n as TestWsRequest, r as getWsConnector, t as TestWsConnection } from "./test-ws-connection-BHlEKwQz.js";
2
- import { REQUEST_CONTEXT, defineProvider } from "@velajs/vela";
3
- import { MetadataRegistry, VelaApplication as VelaApplication$1, bootstrap, createRequestContext, setRequestContainer } from "@velajs/vela/internal";
2
+ import { REQUEST_CONTEXT, defineProvider, runInEntrypointScope } from "@velajs/vela";
3
+ import { MetadataRegistry, bootstrap, createRequestContext, finalizeApplication, setRequestContainer } from "@velajs/vela/internal";
4
4
  import { Context } from "hono";
5
5
  import { SeederRegistry } from "@velajs/vela/seeder";
6
6
  import { expect } from "vitest";
7
+ import { parseSchemaAsync } from "@velajs/vela/validation";
7
8
  //#region src/http/path-utils.ts
8
9
  /**
9
10
  * Read the value at a dot-notation path (e.g. `data.user.id`).
@@ -76,7 +77,7 @@ var TestResponse = class {
76
77
  async json(parser) {
77
78
  this.jsonData ??= this.response.clone().json();
78
79
  const value = await this.jsonData;
79
- return parser === void 0 ? value : parser.parse(value);
80
+ return parser === void 0 ? value : parseSchemaAsync(parser, value);
80
81
  }
81
82
  /** Read (and cache) the response body as text. */
82
83
  async text() {
@@ -277,7 +278,9 @@ var TestHttpRequest = class {
277
278
  await this.applyAuthentication();
278
279
  const hasBody = this.body !== void 0 && this.body !== null;
279
280
  if (hasBody && !this.requestHeaders.has("Content-Type")) this.requestHeaders.set("Content-Type", "application/json");
280
- const url = new URL(this.path, `http://${this.host ?? "localhost"}`);
281
+ const base = new URL(("baseUrl" in this.module ? this.module.baseUrl : void 0) ?? "http://localhost/");
282
+ if (this.host) base.host = this.host;
283
+ const url = new URL(this.path, base);
281
284
  const request = new Request(url.toString(), {
282
285
  method: this.method,
283
286
  headers: this.requestHeaders,
@@ -287,6 +290,7 @@ var TestHttpRequest = class {
287
290
  }
288
291
  async applyAuthentication() {
289
292
  if (!this.principal) return;
293
+ if (!("getAuthResolver" in this.module)) throw new Error("actingAs() requires a TestingModule; use explicit headers for a remote client.");
290
294
  const resolver = this.resolver ?? this.module.getAuthResolver();
291
295
  if (!resolver) throw new Error("actingAs() requires an auth resolver. Pass one explicitly — actingAs(principal, resolver) — or register a default with module.setAuthResolver(resolver). For better-auth: import { actingAs } from \"@velajs/better-auth/testing\".");
292
296
  const headers = await resolver(this.module, this.principal);
@@ -295,6 +299,15 @@ var TestHttpRequest = class {
295
299
  };
296
300
  //#endregion
297
301
  //#region src/http/test-http-client.ts
302
+ /** Create an independent HTTP client. Headers are explicit; requests are never retried. */
303
+ function createTestHttpClient(options) {
304
+ const url = new URL(options.baseUrl);
305
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new TypeError("Test HTTP baseUrl must use http: or https:");
306
+ return new TestHttpClient({
307
+ baseUrl: url.toString(),
308
+ fetch: options.fetch ?? ((request) => globalThis.fetch(request))
309
+ });
310
+ }
298
311
  /**
299
312
  * TestHttpClient
300
313
  *
@@ -612,28 +625,32 @@ var TestSseRequest = class {
612
625
  * ```
613
626
  */
614
627
  var TestingModule = class {
615
- app;
616
- container;
617
- _http = null;
618
- honoApp = null;
619
- authResolver = null;
628
+ #http = null;
629
+ #honoApp = null;
630
+ #authResolver = null;
631
+ #app;
632
+ #container;
633
+ #cleanups = [];
634
+ #closing;
635
+ #pending = /* @__PURE__ */ new Set();
620
636
  constructor(app, container) {
621
- this.app = app;
622
- this.container = container;
637
+ this.#app = app;
638
+ this.#container = container;
623
639
  }
624
640
  /** Resolve a provider from the root container. */
625
641
  get(token) {
626
- return this.container.resolve(token);
642
+ this.#assertOpen();
643
+ return this.#container.resolve(token);
627
644
  }
628
645
  /** Build (once) and return the underlying application. */
629
646
  async createApplication() {
630
- await this.app.initRoutes();
631
- return this.app;
647
+ this.#assertOpen();
648
+ return this.#app;
632
649
  }
633
650
  /** Lazy fluent HTTP client bound to this module. */
634
651
  get http() {
635
- this._http ??= new TestHttpClient(this);
636
- return this._http;
652
+ this.#http ??= new TestHttpClient(this);
653
+ return this.#http;
637
654
  }
638
655
  /** Start an SSE connection builder for `path`. */
639
656
  sse(path) {
@@ -655,28 +672,32 @@ var TestingModule = class {
655
672
  * resolver is passed explicitly.
656
673
  */
657
674
  setAuthResolver(resolver) {
658
- this.authResolver = resolver;
675
+ this.#authResolver = resolver;
659
676
  return this;
660
677
  }
661
678
  /** The default auth resolver, if one was registered. */
662
679
  getAuthResolver() {
663
- return this.authResolver;
680
+ return this.#authResolver;
664
681
  }
665
682
  /**
666
683
  * Run `callback` inside a request-scoped child container seeded with a real
667
684
  * RequestContext, so REQUEST-scoped providers (and anything injecting
668
685
  * `REQUEST_CONTEXT`) resolve. The child is disposed afterwards.
669
686
  */
670
- async runInRequestScope(callback) {
671
- const child = this.container.createChild();
672
- const hono = new Context(new Request("http://localhost/"));
673
- setRequestContainer(hono, child);
674
- child.setRequestInstance(REQUEST_CONTEXT, createRequestContext(hono));
675
- try {
676
- return await callback(child);
677
- } finally {
678
- await child.dispose();
679
- }
687
+ runInRequestScope(callback) {
688
+ this.#assertOpen();
689
+ const operation = this.#runInRequestScope(callback);
690
+ this.#pending.add(operation);
691
+ operation.then(() => this.#pending.delete(operation), () => this.#pending.delete(operation));
692
+ return operation;
693
+ }
694
+ async #runInRequestScope(callback) {
695
+ return runInEntrypointScope(this.#container, async (child) => {
696
+ const hono = new Context(new Request("http://localhost/"));
697
+ setRequestContainer(hono, child);
698
+ child.setRequestInstance(REQUEST_CONTEXT, createRequestContext(hono));
699
+ return callback(child);
700
+ });
680
701
  }
681
702
  /**
682
703
  * Run the given `@Seeder()` classes, each in its own request scope. Throws if
@@ -684,7 +705,7 @@ var TestingModule = class {
684
705
  * themselves) to be present in the module graph.
685
706
  */
686
707
  async seed(...SeederClasses) {
687
- const registry = this.container.resolve(SeederRegistry);
708
+ const registry = this.#container.resolve(SeederRegistry);
688
709
  const known = new Set(registry.list().map((s) => s.target));
689
710
  for (const SeederClass of SeederClasses) {
690
711
  if (!known.has(SeederClass)) throw new Error(`Seeder "${SeederClass.name}" is not registered. Add it to a module's providers or SeederModule.forRoot({ seeders: [...] }).`);
@@ -708,16 +729,43 @@ var TestingModule = class {
708
729
  const actual = await db.count(table);
709
730
  expect(actual, `Expected ${table} count ${expected}, got ${actual}`).toBe(expected);
710
731
  }
711
- /** Dispose the application. */
712
- async close(signal) {
713
- await this.app.close(signal);
732
+ /** Register a fixture/transport cleanup, awaited in reverse registration order. */
733
+ onClose(cleanup) {
734
+ this.#assertOpen();
735
+ this.#cleanups.push(cleanup);
736
+ return this;
737
+ }
738
+ /** Fully dispose the application and owned transports. Concurrent calls share completion. */
739
+ close(signal) {
740
+ this.#closing ??= Promise.resolve().then(() => this.#dispose(signal));
741
+ return this.#closing;
742
+ }
743
+ async #dispose(signal) {
744
+ const failures = [];
745
+ for (const cleanup of this.#cleanups.splice(0).toReversed()) try {
746
+ await cleanup();
747
+ } catch (error) {
748
+ failures.push(error);
749
+ }
750
+ await Promise.allSettled(this.#pending);
751
+ try {
752
+ await this.#app.dispose(signal);
753
+ } catch (error) {
754
+ failures.push(error);
755
+ }
756
+ if (failures.length === 1) throw failures[0];
757
+ if (failures.length > 1) throw new AggregateError(failures, "Testing module cleanup failed");
758
+ }
759
+ #assertOpen() {
760
+ if (this.#closing) throw new Error("Testing module is closing or closed");
714
761
  }
715
762
  async ensureHono() {
716
- if (!this.honoApp) {
763
+ this.#assertOpen();
764
+ if (!this.#honoApp) {
717
765
  const app = await this.createApplication();
718
- this.honoApp = app.getHonoApp();
766
+ this.#honoApp = app.getHonoApp();
719
767
  }
720
- return this.honoApp;
768
+ return this.#honoApp;
721
769
  }
722
770
  };
723
771
  //#endregion
@@ -743,10 +791,10 @@ var OverrideBy = class {
743
791
  }
744
792
  };
745
793
  var TestingModuleBuilder = class {
746
- metadata;
747
- overrides = [];
794
+ #overrides = [];
795
+ #metadata;
748
796
  constructor(metadata) {
749
- this.metadata = metadata;
797
+ this.#metadata = metadata;
750
798
  }
751
799
  overrideProvider(token) {
752
800
  return new OverrideBy((provider) => {
@@ -770,27 +818,22 @@ var TestingModuleBuilder = class {
770
818
  return this.overrideProvider(filter);
771
819
  }
772
820
  addOverride(entry) {
773
- const idx = this.overrides.findIndex((o) => o.token === entry.token);
774
- if (idx !== -1) this.overrides[idx] = entry;
775
- else this.overrides.push(entry);
821
+ const idx = this.#overrides.findIndex((o) => o.token === entry.token);
822
+ if (idx !== -1) this.#overrides[idx] = entry;
823
+ else this.#overrides.push(entry);
776
824
  }
777
825
  async compile() {
778
826
  class TestRootModule {}
779
827
  MetadataRegistry.setModuleOptions(TestRootModule, {
780
- imports: this.metadata.imports,
781
- providers: this.metadata.providers,
782
- controllers: this.metadata.controllers,
783
- exports: this.metadata.exports
828
+ imports: this.#metadata.imports,
829
+ providers: this.#metadata.providers,
830
+ controllers: this.#metadata.controllers,
831
+ exports: this.#metadata.exports
784
832
  });
785
- const { container, routeManager, loader } = await bootstrap(TestRootModule);
786
- for (const override of this.overrides) container.replaceProvider(override.provider);
787
- const app = new VelaApplication$1(container, routeManager);
788
- const instances = await loader.resolveAllInstances();
789
- app.setInstances(instances);
790
- await app.callOnModuleInit();
791
- await app.callOnApplicationBootstrap();
792
- await app.initRoutes();
793
- return new TestingModule(app, container);
833
+ const prepared = await bootstrap(TestRootModule);
834
+ const { container } = prepared;
835
+ for (const override of this.#overrides) container.replaceProvider(override.provider);
836
+ return new TestingModule(await finalizeApplication(prepared), container);
794
837
  }
795
838
  };
796
839
  //#endregion
@@ -799,6 +842,6 @@ const Test = { createTestingModule(metadata) {
799
842
  return new TestingModuleBuilder(metadata);
800
843
  } };
801
844
  //#endregion
802
- export { OverrideBy, Test, TestHttpClient, TestHttpRequest, TestResponse, TestSseConnection, TestSseRequest, TestWsConnection, TestWsRequest, TestingModule, TestingModuleBuilder, getValueAtPath, getWsConnector, hasValueAtPath, registerWsConnector };
845
+ export { OverrideBy, Test, TestHttpClient, TestHttpRequest, TestResponse, TestSseConnection, TestSseRequest, TestWsConnection, TestWsRequest, TestingModule, TestingModuleBuilder, createTestHttpClient, getValueAtPath, getWsConnector, hasValueAtPath, registerWsConnector };
803
846
 
804
847
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["VelaApplication"],"sources":["../src/http/path-utils.ts","../src/http/test-response.ts","../src/http/test-http-request.ts","../src/http/test-http-client.ts","../src/sse/test-sse-connection.ts","../src/sse/test-sse-request.ts","../src/testing-module.ts","../src/testing-module.builder.ts","../src/test.ts"],"sourcesContent":["// Ported from @stratal/testing (MIT, © Temitayo Fadojutimi).\n\n/**\n * Read the value at a dot-notation path (e.g. `data.user.id`).\n * Returns `undefined` when any segment along the way is null/undefined.\n */\nexport function getValueAtPath(obj: unknown, path: string): unknown {\n const parts = path.split('.');\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === null || current === undefined) {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n\n return current;\n}\n\n/**\n * Whether a dot-notation path exists on the object, even when the value at the\n * path is `null`/`undefined`. Distinguishes \"key present but null\" from \"key\n * absent\".\n */\nexport function hasValueAtPath(obj: unknown, path: string): boolean {\n const parts = path.split('.');\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === null || current === undefined) {\n return false;\n }\n\n if (typeof current !== 'object') {\n return false;\n }\n\n const record = current as Record<string, unknown>;\n\n if (!(part in record)) {\n return false;\n }\n\n current = record[part];\n }\n\n return true;\n}\n","// Ported from @stratal/testing (MIT, © Temitayo Fadojutimi), minus Macroable —\n// vela has no Macroable, so TestResponse is a plain class.\nimport { expect } from 'vitest';\nimport type { SchemaParser } from '@velajs/vela';\nimport { getValueAtPath, hasValueAtPath } from './path-utils.js';\n\nfunction isJsonObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * TestResponse\n *\n * Wraps a `Response` with fluent, chainable assertions. Synchronous status /\n * header assertions return `this`; JSON assertions (which must read the body)\n * return `Promise<this>`.\n *\n * @example\n * ```ts\n * const res = await module.http.get('/users/1').send();\n * res.assertOk();\n * await res.assertJsonPath('data.id', 1);\n * ```\n */\nexport class TestResponse {\n private jsonData: Promise<unknown> | undefined;\n private textData: string | null = null;\n\n constructor(private readonly response: Response) {}\n\n /** The raw `Response`. */\n get raw(): Response {\n return this.response;\n }\n\n /** The response status code. */\n get status(): number {\n return this.response.status;\n }\n\n /** The response headers. */\n get headers(): Headers {\n return this.response.headers;\n }\n\n /** Read JSON as unknown, or infer validated output from a supplied parser. */\n json(): Promise<unknown>;\n json<Value>(parser: SchemaParser<Value>): Promise<Value>;\n async json<Value>(parser?: SchemaParser<Value>): Promise<unknown> {\n this.jsonData ??= this.response.clone().json();\n const value = await this.jsonData;\n return parser === undefined ? value : parser.parse(value);\n }\n\n /** Read (and cache) the response body as text. */\n async text(): Promise<string> {\n this.textData ??= await this.response.clone().text();\n return this.textData;\n }\n\n // ============================================================\n // Status assertions\n // ============================================================\n\n /** Assert status is 200 OK. */\n assertOk(): this {\n return this.assertStatus(200);\n }\n\n /** Assert status is 201 Created. */\n assertCreated(): this {\n return this.assertStatus(201);\n }\n\n /** Assert status is 204 No Content. */\n assertNoContent(): this {\n return this.assertStatus(204);\n }\n\n /** Assert status is 400 Bad Request. */\n assertBadRequest(): this {\n return this.assertStatus(400);\n }\n\n /** Assert status is 401 Unauthorized. */\n assertUnauthorized(): this {\n return this.assertStatus(401);\n }\n\n /** Assert status is 403 Forbidden. */\n assertForbidden(): this {\n return this.assertStatus(403);\n }\n\n /** Assert status is 404 Not Found. */\n assertNotFound(): this {\n return this.assertStatus(404);\n }\n\n /** Assert status is 422 Unprocessable Entity. */\n assertUnprocessable(): this {\n return this.assertStatus(422);\n }\n\n /** Assert status is 500 Internal Server Error. */\n assertServerError(): this {\n return this.assertStatus(500);\n }\n\n /** Assert the response has the given status code. */\n assertStatus(expected: number): this {\n expect(this.response.status, `Expected status ${expected}, got ${this.response.status}`).toBe(\n expected,\n );\n return this;\n }\n\n /** Assert the status is in the 2xx range. */\n assertSuccessful(): this {\n expect(\n this.response.status >= 200 && this.response.status < 300,\n `Expected successful status (2xx), got ${this.response.status}`,\n ).toBe(true);\n return this;\n }\n\n // ============================================================\n // JSON assertions\n // ============================================================\n\n /** Assert each key in `expected` equals the corresponding top-level value. */\n async assertJson(expected: Record<string, unknown>): Promise<this> {\n const actual = await this.json();\n if (!isJsonObject(actual)) {\n expect.fail('Expected JSON body to be an object.');\n }\n\n for (const [key, value] of Object.entries(expected)) {\n expect(\n actual[key],\n `Expected JSON key \"${key}\" to be ${JSON.stringify(value)}, got ${JSON.stringify(actual[key])}`,\n ).toStrictEqual(value);\n }\n\n return this;\n }\n\n /** Assert the value at a dot-notation path equals `expected`. */\n async assertJsonPath(path: string, expected: unknown): Promise<this> {\n const json = await this.json();\n const actual = getValueAtPath(json, path);\n\n expect(\n actual,\n `Expected JSON path \"${path}\" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,\n ).toStrictEqual(expected);\n\n return this;\n }\n\n /** Assert every path/value pair in `expectations` matches (batch assert). */\n async assertJsonPaths(expectations: Record<string, unknown>): Promise<this> {\n const json = await this.json();\n\n for (const [path, expected] of Object.entries(expectations)) {\n const actual = getValueAtPath(json, path);\n expect(\n actual,\n `Expected JSON path \"${path}\" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,\n ).toStrictEqual(expected);\n }\n\n return this;\n }\n\n /** Assert the top-level JSON object has every key in `structure`. */\n async assertJsonStructure(structure: string[]): Promise<this> {\n const json = await this.json();\n if (!isJsonObject(json)) {\n expect.fail('Expected JSON body to be an object.');\n }\n\n for (const key of structure) {\n expect(\n key in json,\n `Expected JSON to have key \"${key}\", got keys: ${JSON.stringify(Object.keys(json))}`,\n ).toBe(true);\n }\n\n return this;\n }\n\n /** Assert a path exists (value may be anything, including `null`). */\n async assertJsonPathExists(path: string): Promise<this> {\n const json = await this.json();\n\n expect(hasValueAtPath(json, path), `Expected JSON path \"${path}\" to exist`).toBe(true);\n\n return this;\n }\n\n /** Assert a path does not exist. */\n async assertJsonPathMissing(path: string): Promise<this> {\n const json = await this.json();\n\n expect(hasValueAtPath(json, path), `Expected JSON path \"${path}\" to not exist`).toBe(false);\n\n return this;\n }\n\n /** Assert the value at a path satisfies a predicate. */\n async assertJsonPathMatches(path: string, matcher: (value: unknown) => boolean): Promise<this> {\n const json = await this.json();\n const value = getValueAtPath(json, path);\n\n expect(\n matcher(value),\n `Expected JSON path \"${path}\" to match predicate, got ${JSON.stringify(value)}`,\n ).toBe(true);\n\n return this;\n }\n\n /** Assert the string value at a path contains `substring`. */\n async assertJsonPathContains(path: string, substring: string): Promise<this> {\n const json = await this.json();\n const value = getValueAtPath(json, path);\n\n if (typeof value !== 'string') {\n expect.fail(`Expected JSON path \"${path}\" to be a string, got ${typeof value}`);\n }\n\n expect(\n value.includes(substring),\n `Expected JSON path \"${path}\" to contain \"${substring}\", got \"${String(value)}\"`,\n ).toBe(true);\n\n return this;\n }\n\n /** Assert the array value at a path includes `item`. */\n async assertJsonPathIncludes(path: string, item: unknown): Promise<this> {\n const json = await this.json();\n const value = getValueAtPath(json, path);\n\n if (!Array.isArray(value)) {\n expect.fail(`Expected JSON path \"${path}\" to be an array, got ${typeof value}`);\n }\n\n expect(\n value.includes(item),\n `Expected JSON path \"${path}\" to include ${JSON.stringify(item)}`,\n ).toBe(true);\n\n return this;\n }\n\n /** Assert the array value at a path has `count` items. */\n async assertJsonPathCount(path: string, count: number): Promise<this> {\n const json = await this.json();\n const value = getValueAtPath(json, path);\n\n if (!Array.isArray(value)) {\n expect.fail(`Expected JSON path \"${path}\" to be an array, got ${typeof value}`);\n }\n\n expect(\n value.length,\n `Expected JSON path \"${path}\" to have ${count} items, got ${value.length}`,\n ).toBe(count);\n\n return this;\n }\n\n // ============================================================\n // Header assertions\n // ============================================================\n\n /** Assert a header is present, optionally equal to `expected`. */\n assertHeader(name: string, expected?: string): this {\n const actual = this.response.headers.get(name);\n\n expect(actual !== null, `Expected header \"${name}\" to be present`).toBe(true);\n\n if (expected !== undefined) {\n expect(actual, `Expected header \"${name}\" to be \"${expected}\", got \"${actual}\"`).toBe(\n expected,\n );\n }\n\n return this;\n }\n\n /** Assert a header is absent. */\n assertHeaderMissing(name: string): this {\n const actual = this.response.headers.get(name);\n\n expect(actual, `Expected header \"${name}\" to be absent, but got \"${actual}\"`).toBeNull();\n\n return this;\n }\n}\n","// Adapted from @stratal/testing (MIT, © Temitayo Fadojutimi): stratal's hard\n// AuthService import is replaced by a generic auth-resolver seam so\n// @velajs/testing stays free of optional-package dependencies.\nimport type { ActingAsResolver, TestPrincipal, TestingModule } from '../testing-module.js';\nimport { TestResponse } from './test-response.js';\n\n/**\n * TestHttpRequest\n *\n * Fluent builder for a single test HTTP request. `send()` builds a `Request`\n * and drives it through `module.fetch()` (the full Hono pipeline).\n *\n * @example\n * ```ts\n * const res = await module.http\n * .post('/users')\n * .withBody({ name: 'A' })\n * .withHeaders({ 'X-Trace': '1' })\n * .send();\n * res.assertCreated();\n * ```\n */\nexport class TestHttpRequest {\n private body: unknown = undefined;\n private readonly requestHeaders: Headers;\n private principal: TestPrincipal | null = null;\n private resolver: ActingAsResolver | null = null;\n\n constructor(\n private readonly method: string,\n private readonly path: string,\n headers: Headers,\n private readonly module: TestingModule,\n private readonly host: string | null = null,\n ) {\n this.requestHeaders = new Headers(headers);\n }\n\n /** Set the request body (JSON-serialized on send). */\n withBody(data: unknown): this {\n this.body = data;\n return this;\n }\n\n /** Merge additional headers. */\n withHeaders(headers: Record<string, string>): this {\n for (const [key, value] of Object.entries(headers)) {\n this.requestHeaders.set(key, value);\n }\n return this;\n }\n\n /** Set `Content-Type: application/json`. */\n asJson(): this {\n this.requestHeaders.set('Content-Type', 'application/json');\n return this;\n }\n\n /**\n * Authenticate the request as `principal`. The `resolver` (or a default one\n * registered via `module.setAuthResolver`) turns the principal into request\n * headers. The resolver signature `(module, principal) => Promise<Headers>`\n * is the cross-package contract sibling packages (e.g. `@velajs/better-auth`)\n * build against.\n */\n actingAs(principal: TestPrincipal, resolver?: ActingAsResolver): this {\n this.principal = principal;\n this.resolver = resolver ?? null;\n return this;\n }\n\n /** Build the `Request` and send it through `module.fetch()`. */\n async send(): Promise<TestResponse> {\n await this.applyAuthentication();\n\n const hasBody = this.body !== undefined && this.body !== null;\n if (hasBody && !this.requestHeaders.has('Content-Type')) {\n this.requestHeaders.set('Content-Type', 'application/json');\n }\n\n const url = new URL(this.path, `http://${this.host ?? 'localhost'}`);\n const request = new Request(url.toString(), {\n method: this.method,\n headers: this.requestHeaders,\n body: hasBody ? JSON.stringify(this.body) : null,\n });\n\n const response = await this.module.fetch(request);\n return new TestResponse(response);\n }\n\n private async applyAuthentication(): Promise<void> {\n if (!this.principal) return;\n\n const resolver = this.resolver ?? this.module.getAuthResolver();\n if (!resolver) {\n throw new Error(\n 'actingAs() requires an auth resolver. Pass one explicitly — ' +\n 'actingAs(principal, resolver) — or register a default with ' +\n 'module.setAuthResolver(resolver). For better-auth: ' +\n 'import { actingAs } from \"@velajs/better-auth/testing\".',\n );\n }\n\n const headers = await resolver(this.module, this.principal);\n for (const [key, value] of headers.entries()) {\n this.requestHeaders.set(key, value);\n }\n }\n}\n","// Adapted from @stratal/testing (MIT, © Temitayo Fadojutimi). Stratal's i18n\n// `withLocale` is dropped (vela i18n differs — optional follow-up).\nimport type { TestingModule } from '../testing-module.js';\nimport { TestHttpRequest } from './test-http-request.js';\n\n/**\n * TestHttpClient\n *\n * Fluent entry point for test HTTP requests. `forHost`/`withHeaders` return a\n * new immutable client; the verb methods start a {@link TestHttpRequest}.\n *\n * @example\n * ```ts\n * const res = await module.http\n * .forHost('example.com')\n * .post('/users')\n * .withBody({ name: 'A' })\n * .send();\n * res.assertCreated();\n * ```\n */\nexport class TestHttpClient {\n constructor(\n private readonly module: TestingModule,\n private readonly host: string | null = null,\n private readonly defaultHeaders: Headers = new Headers(),\n ) {}\n\n /**\n * Return a new client bound to `host`. Also sets the `Host` header so domain\n * routing works even when the runtime reads the header rather than the URL.\n */\n forHost(host: string): TestHttpClient {\n const headers = new Headers(this.defaultHeaders);\n headers.set('Host', host);\n return new TestHttpClient(this.module, host, headers);\n }\n\n /** Return a new client with additional default headers on every request. */\n withHeaders(headers: Record<string, string>): TestHttpClient {\n const next = new Headers(this.defaultHeaders);\n for (const [key, value] of Object.entries(headers)) {\n next.set(key, value);\n }\n return new TestHttpClient(this.module, this.host, next);\n }\n\n get(path: string): TestHttpRequest {\n return this.createRequest('GET', path);\n }\n\n post(path: string): TestHttpRequest {\n return this.createRequest('POST', path);\n }\n\n put(path: string): TestHttpRequest {\n return this.createRequest('PUT', path);\n }\n\n patch(path: string): TestHttpRequest {\n return this.createRequest('PATCH', path);\n }\n\n delete(path: string): TestHttpRequest {\n return this.createRequest('DELETE', path);\n }\n\n private createRequest(method: string, path: string): TestHttpRequest {\n return new TestHttpRequest(method, path, this.defaultHeaders, this.module, this.host);\n }\n}\n","// Ported near-verbatim from @stratal/testing (MIT, © Temitayo Fadojutimi).\n// Web-standard only (ReadableStream + TextDecoder), so it is edge-pure.\nimport { expect } from 'vitest';\n\n/** A parsed Server-Sent Event. */\nexport interface TestSseEvent {\n data: string;\n event?: string;\n id?: string;\n retry?: number;\n}\n\n/**\n * TestSseConnection\n *\n * Reads a streaming `text/event-stream` response body and exposes queue-based\n * wait/assert helpers over the parsed events.\n *\n * @example\n * ```ts\n * const sse = await module.sse('/stream/events').connect();\n * await sse.assertEventData('ping');\n * await sse.waitForEnd();\n * ```\n */\nexport class TestSseConnection {\n private readonly eventQueue: TestSseEvent[] = [];\n private eventWaiters: ((event: TestSseEvent) => void)[] = [];\n private streamEnded = false;\n private endWaiters: (() => void)[] = [];\n\n constructor(private readonly response: Response) {\n this.startReading();\n }\n\n /** The raw `Response`. */\n get raw(): Response {\n return this.response;\n }\n\n /** Wait for the next event (rejects after `timeout` ms). */\n async waitForEvent(timeout = 5000): Promise<TestSseEvent> {\n if (this.eventQueue.length > 0) {\n return this.eventQueue.shift()!;\n }\n\n if (this.streamEnded) {\n throw new Error('SSE: stream has ended, no more events');\n }\n\n return new Promise<TestSseEvent>((resolve, reject) => {\n const waiter = (event: TestSseEvent): void => {\n clearTimeout(timer);\n resolve(event);\n };\n\n const timer = setTimeout(() => {\n const index = this.eventWaiters.indexOf(waiter);\n if (index !== -1) this.eventWaiters.splice(index, 1);\n reject(new Error(`SSE: no event received within ${timeout}ms`));\n }, timeout);\n\n this.eventWaiters.push(waiter);\n });\n }\n\n /** Wait for the stream to end (rejects after `timeout` ms). */\n async waitForEnd(timeout = 5000): Promise<void> {\n if (this.streamEnded) return;\n\n return new Promise<void>((resolve, reject) => {\n const waiter = (): void => {\n clearTimeout(timer);\n resolve();\n };\n\n const timer = setTimeout(() => {\n const index = this.endWaiters.indexOf(waiter);\n if (index !== -1) this.endWaiters.splice(index, 1);\n reject(new Error(`SSE: stream did not end within ${timeout}ms`));\n }, timeout);\n\n this.endWaiters.push(waiter);\n });\n }\n\n /** Collect all remaining events until the stream ends. */\n async collectEvents(timeout = 5000): Promise<TestSseEvent[]> {\n const events: TestSseEvent[] = [];\n\n if (this.streamEnded) {\n return [...this.eventQueue.splice(0)];\n }\n\n return new Promise<TestSseEvent[]>((resolve, reject) => {\n const originalDispatch = this.dispatchEvent.bind(this);\n this.dispatchEvent = (event: TestSseEvent): void => {\n events.push(event);\n originalDispatch(event);\n };\n\n const endWaiter = (): void => {\n clearTimeout(timer);\n this.dispatchEvent = originalDispatch;\n resolve(events);\n };\n\n const timer = setTimeout(() => {\n this.dispatchEvent = originalDispatch;\n const index = this.endWaiters.indexOf(endWaiter);\n if (index !== -1) this.endWaiters.splice(index, 1);\n reject(new Error(`SSE: stream did not end within ${timeout}ms`));\n }, timeout);\n\n events.push(...this.eventQueue.splice(0));\n\n this.endWaiters.push(endWaiter);\n });\n }\n\n /** Assert the next event matches the expected partial shape. */\n async assertEvent(expected: Partial<TestSseEvent>, timeout = 5000): Promise<void> {\n const event = await this.waitForEvent(timeout);\n expect(event).toMatchObject(expected);\n }\n\n /** Assert the next event's `data` equals `expected`. */\n async assertEventData(expected: string, timeout = 5000): Promise<void> {\n const event = await this.waitForEvent(timeout);\n expect(event.data, `Expected SSE data \"${expected}\", got \"${event.data}\"`).toBe(expected);\n }\n\n /** Assert the next event's `data` is JSON equal to `expected`. */\n async assertJsonEventData<T>(expected: T, timeout = 5000): Promise<void> {\n const event = await this.waitForEvent(timeout);\n const parsed = JSON.parse(event.data) as unknown;\n expect(parsed).toEqual(expected);\n }\n\n private startReading(): void {\n const body = this.response.body;\n if (!body) {\n this.streamEnded = true;\n return;\n }\n\n const reader = body.getReader() as ReadableStreamDefaultReader<Uint8Array>;\n const decoder = new TextDecoder();\n let buffer = '';\n\n const read = async (): Promise<void> => {\n try {\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) {\n if (buffer.trim()) {\n const event = this.parseEvent(buffer);\n if (event) this.dispatchEvent(event);\n }\n this.endStream();\n return;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n const parts = buffer.split('\\n\\n');\n buffer = parts.pop()!;\n\n for (const part of parts) {\n if (!part.trim()) continue;\n const event = this.parseEvent(part);\n if (event) this.dispatchEvent(event);\n }\n }\n } catch {\n this.endStream();\n }\n };\n\n void read();\n }\n\n private endStream(): void {\n this.streamEnded = true;\n for (const waiter of this.endWaiters) {\n waiter();\n }\n this.endWaiters = [];\n }\n\n private parseEvent(raw: string): TestSseEvent | null {\n const lines = raw.split('\\n');\n const dataLines: string[] = [];\n let event: string | undefined;\n let id: string | undefined;\n let retry: number | undefined;\n\n for (const line of lines) {\n if (line.startsWith(':')) continue; // comment line\n\n const colonIndex = line.indexOf(':');\n if (colonIndex === -1) continue;\n\n const field = line.slice(0, colonIndex);\n const value =\n line[colonIndex + 1] === ' ' ? line.slice(colonIndex + 2) : line.slice(colonIndex + 1);\n\n switch (field) {\n case 'data':\n dataLines.push(value);\n break;\n case 'event':\n event = value;\n break;\n case 'id':\n id = value;\n break;\n case 'retry': {\n const parsed = parseInt(value, 10);\n if (!Number.isNaN(parsed)) retry = parsed;\n break;\n }\n }\n }\n\n if (dataLines.length === 0) return null;\n\n const result: TestSseEvent = { data: dataLines.join('\\n') };\n if (event !== undefined) result.event = event;\n if (id !== undefined) result.id = id;\n if (retry !== undefined) result.retry = retry;\n\n return result;\n }\n\n private dispatchEvent(event: TestSseEvent): void {\n if (this.eventWaiters.length > 0) {\n this.eventWaiters.shift()!(event);\n } else {\n this.eventQueue.push(event);\n }\n }\n}\n","// Adapted from @stratal/testing (MIT, © Temitayo Fadojutimi). Auth uses the\n// generic resolver seam instead of a hard AuthService import.\nimport { expect } from 'vitest';\nimport type { ActingAsResolver, TestPrincipal, TestingModule } from '../testing-module.js';\nimport { TestSseConnection } from './test-sse-connection.js';\n\n/**\n * TestSseRequest\n *\n * Builder for a Server-Sent Events connection. `connect()` issues a GET through\n * `module.fetch()`, asserts a `text/event-stream` 200, and wraps the streaming\n * body in a {@link TestSseConnection}.\n *\n * @example\n * ```ts\n * const sse = await module.sse('/stream/events').connect();\n * await sse.assertEvent({ event: 'message', data: 'hello' });\n * ```\n */\nexport class TestSseRequest {\n private readonly requestHeaders = new Headers();\n private principal: TestPrincipal | null = null;\n private resolver: ActingAsResolver | null = null;\n\n constructor(\n private readonly path: string,\n private readonly module: TestingModule,\n ) {}\n\n /** Merge additional headers onto the SSE request. */\n withHeaders(headers: Record<string, string>): this {\n for (const [key, value] of Object.entries(headers)) {\n this.requestHeaders.set(key, value);\n }\n return this;\n }\n\n /** Authenticate the connection (see {@link TestHttpRequest.actingAs}). */\n actingAs(principal: TestPrincipal, resolver?: ActingAsResolver): this {\n this.principal = principal;\n this.resolver = resolver ?? null;\n return this;\n }\n\n /** Open the stream and return a live {@link TestSseConnection}. */\n async connect(): Promise<TestSseConnection> {\n await this.applyAuthentication();\n\n this.requestHeaders.set('Accept', 'text/event-stream');\n\n const url = new URL(this.path, 'http://localhost');\n const request = new Request(url.toString(), { headers: this.requestHeaders });\n\n const response = await this.module.fetch(request);\n\n expect(response.status, `Expected status 200, got ${response.status}`).toBe(200);\n\n const contentType = response.headers.get('content-type') ?? '';\n expect(\n contentType.includes('text/event-stream'),\n `Expected content-type \"text/event-stream\", got \"${contentType}\"`,\n ).toBe(true);\n\n return new TestSseConnection(response);\n }\n\n private async applyAuthentication(): Promise<void> {\n if (!this.principal) return;\n\n const resolver = this.resolver ?? this.module.getAuthResolver();\n if (!resolver) {\n throw new Error(\n 'actingAs() requires an auth resolver. Pass one explicitly or register ' +\n 'a default with module.setAuthResolver(resolver).',\n );\n }\n\n const headers = await resolver(this.module, this.principal);\n for (const [key, value] of headers.entries()) {\n this.requestHeaders.set(key, value);\n }\n }\n}\n","import { Context } from 'hono';\nimport {\n REQUEST_CONTEXT,\n type InferToken,\n type Token,\n type Type,\n type VelaApplication,\n} from '@velajs/vela';\nimport { createRequestContext, setRequestContainer, type Container } from '@velajs/vela/internal';\nimport { SeederRegistry, type ISeeder } from '@velajs/vela/seeder';\nimport { expect } from 'vitest';\nimport type { TestDatabase } from './db/test-database.js';\nimport { TestHttpClient } from './http/test-http-client.js';\nimport { TestSseRequest } from './sse/test-sse-request.js';\nimport { TestWsRequest } from './ws/test-ws-request.js';\n\n/** A test principal — an opaque object the auth resolver turns into headers. */\nexport type TestPrincipal = Record<string, unknown>;\n\n/**\n * Turns a principal into request headers (session cookie, bearer token, …).\n * The signature `(module, principal) => Promise<Headers>` is a cross-package\n * contract: sibling packages (e.g. `@velajs/better-auth/testing`) build a\n * resolver against it. Kept generic so `@velajs/testing` needs no auth deps.\n */\nexport type ActingAsResolver = (\n module: TestingModule,\n principal: TestPrincipal,\n) => Promise<Headers>;\n\ntype HonoApp = ReturnType<VelaApplication['getHonoApp']>;\n\n/**\n * TestingModule\n *\n * The compiled test harness. Beyond `get`/`createApplication`/`close`, it adds\n * Laravel-flavored ergonomics: a fluent HTTP client, SSE/WS builders, request-\n * scope execution, seeding, and database assertion wrappers.\n *\n * @example\n * ```ts\n * const module = await Test.createTestingModule({ imports: [AppModule] }).compile();\n * await module.http.post('/users').withBody({ name: 'A' }).send()\n * .then((r) => r.assertCreated());\n * ```\n */\nexport class TestingModule {\n private _http: TestHttpClient | null = null;\n private honoApp: HonoApp | null = null;\n private authResolver: ActingAsResolver | null = null;\n\n constructor(\n private readonly app: VelaApplication,\n private readonly container: Container,\n ) {}\n\n /** Resolve a provider from the root container. */\n get<const Key extends Token>(token: Key): InferToken<Key> {\n return this.container.resolve(token);\n }\n\n /** Build (once) and return the underlying application. */\n async createApplication(): Promise<VelaApplication> {\n await this.app.initRoutes();\n return this.app;\n }\n\n /** Lazy fluent HTTP client bound to this module. */\n get http(): TestHttpClient {\n this._http ??= new TestHttpClient(this);\n return this._http;\n }\n\n /** Start an SSE connection builder for `path`. */\n sse(path: string): TestSseRequest {\n return new TestSseRequest(path, this);\n }\n\n /** Start a WebSocket connection builder for `path` (needs a transport adapter). */\n ws(path: string): TestWsRequest {\n return new TestWsRequest(path, this);\n }\n\n /**\n * Drive a `Request` through the full Hono pipeline. The Hono app is built\n * once and reused across requests.\n */\n async fetch(...args: Parameters<HonoApp['fetch']>): Promise<Response> {\n const hono = await this.ensureHono();\n return hono.fetch(...args);\n }\n\n /**\n * Register a default auth resolver used by `actingAs(principal)` when no\n * resolver is passed explicitly.\n */\n setAuthResolver(resolver: ActingAsResolver): this {\n this.authResolver = resolver;\n return this;\n }\n\n /** The default auth resolver, if one was registered. */\n getAuthResolver(): ActingAsResolver | null {\n return this.authResolver;\n }\n\n /**\n * Run `callback` inside a request-scoped child container seeded with a real\n * RequestContext, so REQUEST-scoped providers (and anything injecting\n * `REQUEST_CONTEXT`) resolve. The child is disposed afterwards.\n */\n async runInRequestScope<T>(callback: (container: Container) => T | Promise<T>): Promise<T> {\n const child = this.container.createChild();\n const hono = new Context(new Request('http://localhost/'));\n setRequestContainer(hono, child);\n child.setRequestInstance(REQUEST_CONTEXT, createRequestContext(hono));\n try {\n return await callback(child);\n } finally {\n await child.dispose();\n }\n }\n\n /**\n * Run the given `@Seeder()` classes, each in its own request scope. Throws if\n * a class is not a registered seeder. Requires `SeederModule` (or the seeders\n * themselves) to be present in the module graph.\n */\n async seed(...SeederClasses: Type<ISeeder>[]): Promise<void> {\n const registry = this.container.resolve(SeederRegistry);\n const known = new Set<unknown>(registry.list().map((s) => s.target));\n\n for (const SeederClass of SeederClasses) {\n if (!known.has(SeederClass)) {\n throw new Error(\n `Seeder \"${SeederClass.name}\" is not registered. Add it to a module's ` +\n 'providers or SeederModule.forRoot({ seeders: [...] }).',\n );\n }\n await this.runInRequestScope(async (child) => {\n const instance = child.resolve(SeederClass);\n await instance.run();\n });\n }\n }\n\n /** Assert a row matching `where` exists in `table` (via a {@link TestDatabase}). */\n async assertDatabaseHas(\n db: TestDatabase,\n table: string,\n where: Record<string, unknown>,\n ): Promise<void> {\n const exists = await db.has(table, where);\n expect(exists, `Expected ${table} to have a row matching ${JSON.stringify(where)}`).toBe(true);\n }\n\n /** Assert no row matching `where` exists in `table`. */\n async assertDatabaseMissing(\n db: TestDatabase,\n table: string,\n where: Record<string, unknown>,\n ): Promise<void> {\n const exists = await db.has(table, where);\n expect(exists, `Expected ${table} NOT to have a row matching ${JSON.stringify(where)}`).toBe(\n false,\n );\n }\n\n /** Assert `table` has exactly `expected` rows. */\n async assertDatabaseCount(db: TestDatabase, table: string, expected: number): Promise<void> {\n const actual = await db.count(table);\n expect(actual, `Expected ${table} count ${expected}, got ${actual}`).toBe(expected);\n }\n\n /** Dispose the application. */\n async close(signal?: string): Promise<void> {\n await this.app.close(signal);\n }\n\n private async ensureHono(): Promise<HonoApp> {\n if (!this.honoApp) {\n const app = await this.createApplication();\n this.honoApp = app.getHonoApp();\n }\n return this.honoApp;\n }\n}\n","import {\n defineProvider,\n type CanActivate,\n type DependencyToken,\n type ExceptionFilter,\n type InferToken,\n type InferTokens,\n type ModuleOptions,\n type NestInterceptor,\n type PipeTransform,\n type ProviderDefinition,\n type Token,\n type Type,\n} from '@velajs/vela';\nimport { MetadataRegistry, VelaApplication, bootstrap } from '@velajs/vela/internal';\nimport { TestingModule } from './testing-module.js';\n\ninterface OverrideEntry {\n token: Token;\n provider: ProviderDefinition;\n}\n\n// Keep the same token proof required by core provider authoring. An erased\n// registry identity can be resolved, but cannot authorize a typed replacement.\ntype OverrideToken<Key extends Token> = Parameters<typeof defineProvider<Key>>[0];\n\nexport class OverrideBy<Key extends Token> {\n constructor(\n private readonly commit: (provider: ProviderDefinition) => TestingModuleBuilder,\n private readonly token: OverrideToken<Key>,\n ) {}\n\n useValue(value: NoInfer<InferToken<Key>>): TestingModuleBuilder {\n return this.commit(defineProvider<Key>(this.token, { useValue: value }));\n }\n\n useClass(cls: Type<NoInfer<InferToken<Key>>>): TestingModuleBuilder {\n return this.commit(defineProvider<Key>(this.token, { useClass: cls }));\n }\n\n useFactory<const Inject extends readonly DependencyToken[] = readonly []>(options: {\n factory: (\n ...args: InferTokens<Inject>\n ) => NoInfer<InferToken<Key>> | Promise<NoInfer<InferToken<Key>>>;\n inject: Inject;\n }): TestingModuleBuilder {\n return this.commit(\n defineProvider<Key, Inject>(this.token, {\n useFactory: options.factory,\n inject: options.inject,\n }),\n );\n }\n}\n\nexport class TestingModuleBuilder {\n private overrides: OverrideEntry[] = [];\n\n constructor(private readonly metadata: ModuleOptions) {}\n\n overrideProvider<const Key extends Token>(token: OverrideToken<Key>): OverrideBy<Key> {\n return new OverrideBy<Key>((provider) => {\n this.addOverride({ token, provider });\n return this;\n }, token);\n }\n\n overrideGuard<const Guard extends Type<CanActivate>>(\n guard: OverrideToken<Guard>,\n ): OverrideBy<Guard> {\n return this.overrideProvider<Guard>(guard);\n }\n\n overridePipe<const Pipe extends Type<PipeTransform>>(\n pipe: OverrideToken<Pipe>,\n ): OverrideBy<Pipe> {\n return this.overrideProvider<Pipe>(pipe);\n }\n\n overrideInterceptor<const Interceptor extends Type<NestInterceptor>>(\n interceptor: OverrideToken<Interceptor>,\n ): OverrideBy<Interceptor> {\n return this.overrideProvider<Interceptor>(interceptor);\n }\n\n overrideFilter<const Filter extends Type<ExceptionFilter>>(\n filter: OverrideToken<Filter>,\n ): OverrideBy<Filter> {\n return this.overrideProvider<Filter>(filter);\n }\n\n private addOverride(entry: OverrideEntry): void {\n const idx = this.overrides.findIndex((o) => o.token === entry.token);\n if (idx !== -1) {\n this.overrides[idx] = entry;\n } else {\n this.overrides.push(entry);\n }\n }\n\n async compile(): Promise<TestingModule> {\n class TestRootModule {}\n MetadataRegistry.setModuleOptions(TestRootModule, {\n imports: this.metadata.imports,\n providers: this.metadata.providers,\n controllers: this.metadata.controllers,\n exports: this.metadata.exports,\n });\n\n // Use the framework's single bootstrap primitive. Hand-copying its\n // registrations caused test applications to drift from production (most\n // critically REQUEST_CONTEXT token/request-child behavior).\n const { container, routeManager, loader } = await bootstrap(TestRootModule);\n\n // Force-apply overrides into every module bucket that already holds the\n // token (plus root). Without this, controller constructor-injection (which\n // passes requestingModuleId to findRegistration) finds the module's own\n // registration first and never consults the root override. The default\n // 'all-existing' buckets replace every non-root bucket holding the token\n // and re-register at root — the supported form of the old private loop.\n for (const override of this.overrides) {\n container.replaceProvider(override.provider);\n }\n\n const app = new VelaApplication(container, routeManager);\n const instances = await loader.resolveAllInstances();\n app.setInstances(instances);\n\n await app.callOnModuleInit();\n await app.callOnApplicationBootstrap();\n await app.initRoutes();\n\n return new TestingModule(app, container);\n }\n}\n","import type { ModuleOptions } from '@velajs/vela';\nimport { TestingModuleBuilder } from './testing-module.builder.js';\n\nexport const Test = {\n createTestingModule(metadata: ModuleOptions): TestingModuleBuilder {\n return new TestingModuleBuilder(metadata);\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/http/path-utils.ts","../src/http/test-response.ts","../src/http/test-http-request.ts","../src/http/test-http-client.ts","../src/sse/test-sse-connection.ts","../src/sse/test-sse-request.ts","../src/testing-module.ts","../src/testing-module.builder.ts","../src/test.ts"],"sourcesContent":["// Ported from @stratal/testing (MIT, © Temitayo Fadojutimi).\n\n/**\n * Read the value at a dot-notation path (e.g. `data.user.id`).\n * Returns `undefined` when any segment along the way is null/undefined.\n */\nexport function getValueAtPath(obj: unknown, path: string): unknown {\n const parts = path.split('.');\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === null || current === undefined) {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n\n return current;\n}\n\n/**\n * Whether a dot-notation path exists on the object, even when the value at the\n * path is `null`/`undefined`. Distinguishes \"key present but null\" from \"key\n * absent\".\n */\nexport function hasValueAtPath(obj: unknown, path: string): boolean {\n const parts = path.split('.');\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === null || current === undefined) {\n return false;\n }\n\n if (typeof current !== 'object') {\n return false;\n }\n\n const record = current as Record<string, unknown>;\n\n if (!(part in record)) {\n return false;\n }\n\n current = record[part];\n }\n\n return true;\n}\n","// Ported from @stratal/testing (MIT, © Temitayo Fadojutimi), minus Macroable —\n// vela has no Macroable, so TestResponse is a plain class.\nimport { expect } from 'vitest';\nimport type { SchemaParser } from '@velajs/vela';\nimport {\n parseSchemaAsync,\n type SchemaOutput,\n type ValidationSchema,\n} from '@velajs/vela/validation';\nimport { getValueAtPath, hasValueAtPath } from './path-utils.js';\n\nfunction isJsonObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * TestResponse\n *\n * Wraps a `Response` with fluent, chainable assertions. Synchronous status /\n * header assertions return `this`; JSON assertions (which must read the body)\n * return `Promise<this>`.\n *\n * @example\n * ```ts\n * const res = await module.http.get('/users/1').send();\n * res.assertOk();\n * await res.assertJsonPath('data.id', 1);\n * ```\n */\nexport class TestResponse {\n private jsonData: Promise<unknown> | undefined;\n private textData: string | null = null;\n\n constructor(private readonly response: Response) {}\n\n /** The raw `Response`. */\n get raw(): Response {\n return this.response;\n }\n\n /** The response status code. */\n get status(): number {\n return this.response.status;\n }\n\n /** The response headers. */\n get headers(): Headers {\n return this.response.headers;\n }\n\n /** Read JSON as unknown, or infer validated output from a supplied parser. */\n json(): Promise<unknown>;\n json<Schema extends ValidationSchema>(parser: Schema): Promise<SchemaOutput<Schema>>;\n json<Value>(parser: SchemaParser<Value>): Promise<Value>;\n async json(parser?: ValidationSchema): Promise<unknown> {\n this.jsonData ??= this.response.clone().json();\n const value = await this.jsonData;\n return parser === undefined ? value : parseSchemaAsync(parser, value);\n }\n\n /** Read (and cache) the response body as text. */\n async text(): Promise<string> {\n this.textData ??= await this.response.clone().text();\n return this.textData;\n }\n\n // ============================================================\n // Status assertions\n // ============================================================\n\n /** Assert status is 200 OK. */\n assertOk(): this {\n return this.assertStatus(200);\n }\n\n /** Assert status is 201 Created. */\n assertCreated(): this {\n return this.assertStatus(201);\n }\n\n /** Assert status is 204 No Content. */\n assertNoContent(): this {\n return this.assertStatus(204);\n }\n\n /** Assert status is 400 Bad Request. */\n assertBadRequest(): this {\n return this.assertStatus(400);\n }\n\n /** Assert status is 401 Unauthorized. */\n assertUnauthorized(): this {\n return this.assertStatus(401);\n }\n\n /** Assert status is 403 Forbidden. */\n assertForbidden(): this {\n return this.assertStatus(403);\n }\n\n /** Assert status is 404 Not Found. */\n assertNotFound(): this {\n return this.assertStatus(404);\n }\n\n /** Assert status is 422 Unprocessable Entity. */\n assertUnprocessable(): this {\n return this.assertStatus(422);\n }\n\n /** Assert status is 500 Internal Server Error. */\n assertServerError(): this {\n return this.assertStatus(500);\n }\n\n /** Assert the response has the given status code. */\n assertStatus(expected: number): this {\n expect(this.response.status, `Expected status ${expected}, got ${this.response.status}`).toBe(\n expected,\n );\n return this;\n }\n\n /** Assert the status is in the 2xx range. */\n assertSuccessful(): this {\n expect(\n this.response.status >= 200 && this.response.status < 300,\n `Expected successful status (2xx), got ${this.response.status}`,\n ).toBe(true);\n return this;\n }\n\n // ============================================================\n // JSON assertions\n // ============================================================\n\n /** Assert each key in `expected` equals the corresponding top-level value. */\n async assertJson(expected: Record<string, unknown>): Promise<this> {\n const actual = await this.json();\n if (!isJsonObject(actual)) {\n expect.fail('Expected JSON body to be an object.');\n }\n\n for (const [key, value] of Object.entries(expected)) {\n expect(\n actual[key],\n `Expected JSON key \"${key}\" to be ${JSON.stringify(value)}, got ${JSON.stringify(actual[key])}`,\n ).toStrictEqual(value);\n }\n\n return this;\n }\n\n /** Assert the value at a dot-notation path equals `expected`. */\n async assertJsonPath(path: string, expected: unknown): Promise<this> {\n const json = await this.json();\n const actual = getValueAtPath(json, path);\n\n expect(\n actual,\n `Expected JSON path \"${path}\" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,\n ).toStrictEqual(expected);\n\n return this;\n }\n\n /** Assert every path/value pair in `expectations` matches (batch assert). */\n async assertJsonPaths(expectations: Record<string, unknown>): Promise<this> {\n const json = await this.json();\n\n for (const [path, expected] of Object.entries(expectations)) {\n const actual = getValueAtPath(json, path);\n expect(\n actual,\n `Expected JSON path \"${path}\" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,\n ).toStrictEqual(expected);\n }\n\n return this;\n }\n\n /** Assert the top-level JSON object has every key in `structure`. */\n async assertJsonStructure(structure: string[]): Promise<this> {\n const json = await this.json();\n if (!isJsonObject(json)) {\n expect.fail('Expected JSON body to be an object.');\n }\n\n for (const key of structure) {\n expect(\n key in json,\n `Expected JSON to have key \"${key}\", got keys: ${JSON.stringify(Object.keys(json))}`,\n ).toBe(true);\n }\n\n return this;\n }\n\n /** Assert a path exists (value may be anything, including `null`). */\n async assertJsonPathExists(path: string): Promise<this> {\n const json = await this.json();\n\n expect(hasValueAtPath(json, path), `Expected JSON path \"${path}\" to exist`).toBe(true);\n\n return this;\n }\n\n /** Assert a path does not exist. */\n async assertJsonPathMissing(path: string): Promise<this> {\n const json = await this.json();\n\n expect(hasValueAtPath(json, path), `Expected JSON path \"${path}\" to not exist`).toBe(false);\n\n return this;\n }\n\n /** Assert the value at a path satisfies a predicate. */\n async assertJsonPathMatches(path: string, matcher: (value: unknown) => boolean): Promise<this> {\n const json = await this.json();\n const value = getValueAtPath(json, path);\n\n expect(\n matcher(value),\n `Expected JSON path \"${path}\" to match predicate, got ${JSON.stringify(value)}`,\n ).toBe(true);\n\n return this;\n }\n\n /** Assert the string value at a path contains `substring`. */\n async assertJsonPathContains(path: string, substring: string): Promise<this> {\n const json = await this.json();\n const value = getValueAtPath(json, path);\n\n if (typeof value !== 'string') {\n expect.fail(`Expected JSON path \"${path}\" to be a string, got ${typeof value}`);\n }\n\n expect(\n value.includes(substring),\n `Expected JSON path \"${path}\" to contain \"${substring}\", got \"${String(value)}\"`,\n ).toBe(true);\n\n return this;\n }\n\n /** Assert the array value at a path includes `item`. */\n async assertJsonPathIncludes(path: string, item: unknown): Promise<this> {\n const json = await this.json();\n const value = getValueAtPath(json, path);\n\n if (!Array.isArray(value)) {\n expect.fail(`Expected JSON path \"${path}\" to be an array, got ${typeof value}`);\n }\n\n expect(\n value.includes(item),\n `Expected JSON path \"${path}\" to include ${JSON.stringify(item)}`,\n ).toBe(true);\n\n return this;\n }\n\n /** Assert the array value at a path has `count` items. */\n async assertJsonPathCount(path: string, count: number): Promise<this> {\n const json = await this.json();\n const value = getValueAtPath(json, path);\n\n if (!Array.isArray(value)) {\n expect.fail(`Expected JSON path \"${path}\" to be an array, got ${typeof value}`);\n }\n\n expect(\n value.length,\n `Expected JSON path \"${path}\" to have ${count} items, got ${value.length}`,\n ).toBe(count);\n\n return this;\n }\n\n // ============================================================\n // Header assertions\n // ============================================================\n\n /** Assert a header is present, optionally equal to `expected`. */\n assertHeader(name: string, expected?: string): this {\n const actual = this.response.headers.get(name);\n\n expect(actual !== null, `Expected header \"${name}\" to be present`).toBe(true);\n\n if (expected !== undefined) {\n expect(actual, `Expected header \"${name}\" to be \"${expected}\", got \"${actual}\"`).toBe(\n expected,\n );\n }\n\n return this;\n }\n\n /** Assert a header is absent. */\n assertHeaderMissing(name: string): this {\n const actual = this.response.headers.get(name);\n\n expect(actual, `Expected header \"${name}\" to be absent, but got \"${actual}\"`).toBeNull();\n\n return this;\n }\n}\n","// Adapted from @stratal/testing (MIT, © Temitayo Fadojutimi): stratal's hard\n// AuthService import is replaced by a generic auth-resolver seam so\n// @velajs/testing stays free of optional-package dependencies.\nimport type { ActingAsResolver, TestPrincipal, TestingModule } from '../testing-module.js';\nimport { TestResponse } from './test-response.js';\nimport type { TestHttpTransport } from './test-http-client.js';\n\n/**\n * TestHttpRequest\n *\n * Fluent builder for a single test HTTP request. `send()` builds a `Request`\n * and drives it through `module.fetch()` (the full Hono pipeline).\n *\n * @example\n * ```ts\n * const res = await module.http\n * .post('/users')\n * .withBody({ name: 'A' })\n * .withHeaders({ 'X-Trace': '1' })\n * .send();\n * res.assertCreated();\n * ```\n */\nexport class TestHttpRequest {\n private body: unknown = undefined;\n private readonly requestHeaders: Headers;\n private principal: TestPrincipal | null = null;\n private resolver: ActingAsResolver | null = null;\n\n constructor(\n private readonly method: string,\n private readonly path: string,\n headers: Headers,\n private readonly module: TestingModule | TestHttpTransport,\n private readonly host: string | null = null,\n ) {\n this.requestHeaders = new Headers(headers);\n }\n\n /** Set the request body (JSON-serialized on send). */\n withBody(data: unknown): this {\n this.body = data;\n return this;\n }\n\n /** Merge additional headers. */\n withHeaders(headers: Record<string, string>): this {\n for (const [key, value] of Object.entries(headers)) {\n this.requestHeaders.set(key, value);\n }\n return this;\n }\n\n /** Set `Content-Type: application/json`. */\n asJson(): this {\n this.requestHeaders.set('Content-Type', 'application/json');\n return this;\n }\n\n /**\n * Authenticate the request as `principal`. The `resolver` (or a default one\n * registered via `module.setAuthResolver`) turns the principal into request\n * headers. The resolver signature `(module, principal) => Promise<Headers>`\n * is the cross-package contract sibling packages (e.g. `@velajs/better-auth`)\n * build against.\n */\n actingAs(principal: TestPrincipal, resolver?: ActingAsResolver): this {\n this.principal = principal;\n this.resolver = resolver ?? null;\n return this;\n }\n\n /** Build the `Request` and send it through `module.fetch()`. */\n async send(): Promise<TestResponse> {\n await this.applyAuthentication();\n\n const hasBody = this.body !== undefined && this.body !== null;\n if (hasBody && !this.requestHeaders.has('Content-Type')) {\n this.requestHeaders.set('Content-Type', 'application/json');\n }\n\n const base = new URL(\n ('baseUrl' in this.module ? this.module.baseUrl : undefined) ?? 'http://localhost/',\n );\n if (this.host) base.host = this.host;\n const url = new URL(this.path, base);\n const request = new Request(url.toString(), {\n method: this.method,\n headers: this.requestHeaders,\n body: hasBody ? JSON.stringify(this.body) : null,\n });\n\n const response = await this.module.fetch(request);\n return new TestResponse(response);\n }\n\n private async applyAuthentication(): Promise<void> {\n if (!this.principal) return;\n\n if (!('getAuthResolver' in this.module)) {\n throw new Error(\n 'actingAs() requires a TestingModule; use explicit headers for a remote client.',\n );\n }\n const resolver = this.resolver ?? this.module.getAuthResolver();\n if (!resolver) {\n throw new Error(\n 'actingAs() requires an auth resolver. Pass one explicitly — ' +\n 'actingAs(principal, resolver) — or register a default with ' +\n 'module.setAuthResolver(resolver). For better-auth: ' +\n 'import { actingAs } from \"@velajs/better-auth/testing\".',\n );\n }\n\n const headers = await resolver(this.module, this.principal);\n for (const [key, value] of headers.entries()) {\n this.requestHeaders.set(key, value);\n }\n }\n}\n","// Adapted from @stratal/testing (MIT, © Temitayo Fadojutimi). Stratal's i18n\n// `withLocale` is dropped (vela i18n differs — optional follow-up).\nimport type { TestingModule } from '../testing-module.js';\nimport { TestHttpRequest } from './test-http-request.js';\n\n/** A Web-API transport for a live worker, SELF.fetch, or an in-process app. */\nexport interface TestHttpTransport {\n fetch(request: Request): Response | Promise<Response>;\n readonly baseUrl?: string;\n}\n\nexport interface TestHttpClientOptions {\n baseUrl: string;\n fetch?: (request: Request) => Response | Promise<Response>;\n}\n\n/** Create an independent HTTP client. Headers are explicit; requests are never retried. */\nexport function createTestHttpClient(options: TestHttpClientOptions): TestHttpClient {\n const url = new URL(options.baseUrl);\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new TypeError('Test HTTP baseUrl must use http: or https:');\n }\n return new TestHttpClient({\n baseUrl: url.toString(),\n fetch: options.fetch ?? ((request: Request) => globalThis.fetch(request)),\n });\n}\n\n/**\n * TestHttpClient\n *\n * Fluent entry point for test HTTP requests. `forHost`/`withHeaders` return a\n * new immutable client; the verb methods start a {@link TestHttpRequest}.\n *\n * @example\n * ```ts\n * const res = await module.http\n * .forHost('example.com')\n * .post('/users')\n * .withBody({ name: 'A' })\n * .send();\n * res.assertCreated();\n * ```\n */\nexport class TestHttpClient {\n constructor(\n private readonly module: TestingModule | TestHttpTransport,\n private readonly host: string | null = null,\n private readonly defaultHeaders: Headers = new Headers(),\n ) {}\n\n /**\n * Return a new client bound to `host`. Also sets the `Host` header so domain\n * routing works even when the runtime reads the header rather than the URL.\n */\n forHost(host: string): TestHttpClient {\n const headers = new Headers(this.defaultHeaders);\n headers.set('Host', host);\n return new TestHttpClient(this.module, host, headers);\n }\n\n /** Return a new client with additional default headers on every request. */\n withHeaders(headers: Record<string, string>): TestHttpClient {\n const next = new Headers(this.defaultHeaders);\n for (const [key, value] of Object.entries(headers)) {\n next.set(key, value);\n }\n return new TestHttpClient(this.module, this.host, next);\n }\n\n get(path: string): TestHttpRequest {\n return this.createRequest('GET', path);\n }\n\n post(path: string): TestHttpRequest {\n return this.createRequest('POST', path);\n }\n\n put(path: string): TestHttpRequest {\n return this.createRequest('PUT', path);\n }\n\n patch(path: string): TestHttpRequest {\n return this.createRequest('PATCH', path);\n }\n\n delete(path: string): TestHttpRequest {\n return this.createRequest('DELETE', path);\n }\n\n private createRequest(method: string, path: string): TestHttpRequest {\n return new TestHttpRequest(method, path, this.defaultHeaders, this.module, this.host);\n }\n}\n","// Ported near-verbatim from @stratal/testing (MIT, © Temitayo Fadojutimi).\n// Web-standard only (ReadableStream + TextDecoder), so it is edge-pure.\nimport { expect } from 'vitest';\n\n/** A parsed Server-Sent Event. */\nexport interface TestSseEvent {\n data: string;\n event?: string;\n id?: string;\n retry?: number;\n}\n\n/**\n * TestSseConnection\n *\n * Reads a streaming `text/event-stream` response body and exposes queue-based\n * wait/assert helpers over the parsed events.\n *\n * @example\n * ```ts\n * const sse = await module.sse('/stream/events').connect();\n * await sse.assertEventData('ping');\n * await sse.waitForEnd();\n * ```\n */\nexport class TestSseConnection {\n private readonly eventQueue: TestSseEvent[] = [];\n private eventWaiters: ((event: TestSseEvent) => void)[] = [];\n private streamEnded = false;\n private endWaiters: (() => void)[] = [];\n\n constructor(private readonly response: Response) {\n this.startReading();\n }\n\n /** The raw `Response`. */\n get raw(): Response {\n return this.response;\n }\n\n /** Wait for the next event (rejects after `timeout` ms). */\n async waitForEvent(timeout = 5000): Promise<TestSseEvent> {\n if (this.eventQueue.length > 0) {\n return this.eventQueue.shift()!;\n }\n\n if (this.streamEnded) {\n throw new Error('SSE: stream has ended, no more events');\n }\n\n return new Promise<TestSseEvent>((resolve, reject) => {\n const waiter = (event: TestSseEvent): void => {\n clearTimeout(timer);\n resolve(event);\n };\n\n const timer = setTimeout(() => {\n const index = this.eventWaiters.indexOf(waiter);\n if (index !== -1) this.eventWaiters.splice(index, 1);\n reject(new Error(`SSE: no event received within ${timeout}ms`));\n }, timeout);\n\n this.eventWaiters.push(waiter);\n });\n }\n\n /** Wait for the stream to end (rejects after `timeout` ms). */\n async waitForEnd(timeout = 5000): Promise<void> {\n if (this.streamEnded) return;\n\n return new Promise<void>((resolve, reject) => {\n const waiter = (): void => {\n clearTimeout(timer);\n resolve();\n };\n\n const timer = setTimeout(() => {\n const index = this.endWaiters.indexOf(waiter);\n if (index !== -1) this.endWaiters.splice(index, 1);\n reject(new Error(`SSE: stream did not end within ${timeout}ms`));\n }, timeout);\n\n this.endWaiters.push(waiter);\n });\n }\n\n /** Collect all remaining events until the stream ends. */\n async collectEvents(timeout = 5000): Promise<TestSseEvent[]> {\n const events: TestSseEvent[] = [];\n\n if (this.streamEnded) {\n return [...this.eventQueue.splice(0)];\n }\n\n return new Promise<TestSseEvent[]>((resolve, reject) => {\n const originalDispatch = this.dispatchEvent.bind(this);\n this.dispatchEvent = (event: TestSseEvent): void => {\n events.push(event);\n originalDispatch(event);\n };\n\n const endWaiter = (): void => {\n clearTimeout(timer);\n this.dispatchEvent = originalDispatch;\n resolve(events);\n };\n\n const timer = setTimeout(() => {\n this.dispatchEvent = originalDispatch;\n const index = this.endWaiters.indexOf(endWaiter);\n if (index !== -1) this.endWaiters.splice(index, 1);\n reject(new Error(`SSE: stream did not end within ${timeout}ms`));\n }, timeout);\n\n events.push(...this.eventQueue.splice(0));\n\n this.endWaiters.push(endWaiter);\n });\n }\n\n /** Assert the next event matches the expected partial shape. */\n async assertEvent(expected: Partial<TestSseEvent>, timeout = 5000): Promise<void> {\n const event = await this.waitForEvent(timeout);\n expect(event).toMatchObject(expected);\n }\n\n /** Assert the next event's `data` equals `expected`. */\n async assertEventData(expected: string, timeout = 5000): Promise<void> {\n const event = await this.waitForEvent(timeout);\n expect(event.data, `Expected SSE data \"${expected}\", got \"${event.data}\"`).toBe(expected);\n }\n\n /** Assert the next event's `data` is JSON equal to `expected`. */\n async assertJsonEventData<T>(expected: T, timeout = 5000): Promise<void> {\n const event = await this.waitForEvent(timeout);\n const parsed = JSON.parse(event.data) as unknown;\n expect(parsed).toEqual(expected);\n }\n\n private startReading(): void {\n const body = this.response.body;\n if (!body) {\n this.streamEnded = true;\n return;\n }\n\n const reader = body.getReader() as ReadableStreamDefaultReader<Uint8Array>;\n const decoder = new TextDecoder();\n let buffer = '';\n\n const read = async (): Promise<void> => {\n try {\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) {\n if (buffer.trim()) {\n const event = this.parseEvent(buffer);\n if (event) this.dispatchEvent(event);\n }\n this.endStream();\n return;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n const parts = buffer.split('\\n\\n');\n buffer = parts.pop()!;\n\n for (const part of parts) {\n if (!part.trim()) continue;\n const event = this.parseEvent(part);\n if (event) this.dispatchEvent(event);\n }\n }\n } catch {\n this.endStream();\n }\n };\n\n void read();\n }\n\n private endStream(): void {\n this.streamEnded = true;\n for (const waiter of this.endWaiters) {\n waiter();\n }\n this.endWaiters = [];\n }\n\n private parseEvent(raw: string): TestSseEvent | null {\n const lines = raw.split('\\n');\n const dataLines: string[] = [];\n let event: string | undefined;\n let id: string | undefined;\n let retry: number | undefined;\n\n for (const line of lines) {\n if (line.startsWith(':')) continue; // comment line\n\n const colonIndex = line.indexOf(':');\n if (colonIndex === -1) continue;\n\n const field = line.slice(0, colonIndex);\n const value =\n line[colonIndex + 1] === ' ' ? line.slice(colonIndex + 2) : line.slice(colonIndex + 1);\n\n switch (field) {\n case 'data':\n dataLines.push(value);\n break;\n case 'event':\n event = value;\n break;\n case 'id':\n id = value;\n break;\n case 'retry': {\n const parsed = parseInt(value, 10);\n if (!Number.isNaN(parsed)) retry = parsed;\n break;\n }\n }\n }\n\n if (dataLines.length === 0) return null;\n\n const result: TestSseEvent = { data: dataLines.join('\\n') };\n if (event !== undefined) result.event = event;\n if (id !== undefined) result.id = id;\n if (retry !== undefined) result.retry = retry;\n\n return result;\n }\n\n private dispatchEvent(event: TestSseEvent): void {\n if (this.eventWaiters.length > 0) {\n this.eventWaiters.shift()!(event);\n } else {\n this.eventQueue.push(event);\n }\n }\n}\n","// Adapted from @stratal/testing (MIT, © Temitayo Fadojutimi). Auth uses the\n// generic resolver seam instead of a hard AuthService import.\nimport { expect } from 'vitest';\nimport type { ActingAsResolver, TestPrincipal, TestingModule } from '../testing-module.js';\nimport { TestSseConnection } from './test-sse-connection.js';\n\n/**\n * TestSseRequest\n *\n * Builder for a Server-Sent Events connection. `connect()` issues a GET through\n * `module.fetch()`, asserts a `text/event-stream` 200, and wraps the streaming\n * body in a {@link TestSseConnection}.\n *\n * @example\n * ```ts\n * const sse = await module.sse('/stream/events').connect();\n * await sse.assertEvent({ event: 'message', data: 'hello' });\n * ```\n */\nexport class TestSseRequest {\n private readonly requestHeaders = new Headers();\n private principal: TestPrincipal | null = null;\n private resolver: ActingAsResolver | null = null;\n\n constructor(\n private readonly path: string,\n private readonly module: TestingModule,\n ) {}\n\n /** Merge additional headers onto the SSE request. */\n withHeaders(headers: Record<string, string>): this {\n for (const [key, value] of Object.entries(headers)) {\n this.requestHeaders.set(key, value);\n }\n return this;\n }\n\n /** Authenticate the connection (see {@link TestHttpRequest.actingAs}). */\n actingAs(principal: TestPrincipal, resolver?: ActingAsResolver): this {\n this.principal = principal;\n this.resolver = resolver ?? null;\n return this;\n }\n\n /** Open the stream and return a live {@link TestSseConnection}. */\n async connect(): Promise<TestSseConnection> {\n await this.applyAuthentication();\n\n this.requestHeaders.set('Accept', 'text/event-stream');\n\n const url = new URL(this.path, 'http://localhost');\n const request = new Request(url.toString(), { headers: this.requestHeaders });\n\n const response = await this.module.fetch(request);\n\n expect(response.status, `Expected status 200, got ${response.status}`).toBe(200);\n\n const contentType = response.headers.get('content-type') ?? '';\n expect(\n contentType.includes('text/event-stream'),\n `Expected content-type \"text/event-stream\", got \"${contentType}\"`,\n ).toBe(true);\n\n return new TestSseConnection(response);\n }\n\n private async applyAuthentication(): Promise<void> {\n if (!this.principal) return;\n\n const resolver = this.resolver ?? this.module.getAuthResolver();\n if (!resolver) {\n throw new Error(\n 'actingAs() requires an auth resolver. Pass one explicitly or register ' +\n 'a default with module.setAuthResolver(resolver).',\n );\n }\n\n const headers = await resolver(this.module, this.principal);\n for (const [key, value] of headers.entries()) {\n this.requestHeaders.set(key, value);\n }\n }\n}\n","import { Context } from 'hono';\nimport {\n REQUEST_CONTEXT,\n runInEntrypointScope,\n type InferToken,\n type Token,\n type Type,\n type VelaApplication,\n} from '@velajs/vela';\nimport { createRequestContext, setRequestContainer, type Container } from '@velajs/vela/internal';\nimport { SeederRegistry, type ISeeder } from '@velajs/vela/seeder';\nimport { expect } from 'vitest';\nimport type { TestDatabase } from './db/test-database.js';\nimport { TestHttpClient } from './http/test-http-client.js';\nimport { TestSseRequest } from './sse/test-sse-request.js';\nimport { TestWsRequest } from './ws/test-ws-request.js';\n\n/** A test principal — an opaque object the auth resolver turns into headers. */\nexport type TestPrincipal = Record<string, unknown>;\n\n/**\n * Turns a principal into request headers (session cookie, bearer token, …).\n * The signature `(module, principal) => Promise<Headers>` is a cross-package\n * contract: sibling packages (e.g. `@velajs/better-auth/testing`) build a\n * resolver against it. Kept generic so `@velajs/testing` needs no auth deps.\n */\nexport type ActingAsResolver = (\n module: TestingModule,\n principal: TestPrincipal,\n) => Promise<Headers>;\n\ntype HonoApp = ReturnType<VelaApplication['getHonoApp']>;\n\n/**\n * TestingModule\n *\n * The compiled test harness. Beyond `get`/`createApplication`/`close`, it adds\n * Laravel-flavored ergonomics: a fluent HTTP client, SSE/WS builders, request-\n * scope execution, seeding, and database assertion wrappers.\n *\n * @example\n * ```ts\n * const module = await Test.createTestingModule({ imports: [AppModule] }).compile();\n * await module.http.post('/users').withBody({ name: 'A' }).send()\n * .then((r) => r.assertCreated());\n * ```\n */\nexport class TestingModule {\n #http: TestHttpClient | null = null;\n #honoApp: HonoApp | null = null;\n #authResolver: ActingAsResolver | null = null;\n\n readonly #app: VelaApplication;\n readonly #container: Container;\n readonly #cleanups: Array<() => void | Promise<void>> = [];\n #closing: Promise<void> | undefined;\n readonly #pending = new Set<Promise<unknown>>();\n\n constructor(app: VelaApplication, container: Container) {\n this.#app = app;\n this.#container = container;\n }\n\n /** Resolve a provider from the root container. */\n get<const Key extends Token>(token: Key): InferToken<Key> {\n this.#assertOpen();\n return this.#container.resolve(token);\n }\n\n /** Build (once) and return the underlying application. */\n async createApplication(): Promise<VelaApplication> {\n this.#assertOpen();\n return this.#app;\n }\n\n /** Lazy fluent HTTP client bound to this module. */\n get http(): TestHttpClient {\n this.#http ??= new TestHttpClient(this);\n return this.#http;\n }\n\n /** Start an SSE connection builder for `path`. */\n sse(path: string): TestSseRequest {\n return new TestSseRequest(path, this);\n }\n\n /** Start a WebSocket connection builder for `path` (needs a transport adapter). */\n ws(path: string): TestWsRequest {\n return new TestWsRequest(path, this);\n }\n\n /**\n * Drive a `Request` through the full Hono pipeline. The Hono app is built\n * once and reused across requests.\n */\n async fetch(...args: Parameters<HonoApp['fetch']>): Promise<Response> {\n const hono = await this.ensureHono();\n return hono.fetch(...args);\n }\n\n /**\n * Register a default auth resolver used by `actingAs(principal)` when no\n * resolver is passed explicitly.\n */\n setAuthResolver(resolver: ActingAsResolver): this {\n this.#authResolver = resolver;\n return this;\n }\n\n /** The default auth resolver, if one was registered. */\n getAuthResolver(): ActingAsResolver | null {\n return this.#authResolver;\n }\n\n /**\n * Run `callback` inside a request-scoped child container seeded with a real\n * RequestContext, so REQUEST-scoped providers (and anything injecting\n * `REQUEST_CONTEXT`) resolve. The child is disposed afterwards.\n */\n runInRequestScope<T>(callback: (container: Container) => T | Promise<T>): Promise<T> {\n this.#assertOpen();\n const operation = this.#runInRequestScope(callback);\n this.#pending.add(operation);\n // Observe completion without creating an unhandled rejected promise.\n void operation.then(\n () => this.#pending.delete(operation),\n () => this.#pending.delete(operation),\n );\n return operation;\n }\n\n async #runInRequestScope<T>(callback: (container: Container) => T | Promise<T>): Promise<T> {\n return runInEntrypointScope(this.#container, async (child) => {\n const hono = new Context(new Request('http://localhost/'));\n setRequestContainer(hono, child);\n child.setRequestInstance(REQUEST_CONTEXT, createRequestContext(hono));\n return callback(child);\n });\n }\n\n /**\n * Run the given `@Seeder()` classes, each in its own request scope. Throws if\n * a class is not a registered seeder. Requires `SeederModule` (or the seeders\n * themselves) to be present in the module graph.\n */\n async seed(...SeederClasses: Type<ISeeder>[]): Promise<void> {\n const registry = this.#container.resolve(SeederRegistry);\n const known = new Set<unknown>(registry.list().map((s) => s.target));\n\n for (const SeederClass of SeederClasses) {\n if (!known.has(SeederClass)) {\n throw new Error(\n `Seeder \"${SeederClass.name}\" is not registered. Add it to a module's ` +\n 'providers or SeederModule.forRoot({ seeders: [...] }).',\n );\n }\n await this.runInRequestScope(async (child) => {\n const instance = child.resolve(SeederClass);\n await instance.run();\n });\n }\n }\n\n /** Assert a row matching `where` exists in `table` (via a {@link TestDatabase}). */\n async assertDatabaseHas(\n db: TestDatabase,\n table: string,\n where: Record<string, unknown>,\n ): Promise<void> {\n const exists = await db.has(table, where);\n expect(exists, `Expected ${table} to have a row matching ${JSON.stringify(where)}`).toBe(true);\n }\n\n /** Assert no row matching `where` exists in `table`. */\n async assertDatabaseMissing(\n db: TestDatabase,\n table: string,\n where: Record<string, unknown>,\n ): Promise<void> {\n const exists = await db.has(table, where);\n expect(exists, `Expected ${table} NOT to have a row matching ${JSON.stringify(where)}`).toBe(\n false,\n );\n }\n\n /** Assert `table` has exactly `expected` rows. */\n async assertDatabaseCount(db: TestDatabase, table: string, expected: number): Promise<void> {\n const actual = await db.count(table);\n expect(actual, `Expected ${table} count ${expected}, got ${actual}`).toBe(expected);\n }\n\n /** Register a fixture/transport cleanup, awaited in reverse registration order. */\n onClose(cleanup: () => void | Promise<void>): this {\n this.#assertOpen();\n this.#cleanups.push(cleanup);\n return this;\n }\n\n /** Fully dispose the application and owned transports. Concurrent calls share completion. */\n close(signal?: string): Promise<void> {\n this.#closing ??= Promise.resolve().then(() => this.#dispose(signal));\n return this.#closing;\n }\n\n async #dispose(signal?: string): Promise<void> {\n const failures: unknown[] = [];\n for (const cleanup of this.#cleanups.splice(0).toReversed()) {\n try {\n // Cleanup order follows reverse fixture acquisition.\n // eslint-disable-next-line no-await-in-loop\n await cleanup();\n } catch (error) {\n failures.push(error);\n }\n }\n // Existing scopes may still use singletons. Drain them before closing the root.\n await Promise.allSettled(this.#pending);\n try {\n await this.#app.dispose(signal);\n } catch (error) {\n failures.push(error);\n }\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, 'Testing module cleanup failed');\n }\n\n #assertOpen(): void {\n if (this.#closing) throw new Error('Testing module is closing or closed');\n }\n\n private async ensureHono(): Promise<HonoApp> {\n this.#assertOpen();\n if (!this.#honoApp) {\n const app = await this.createApplication();\n this.#honoApp = app.getHonoApp();\n }\n return this.#honoApp;\n }\n}\n","import {\n defineProvider,\n type CanActivate,\n type DependencyToken,\n type ExceptionFilter,\n type InferToken,\n type InferTokens,\n type ModuleOptions,\n type NestInterceptor,\n type PipeTransform,\n type ProviderDefinition,\n type Token,\n type Type,\n} from '@velajs/vela';\nimport { MetadataRegistry, bootstrap, finalizeApplication } from '@velajs/vela/internal';\nimport { TestingModule } from './testing-module.js';\n\ninterface OverrideEntry {\n token: Token;\n provider: ProviderDefinition;\n}\n\n// Keep the same token proof required by core provider authoring. An erased\n// registry identity can be resolved, but cannot authorize a typed replacement.\ntype OverrideToken<Key extends Token> = Parameters<typeof defineProvider<Key>>[0];\n\nexport class OverrideBy<Key extends Token> {\n constructor(\n private readonly commit: (provider: ProviderDefinition) => TestingModuleBuilder,\n private readonly token: OverrideToken<Key>,\n ) {}\n\n useValue(value: NoInfer<InferToken<Key>>): TestingModuleBuilder {\n return this.commit(defineProvider<Key>(this.token, { useValue: value }));\n }\n\n useClass(cls: Type<NoInfer<InferToken<Key>>>): TestingModuleBuilder {\n return this.commit(defineProvider<Key>(this.token, { useClass: cls }));\n }\n\n useFactory<const Inject extends readonly DependencyToken[] = readonly []>(options: {\n factory: (\n ...args: InferTokens<Inject>\n ) => NoInfer<InferToken<Key>> | Promise<NoInfer<InferToken<Key>>>;\n inject: Inject;\n }): TestingModuleBuilder {\n return this.commit(\n defineProvider<Key, Inject>(this.token, {\n useFactory: options.factory,\n inject: options.inject,\n }),\n );\n }\n}\n\nexport class TestingModuleBuilder {\n #overrides: OverrideEntry[] = [];\n readonly #metadata: ModuleOptions;\n\n constructor(metadata: ModuleOptions) {\n this.#metadata = metadata;\n }\n\n overrideProvider<const Key extends Token>(token: OverrideToken<Key>): OverrideBy<Key> {\n return new OverrideBy<Key>((provider) => {\n this.addOverride({ token, provider });\n return this;\n }, token);\n }\n\n overrideGuard<const Guard extends Type<CanActivate>>(\n guard: OverrideToken<Guard>,\n ): OverrideBy<Guard> {\n return this.overrideProvider<Guard>(guard);\n }\n\n overridePipe<const Pipe extends Type<PipeTransform>>(\n pipe: OverrideToken<Pipe>,\n ): OverrideBy<Pipe> {\n return this.overrideProvider<Pipe>(pipe);\n }\n\n overrideInterceptor<const Interceptor extends Type<NestInterceptor>>(\n interceptor: OverrideToken<Interceptor>,\n ): OverrideBy<Interceptor> {\n return this.overrideProvider<Interceptor>(interceptor);\n }\n\n overrideFilter<const Filter extends Type<ExceptionFilter>>(\n filter: OverrideToken<Filter>,\n ): OverrideBy<Filter> {\n return this.overrideProvider<Filter>(filter);\n }\n\n private addOverride(entry: OverrideEntry): void {\n const idx = this.#overrides.findIndex((o) => o.token === entry.token);\n if (idx !== -1) {\n this.#overrides[idx] = entry;\n } else {\n this.#overrides.push(entry);\n }\n }\n\n async compile(): Promise<TestingModule> {\n class TestRootModule {}\n MetadataRegistry.setModuleOptions(TestRootModule, {\n imports: this.#metadata.imports,\n providers: this.#metadata.providers,\n controllers: this.#metadata.controllers,\n exports: this.#metadata.exports,\n });\n\n // Use the framework's single bootstrap primitive. Hand-copying its\n // registrations caused test applications to drift from production (most\n // critically REQUEST_CONTEXT token/request-child behavior).\n const prepared = await bootstrap(TestRootModule);\n const { container } = prepared;\n\n // Force-apply overrides into every module bucket that already holds the\n // token (plus root). Without this, controller constructor-injection (which\n // passes requestingModuleId to findRegistration) finds the module's own\n // registration first and never consults the root override. The default\n // 'all-existing' buckets replace every non-root bucket holding the token\n // and re-register at root — the supported form of the old private loop.\n for (const override of this.#overrides) {\n container.replaceProvider(override.provider);\n }\n\n const app = await finalizeApplication(prepared);\n\n return new TestingModule(app, container);\n }\n}\n","import type { ModuleOptions } from '@velajs/vela';\nimport { TestingModuleBuilder } from './testing-module.builder.js';\n\nexport const Test = {\n createTestingModule(metadata: ModuleOptions): TestingModuleBuilder {\n return new TestingModuleBuilder(metadata);\n },\n};\n"],"mappings":"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"}
@@ -17,25 +17,58 @@ async function ensureServer(module) {
17
17
  const { registerWebSocketGateways } = await importOptional("@velajs/vela/websocket-node");
18
18
  const app = await module.createApplication();
19
19
  const hono = app.getHonoApp();
20
- const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: hono });
20
+ const { injectWebSocket, upgradeWebSocket, wss } = createNodeWebSocket({ app: hono });
21
21
  registerWebSocketGateways(app, upgradeWebSocket);
22
- const { server, port } = await new Promise((resolve) => {
23
- const s = serve({
24
- fetch: hono.fetch,
25
- port: 0
26
- }, (info) => {
27
- resolve({
28
- server: s,
29
- port: info.port
22
+ const sockets = /* @__PURE__ */ new Set();
23
+ let server;
24
+ const cleanup = async () => {
25
+ serversByModule.delete(module);
26
+ for (const socket of sockets) socket.destroy();
27
+ const failures = (await Promise.allSettled([new Promise((resolve, reject) => {
28
+ if (!server?.listening) return resolve();
29
+ server.close((error) => {
30
+ if (error) reject(error);
31
+ else resolve();
32
+ });
33
+ }), new Promise((resolve, reject) => {
34
+ if (!wss) return resolve();
35
+ wss.close((error) => {
36
+ if (error) reject(error);
37
+ else resolve();
38
+ });
39
+ })])).flatMap((outcome) => outcome.status === "rejected" ? [outcome.reason] : []);
40
+ if (failures.length === 1) throw failures[0];
41
+ if (failures.length > 1) throw new AggregateError(failures, "WebSocket server cleanup failed");
42
+ };
43
+ try {
44
+ const port = await new Promise((resolve, reject) => {
45
+ server = serve({
46
+ fetch: hono.fetch,
47
+ port: 0
48
+ }, (info) => resolve(info.port));
49
+ server.on("error", reject);
50
+ server.on("connection", (socket) => {
51
+ sockets.add(socket);
52
+ socket.on("close", () => sockets.delete(socket));
30
53
  });
31
54
  });
32
- });
33
- injectWebSocket(server);
34
- server.unref?.();
35
- return { port };
55
+ if (!server) throw new Error("WebSocket test server did not start");
56
+ injectWebSocket(server);
57
+ module.onClose(cleanup);
58
+ server.unref();
59
+ return { port };
60
+ } catch (error) {
61
+ await cleanup();
62
+ throw error;
63
+ }
36
64
  })();
37
65
  serversByModule.set(module, started);
38
- return started;
66
+ try {
67
+ return await started;
68
+ } catch (error) {
69
+ serversByModule.delete(module);
70
+ throw error;
71
+ }
39
72
  }
40
73
  async function openClient(url, headers) {
41
74
  const headerEntries = [...headers.entries()];
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/websocket-node/index.ts"],"sourcesContent":["/**\n * `@velajs/testing/websocket-node`\n *\n * Node transport adapter for `module.ws(path).connect()`. Importing this module\n * (for its side effect) registers a WebSocket connector that:\n * 1. builds the app's Hono instance and a `@hono/node-ws` upgrade factory,\n * 2. registers every `@WebSocketGateway` via `@velajs/vela/websocket-node`,\n * 3. serves it on an ephemeral loopback port, and\n * 4. opens a real client `WebSocket` against it.\n *\n * `@hono/node-ws` and `@hono/node-server` are OPTIONAL peers, imported\n * dynamically — this module loads without them and only errs at `connect()`\n * time with an actionable message. Their types are declared locally so the\n * package typechecks without the peers installed. Cloudflare Durable-Object\n * WebSockets are exercised via `@velajs/cloudflare` + the workerd pool, not\n * this adapter.\n */\nimport type { VelaApplication } from '@velajs/vela';\nimport type { UpgradeWebSocket } from 'hono/ws';\nimport type { TestingModule } from '../testing-module.js';\nimport { TestWsConnection } from '../ws/test-ws-connection.js';\nimport { registerWsConnector } from '../ws/test-ws-request.js';\n\n// Minimal shapes for the optional peers (avoids `typeof import(...)` which would\n// fail to resolve when the peers are not installed).\ninterface NodeServerModule {\n serve: (\n options: { fetch: unknown; port: number },\n onListening?: (info: { port: number }) => void,\n ) => unknown;\n}\ninterface NodeWsModule {\n createNodeWebSocket: (options: { app: unknown }) => {\n injectWebSocket: (server: unknown) => void;\n upgradeWebSocket: UpgradeWebSocket;\n };\n}\ninterface VelaWsNodeModule {\n registerWebSocketGateways: (app: VelaApplication, upgradeWebSocket: UpgradeWebSocket) => void;\n}\ninterface WsPackage {\n default: new (\n url: string,\n protocols?: unknown,\n options?: { headers: Record<string, string> },\n ) => WebSocket;\n}\n\ninterface RunningServer {\n port: number;\n}\n\n// One backing HTTP server per module; gateways are registered exactly once.\nconst serversByModule = new WeakMap<TestingModule, Promise<RunningServer>>();\n\nasync function importOptional<T>(specifier: string): Promise<T> {\n try {\n return (await import(specifier)) as T;\n } catch (error) {\n throw new Error(\n `[@velajs/testing/websocket-node] \"${specifier}\" is required for WebSocket ` +\n 'connect() on Node but is not installed. Install the optional peers: ' +\n '`npm i -D @hono/node-ws @hono/node-server`.',\n { cause: error },\n );\n }\n}\n\nasync function ensureServer(module: TestingModule): Promise<RunningServer> {\n const existing = serversByModule.get(module);\n if (existing) return existing;\n\n const started = (async (): Promise<RunningServer> => {\n const { serve } = await importOptional<NodeServerModule>('@hono/node-server');\n const { createNodeWebSocket } = await importOptional<NodeWsModule>('@hono/node-ws');\n const { registerWebSocketGateways } = await importOptional<VelaWsNodeModule>(\n '@velajs/vela/websocket-node',\n );\n\n const app = await module.createApplication();\n const hono = app.getHonoApp();\n\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: hono });\n registerWebSocketGateways(app, upgradeWebSocket);\n\n const { server, port } = await new Promise<{ server: unknown; port: number }>((resolve) => {\n const s = serve({ fetch: hono.fetch, port: 0 }, (info) => {\n resolve({ server: s, port: info.port });\n });\n });\n\n injectWebSocket(server);\n // Never let the test server keep the process alive; the runtime reclaims it\n // on exit. Tests don't need an explicit server teardown hook this way.\n (server as { unref?: () => void }).unref?.();\n\n return { port };\n })();\n\n serversByModule.set(module, started);\n return started;\n}\n\nasync function openClient(url: string, headers: Headers): Promise<WebSocket> {\n const headerEntries = [...headers.entries()];\n\n // The standard WebSocket client cannot set arbitrary upgrade headers. When\n // headers are needed (e.g. auth cookies), fall back to the `ws` package which\n // supports them; otherwise use the runtime's global WebSocket.\n if (headerEntries.length > 0) {\n const wsSpecifier = 'ws';\n try {\n const wsModule = (await import(wsSpecifier)) as WsPackage;\n return new wsModule.default(url, undefined, {\n headers: Object.fromEntries(headerEntries),\n });\n } catch {\n // `ws` not installed — proceed with the global client (headers dropped).\n }\n }\n\n if (typeof WebSocket === 'undefined') {\n throw new Error(\n '[@velajs/testing/websocket-node] No global WebSocket. Use Node >=22 or ' +\n 'install the `ws` package.',\n );\n }\n return new WebSocket(url);\n}\n\nregisterWsConnector(async (module, path, headers) => {\n const { port } = await ensureServer(module);\n const url = `ws://127.0.0.1:${port}${path}`;\n const ws = await openClient(url, headers);\n\n await new Promise<void>((resolve, reject) => {\n const cleanup = (): void => {\n ws.removeEventListener('open', onOpen);\n ws.removeEventListener('error', onError as EventListener);\n };\n const onOpen = (): void => {\n cleanup();\n resolve();\n };\n const onError = (event: unknown): void => {\n cleanup();\n reject(new Error(`WebSocket failed to open: ${String(event)}`));\n };\n ws.addEventListener('open', onOpen);\n ws.addEventListener('error', onError as EventListener);\n });\n\n return new TestWsConnection(ws);\n});\n"],"mappings":";;AAqDA,MAAM,kCAAkB,IAAI,QAA+C;AAE3E,eAAe,eAAkB,WAA+B;CAC9D,IAAI;EACF,OAAQ,MAAM,OAAO;CACvB,SAAS,OAAO;EACd,MAAM,IAAI,MACR,qCAAqC,UAAU,gJAG/C,EAAE,OAAO,MAAM,CACjB;CACF;AACF;AAEA,eAAe,aAAa,QAA+C;CACzE,MAAM,WAAW,gBAAgB,IAAI,MAAM;CAC3C,IAAI,UAAU,OAAO;CAErB,MAAM,WAAW,YAAoC;EACnD,MAAM,EAAE,UAAU,MAAM,eAAiC,mBAAmB;EAC5E,MAAM,EAAE,wBAAwB,MAAM,eAA6B,eAAe;EAClF,MAAM,EAAE,8BAA8B,MAAM,eAC1C,6BACF;EAEA,MAAM,MAAM,MAAM,OAAO,kBAAkB;EAC3C,MAAM,OAAO,IAAI,WAAW;EAE5B,MAAM,EAAE,iBAAiB,qBAAqB,oBAAoB,EAAE,KAAK,KAAK,CAAC;EAC/E,0BAA0B,KAAK,gBAAgB;EAE/C,MAAM,EAAE,QAAQ,SAAS,MAAM,IAAI,SAA4C,YAAY;GACzF,MAAM,IAAI,MAAM;IAAE,OAAO,KAAK;IAAO,MAAM;GAAE,IAAI,SAAS;IACxD,QAAQ;KAAE,QAAQ;KAAG,MAAM,KAAK;IAAK,CAAC;GACxC,CAAC;EACH,CAAC;EAED,gBAAgB,MAAM;EAGtB,OAAmC,QAAQ;EAE3C,OAAO,EAAE,KAAK;CAChB,EAAA,CAAG;CAEH,gBAAgB,IAAI,QAAQ,OAAO;CACnC,OAAO;AACT;AAEA,eAAe,WAAW,KAAa,SAAsC;CAC3E,MAAM,gBAAgB,CAAC,GAAG,QAAQ,QAAQ,CAAC;CAK3C,IAAI,cAAc,SAAS,GAAG;EAC5B,MAAM,cAAc;EACpB,IAAI;GAEF,OAAO,KAAI,OADa,OAAO,cAAA,CACX,QAAQ,KAAK,KAAA,GAAW,EAC1C,SAAS,OAAO,YAAY,aAAa,EAC3C,CAAC;EACH,QAAQ,CAER;CACF;CAEA,IAAI,OAAO,cAAc,aACvB,MAAM,IAAI,MACR,kGAEF;CAEF,OAAO,IAAI,UAAU,GAAG;AAC1B;AAEA,oBAAoB,OAAO,QAAQ,MAAM,YAAY;CACnD,MAAM,EAAE,SAAS,MAAM,aAAa,MAAM;CAE1C,MAAM,KAAK,MAAM,WAAW,kBADE,OAAO,QACJ,OAAO;CAExC,MAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,MAAM,gBAAsB;GAC1B,GAAG,oBAAoB,QAAQ,MAAM;GACrC,GAAG,oBAAoB,SAAS,OAAwB;EAC1D;EACA,MAAM,eAAqB;GACzB,QAAQ;GACR,QAAQ;EACV;EACA,MAAM,WAAW,UAAyB;GACxC,QAAQ;GACR,uBAAO,IAAI,MAAM,6BAA6B,OAAO,KAAK,GAAG,CAAC;EAChE;EACA,GAAG,iBAAiB,QAAQ,MAAM;EAClC,GAAG,iBAAiB,SAAS,OAAwB;CACvD,CAAC;CAED,OAAO,IAAI,iBAAiB,EAAE;AAChC,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/websocket-node/index.ts"],"sourcesContent":["/**\n * `@velajs/testing/websocket-node`\n *\n * Node transport adapter for `module.ws(path).connect()`. Importing this module\n * (for its side effect) registers a WebSocket connector that:\n * 1. builds the app's Hono instance and a `@hono/node-ws` upgrade factory,\n * 2. registers every `@WebSocketGateway` via `@velajs/vela/websocket-node`,\n * 3. serves it on an ephemeral loopback port, and\n * 4. opens a real client `WebSocket` against it.\n *\n * `@hono/node-ws` and `@hono/node-server` are OPTIONAL peers, imported\n * dynamically — this module loads without them and only errs at `connect()`\n * time with an actionable message. Their types are declared locally so the\n * package typechecks without the peers installed. Cloudflare Durable-Object\n * WebSockets are exercised via `@velajs/cloudflare` + the workerd pool, not\n * this adapter.\n */\nimport type { Server } from 'node:http';\nimport type { Socket } from 'node:net';\nimport type { VelaApplication } from '@velajs/vela';\nimport type { UpgradeWebSocket } from 'hono/ws';\nimport type { TestingModule } from '../testing-module.js';\nimport { TestWsConnection } from '../ws/test-ws-connection.js';\nimport { registerWsConnector } from '../ws/test-ws-request.js';\n\n// Minimal shapes for the optional peers (avoids `typeof import(...)` which would\n// fail to resolve when the peers are not installed).\ninterface NodeServerModule {\n serve: (\n options: { fetch: unknown; port: number },\n onListening?: (info: { port: number }) => void,\n ) => Server;\n}\ninterface NodeWsModule {\n createNodeWebSocket: (options: { app: unknown }) => {\n injectWebSocket: (server: unknown) => void;\n upgradeWebSocket: UpgradeWebSocket;\n wss?: { close(callback: (error?: Error) => void): void };\n };\n}\ninterface VelaWsNodeModule {\n registerWebSocketGateways: (app: VelaApplication, upgradeWebSocket: UpgradeWebSocket) => void;\n}\ninterface WsPackage {\n default: new (\n url: string,\n protocols?: unknown,\n options?: { headers: Record<string, string> },\n ) => WebSocket;\n}\n\ninterface RunningServer {\n port: number;\n}\n\n// One backing HTTP server per module; gateways are registered exactly once.\nconst serversByModule = new WeakMap<TestingModule, Promise<RunningServer>>();\n\nasync function importOptional<T>(specifier: string): Promise<T> {\n try {\n return (await import(specifier)) as T;\n } catch (error) {\n throw new Error(\n `[@velajs/testing/websocket-node] \"${specifier}\" is required for WebSocket ` +\n 'connect() on Node but is not installed. Install the optional peers: ' +\n '`npm i -D @hono/node-ws @hono/node-server`.',\n { cause: error },\n );\n }\n}\n\nasync function ensureServer(module: TestingModule): Promise<RunningServer> {\n const existing = serversByModule.get(module);\n if (existing) return existing;\n\n const started = (async (): Promise<RunningServer> => {\n const { serve } = await importOptional<NodeServerModule>('@hono/node-server');\n const { createNodeWebSocket } = await importOptional<NodeWsModule>('@hono/node-ws');\n const { registerWebSocketGateways } = await importOptional<VelaWsNodeModule>(\n '@velajs/vela/websocket-node',\n );\n\n const app = await module.createApplication();\n const hono = app.getHonoApp();\n\n const { injectWebSocket, upgradeWebSocket, wss } = createNodeWebSocket({ app: hono });\n registerWebSocketGateways(app, upgradeWebSocket);\n\n const sockets = new Set<Socket>();\n let server: Server | undefined;\n const cleanup = async (): Promise<void> => {\n serversByModule.delete(module);\n // Upgraded sockets are not closed by HTTP server.close(). Track all sockets.\n for (const socket of sockets) socket.destroy();\n const outcomes = await Promise.allSettled([\n new Promise<void>((resolve, reject) => {\n if (!server?.listening) return resolve();\n server.close((error) => {\n if (error) reject(error);\n else resolve();\n });\n }),\n new Promise<void>((resolve, reject) => {\n if (!wss) return resolve();\n wss.close((error) => {\n if (error) reject(error);\n else resolve();\n });\n }),\n ]);\n const failures = outcomes.flatMap((outcome) =>\n outcome.status === 'rejected' ? [outcome.reason] : [],\n );\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1)\n throw new AggregateError(failures, 'WebSocket server cleanup failed');\n };\n try {\n const port = await new Promise<number>((resolve, reject) => {\n server = serve({ fetch: hono.fetch, port: 0 }, (info) => resolve(info.port));\n server.on('error', reject);\n server.on('connection', (socket) => {\n sockets.add(socket);\n socket.on('close', () => sockets.delete(socket));\n });\n });\n if (!server) throw new Error('WebSocket test server did not start');\n injectWebSocket(server);\n // Guard a close racing the asynchronous server startup; never orphan the listener.\n module.onClose(cleanup);\n server.unref();\n return { port };\n } catch (error) {\n await cleanup();\n throw error;\n }\n })();\n\n serversByModule.set(module, started);\n try {\n return await started;\n } catch (error) {\n serversByModule.delete(module);\n throw error;\n }\n}\n\nasync function openClient(url: string, headers: Headers): Promise<WebSocket> {\n const headerEntries = [...headers.entries()];\n\n // The standard WebSocket client cannot set arbitrary upgrade headers. When\n // headers are needed (e.g. auth cookies), fall back to the `ws` package which\n // supports them; otherwise use the runtime's global WebSocket.\n if (headerEntries.length > 0) {\n const wsSpecifier = 'ws';\n try {\n const wsModule = (await import(wsSpecifier)) as WsPackage;\n return new wsModule.default(url, undefined, {\n headers: Object.fromEntries(headerEntries),\n });\n } catch {\n // `ws` not installed — proceed with the global client (headers dropped).\n }\n }\n\n if (typeof WebSocket === 'undefined') {\n throw new Error(\n '[@velajs/testing/websocket-node] No global WebSocket. Use Node >=22 or ' +\n 'install the `ws` package.',\n );\n }\n return new WebSocket(url);\n}\n\nregisterWsConnector(async (module, path, headers) => {\n const { port } = await ensureServer(module);\n const url = `ws://127.0.0.1:${port}${path}`;\n const ws = await openClient(url, headers);\n\n await new Promise<void>((resolve, reject) => {\n const cleanup = (): void => {\n ws.removeEventListener('open', onOpen);\n ws.removeEventListener('error', onError as EventListener);\n };\n const onOpen = (): void => {\n cleanup();\n resolve();\n };\n const onError = (event: unknown): void => {\n cleanup();\n reject(new Error(`WebSocket failed to open: ${String(event)}`));\n };\n ws.addEventListener('open', onOpen);\n ws.addEventListener('error', onError as EventListener);\n });\n\n return new TestWsConnection(ws);\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/testing",
3
- "version": "1.22.1",
3
+ "version": "1.23.0",
4
4
  "description": "Testing utilities for Vela framework",
5
5
  "keywords": [
6
6
  "edge",
@@ -63,14 +63,14 @@
63
63
  "typescript": "7.0.2",
64
64
  "unplugin-swc": "1.5.9",
65
65
  "vitest": "4.1.10",
66
- "@velajs/vela": "1.22.1"
66
+ "@velajs/vela": "1.25.0"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@hono/node-server": ">=1",
70
70
  "@hono/node-ws": ">=1",
71
71
  "hono": ">=4",
72
72
  "vitest": ">=3",
73
- "@velajs/vela": "^1.22.1"
73
+ "@velajs/vela": "^1.25.0"
74
74
  },
75
75
  "peerDependenciesMeta": {
76
76
  "@hono/node-ws": {