@velajs/testing 1.22.1 → 1.28.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 +76 -0
- package/README.md +78 -11
- package/dist/index.d.ts +21 -11
- package/dist/index.js +98 -55
- package/dist/index.js.map +1 -1
- package/dist/websocket-node/index.js +47 -14
- package/dist/websocket-node/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Continue the module-based Workers APIs on the 1.x release line. Vela permits breaking changes in minor releases and does not retain compatibility layers. Upgrade the framework and integrations together for native queue dispatch, cron scheduling, RPC modules and asynchronous roots; see docs/module-workers.md.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @velajs/vela@1.28.0
|
|
13
|
+
|
|
14
|
+
## 3.0.0
|
|
15
|
+
|
|
16
|
+
### Major Changes
|
|
17
|
+
|
|
18
|
+
- Publish module-based Workers on the unused 3.x stable release line. Earlier experimental 2.0.0 registry versions are immutable and do not contain this release. Upgrade the framework and integrations together; see docs/module-workers.md for queue bootstrap, native delivery, RPC modules and async root migration details.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies
|
|
23
|
+
- @velajs/vela@3.0.0
|
|
24
|
+
|
|
25
|
+
## 2.0.0
|
|
26
|
+
|
|
27
|
+
### Major Changes
|
|
28
|
+
|
|
29
|
+
- a2d2692: Require the Vela 2 module/runtime peer line. Packages that raise their framework or adapter peer requirement ship a major version so existing 1.x installations are not offered an incompatible patch release. Upgrade the related framework integrations together; native queue/bootstrap migration is documented in docs/module-workers.md.
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- Updated dependencies [b99d71a]
|
|
34
|
+
- @velajs/vela@2.0.0
|
|
35
|
+
|
|
36
|
+
## 1.23.0
|
|
37
|
+
|
|
38
|
+
### Minor Changes
|
|
39
|
+
|
|
40
|
+
- 0765aaa: Use shared application finalization in testing, recalculate request scope after
|
|
41
|
+
provider overrides, and dispose resources on failed startup and shutdown. Await
|
|
42
|
+
concurrent disposal and managed test scopes. Add onClose fixture cleanup and close
|
|
43
|
+
Node WebSocket test servers with their owning testing module.
|
|
44
|
+
- 0ef063e: Add createTestHttpClient for live and injected Web-API request transports.
|
|
45
|
+
Validate test response bodies using shared async-aware Standard Schema and
|
|
46
|
+
legacy parsing, preserving inferred transformed outputs and existing parser calls.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- Updated dependencies [c6a43a6]
|
|
51
|
+
- Updated dependencies [bbe62d4]
|
|
52
|
+
- Updated dependencies [a6ef933]
|
|
53
|
+
- Updated dependencies [dae3654]
|
|
54
|
+
- Updated dependencies [77cca9e]
|
|
55
|
+
- Updated dependencies [b9f75f5]
|
|
56
|
+
- Updated dependencies [df47ea8]
|
|
57
|
+
- Updated dependencies [af019bf]
|
|
58
|
+
- Updated dependencies [6df1059]
|
|
59
|
+
- Updated dependencies [bdd90a1]
|
|
60
|
+
- Updated dependencies [8a3923f]
|
|
61
|
+
- Updated dependencies [c7d108b]
|
|
62
|
+
- Updated dependencies [1c7f635]
|
|
63
|
+
- Updated dependencies [636ffbc]
|
|
64
|
+
- Updated dependencies [54f8864]
|
|
65
|
+
- Updated dependencies [f49db45]
|
|
66
|
+
- Updated dependencies [4fde903]
|
|
67
|
+
- Updated dependencies [6a1b5b3]
|
|
68
|
+
- Updated dependencies [a95951a]
|
|
69
|
+
- Updated dependencies [9e82187]
|
|
70
|
+
- Updated dependencies [c5a3cb0]
|
|
71
|
+
- Updated dependencies [363fb71]
|
|
72
|
+
- Updated dependencies [de4e57e]
|
|
73
|
+
- Updated dependencies [0765aaa]
|
|
74
|
+
- Updated dependencies [6b7cf23]
|
|
75
|
+
- Updated dependencies [5205e58]
|
|
76
|
+
- Updated dependencies [ae45689]
|
|
77
|
+
- @velajs/vela@1.25.0
|
|
78
|
+
|
|
3
79
|
## 1.22.1
|
|
4
80
|
|
|
5
81
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# @velajs/testing
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@velajs/testing)
|
|
4
|
-
[](https://github.com/velajs/
|
|
4
|
+
[](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
|
|
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
|
-
#
|
|
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
|
|
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
|
-
|
|
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.
|
|
64
|
-
inject: [
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
|
622
|
-
this
|
|
637
|
+
this.#app = app;
|
|
638
|
+
this.#container = container;
|
|
623
639
|
}
|
|
624
640
|
/** Resolve a provider from the root container. */
|
|
625
641
|
get(token) {
|
|
626
|
-
|
|
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
|
-
|
|
631
|
-
return this
|
|
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
|
|
636
|
-
return this
|
|
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
|
|
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
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
|
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
|
-
/**
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
763
|
+
this.#assertOpen();
|
|
764
|
+
if (!this.#honoApp) {
|
|
717
765
|
const app = await this.createApplication();
|
|
718
|
-
this
|
|
766
|
+
this.#honoApp = app.getHonoApp();
|
|
719
767
|
}
|
|
720
|
-
return this
|
|
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
|
-
|
|
747
|
-
|
|
794
|
+
#overrides = [];
|
|
795
|
+
#metadata;
|
|
748
796
|
constructor(metadata) {
|
|
749
|
-
this
|
|
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
|
|
774
|
-
if (idx !== -1) this
|
|
775
|
-
else this
|
|
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
|
|
781
|
-
providers: this
|
|
782
|
-
controllers: this
|
|
783
|
-
exports: this
|
|
828
|
+
imports: this.#metadata.imports,
|
|
829
|
+
providers: this.#metadata.providers,
|
|
830
|
+
controllers: this.#metadata.controllers,
|
|
831
|
+
exports: this.#metadata.exports
|
|
784
832
|
});
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
const
|
|
788
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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 ) =>
|
|
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.
|
|
3
|
+
"version": "1.28.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.
|
|
66
|
+
"@velajs/vela": "1.28.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.
|
|
73
|
+
"@velajs/vela": "^1.28.0"
|
|
74
74
|
},
|
|
75
75
|
"peerDependenciesMeta": {
|
|
76
76
|
"@hono/node-ws": {
|