@stratal/testing 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,18 @@ yarn add -D @stratal/testing
19
19
  npm install -D stratal vitest
20
20
  ```
21
21
 
22
+ ### AI Agent Skills
23
+
24
+ Stratal provides [Agent Skills](https://agentskills.io) for AI coding assistants like Claude Code and Cursor. Install to give your AI agent knowledge of Stratal patterns, conventions, and APIs:
25
+
26
+ ```bash
27
+ npx skills add strataljs/stratal
28
+ ```
29
+
30
+ | Skill | Description |
31
+ |---|---|
32
+ | `stratal` | Build Cloudflare Workers apps with the Stratal framework — modules, DI, controllers, routing, OpenAPI, queues, cron, events, seeders, CLI, auth, database, RBAC, testing, and more |
33
+
22
34
  ## Quick Start
23
35
 
24
36
  Set up base modules once in your Vitest setup file, then create test modules in each test:
package/dist/index.d.mts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { t as FakeStorageService } from "./index-D-Q2cR2v.mjs";
2
- import { Application, ApplicationConfig, Constructor, StratalEnv } from "stratal";
2
+ import { Application, ApplicationConfig, Constructor, StratalEnv, StratalExecutionContext } from "stratal";
3
3
  import { DynamicModule, InjectionToken, ModuleClass, ModuleOptions } from "stratal/module";
4
4
  import { Container } from "stratal/di";
5
5
  import { ConnectionName, DatabaseService } from "@stratal/framework/database";
6
6
  import { Seeder } from "stratal/seeder";
7
+ import { DetectionStrategy } from "stratal/i18n";
7
8
  import { AuthService } from "@stratal/framework/auth";
8
9
  import { HttpResponse, RequestHandler, http } from "msw";
9
10
  import { CommandInput, CommandResult } from "stratal/quarry";
@@ -160,14 +161,6 @@ declare class TestResponse {
160
161
  * Assert response does not have the given header.
161
162
  */
162
163
  assertHeaderMissing(name: string): this;
163
- /**
164
- * Get value at dot-notation path.
165
- */
166
- private getValueAtPath;
167
- /**
168
- * Check if a path exists in the object (even if value is null/undefined).
169
- */
170
- private hasValueAtPath;
171
164
  }
172
165
  //#endregion
173
166
  //#region src/core/http/test-http-request.d.ts
@@ -201,7 +194,11 @@ declare class TestHttpRequest {
201
194
  private body;
202
195
  private requestHeaders;
203
196
  private actingAsUser;
204
- constructor(method: string, path: string, headers: Headers, module: TestingModule, host?: string | null);
197
+ private localeConfig;
198
+ constructor(method: string, path: string, headers: Headers, module: TestingModule, host?: string | null, localeConfig?: {
199
+ locale: string;
200
+ strategy: DetectionStrategy;
201
+ } | null);
205
202
  /**
206
203
  * Set the request body
207
204
  */
@@ -210,6 +207,14 @@ declare class TestHttpRequest {
210
207
  * Add headers to the request
211
208
  */
212
209
  withHeaders(headers: Record<string, string>): this;
210
+ /**
211
+ * Set the locale for this request.
212
+ * If strategy is not provided, resolves from the module's I18n configuration.
213
+ *
214
+ * @param locale - Locale code (e.g., 'en', 'fr')
215
+ * @param strategy - Detection strategy override
216
+ */
217
+ withLocale(locale: string, strategy?: DetectionStrategy): this;
213
218
  /**
214
219
  * Set Content-Type to application/json
215
220
  */
@@ -250,6 +255,7 @@ declare class TestHttpClient {
250
255
  private readonly module;
251
256
  private defaultHeaders;
252
257
  private host;
258
+ private localeConfig;
253
259
  constructor(module: TestingModule);
254
260
  /**
255
261
  * Set the host for the request
@@ -259,6 +265,14 @@ declare class TestHttpClient {
259
265
  * Set default headers for all requests
260
266
  */
261
267
  withHeaders(headers: Record<string, string>): this;
268
+ /**
269
+ * Set the locale for all requests from this client.
270
+ * If strategy is not provided, resolves from the module's I18n configuration.
271
+ *
272
+ * @param locale - Locale code (e.g., 'en', 'fr')
273
+ * @param strategy - Detection strategy override
274
+ */
275
+ withLocale(locale: string, strategy?: DetectionStrategy): this;
262
276
  /**
263
277
  * Create a GET request
264
278
  */
@@ -432,6 +446,11 @@ declare class TestSseRequest {
432
446
  * Add custom headers to the request
433
447
  */
434
448
  withHeaders(headers: Record<string, string>): this;
449
+ /**
450
+ * Set the locale for this SSE connection.
451
+ * If strategy is not provided, resolves from the module's I18n configuration.
452
+ */
453
+ withLocale(locale: string, strategy?: DetectionStrategy): this;
435
454
  /**
436
455
  * Authenticate the SSE connection as a specific user
437
456
  */
@@ -529,6 +548,11 @@ declare class TestWsRequest {
529
548
  * Add custom headers to the upgrade request
530
549
  */
531
550
  withHeaders(headers: Record<string, string>): this;
551
+ /**
552
+ * Set the locale for this WebSocket connection.
553
+ * If strategy is not provided, resolves from the module's I18n configuration.
554
+ */
555
+ withLocale(locale: string, strategy?: DetectionStrategy): this;
532
556
  /**
533
557
  * Authenticate the WebSocket connection as a specific user
534
558
  */
@@ -578,7 +602,7 @@ declare class TestingModule {
578
602
  private readonly ctx;
579
603
  private _http;
580
604
  private readonly _requestContainer;
581
- constructor(app: Application, env: StratalEnv, ctx: ExecutionContext);
605
+ constructor(app: Application, env: StratalEnv, ctx: StratalExecutionContext);
582
606
  /**
583
607
  * Resolve a service from the container
584
608
  */
@@ -692,6 +716,7 @@ declare class TestingModuleBuilder {
692
716
  * Merge additional environment bindings
693
717
  */
694
718
  withEnv(env: Partial<StratalEnv>): this;
719
+ private getCloudflareWorkers;
695
720
  /**
696
721
  * Compile the testing module
697
722
  *
@@ -827,6 +852,16 @@ declare class Test {
827
852
  static createTestingModule(config: TestingModuleConfig): TestingModuleBuilder;
828
853
  }
829
854
  //#endregion
855
+ //#region src/core/http/path-utils.d.ts
856
+ /**
857
+ * Get value at dot-notation path.
858
+ */
859
+ declare function getValueAtPath(obj: unknown, path: string): unknown;
860
+ /**
861
+ * Check if a path exists in the object (even if value is null/undefined).
862
+ */
863
+ declare function hasValueAtPath(obj: unknown, path: string): boolean;
864
+ //#endregion
830
865
  //#region src/core/http/fetch-mock.types.d.ts
831
866
  /**
832
867
  * Options for mocking JSON responses
@@ -984,15 +1019,6 @@ declare class ActingAs {
984
1019
  }): Promise<Headers>;
985
1020
  }
986
1021
  //#endregion
987
- //#region src/core/env/test-env.d.ts
988
- /**
989
- * Get test environment with optional overrides
990
- *
991
- * @param overrides - Optional partial env to merge with cloudflare:test env
992
- * @returns Complete Env object for testing
993
- */
994
- declare function getTestEnv(overrides?: Partial<StratalEnv>): StratalEnv;
995
- //#endregion
996
1022
  //#region src/errors/test-error.d.ts
997
1023
  /**
998
1024
  * Base error class for all test framework errors.
@@ -1012,5 +1038,5 @@ declare class TestSetupError extends TestError {
1012
1038
  constructor(message: string, cause?: Error);
1013
1039
  }
1014
1040
  //#endregion
1015
- export { ActingAs, HttpResponse, type MockErrorOptions, MockFetch, type MockJsonOptions, ProviderOverrideBuilder, type ProviderOverrideConfig, Test, TestCommandRequest, TestCommandResult, TestError, TestHttpClient, TestHttpRequest, TestResponse, TestSetupError, TestSseConnection, type TestSseEvent, TestSseRequest, TestWsConnection, TestWsRequest, TestingModule, TestingModuleBuilder, type TestingModuleConfig, createMockFetch, getTestEnv, http };
1041
+ export { ActingAs, HttpResponse, type MockErrorOptions, MockFetch, type MockJsonOptions, ProviderOverrideBuilder, type ProviderOverrideConfig, Test, TestCommandRequest, TestCommandResult, TestError, TestHttpClient, TestHttpRequest, TestResponse, TestSetupError, TestSseConnection, type TestSseEvent, TestSseRequest, TestWsConnection, TestWsRequest, TestingModule, TestingModuleBuilder, type TestingModuleConfig, createMockFetch, getValueAtPath, hasValueAtPath, http };
1016
1042
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/core/http/test-response.ts","../src/core/http/test-http-request.ts","../src/core/http/test-http-client.ts","../src/core/quarry/test-command-result.ts","../src/core/quarry/test-command-request.ts","../src/core/sse/test-sse-connection.ts","../src/core/sse/test-sse-request.ts","../src/core/ws/test-ws-connection.ts","../src/core/ws/test-ws-request.ts","../src/core/testing-module.ts","../src/core/testing-module-builder.ts","../src/core/override/provider-override-builder.ts","../src/core/test.ts","../src/core/http/fetch-mock.types.ts","../src/core/http/mock-fetch.ts","../src/auth/acting-as.ts","../src/core/env/test-env.ts","../src/errors/test-error.ts","../src/errors/setup-error.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/core/http/test-response.ts","../src/core/http/test-http-request.ts","../src/core/http/test-http-client.ts","../src/core/quarry/test-command-result.ts","../src/core/quarry/test-command-request.ts","../src/core/sse/test-sse-connection.ts","../src/core/sse/test-sse-request.ts","../src/core/ws/test-ws-connection.ts","../src/core/ws/test-ws-request.ts","../src/core/testing-module.ts","../src/core/testing-module-builder.ts","../src/core/override/provider-override-builder.ts","../src/core/test.ts","../src/core/http/path-utils.ts","../src/core/http/fetch-mock.types.ts","../src/core/http/mock-fetch.ts","../src/auth/acting-as.ts","../src/errors/test-error.ts","../src/errors/setup-error.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,4 @@
1
- import { n as __decorate, t as FakeStorageService } from "./storage-PcJUKxwp.mjs";
2
- import { env, waitUntil } from "cloudflare:workers";
1
+ import { n as __decorate, t as FakeStorageService } from "./storage-CGRm_2Nh.mjs";
3
2
  import { Application } from "stratal";
4
3
  import { LogLevel } from "stratal/logger";
5
4
  import { Module } from "stratal/module";
@@ -8,6 +7,7 @@ import { DI_TOKENS } from "stratal/di";
8
7
  import { expect } from "vitest";
9
8
  import { connectionSymbol } from "@stratal/framework/database";
10
9
  import { SEEDER_TOKENS, SeederNotRegisteredError } from "stratal/seeder";
10
+ import { I18N_TOKENS } from "stratal/i18n";
11
11
  import { AUTH_SERVICE } from "@stratal/framework/auth";
12
12
  import { setSessionCookie } from "better-auth/cookies";
13
13
  import { convertSetCookieToCookie } from "better-auth/test";
@@ -106,18 +106,38 @@ var ProviderOverrideBuilder = class {
106
106
  }
107
107
  };
108
108
  //#endregion
109
- //#region src/core/env/test-env.ts
109
+ //#region src/core/http/locale-helper.ts
110
110
  /**
111
- * Get test environment with optional overrides
112
- *
113
- * @param overrides - Optional partial env to merge with cloudflare:test env
114
- * @returns Complete Env object for testing
111
+ * Resolve the configured detection strategy from the testing module's DI container.
112
+ * Falls back to 'cookie' if I18n is not configured.
115
113
  */
116
- function getTestEnv(overrides) {
117
- return {
118
- ...env,
119
- ...overrides
120
- };
114
+ function resolveLocaleStrategy(module) {
115
+ try {
116
+ const detection = module.get(I18N_TOKENS.Options).detection;
117
+ if (detection && "strategy" in detection && detection.strategy) return detection.strategy;
118
+ return "cookie";
119
+ } catch {
120
+ return "cookie";
121
+ }
122
+ }
123
+ /**
124
+ * Apply locale to request headers based on detection strategy.
125
+ */
126
+ function applyLocaleToHeaders(headers, locale, strategy) {
127
+ switch (strategy) {
128
+ case "cookie":
129
+ headers.set("Cookie", `locale=${locale}`);
130
+ break;
131
+ case "header":
132
+ headers.set("Accept-Language", locale);
133
+ break;
134
+ }
135
+ }
136
+ /**
137
+ * Apply locale to URL based on detection strategy.
138
+ */
139
+ function applyLocaleToUrl(url, locale, strategy) {
140
+ if (strategy === "querystring") url.searchParams.set("locale", locale);
121
141
  }
122
142
  //#endregion
123
143
  //#region src/auth/acting-as.ts
@@ -184,6 +204,35 @@ var ActingAs = class {
184
204
  }
185
205
  };
186
206
  //#endregion
207
+ //#region src/core/http/path-utils.ts
208
+ /**
209
+ * Get value at dot-notation path.
210
+ */
211
+ function getValueAtPath(obj, path) {
212
+ const parts = path.split(".");
213
+ let current = obj;
214
+ for (const part of parts) {
215
+ if (current === null || current === void 0) return;
216
+ current = current[part];
217
+ }
218
+ return current;
219
+ }
220
+ /**
221
+ * Check if a path exists in the object (even if value is null/undefined).
222
+ */
223
+ function hasValueAtPath(obj, path) {
224
+ const parts = path.split(".");
225
+ let current = obj;
226
+ for (const part of parts) {
227
+ if (current === null || current === void 0) return false;
228
+ if (typeof current !== "object") return false;
229
+ const record = current;
230
+ if (!(part in record)) return false;
231
+ current = record[part];
232
+ }
233
+ return true;
234
+ }
235
+ //#endregion
187
236
  //#region src/core/http/test-response.ts
188
237
  /**
189
238
  * TestResponse
@@ -309,7 +358,7 @@ var TestResponse = class {
309
358
  */
310
359
  async assertJson(expected) {
311
360
  const actual = await this.json();
312
- for (const [key, value] of Object.entries(expected)) expect(actual[key], `Expected JSON key "${key}" to be ${JSON.stringify(value)}, got ${JSON.stringify(actual[key])}`).toBe(value);
361
+ for (const [key, value] of Object.entries(expected)) expect(actual[key], `Expected JSON key "${key}" to be ${JSON.stringify(value)}, got ${JSON.stringify(actual[key])}`).toStrictEqual(value);
313
362
  return this;
314
363
  }
315
364
  /**
@@ -319,9 +368,8 @@ var TestResponse = class {
319
368
  * @param expected - Expected value at path
320
369
  */
321
370
  async assertJsonPath(path, expected) {
322
- const json = await this.json();
323
- const actual = this.getValueAtPath(json, path);
324
- expect(actual, `Expected JSON path "${path}" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`).toBe(expected);
371
+ const actual = getValueAtPath(await this.json(), path);
372
+ expect(actual, `Expected JSON path "${path}" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`).toStrictEqual(expected);
325
373
  return this;
326
374
  }
327
375
  /**
@@ -338,8 +386,7 @@ var TestResponse = class {
338
386
  * @param path - Dot-notation path (e.g., 'data.user.id')
339
387
  */
340
388
  async assertJsonPathExists(path) {
341
- const json = await this.json();
342
- expect(this.hasValueAtPath(json, path), `Expected JSON path "${path}" to exist`).toBe(true);
389
+ expect(hasValueAtPath(await this.json(), path), `Expected JSON path "${path}" to exist`).toBe(true);
343
390
  return this;
344
391
  }
345
392
  /**
@@ -348,8 +395,7 @@ var TestResponse = class {
348
395
  * @param path - Dot-notation path (e.g., 'data.user.deletedAt')
349
396
  */
350
397
  async assertJsonPathMissing(path) {
351
- const json = await this.json();
352
- expect(this.hasValueAtPath(json, path), `Expected JSON path "${path}" to not exist`).toBe(false);
398
+ expect(hasValueAtPath(await this.json(), path), `Expected JSON path "${path}" to not exist`).toBe(false);
353
399
  return this;
354
400
  }
355
401
  /**
@@ -359,8 +405,7 @@ var TestResponse = class {
359
405
  * @param matcher - Predicate function to validate the value
360
406
  */
361
407
  async assertJsonPathMatches(path, matcher) {
362
- const json = await this.json();
363
- const value = this.getValueAtPath(json, path);
408
+ const value = getValueAtPath(await this.json(), path);
364
409
  expect(matcher(value), `Expected JSON path "${path}" to match predicate, got ${JSON.stringify(value)}`).toBe(true);
365
410
  return this;
366
411
  }
@@ -371,8 +416,7 @@ var TestResponse = class {
371
416
  * @param substring - Substring to search for
372
417
  */
373
418
  async assertJsonPathContains(path, substring) {
374
- const json = await this.json();
375
- const value = this.getValueAtPath(json, path);
419
+ const value = getValueAtPath(await this.json(), path);
376
420
  expect(typeof value === "string", `Expected JSON path "${path}" to be a string, got ${typeof value}`).toBe(true);
377
421
  expect(value.includes(substring), `Expected JSON path "${path}" to contain "${substring}", got "${String(value)}"`).toBe(true);
378
422
  return this;
@@ -384,8 +428,7 @@ var TestResponse = class {
384
428
  * @param item - Item to search for in the array
385
429
  */
386
430
  async assertJsonPathIncludes(path, item) {
387
- const json = await this.json();
388
- const value = this.getValueAtPath(json, path);
431
+ const value = getValueAtPath(await this.json(), path);
389
432
  expect(Array.isArray(value), `Expected JSON path "${path}" to be an array, got ${typeof value}`).toBe(true);
390
433
  expect(value.includes(item), `Expected JSON path "${path}" to include ${JSON.stringify(item)}`).toBe(true);
391
434
  return this;
@@ -397,8 +440,7 @@ var TestResponse = class {
397
440
  * @param count - Expected array length
398
441
  */
399
442
  async assertJsonPathCount(path, count) {
400
- const json = await this.json();
401
- const value = this.getValueAtPath(json, path);
443
+ const value = getValueAtPath(await this.json(), path);
402
444
  expect(Array.isArray(value), `Expected JSON path "${path}" to be an array, got ${typeof value}`).toBe(true);
403
445
  expect(value.length, `Expected JSON path "${path}" to have ${count} items, got ${value.length}`).toBe(count);
404
446
  return this;
@@ -411,8 +453,8 @@ var TestResponse = class {
411
453
  async assertJsonPaths(expectations) {
412
454
  const json = await this.json();
413
455
  for (const [path, expected] of Object.entries(expectations)) {
414
- const actual = this.getValueAtPath(json, path);
415
- expect(actual, `Expected JSON path "${path}" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`).toBe(expected);
456
+ const actual = getValueAtPath(json, path);
457
+ expect(actual, `Expected JSON path "${path}" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`).toStrictEqual(expected);
416
458
  }
417
459
  return this;
418
460
  }
@@ -433,33 +475,6 @@ var TestResponse = class {
433
475
  expect(actual, `Expected header "${name}" to be absent, but got "${actual}"`).toBeNull();
434
476
  return this;
435
477
  }
436
- /**
437
- * Get value at dot-notation path.
438
- */
439
- getValueAtPath(obj, path) {
440
- const parts = path.split(".");
441
- let current = obj;
442
- for (const part of parts) {
443
- if (current === null || current === void 0) return;
444
- current = current[part];
445
- }
446
- return current;
447
- }
448
- /**
449
- * Check if a path exists in the object (even if value is null/undefined).
450
- */
451
- hasValueAtPath(obj, path) {
452
- const parts = path.split(".");
453
- let current = obj;
454
- for (const part of parts) {
455
- if (current === null || current === void 0) return false;
456
- if (typeof current !== "object") return false;
457
- const record = current;
458
- if (!(part in record)) return false;
459
- current = record[part];
460
- }
461
- return true;
462
- }
463
478
  };
464
479
  //#endregion
465
480
  //#region src/core/http/test-http-request.ts
@@ -489,12 +504,14 @@ var TestHttpRequest = class {
489
504
  body = null;
490
505
  requestHeaders;
491
506
  actingAsUser = null;
492
- constructor(method, path, headers, module, host = null) {
507
+ localeConfig;
508
+ constructor(method, path, headers, module, host = null, localeConfig = null) {
493
509
  this.method = method;
494
510
  this.path = path;
495
511
  this.module = module;
496
512
  this.host = host;
497
513
  this.requestHeaders = new Headers(headers);
514
+ this.localeConfig = localeConfig;
498
515
  }
499
516
  /**
500
517
  * Set the request body
@@ -511,6 +528,22 @@ var TestHttpRequest = class {
511
528
  return this;
512
529
  }
513
530
  /**
531
+ * Set the locale for this request.
532
+ * If strategy is not provided, resolves from the module's I18n configuration.
533
+ *
534
+ * @param locale - Locale code (e.g., 'en', 'fr')
535
+ * @param strategy - Detection strategy override
536
+ */
537
+ withLocale(locale, strategy) {
538
+ const resolved = strategy ?? resolveLocaleStrategy(this.module);
539
+ this.localeConfig = {
540
+ locale,
541
+ strategy: resolved
542
+ };
543
+ applyLocaleToHeaders(this.requestHeaders, locale, resolved);
544
+ return this;
545
+ }
546
+ /**
514
547
  * Set Content-Type to application/json
515
548
  */
516
549
  asJson() {
@@ -533,6 +566,7 @@ var TestHttpRequest = class {
533
566
  await this.applyAuthentication();
534
567
  if (this.body && !this.requestHeaders.has("Content-Type")) this.requestHeaders.set("Content-Type", "application/json");
535
568
  const url = new URL(this.path, `http://${this.host ?? "localhost"}`);
569
+ if (this.localeConfig) applyLocaleToUrl(url, this.localeConfig.locale, this.localeConfig.strategy);
536
570
  const request = new Request(url.toString(), {
537
571
  method: this.method,
538
572
  headers: this.requestHeaders,
@@ -570,6 +604,7 @@ var TestHttpRequest = class {
570
604
  var TestHttpClient = class {
571
605
  defaultHeaders = new Headers();
572
606
  host = null;
607
+ localeConfig = null;
573
608
  constructor(module) {
574
609
  this.module = module;
575
610
  }
@@ -588,6 +623,22 @@ var TestHttpClient = class {
588
623
  return this;
589
624
  }
590
625
  /**
626
+ * Set the locale for all requests from this client.
627
+ * If strategy is not provided, resolves from the module's I18n configuration.
628
+ *
629
+ * @param locale - Locale code (e.g., 'en', 'fr')
630
+ * @param strategy - Detection strategy override
631
+ */
632
+ withLocale(locale, strategy) {
633
+ const resolved = strategy ?? resolveLocaleStrategy(this.module);
634
+ this.localeConfig = {
635
+ locale,
636
+ strategy: resolved
637
+ };
638
+ applyLocaleToHeaders(this.defaultHeaders, locale, resolved);
639
+ return this;
640
+ }
641
+ /**
591
642
  * Create a GET request
592
643
  */
593
644
  get(path) {
@@ -618,7 +669,7 @@ var TestHttpClient = class {
618
669
  return this.createRequest("DELETE", path);
619
670
  }
620
671
  createRequest(method, path) {
621
- return new TestHttpRequest(method, path, this.defaultHeaders, this.module, this.host);
672
+ return new TestHttpRequest(method, path, this.defaultHeaders, this.module, this.host, this.localeConfig);
622
673
  }
623
674
  };
624
675
  //#endregion
@@ -945,6 +996,15 @@ var TestSseRequest = class {
945
996
  return this;
946
997
  }
947
998
  /**
999
+ * Set the locale for this SSE connection.
1000
+ * If strategy is not provided, resolves from the module's I18n configuration.
1001
+ */
1002
+ withLocale(locale, strategy) {
1003
+ const resolved = strategy ?? resolveLocaleStrategy(this.module);
1004
+ applyLocaleToHeaders(this.requestHeaders, locale, resolved);
1005
+ return this;
1006
+ }
1007
+ /**
948
1008
  * Authenticate the SSE connection as a specific user
949
1009
  */
950
1010
  actingAs(user) {
@@ -1116,6 +1176,15 @@ var TestWsRequest = class {
1116
1176
  return this;
1117
1177
  }
1118
1178
  /**
1179
+ * Set the locale for this WebSocket connection.
1180
+ * If strategy is not provided, resolves from the module's I18n configuration.
1181
+ */
1182
+ withLocale(locale, strategy) {
1183
+ const resolved = strategy ?? resolveLocaleStrategy(this.module);
1184
+ applyLocaleToHeaders(this.requestHeaders, locale, resolved);
1185
+ return this;
1186
+ }
1187
+ /**
1119
1188
  * Authenticate the WebSocket connection as a specific user
1120
1189
  */
1121
1190
  actingAs(user) {
@@ -1342,14 +1411,27 @@ var TestingModuleBuilder = class {
1342
1411
  };
1343
1412
  return this;
1344
1413
  }
1414
+ async getCloudflareWorkers() {
1415
+ try {
1416
+ return await import("cloudflare:workers");
1417
+ } catch {
1418
+ return null;
1419
+ }
1420
+ }
1345
1421
  /**
1346
1422
  * Compile the testing module
1347
1423
  *
1348
1424
  * Creates the Application, applies overrides, initializes, and returns TestingModule.
1349
1425
  */
1350
1426
  async compile() {
1351
- const env = getTestEnv(this.config.env);
1352
- const ctx = { waitUntil };
1427
+ const cf = await this.getCloudflareWorkers();
1428
+ const env = {
1429
+ ...cf?.env,
1430
+ ...this.config.env
1431
+ };
1432
+ const ctx = { waitUntil: cf ? cf.waitUntil : (p) => {
1433
+ p.catch(() => {});
1434
+ } };
1353
1435
  const allImports = [...Test.getBaseModules(), ...this.config.imports ?? []];
1354
1436
  const app = new Application({
1355
1437
  module: this.createTestRootModule({
@@ -1367,6 +1449,7 @@ var TestingModuleBuilder = class {
1367
1449
  ctx
1368
1450
  });
1369
1451
  app.container.registerSingleton(STORAGE_TOKENS.StorageService, FakeStorageService);
1452
+ await app.initialize();
1370
1453
  for (const override of this.overrides) switch (override.type) {
1371
1454
  case "value":
1372
1455
  app.container.registerValue(override.token, override.implementation);
@@ -1381,7 +1464,6 @@ var TestingModuleBuilder = class {
1381
1464
  app.container.registerExisting(override.token, override.implementation);
1382
1465
  break;
1383
1466
  }
1384
- await app.initialize();
1385
1467
  return new TestingModule(app, env, ctx);
1386
1468
  }
1387
1469
  /**
@@ -1584,6 +1666,6 @@ var TestSetupError = class extends TestError {
1584
1666
  }
1585
1667
  };
1586
1668
  //#endregion
1587
- export { ActingAs, HttpResponse, MockFetch, ProviderOverrideBuilder, Test, TestCommandRequest, TestCommandResult, TestError, TestHttpClient, TestHttpRequest, TestResponse, TestSetupError, TestSseConnection, TestSseRequest, TestWsConnection, TestWsRequest, TestingModule, TestingModuleBuilder, createMockFetch, getTestEnv, http };
1669
+ export { ActingAs, HttpResponse, MockFetch, ProviderOverrideBuilder, Test, TestCommandRequest, TestCommandResult, TestError, TestHttpClient, TestHttpRequest, TestResponse, TestSetupError, TestSseConnection, TestSseRequest, TestWsConnection, TestWsRequest, TestingModule, TestingModuleBuilder, createMockFetch, getValueAtPath, hasValueAtPath, http };
1588
1670
 
1589
1671
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["cloudflareEnv","http","HttpResponse"],"sources":["../src/core/override/provider-override-builder.ts","../src/core/env/test-env.ts","../src/auth/acting-as.ts","../src/core/http/test-response.ts","../src/core/http/test-http-request.ts","../src/core/http/test-http-client.ts","../src/core/quarry/test-command-result.ts","../src/core/quarry/test-command-request.ts","../src/core/sse/test-sse-connection.ts","../src/core/sse/test-sse-request.ts","../src/core/ws/test-ws-connection.ts","../src/core/ws/test-ws-request.ts","../src/core/testing-module.ts","../src/core/testing-module-builder.ts","../src/core/test.ts","../src/core/http/mock-fetch.ts","../src/errors/test-error.ts","../src/errors/setup-error.ts"],"sourcesContent":["import { type Container } from 'stratal/di'\nimport { type InjectionToken } from 'stratal/module'\nimport type { TestingModuleBuilder } from '../testing-module-builder'\n\n/**\n * Provider override configuration\n */\nexport interface ProviderOverrideConfig<T = unknown> {\n token: InjectionToken<T>\n type: 'value' | 'class' | 'factory' | 'existing'\n implementation: T | (new (...args: unknown[]) => T) | ((container: Container) => T) | InjectionToken<T>\n}\n\n/**\n * Fluent builder for provider overrides\n *\n * Provides a NestJS-style API for overriding providers in tests.\n *\n * @example\n * ```typescript\n * const module = await Test.createTestingModule({\n * imports: [RegistrationModule],\n * })\n * .overrideProvider(EMAIL_TOKENS.EmailService)\n * .useValue(mockEmailService)\n * .compile()\n * ```\n */\nexport class ProviderOverrideBuilder<T> {\n constructor(\n private readonly parent: TestingModuleBuilder,\n private readonly token: InjectionToken<T>\n ) { }\n\n /**\n * Use a static value as the provider\n *\n * The value will be registered directly in the container.\n *\n * @param value - The value to use as the provider\n * @returns The parent TestingModuleBuilder for chaining\n */\n useValue(value: T): TestingModuleBuilder {\n return this.parent.addProviderOverride({\n token: this.token,\n type: 'value',\n implementation: value,\n })\n }\n\n /**\n * Use a class as the provider\n *\n * The class will be registered as a singleton.\n *\n * @param cls - The class constructor to use as the provider\n * @returns The parent TestingModuleBuilder for chaining\n */\n useClass(cls: new (...args: unknown[]) => T): TestingModuleBuilder {\n return this.parent.addProviderOverride({\n token: this.token,\n type: 'class',\n implementation: cls,\n })\n }\n\n /**\n * Use a factory function as the provider\n *\n * The factory receives the container and should return the provider instance.\n *\n * @param factory - Factory function that creates the provider\n * @returns The parent TestingModuleBuilder for chaining\n */\n useFactory(factory: (container: Container) => T): TestingModuleBuilder {\n return this.parent.addProviderOverride({\n token: this.token,\n type: 'factory',\n implementation: factory,\n })\n }\n\n /**\n * Use an existing token as the provider (alias)\n *\n * The override token will resolve to the same instance as the target token.\n *\n * @param existingToken - The token to alias\n * @returns The parent TestingModuleBuilder for chaining\n *\n * @example\n * ```typescript\n * const module = await Test.createTestingModule({\n * imports: [MyModule],\n * })\n * .overrideProvider(ABSTRACT_TOKEN)\n * .useExisting(ConcreteService)\n * .compile()\n * ```\n */\n useExisting(existingToken: InjectionToken<T>): TestingModuleBuilder {\n return this.parent.addProviderOverride({\n token: this.token,\n type: 'existing',\n implementation: existingToken,\n })\n }\n}\n","import { type StratalEnv } from 'stratal'\nimport { env as cloudflareEnv } from 'cloudflare:workers'\n\n/**\n * Get test environment with optional overrides\n *\n * @param overrides - Optional partial env to merge with cloudflare:test env\n * @returns Complete Env object for testing\n */\nexport function getTestEnv(overrides?: Partial<StratalEnv>): StratalEnv {\n return {\n ...cloudflareEnv,\n ...overrides,\n } as StratalEnv\n}\n","import type { AuthService } from '@stratal/framework/auth'\nimport { type GenericEndpointContext } from 'better-auth'\nimport { setSessionCookie } from 'better-auth/cookies'\nimport { convertSetCookieToCookie } from 'better-auth/test'\n\nasync function makeSignature(value: string, secret: string): Promise<string> {\n const algorithm = { name: 'HMAC', hash: 'SHA-256' }\n const secretBuf = new TextEncoder().encode(secret)\n const key = await crypto.subtle.importKey('raw', secretBuf, algorithm, false, ['sign'])\n const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value))\n return btoa(String.fromCharCode(...new Uint8Array(signature)))\n}\n\nfunction buildCookieString(name: string, value: string, options: Record<string, unknown> = {}): string {\n const encodedValue = encodeURIComponent(value)\n let str = `${name}=${encodedValue}`\n if (options.path) str += `; Path=${options.path as string}`\n if (options.httpOnly) str += '; HttpOnly'\n if (options.secure) str += '; Secure'\n if (options.sameSite) str += `; SameSite=${options.sameSite as string}`\n if (options.maxAge !== undefined) str += `; Max-Age=${Math.floor(options.maxAge as number)}`\n return str\n}\n\n/**\n * ActingAs\n *\n * Creates authentication sessions for testing.\n * Uses Better Auth's internalAdapter to create real database sessions.\n *\n * @example\n * ```typescript\n * const actingAs = new ActingAs(authService)\n * const headers = await actingAs.createSessionForUser({ id: 'user-123' })\n * ```\n */\nexport class ActingAs {\n constructor(private readonly authService: AuthService) { }\n\n async createSessionForUser(user: { id: string }): Promise<Headers> {\n const auth = this.authService.auth\n const ctx = await auth.$context\n\n const secret = ctx.secret\n\n const session = await ctx.internalAdapter.createSession(\n user.id,\n undefined,\n { ipAddress: '127.0.0.1', userAgent: 'test-client' }\n )\n\n const dbUser = await ctx.internalAdapter.findUserById(user.id)\n if (!dbUser) {\n throw new Error(`User not found: ${user.id}`)\n }\n\n const responseHeaders = new Headers()\n const mockCtx = {\n context: ctx,\n getSignedCookie: () => null,\n setSignedCookie: async (name: string, value: string, _secret: string, options: Record<string, unknown> = {}) => {\n const signature = await makeSignature(value, secret)\n const signedValue = `${value}.${signature}`\n responseHeaders.append('Set-Cookie', buildCookieString(name, signedValue, options))\n },\n setCookie: (name: string, value: string, options: Record<string, unknown> = {}) => {\n responseHeaders.append('Set-Cookie', buildCookieString(name, value, options))\n },\n }\n\n await setSessionCookie(mockCtx as unknown as GenericEndpointContext, { session, user: dbUser }, false)\n return convertSetCookieToCookie(responseHeaders)\n }\n}\n","import { expect } from 'vitest'\n\n/**\n * TestResponse\n *\n * Wrapper around Response with assertion methods.\n *\n * @example\n * ```typescript\n * response\n * .assertOk()\n * .assertStatus(200)\n * .assertJsonPath('data.id', userId)\n * ```\n */\nexport class TestResponse {\n private jsonData: unknown = null\n private textData: string | null = null\n\n constructor(private readonly response: Response) {}\n\n /**\n * Get the raw Response object.\n */\n get raw(): Response {\n return this.response\n }\n\n /**\n * Get the response status code.\n */\n get status(): number {\n return this.response.status\n }\n\n /**\n * Get response headers.\n */\n get headers(): Headers {\n return this.response.headers\n }\n\n /**\n * Get the response body as JSON.\n */\n async json<T = unknown>(): Promise<T> {\n if (this.jsonData === null) {\n this.jsonData = await this.response.clone().json()\n }\n return this.jsonData as T\n }\n\n /**\n * Get the response body as text.\n */\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 /**\n * Assert response status is 200 OK.\n */\n assertOk(): this {\n return this.assertStatus(200)\n }\n\n /**\n * Assert response status is 201 Created.\n */\n assertCreated(): this {\n return this.assertStatus(201)\n }\n\n /**\n * Assert response status is 204 No Content.\n */\n assertNoContent(): this {\n return this.assertStatus(204)\n }\n\n /**\n * Assert response status is 400 Bad Request.\n */\n assertBadRequest(): this {\n return this.assertStatus(400)\n }\n\n /**\n * Assert response status is 401 Unauthorized.\n */\n assertUnauthorized(): this {\n return this.assertStatus(401)\n }\n\n /**\n * Assert response status is 403 Forbidden.\n */\n assertForbidden(): this {\n return this.assertStatus(403)\n }\n\n /**\n * Assert response status is 404 Not Found.\n */\n assertNotFound(): this {\n return this.assertStatus(404)\n }\n\n /**\n * Assert response status is 422 Unprocessable Entity.\n */\n assertUnprocessable(): this {\n return this.assertStatus(422)\n }\n\n /**\n * Assert response status is 500 Internal Server Error.\n */\n assertServerError(): this {\n return this.assertStatus(500)\n }\n\n /**\n * Assert response has the given status code.\n */\n assertStatus(expected: number): this {\n expect(\n this.response.status,\n `Expected status ${expected}, got ${this.response.status}`\n ).toBe(expected)\n return this\n }\n\n /**\n * Assert response status is in the 2xx success range.\n */\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 /**\n * Assert JSON response contains the given data.\n */\n async assertJson(expected: Record<string, unknown>): Promise<this> {\n const actual = await this.json<Record<string, unknown>>()\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 ).toBe(value)\n }\n\n return this\n }\n\n /**\n * Assert JSON response has value at the given path.\n *\n * @param path - Dot-notation path (e.g., 'data.user.id')\n * @param expected - Expected value at path\n */\n async assertJsonPath(path: string, expected: unknown): Promise<this> {\n const json = await this.json()\n const actual = this.getValueAtPath(json, path)\n\n expect(\n actual,\n `Expected JSON path \"${path}\" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`\n ).toBe(expected)\n\n return this\n }\n\n /**\n * Assert JSON response structure matches the given schema.\n */\n async assertJsonStructure(structure: string[]): Promise<this> {\n const json = await this.json<Record<string, unknown>>()\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 /**\n * Assert a JSON path exists (value can be anything, including null).\n *\n * @param path - Dot-notation path (e.g., 'data.user.id')\n */\n async assertJsonPathExists(path: string): Promise<this> {\n const json = await this.json()\n const exists = this.hasValueAtPath(json, path)\n\n expect(\n exists,\n `Expected JSON path \"${path}\" to exist`\n ).toBe(true)\n\n return this\n }\n\n /**\n * Assert a JSON path does not exist.\n *\n * @param path - Dot-notation path (e.g., 'data.user.deletedAt')\n */\n async assertJsonPathMissing(path: string): Promise<this> {\n const json = await this.json()\n const exists = this.hasValueAtPath(json, path)\n\n expect(\n exists,\n `Expected JSON path \"${path}\" to not exist`\n ).toBe(false)\n\n return this\n }\n\n /**\n * Assert JSON value at path matches a custom predicate.\n *\n * @param path - Dot-notation path (e.g., 'data.items')\n * @param matcher - Predicate function to validate the value\n */\n async assertJsonPathMatches(\n path: string,\n matcher: (value: unknown) => boolean\n ): Promise<this> {\n const json = await this.json()\n const value = this.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 /**\n * Assert string value at path contains a substring.\n *\n * @param path - Dot-notation path (e.g., 'data.message')\n * @param substring - Substring to search for\n */\n async assertJsonPathContains(path: string, substring: string): Promise<this> {\n const json = await this.json()\n const value = this.getValueAtPath(json, path)\n\n expect(\n typeof value === 'string',\n `Expected JSON path \"${path}\" to be a string, got ${typeof value}`\n ).toBe(true)\n\n expect(\n (value as string).includes(substring),\n `Expected JSON path \"${path}\" to contain \"${substring}\", got \"${String(value)}\"`\n ).toBe(true)\n\n return this\n }\n\n /**\n * Assert array value at path includes a specific item.\n *\n * @param path - Dot-notation path (e.g., 'data.tags')\n * @param item - Item to search for in the array\n */\n async assertJsonPathIncludes(path: string, item: unknown): Promise<this> {\n const json = await this.json()\n const value = this.getValueAtPath(json, path)\n\n expect(\n Array.isArray(value),\n `Expected JSON path \"${path}\" to be an array, got ${typeof value}`\n ).toBe(true)\n\n expect(\n (value as unknown[]).includes(item),\n `Expected JSON path \"${path}\" to include ${JSON.stringify(item)}`\n ).toBe(true)\n\n return this\n }\n\n /**\n * Assert array length at path equals the expected count.\n *\n * @param path - Dot-notation path (e.g., 'data.items')\n * @param count - Expected array length\n */\n async assertJsonPathCount(path: string, count: number): Promise<this> {\n const json = await this.json()\n const value = this.getValueAtPath(json, path)\n\n expect(\n Array.isArray(value),\n `Expected JSON path \"${path}\" to be an array, got ${typeof value}`\n ).toBe(true)\n\n expect(\n (value as unknown[]).length,\n `Expected JSON path \"${path}\" to have ${count} items, got ${(value as unknown[]).length}`\n ).toBe(count)\n\n return this\n }\n\n /**\n * Assert multiple JSON paths at once (batch assertion).\n *\n * @param expectations - Object mapping paths to expected values\n */\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 = this.getValueAtPath(json, path)\n expect(\n actual,\n `Expected JSON path \"${path}\" to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`\n ).toBe(expected)\n }\n\n return this\n }\n\n // ============================================================\n // Header Assertions\n // ============================================================\n\n /**\n * Assert response has the given header.\n */\n assertHeader(name: string, expected?: string): this {\n const actual = this.response.headers.get(name)\n\n expect(\n actual !== null,\n `Expected header \"${name}\" to be present`\n ).toBe(true)\n\n if (expected !== undefined) {\n expect(\n actual,\n `Expected header \"${name}\" to be \"${expected}\", got \"${actual}\"`\n ).toBe(expected)\n }\n\n return this\n }\n\n /**\n * Assert response does not have the given header.\n */\n assertHeaderMissing(name: string): this {\n const actual = this.response.headers.get(name)\n\n expect(\n actual,\n `Expected header \"${name}\" to be absent, but got \"${actual}\"`\n ).toBeNull()\n\n return this\n }\n\n // ============================================================\n // Private Helpers\n // ============================================================\n\n /**\n * Get value at dot-notation path.\n */\n private 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 * Check if a path exists in the object (even if value is null/undefined).\n */\n private 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}\n","import type { AuthService } from '@stratal/framework/auth'\nimport { AUTH_SERVICE } from '@stratal/framework/auth'\nimport { ActingAs } from '../../auth'\nimport type { TestingModule } from '../testing-module'\nimport { TestResponse } from './test-response'\n\n/**\n * TestHttpRequest\n *\n * Request builder with fluent API for configuring test HTTP requests.\n *\n * @example\n * ```typescript\n * const response = await module.http\n * .post('/api/v1/register')\n * .withBody({ name: 'Test School' })\n * .withHeaders({ 'X-Custom': 'value' })\n * .send()\n * ```\n *\n * @example Authenticated request\n * ```typescript\n * const response = await module.http\n * .get('/api/v1/profile')\n * .actingAs({ id: user.id })\n * .send()\n * ```\n */\nexport class TestHttpRequest {\n\tprivate body: unknown = null\n\tprivate requestHeaders: Headers\n\tprivate actingAsUser: { id: string } | null = null\n\n\tconstructor(\n\t\tprivate readonly method: string,\n\t\tprivate readonly path: string,\n\t\theaders: Headers,\n\t\tprivate readonly module: TestingModule,\n\t\tprivate readonly host: string | null = null\n\t) {\n\t\tthis.requestHeaders = new Headers(headers)\n\t}\n\n\t/**\n\t * Set the request body\n\t */\n\twithBody(data: unknown): this {\n\t\tthis.body = data\n\t\treturn this\n\t}\n\n\t/**\n\t * Add headers to the request\n\t */\n\twithHeaders(headers: Record<string, string>): this {\n\t\tfor (const [key, value] of Object.entries(headers)) {\n\t\t\tthis.requestHeaders.set(key, value)\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Set Content-Type to application/json\n\t */\n\tasJson(): this {\n\t\tthis.requestHeaders.set('Content-Type', 'application/json')\n\t\treturn this\n\t}\n\n\t/**\n\t * Authenticate the request as a specific user\n\t */\n\tactingAs(user: { id: string }): this {\n\t\tthis.actingAsUser = user\n\t\treturn this\n\t}\n\n\t/**\n\t * Send the request and return response\n\t *\n\t * Calls module.fetch() - NOT SELF.fetch()\n\t */\n\tasync send(): Promise<TestResponse> {\n\t\tawait this.applyAuthentication()\n\n\t\t// Auto-set Content-Type for body\n\t\tif (this.body && !this.requestHeaders.has('Content-Type')) {\n\t\t\tthis.requestHeaders.set('Content-Type', 'application/json')\n\t\t}\n\n\t\t// Build request\n\t\tconst url = new URL(this.path, `http://${this.host ?? 'localhost'}`)\n\t\tconst request = new Request(url.toString(), {\n\t\t\tmethod: this.method,\n\t\t\theaders: this.requestHeaders,\n\t\t\tbody: this.body ? JSON.stringify(this.body) : null,\n\t\t})\n\n\t\t// Call module.fetch() - NO SELF.fetch()\n\t\tconst response = await this.module.fetch(request)\n\t\treturn new TestResponse(response)\n\t}\n\n\tprivate async applyAuthentication(): Promise<void> {\n\t\tif (!this.actingAsUser) return\n\n\t\tawait this.module.runInRequestScope(async () => {\n\t\t\tconst authService = this.module.get<AuthService>(AUTH_SERVICE)\n\t\t\tconst actingAs = new ActingAs(authService)\n\t\t\tconst authHeaders = this.actingAsUser ? await actingAs.createSessionForUser(this.actingAsUser) : new Headers()\n\n\t\t\tfor (const [key, value] of authHeaders.entries()) {\n\t\t\t\tthis.requestHeaders.set(key, value)\n\t\t\t}\n\t\t})\n\t}\n}\n","import type { TestingModule } from '../testing-module'\nimport { TestHttpRequest } from './test-http-request'\n\n/**\n * TestHttpClient\n *\n * Fluent HTTP client for making test requests.\n *\n * @example\n * ```typescript\n * const response = await module.http\n * .forHost('example.com')\n * .post('/api/v1/users')\n * .withBody({ name: 'Test' })\n * .send()\n *\n * response.assertCreated()\n * ```\n */\nexport class TestHttpClient {\n private defaultHeaders: Headers = new Headers()\n private host: string | null = null\n\n constructor(private readonly module: TestingModule) { }\n\n /**\n * Set the host for the request\n */\n forHost(host: string): this {\n this.host = host\n return this\n }\n\n /**\n * Set default headers for all requests\n */\n withHeaders(headers: Record<string, string>): this {\n for (const [key, value] of Object.entries(headers)) {\n this.defaultHeaders.set(key, value)\n }\n return this\n }\n\n /**\n * Create a GET request\n */\n get(path: string): TestHttpRequest {\n return this.createRequest('GET', path)\n }\n\n /**\n * Create a POST request\n */\n post(path: string): TestHttpRequest {\n return this.createRequest('POST', path)\n }\n\n /**\n * Create a PUT request\n */\n put(path: string): TestHttpRequest {\n return this.createRequest('PUT', path)\n }\n\n /**\n * Create a PATCH request\n */\n patch(path: string): TestHttpRequest {\n return this.createRequest('PATCH', path)\n }\n\n /**\n * Create a DELETE request\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","import type { CommandResult } from 'stratal/quarry'\nimport { expect } from 'vitest'\n\n/**\n * Fluent assertion wrapper for command results.\n *\n * @example\n * ```typescript\n * const result = await module\n * .quarry('users:create')\n * .withInput({ email: 'test@example.com' })\n * .run()\n *\n * result.assertSuccessful()\n * result.assertOutputContains('User created')\n * ```\n */\nexport class TestCommandResult {\n constructor(private readonly result: CommandResult) {}\n\n get exitCode(): number {\n return this.result.exitCode\n }\n\n get output(): string[] {\n return this.result.output\n }\n\n get errors(): string[] {\n return this.result.errors\n }\n\n assertSuccessful(): this {\n expect(this.result.exitCode, `Expected exit code 0, got ${this.result.exitCode}. Errors: ${this.result.errors.join(', ')}`).toBe(0)\n expect(this.result.errors, 'Expected no errors').toHaveLength(0)\n return this\n }\n\n assertFailed(exitCode?: number): this {\n if (exitCode !== undefined) {\n expect(this.result.exitCode, `Expected exit code ${exitCode}, got ${this.result.exitCode}`).toBe(exitCode)\n } else {\n expect(this.result.exitCode, 'Expected non-zero exit code').not.toBe(0)\n }\n return this\n }\n\n assertExitCode(code: number): this {\n expect(this.result.exitCode, `Expected exit code ${code}, got ${this.result.exitCode}`).toBe(code)\n return this\n }\n\n assertOutputContains(text: string): this {\n const joined = this.result.output.join('\\n')\n expect(joined, `Expected output to contain \"${text}\"`).toContain(text)\n return this\n }\n\n assertOutputMissing(text: string): this {\n const joined = this.result.output.join('\\n')\n expect(joined, `Expected output NOT to contain \"${text}\"`).not.toContain(text)\n return this\n }\n\n assertErrorContains(text: string): this {\n const joined = this.result.errors.join('\\n')\n expect(joined, `Expected errors to contain \"${text}\"`).toContain(text)\n return this\n }\n\n assertErrorMissing(text: string): this {\n const joined = this.result.errors.join('\\n')\n expect(joined, `Expected errors NOT to contain \"${text}\"`).not.toContain(text)\n return this\n }\n}\n","import type { CommandInput } from 'stratal/quarry'\nimport type { TestingModule } from '../testing-module'\nimport { TestCommandResult } from './test-command-result'\n\n/**\n * Fluent builder for testing Quarry commands.\n *\n * @example\n * ```typescript\n * const result = await module\n * .quarry('users:create')\n * .withInput({ email: 'test@example.com', admin: true })\n * .run()\n *\n * result.assertSuccessful()\n * result.assertOutputContains('User created')\n * ```\n */\nexport class TestCommandRequest {\n private _input: CommandInput = {}\n\n constructor(\n private readonly commandName: string,\n private readonly module: TestingModule,\n ) {}\n\n /**\n * Set the flat input for the command.\n */\n withInput(input: CommandInput): this {\n this._input = { ...input }\n return this\n }\n\n /**\n * Execute the command and return a TestCommandResult for assertions.\n */\n async run(): Promise<TestCommandResult> {\n const result = await this.module.application.handleCommand(this.commandName, this._input)\n return new TestCommandResult(result)\n }\n}\n","import { expect } from \"vitest\"\n\n/**\n * Represents a parsed SSE event\n */\nexport interface TestSseEvent {\n\tdata: string\n\tevent?: string\n\tid?: string\n\tretry?: number\n}\n\n/**\n * TestSseConnection\n *\n * Live SSE connection wrapper with assertion helpers for testing.\n *\n * @example\n * ```typescript\n * const sse = await module.sse('/streaming/sse').connect()\n * await sse.assertEvent({ event: 'message', data: 'hello', id: '1' })\n * await sse.waitForEnd()\n * ```\n */\nexport class TestSseConnection {\n\tprivate readonly eventQueue: TestSseEvent[] = []\n\tprivate eventWaiters: ((event: TestSseEvent) => void)[] = []\n\tprivate streamEnded = false\n\tprivate endWaiters: (() => void)[] = []\n\n\tconstructor(private readonly response: Response) {\n\t\tthis.startReading()\n\t}\n\n\t/**\n\t * Wait for the next SSE event\n\t */\n\tasync waitForEvent(timeout = 5000): Promise<TestSseEvent> {\n\t\tif (this.eventQueue.length > 0) {\n\t\t\treturn this.eventQueue.shift()!\n\t\t}\n\n\t\tif (this.streamEnded) {\n\t\t\tthrow new Error('SSE: stream has ended, no more events')\n\t\t}\n\n\t\treturn new Promise<TestSseEvent>((resolve, reject) => {\n\t\t\tconst waiter = (event: TestSseEvent) => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tresolve(event)\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tconst index = this.eventWaiters.indexOf(waiter)\n\t\t\t\tif (index !== -1) this.eventWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`SSE: no event received within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\tthis.eventWaiters.push(waiter)\n\t\t})\n\t}\n\n\t/**\n\t * Wait for the stream to end\n\t */\n\tasync waitForEnd(timeout = 5000): Promise<void> {\n\t\tif (this.streamEnded) return\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst waiter = () => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tresolve()\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tconst index = this.endWaiters.indexOf(waiter)\n\t\t\t\tif (index !== -1) this.endWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`SSE: stream did not end within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\tthis.endWaiters.push(waiter)\n\t\t})\n\t}\n\n\t/**\n\t * Collect all remaining events until the stream ends\n\t */\n\tasync collectEvents(timeout = 5000): Promise<TestSseEvent[]> {\n\t\tconst events: TestSseEvent[] = []\n\n\t\tif (this.streamEnded) {\n\t\t\treturn [...this.eventQueue.splice(0)]\n\t\t}\n\n\t\treturn new Promise<TestSseEvent[]>((resolve, reject) => {\n\t\t\t// Listen for new events until stream ends\n\t\t\tconst originalDispatch = this.dispatchEvent.bind(this)\n\t\t\tthis.dispatchEvent = (event: TestSseEvent) => {\n\t\t\t\tevents.push(event)\n\t\t\t\toriginalDispatch(event)\n\t\t\t}\n\n\t\t\tconst endWaiter = () => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tthis.dispatchEvent = originalDispatch\n\t\t\t\tresolve(events)\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.dispatchEvent = originalDispatch\n\t\t\t\tconst index = this.endWaiters.indexOf(endWaiter)\n\t\t\t\tif (index !== -1) this.endWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`SSE: stream did not end within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\t// Drain any queued events first\n\t\t\tevents.push(...this.eventQueue.splice(0))\n\n\t\t\tthis.endWaiters.push(endWaiter)\n\t\t})\n\t}\n\n\t/**\n\t * Assert that the next event matches the expected partial shape\n\t */\n\tasync assertEvent(expected: Partial<TestSseEvent>, timeout = 5000): Promise<void> {\n\t\tconst event = await this.waitForEvent(timeout)\n\t\texpect(event).toMatchObject(expected)\n\t}\n\n\t/**\n\t * Assert that the next event's data matches the expected string\n\t */\n\tasync assertEventData(expected: string, timeout = 5000): Promise<void> {\n\t\tconst event = await this.waitForEvent(timeout)\n\t\texpect(event.data, `Expected SSE data \"${expected}\", got \"${event.data}\"`).toBe(expected)\n\t}\n\n\t/**\n\t * Assert that the next event's data is JSON matching the expected value\n\t */\n\tasync assertJsonEventData<T>(expected: T, timeout = 5000): Promise<void> {\n\t\tconst event = await this.waitForEvent(timeout)\n\t\tconst parsed = JSON.parse(event.data) as unknown\n\t\texpect(parsed).toEqual(expected)\n\t}\n\n\t/**\n\t * Access the raw Response\n\t */\n\tget raw(): Response {\n\t\treturn this.response\n\t}\n\n\tprivate startReading(): void {\n\t\tconst body = this.response.body\n\t\tif (!body) {\n\t\t\tthis.streamEnded = true\n\t\t\treturn\n\t\t}\n\n\t\tconst reader = body.getReader() as ReadableStreamDefaultReader<Uint8Array>\n\t\tconst decoder = new TextDecoder()\n\t\tlet buffer = ''\n\n\t\tconst read = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read()\n\n\t\t\t\t\tif (done) {\n\t\t\t\t\t\t// Parse any remaining buffered data\n\t\t\t\t\t\tif (buffer.trim()) {\n\t\t\t\t\t\t\tconst event = this.parseEvent(buffer)\n\t\t\t\t\t\t\tif (event) this.dispatchEvent(event)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.streamEnded = true\n\t\t\t\t\t\tfor (const waiter of this.endWaiters) {\n\t\t\t\t\t\t\twaiter()\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.endWaiters = []\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true })\n\n\t\t\t\t\t// Split on double newlines (SSE event boundary)\n\t\t\t\t\tconst parts = buffer.split('\\n\\n')\n\t\t\t\t\t// Last part may be incomplete, keep it in the buffer\n\t\t\t\t\tbuffer = parts.pop()!\n\n\t\t\t\t\tfor (const part of parts) {\n\t\t\t\t\t\tif (!part.trim()) continue\n\t\t\t\t\t\tconst event = this.parseEvent(part)\n\t\t\t\t\t\tif (event) this.dispatchEvent(event)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tthis.streamEnded = true\n\t\t\t\tfor (const waiter of this.endWaiters) {\n\t\t\t\t\twaiter()\n\t\t\t\t}\n\t\t\t\tthis.endWaiters = []\n\t\t\t}\n\t\t}\n\n\t\tvoid read()\n\t}\n\n\tprivate parseEvent(raw: string): TestSseEvent | null {\n\t\tconst lines = raw.split('\\n')\n\t\tconst dataLines: string[] = []\n\t\tlet event: string | undefined\n\t\tlet id: string | undefined\n\t\tlet retry: number | undefined\n\n\t\tfor (const line of lines) {\n\t\t\tif (line.startsWith(':')) continue // comment line\n\n\t\t\tconst colonIndex = line.indexOf(':')\n\t\t\tif (colonIndex === -1) continue\n\n\t\t\tconst field = line.slice(0, colonIndex)\n\t\t\t// Strip optional leading space after colon\n\t\t\tconst value = line[colonIndex + 1] === ' ' ? line.slice(colonIndex + 2) : line.slice(colonIndex + 1)\n\n\t\t\tswitch (field) {\n\t\t\t\tcase 'data':\n\t\t\t\t\tdataLines.push(value)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'event':\n\t\t\t\t\tevent = value\n\t\t\t\t\tbreak\n\t\t\t\tcase 'id':\n\t\t\t\t\tid = value\n\t\t\t\t\tbreak\n\t\t\t\tcase 'retry': {\n\t\t\t\t\tconst parsed = parseInt(value, 10)\n\t\t\t\t\tif (!isNaN(parsed)) retry = parsed\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (dataLines.length === 0) return null\n\n\t\tconst result: TestSseEvent = { data: dataLines.join('\\n') }\n\t\tif (event !== undefined) result.event = event\n\t\tif (id !== undefined) result.id = id\n\t\tif (retry !== undefined) result.retry = retry\n\n\t\treturn result\n\t}\n\n\tprivate dispatchEvent(event: TestSseEvent): void {\n\t\tif (this.eventWaiters.length > 0) {\n\t\t\tthis.eventWaiters.shift()!(event)\n\t\t} else {\n\t\t\tthis.eventQueue.push(event)\n\t\t}\n\t}\n}\n","import type { AuthService } from '@stratal/framework/auth'\nimport { AUTH_SERVICE } from '@stratal/framework/auth'\nimport { expect } from 'vitest'\nimport { ActingAs } from '../../auth'\nimport type { TestingModule } from '../testing-module'\nimport { TestSseConnection } from './test-sse-connection'\n\n/**\n * TestSseRequest\n *\n * Builder for SSE connection requests. Follows the TestWsRequest pattern.\n *\n * @example\n * ```typescript\n * const sse = await module.sse('/streaming/sse').connect()\n * await sse.assertEvent({ event: 'message', data: 'hello' })\n * await sse.waitForEnd()\n * ```\n *\n * @example Authenticated SSE\n * ```typescript\n * const sse = await module.sse('/streaming/sse').actingAs({ id: user.id }).connect()\n * ```\n */\nexport class TestSseRequest {\n\tprivate requestHeaders: Headers = new Headers()\n\tprivate actingAsUser: { id: string } | null = null\n\n\tconstructor(\n\t\tprivate readonly path: string,\n\t\tprivate readonly module: TestingModule,\n\t) { }\n\n\t/**\n\t * Add custom headers to the request\n\t */\n\twithHeaders(headers: Record<string, string>): this {\n\t\tfor (const [key, value] of Object.entries(headers)) {\n\t\t\tthis.requestHeaders.set(key, value)\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Authenticate the SSE connection as a specific user\n\t */\n\tactingAs(user: { id: string }): this {\n\t\tthis.actingAsUser = user\n\t\treturn this\n\t}\n\n\t/**\n\t * Send the request and return a live SSE connection\n\t */\n\tasync connect(): Promise<TestSseConnection> {\n\t\tawait this.applyAuthentication()\n\n\t\tthis.requestHeaders.set('Accept', 'text/event-stream')\n\n\t\tconst url = new URL(this.path, 'http://localhost')\n\t\tconst request = new Request(url.toString(), {\n\t\t\theaders: this.requestHeaders,\n\t\t})\n\n\t\tconst response = await this.module.fetch(request)\n\n\t\texpect(\n\t\t\tresponse.status,\n\t\t\t`Expected status 200, got ${response.status}`,\n\t\t).toBe(200)\n\n\t\tconst contentType = response.headers.get('content-type') ?? ''\n\t\texpect(\n\t\t\tcontentType.includes('text/event-stream'),\n\t\t\t`Expected content-type \"text/event-stream\", got \"${contentType}\"`,\n\t\t).toBe(true)\n\n\t\treturn new TestSseConnection(response)\n\t}\n\n\tprivate async applyAuthentication(): Promise<void> {\n\t\tif (!this.actingAsUser) return\n\n\t\tawait this.module.runInRequestScope(async () => {\n\t\t\tconst authService = this.module.get<AuthService>(AUTH_SERVICE)\n\t\t\tconst actingAs = new ActingAs(authService)\n\t\t\tconst authHeaders = this.actingAsUser ? await actingAs.createSessionForUser(this.actingAsUser) : new Headers()\n\n\t\t\tfor (const [key, value] of authHeaders.entries()) {\n\t\t\t\tthis.requestHeaders.set(key, value)\n\t\t\t}\n\t\t})\n\t}\n}\n","import { expect } from \"vitest\";\n\n/**\n * TestWsConnection\n *\n * Live WebSocket connection wrapper with assertion helpers for testing.\n *\n * @example\n * ```typescript\n * const ws = await module.ws('/ws/chat').connect()\n * ws.send('hello')\n * await ws.assertMessage('echo:hello')\n * ws.close()\n * await ws.waitForClose()\n * ```\n */\nexport class TestWsConnection {\n\tprivate readonly messageQueue: (string | ArrayBuffer)[] = []\n\tprivate messageWaiters: ((data: string | ArrayBuffer) => void)[] = []\n\tprivate closeEvent: { code?: number; reason?: string } | null = null\n\tprivate closeWaiters: ((event: { code?: number; reason?: string }) => void)[] = []\n\n\tconstructor(private readonly ws: WebSocket) {\n\t\tthis.ws.addEventListener('message', (event: MessageEvent) => {\n\t\t\tconst data = event.data as string | ArrayBuffer\n\t\t\tif (this.messageWaiters.length > 0) {\n\t\t\t\tthis.messageWaiters.shift()!(data)\n\t\t\t} else {\n\t\t\t\tthis.messageQueue.push(data)\n\t\t\t}\n\t\t})\n\n\t\tthis.ws.addEventListener('close', (event: CloseEvent) => {\n\t\t\tthis.closeEvent = { code: event.code, reason: event.reason }\n\t\t\tfor (const waiter of this.closeWaiters) {\n\t\t\t\twaiter(this.closeEvent)\n\t\t\t}\n\t\t\tthis.closeWaiters = []\n\t\t})\n\t}\n\n\t/**\n\t * Send a message through the WebSocket\n\t */\n\tsend(data: string | ArrayBuffer | Uint8Array): void {\n\t\tthis.ws.send(data)\n\t}\n\n\t/**\n\t * Close the WebSocket connection\n\t */\n\tclose(code?: number, reason?: string): void {\n\t\tthis.ws.close(code, reason)\n\t}\n\n\t/**\n\t * Wait for the next message, returning its data\n\t */\n\tasync waitForMessage(timeout = 5000): Promise<string | ArrayBuffer> {\n\t\tif (this.messageQueue.length > 0) {\n\t\t\treturn this.messageQueue.shift()!\n\t\t}\n\n\t\treturn new Promise<string | ArrayBuffer>((resolve, reject) => {\n\t\t\tconst waiter = (data: string | ArrayBuffer) => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tresolve(data)\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tconst index = this.messageWaiters.indexOf(waiter)\n\t\t\t\tif (index !== -1) this.messageWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`WebSocket: no message received within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\tthis.messageWaiters.push(waiter)\n\t\t})\n\t}\n\n\t/**\n\t * Wait for the connection to close\n\t */\n\tasync waitForClose(timeout = 5000): Promise<{ code?: number; reason?: string }> {\n\t\tif (this.closeEvent) {\n\t\t\treturn this.closeEvent\n\t\t}\n\n\t\treturn new Promise<{ code?: number; reason?: string }>((resolve, reject) => {\n\t\t\tconst waiter = (event: { code?: number; reason?: string }) => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tresolve(event)\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tconst index = this.closeWaiters.indexOf(waiter)\n\t\t\t\tif (index !== -1) this.closeWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`WebSocket: connection did not close within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\tthis.closeWaiters.push(waiter)\n\t\t})\n\t}\n\n\t/**\n\t * Assert that the next message equals the expected value\n\t */\n\tasync assertMessage(expected: string, timeout = 5000): Promise<void> {\n\t\tconst data = await this.waitForMessage(timeout)\n\t\tconst message = typeof data === 'string' ? data : '[ArrayBuffer]'\n\t\texpect(message, `Expected WebSocket message \"${expected}\", got \"${message}\"`).toBe(expected)\n\t}\n\n\t/**\n\t * Assert that the connection closes, optionally with an expected code\n\t */\n\tasync assertClosed(expectedCode?: number, timeout = 5000): Promise<void> {\n\t\tconst event = await this.waitForClose(timeout)\n\t\tif (expectedCode !== undefined) {\n\t\t\texpect(event.code, `Expected close code ${expectedCode}, got ${event.code}`).toBe(expectedCode)\n\t\t}\n\t}\n\n\t/**\n\t * Access the raw Cloudflare WebSocket\n\t */\n\tget raw(): WebSocket {\n\t\treturn this.ws\n\t}\n}\n","import type { AuthService } from '@stratal/framework/auth'\nimport { AUTH_SERVICE } from '@stratal/framework/auth'\nimport { expect } from 'vitest'\nimport { ActingAs } from '../../auth'\nimport type { TestingModule } from '../testing-module'\nimport { TestWsConnection } from './test-ws-connection'\n\n/**\n * TestWsRequest\n *\n * Builder for WebSocket upgrade requests. Follows the TestHttpRequest pattern.\n *\n * @example\n * ```typescript\n * const ws = await module.ws('/ws/chat').connect()\n * ws.send('hello')\n * await ws.assertMessage('echo:hello')\n * ws.close()\n * ```\n *\n * @example Authenticated WebSocket\n * ```typescript\n * const ws = await module.ws('/ws/chat').actingAs({ id: user.id }).connect()\n * ```\n */\nexport class TestWsRequest {\n\tprivate requestHeaders: Headers = new Headers()\n\tprivate actingAsUser: { id: string } | null = null\n\n\tconstructor(\n\t\tprivate readonly path: string,\n\t\tprivate readonly module: TestingModule,\n\t) { }\n\n\t/**\n\t * Add custom headers to the upgrade request\n\t */\n\twithHeaders(headers: Record<string, string>): this {\n\t\tfor (const [key, value] of Object.entries(headers)) {\n\t\t\tthis.requestHeaders.set(key, value)\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Authenticate the WebSocket connection as a specific user\n\t */\n\tactingAs(user: { id: string }): this {\n\t\tthis.actingAsUser = user\n\t\treturn this\n\t}\n\n\t/**\n\t * Send the upgrade request and return a live WebSocket connection\n\t */\n\tasync connect(): Promise<TestWsConnection> {\n\t\tawait this.applyAuthentication()\n\n\t\tthis.requestHeaders.set('Upgrade', 'websocket')\n\t\tthis.requestHeaders.set('Connection', 'Upgrade')\n\t\tthis.requestHeaders.set('Sec-WebSocket-Key', 'dGhlIHNhbXBsZSBub25jZQ==')\n\t\tthis.requestHeaders.set('Sec-WebSocket-Version', '13')\n\n\t\tconst url = new URL(this.path, 'http://localhost')\n\t\tconst request = new Request(url.toString(), {\n\t\t\theaders: this.requestHeaders,\n\t\t})\n\n\t\tconst response = await this.module.fetch(request)\n\n\t\texpect(\n\t\t\tresponse.status,\n\t\t\t`Expected status 101 (Switching Protocols), got ${response.status}`,\n\t\t).toBe(101)\n\n\t\tconst ws = (response as Response & { webSocket: WebSocket | null }).webSocket\n\t\tif (!ws) {\n\t\t\tthrow new Error('Response did not include a WebSocket connection')\n\t\t}\n\n\t\tws.accept()\n\n\t\treturn new TestWsConnection(ws)\n\t}\n\n\tprivate async applyAuthentication(): Promise<void> {\n\t\tif (!this.actingAsUser) return\n\n\t\tawait this.module.runInRequestScope(async () => {\n\t\t\tconst authService = this.module.get<AuthService>(AUTH_SERVICE)\n\t\t\tconst actingAs = new ActingAs(authService)\n\t\t\tconst authHeaders = this.actingAsUser ? await actingAs.createSessionForUser(this.actingAsUser) : new Headers()\n\n\t\t\tfor (const [key, value] of authHeaders.entries()) {\n\t\t\t\tthis.requestHeaders.set(key, value)\n\t\t\t}\n\t\t})\n\t}\n}\n","import type { ConnectionName, DatabaseService } from '@stratal/framework/database'\nimport { connectionSymbol } from '@stratal/framework/database'\nimport type { Application, Constructor, StratalEnv } from 'stratal'\nimport { DI_TOKENS, type Container } from 'stratal/di'\nimport { type InjectionToken } from 'stratal/module'\nimport { SEEDER_TOKENS, type Seeder, type SeederRegistry, SeederNotRegisteredError } from 'stratal/seeder'\nimport { STORAGE_TOKENS } from 'stratal/storage'\nimport { expect } from 'vitest'\nimport type { FakeStorageService } from '../storage'\nimport { TestHttpClient } from './http/test-http-client'\nimport { TestCommandRequest } from './quarry/test-command-request'\nimport { TestSseRequest } from './sse/test-sse-request'\nimport { TestWsRequest } from './ws/test-ws-request'\n\n/**\n * TestingModule\n *\n * Provides access to the test application, container, HTTP client, and utilities.\n *\n * @example\n * ```typescript\n * const module = await Test.createTestingModule({\n * modules: [RegistrationModule],\n * }).compile()\n *\n * // Make HTTP requests\n * const response = await module.http\n * .post('/api/v1/register')\n * .withBody({ ... })\n * .send()\n *\n * // Access services\n * const service = module.get(REGISTRATION_TOKENS.RegistrationService)\n *\n * // Database utilities\n * await module.truncateDb()\n * await module.seed(UserSeeder)\n * await module.assertDatabaseHas('user', { email: 'test@example.com' })\n *\n * // Cleanup\n * await module.close()\n * ```\n */\nexport class TestingModule {\n private _http: TestHttpClient | null = null\n private readonly _requestContainer: Container\n\n constructor(\n private readonly app: Application,\n private readonly env: StratalEnv,\n private readonly ctx: ExecutionContext,\n ) {\n const mockContext = this.app.createMockRouterContext()\n this._requestContainer = this.app.container.createRequestScope(mockContext)\n }\n\n /**\n * Resolve a service from the container\n */\n get<T>(token: InjectionToken<T>): T {\n return this._requestContainer.resolve(token)\n }\n\n /**\n * Get HTTP test client for making requests\n */\n get http(): TestHttpClient {\n this._http ??= new TestHttpClient(this)\n return this._http\n }\n\n /**\n * Get fake storage service for assertions\n */\n get storage(): FakeStorageService {\n return this.get<FakeStorageService>(STORAGE_TOKENS.StorageService)\n }\n\n /**\n * Create a WebSocket test request builder for the given path\n */\n ws(path: string): TestWsRequest {\n return new TestWsRequest(path, this)\n }\n\n /**\n * Create an SSE test request builder for the given path\n */\n sse(path: string): TestSseRequest {\n return new TestSseRequest(path, this)\n }\n\n /**\n * Create a Quarry command test request builder\n */\n quarry(name: string): TestCommandRequest {\n return new TestCommandRequest(name, this)\n }\n\n /**\n * Get Application instance\n */\n get application(): Application {\n return this.app\n }\n\n /**\n * Get DI Container (request-scoped)\n */\n get container(): Container {\n return this._requestContainer\n }\n\n /**\n * Execute an HTTP request through HonoApp\n */\n async fetch(request: Request): Promise<Response> {\n return this.app.hono.fetch(request, this.env, this.ctx)\n }\n\n /**\n * Run callback in request scope (for DB operations, service access)\n */\n async runInRequestScope<T>(callback: (container: Container) => T | Promise<T>): Promise<T> {\n const mockContext = this.app.createMockRouterContext()\n return this.app.container.runInRequestScope(mockContext, callback)\n }\n\n /**\n * Get database service instance (resolved in request scope)\n */\n getDb(): DatabaseService\n getDb<K extends ConnectionName>(name: K): DatabaseService<K>\n getDb(name?: string): unknown {\n const token = name ? connectionSymbol(name) : DI_TOKENS.Database\n return this._requestContainer.resolve(token)\n }\n\n /**\n * Truncate all non-prisma tables in the database\n */\n async truncateDb(name?: ConnectionName): Promise<void> {\n const db = this.getDb(name!)\n const tables = await db.$queryRaw<{ tablename: string }[]>`\n SELECT tablename::text as tablename FROM pg_tables\n WHERE schemaname = current_schema()\n AND tablename NOT LIKE '_prisma%'\n `\n if (tables.length === 0) return\n const tableList = tables.map((t) => `\"${t.tablename}\"`).join(', ')\n await db.$executeRawUnsafe(`TRUNCATE ${tableList} RESTART IDENTITY CASCADE`)\n }\n\n /**\n * Run seeders by class constructor in the request-scoped container\n */\n async seed(...SeederClasses: Constructor<Seeder>[]): Promise<void> {\n const registry = this._requestContainer.resolve<SeederRegistry>(SEEDER_TOKENS.SeederRegistry)\n for (const SeederClass of SeederClasses) {\n if (!registry.has(SeederClass)) {\n throw new SeederNotRegisteredError(SeederClass.name)\n }\n await registry.run(SeederClass, { container: this._requestContainer })\n }\n }\n\n /**\n * Assert that a record exists in the database\n */\n async assertDatabaseHas(table: string, data: Record<string, unknown>, name?: ConnectionName): Promise<void> {\n const db = this.getDb(name!)\n const model = (db as unknown as Record<string, unknown>)[table] as { findFirst: (opts: unknown) => Promise<unknown> }\n const result = await model.findFirst({ where: data })\n expect(result, `Expected ${table} with ${JSON.stringify(data)}`).not.toBeNull()\n }\n\n /**\n * Assert that a record does not exist in the database\n */\n async assertDatabaseMissing(table: string, data: Record<string, unknown>, name?: ConnectionName): Promise<void> {\n const db = this.getDb(name!)\n const model = (db as unknown as Record<string, unknown>)[table] as { findFirst: (opts: unknown) => Promise<unknown> }\n const result = await model.findFirst({ where: data })\n expect(result, `Expected ${table} NOT to have ${JSON.stringify(data)}`).toBeNull()\n }\n\n /**\n * Assert the number of records in a table\n */\n async assertDatabaseCount(table: string, expected: number, name?: ConnectionName): Promise<void> {\n const db = this.getDb(name!)\n const model = (db as unknown as Record<string, unknown>)[table] as { count: () => Promise<number> }\n const actual = await model.count()\n expect(actual, `Expected ${table} count ${expected}, got ${actual}`).toBe(expected)\n }\n\n /**\n * Cleanup - call in afterAll\n */\n async close(): Promise<void> {\n await this._requestContainer.dispose()\n await this.app.shutdown()\n }\n}\n","import { waitUntil } from 'cloudflare:workers'\nimport {\n Application,\n ApplicationConfig,\n type Constructor,\n type StratalEnv,\n} from 'stratal'\nimport { Container } from 'stratal/di'\nimport { LogLevel } from 'stratal/logger'\nimport { InjectionToken, Module, ModuleClass, ModuleOptions } from 'stratal/module'\nimport { STORAGE_TOKENS } from 'stratal/storage'\nimport { FakeStorageService } from '../storage'\nimport { getTestEnv } from './env'\nimport { ProviderOverrideBuilder, type ProviderOverrideConfig } from './override'\nimport { Test } from './test'\nimport { TestingModule } from './testing-module'\n\n/**\n * Configuration for creating a testing module\n *\n * Extends ModuleOptions to support all module properties like NestJS.\n *\n * @example\n * ```typescript\n * const module = await Test.createTestingModule({\n * imports: [RegistrationModule, GeoModule],\n * providers: [{ provide: MOCK_TOKEN, useValue: mockValue }],\n * controllers: [TestController],\n * }).compile()\n * ```\n */\nexport interface TestingModuleConfig extends ModuleOptions {\n /** Optional environment overrides */\n env?: Partial<StratalEnv>\n /** Logging configuration. Defaults: level=ERROR, formatter='json' */\n logging?: ApplicationConfig['logging']\n}\n\n/**\n * Builder for creating test modules with provider overrides\n */\nexport class TestingModuleBuilder {\n private overrides: ProviderOverrideConfig<object>[] = []\n\n constructor(private config: TestingModuleConfig) { }\n\n /**\n * Override a provider with a custom implementation\n */\n overrideProvider<T>(token: InjectionToken<T>): ProviderOverrideBuilder<T> {\n return new ProviderOverrideBuilder(this, token)\n }\n\n /**\n * Add a provider override (internal use by ProviderOverrideBuilder)\n *\n * @internal\n */\n addProviderOverride<T>(override: ProviderOverrideConfig<T>): this {\n this.overrides.push(override as ProviderOverrideConfig<object>)\n return this\n }\n\n /**\n * Merge additional environment bindings\n */\n withEnv(env: Partial<StratalEnv>): this {\n this.config.env = { ...this.config.env, ...env }\n return this\n }\n\n /**\n * Compile the testing module\n *\n * Creates the Application, applies overrides, initializes, and returns TestingModule.\n */\n async compile(): Promise<TestingModule> {\n const env = getTestEnv(this.config.env)\n const ctx = { waitUntil } as ExecutionContext\n\n // Build root module from config\n const baseModules = Test.getBaseModules()\n const allImports = [...baseModules, ...(this.config.imports ?? [])]\n\n const rootModule = this.createTestRootModule({\n imports: allImports,\n providers: this.config.providers,\n controllers: this.config.controllers,\n consumers: this.config.consumers,\n jobs: this.config.jobs,\n })\n\n const app = new Application({\n module: rootModule,\n logging: {\n level: this.config.logging?.level ?? LogLevel.ERROR,\n formatter: this.config.logging?.formatter ?? 'pretty',\n },\n env,\n ctx,\n })\n\n // Auto-register FakeStorageService (can be overridden by user)\n app.container.registerSingleton(STORAGE_TOKENS.StorageService, FakeStorageService)\n\n // Apply user overrides BEFORE initialize\n for (const override of this.overrides) {\n switch (override.type) {\n case 'value':\n app.container.registerValue(override.token, override.implementation)\n break\n case 'class':\n app.container.registerSingleton(\n override.token,\n override.implementation as Constructor\n )\n break\n case 'factory':\n app.container.registerFactory(\n override.token,\n override.implementation as (c: Container) => object\n )\n break\n case 'existing':\n app.container.registerExisting(\n override.token,\n override.implementation as InjectionToken<object>\n )\n break\n }\n }\n\n await app.initialize()\n\n return new TestingModule(app, env, ctx)\n }\n\n /**\n * Create a test root module with the given options\n */\n private createTestRootModule(options: ModuleOptions): ModuleClass {\n @Module(options)\n class TestRootModule { }\n return TestRootModule\n }\n}\n","import { type DynamicModule, type ModuleClass } from 'stratal/module'\nimport { TestingModuleBuilder, type TestingModuleConfig } from './testing-module-builder'\n\n/**\n * Test\n *\n * Static class for creating testing modules.\n * Provides a NestJS-style API for configuring test modules.\n *\n * @example\n * ```typescript\n * // In vitest.setup.ts:\n * Test.setBaseModules([CoreModule])\n *\n * // In test files:\n * const module = await Test.createTestingModule({\n * imports: [RegistrationModule, GeoModule],\n * })\n * .overrideProvider(EMAIL_TOKENS.EmailService)\n * .useValue(mockEmailService)\n * .compile()\n * ```\n */\nexport class Test {\n /**\n * Base modules to include in all test modules\n * Set once in vitest.setup.ts\n */\n private static baseModules: (ModuleClass | DynamicModule)[] = []\n\n /**\n * Set base modules to include in all test modules\n * Should be called once in vitest.setup.ts\n *\n * @param modules - Modules to include before test-specific modules (e.g., CoreModule)\n */\n static setBaseModules(modules: (ModuleClass | DynamicModule)[]): void {\n this.baseModules = modules\n }\n\n /**\n * Get base modules\n */\n static getBaseModules(): (ModuleClass | DynamicModule)[] {\n return this.baseModules\n }\n\n /**\n * Create a testing module builder\n *\n * @param config - Configuration with modules and optional env overrides\n * @returns TestingModuleBuilder for configuring and compiling the module\n */\n static createTestingModule(config: TestingModuleConfig): TestingModuleBuilder {\n return new TestingModuleBuilder(config)\n }\n}\n","import { http, HttpResponse, type RequestHandler } from 'msw'\nimport { setupServer, type SetupServer } from 'msw/node'\nimport type { MockErrorOptions, MockJsonOptions } from './fetch-mock.types'\n\ntype HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options'\n\n/**\n * MSW-based fetch mock for declarative HTTP mocking in tests.\n *\n * Replaces the old Cloudflare `fetchMock` (undici MockAgent) with MSW's `setupServer`.\n * Works in both Node.js and workerd test environments.\n *\n * @example\n * ```typescript\n * import { createMockFetch } from '@stratal/testing'\n *\n * const mock = createMockFetch()\n *\n * beforeAll(() => mock.listen())\n * afterEach(() => mock.reset())\n * afterAll(() => mock.close())\n *\n * it('should mock external API', async () => {\n * mock.mockJsonResponse('https://api.example.com/data', { success: true })\n *\n * const response = await fetch('https://api.example.com/data')\n * const json = await response.json()\n *\n * expect(json.success).toBe(true)\n * })\n * ```\n */\nexport class MockFetch {\n private server: SetupServer\n\n constructor(handlers: RequestHandler[] = []) {\n this.server = setupServer(...handlers)\n }\n\n /** Start intercepting. Call in beforeAll/beforeEach. */\n listen() {\n this.server.listen({ onUnhandledRequest: 'error' })\n }\n\n /** Reset runtime handlers. Call in afterEach. */\n reset() {\n this.server.resetHandlers()\n }\n\n /** Stop intercepting. Call in afterAll. */\n close() {\n this.server.close()\n }\n\n /** Add runtime handler(s) for a single test. */\n use(...handlers: RequestHandler[]) {\n this.server.use(...handlers)\n }\n\n /**\n * Mock a JSON response.\n *\n * @param url - Full URL to mock (e.g., 'https://api.example.com/users')\n * @param data - JSON data to return\n * @param options - HTTP method, status code, headers\n *\n * @example\n * ```typescript\n * mock.mockJsonResponse('https://api.example.com/users', { users: [] })\n * mock.mockJsonResponse('https://api.example.com/users', { created: true }, { method: 'POST', status: 201 })\n * ```\n */\n mockJsonResponse(url: string, data: Record<string, unknown> | unknown[], options: MockJsonOptions = {}) {\n const method = (options.method ?? 'GET').toLowerCase() as HttpMethod\n const handler = http[method](url, () =>\n HttpResponse.json(data, {\n status: options.status ?? 200,\n headers: options.headers,\n }),\n )\n this.server.use(handler)\n }\n\n /**\n * Mock an error response.\n *\n * @param url - Full URL to mock\n * @param status - HTTP error status code\n * @param message - Optional error message\n * @param options - HTTP method, headers\n *\n * @example\n * ```typescript\n * mock.mockError('https://api.example.com/fail', 401, 'Unauthorized')\n * mock.mockError('https://api.example.com/fail', 500, 'Server Error', { method: 'POST' })\n * ```\n */\n mockError(url: string, status: number, message?: string, options: MockErrorOptions = {}) {\n const method = (options.method ?? 'GET').toLowerCase() as HttpMethod\n const body = message ? { error: message } : undefined\n this.server.use(\n http[method](url, () =>\n HttpResponse.json(body, { status, headers: options.headers }),\n ),\n )\n }\n}\n\n/**\n * Factory function to create a new MockFetch instance\n *\n * @param handlers - Optional initial MSW request handlers\n * @returns A new MockFetch instance\n *\n * @example\n * ```typescript\n * import { createMockFetch } from '@stratal/testing'\n *\n * const mock = createMockFetch()\n *\n * beforeAll(() => mock.listen())\n * afterEach(() => mock.reset())\n * afterAll(() => mock.close())\n * ```\n */\nexport function createMockFetch(handlers?: RequestHandler[]): MockFetch {\n return new MockFetch(handlers)\n}\n","/**\n * Base error class for all test framework errors.\n * Extends from Error and allows easy identification via `instanceof`.\n */\nexport class TestError extends Error {\n constructor(\n message: string,\n public readonly cause?: Error\n ) {\n super(message)\n this.name = 'TestError'\n\n // Maintain proper stack trace\n Error.captureStackTrace(this, this.constructor)\n }\n}\n","import { TestError } from './test-error'\n\n/**\n * Error thrown when test setup fails.\n * Examples: schema creation failure, migration failure, application bootstrap failure.\n */\nexport class TestSetupError extends TestError {\n constructor(message: string, cause?: Error) {\n super(`Test setup failed: ${message}`, cause)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["http","HttpResponse"],"sources":["../src/core/override/provider-override-builder.ts","../src/core/http/locale-helper.ts","../src/auth/acting-as.ts","../src/core/http/path-utils.ts","../src/core/http/test-response.ts","../src/core/http/test-http-request.ts","../src/core/http/test-http-client.ts","../src/core/quarry/test-command-result.ts","../src/core/quarry/test-command-request.ts","../src/core/sse/test-sse-connection.ts","../src/core/sse/test-sse-request.ts","../src/core/ws/test-ws-connection.ts","../src/core/ws/test-ws-request.ts","../src/core/testing-module.ts","../src/core/testing-module-builder.ts","../src/core/test.ts","../src/core/http/mock-fetch.ts","../src/errors/test-error.ts","../src/errors/setup-error.ts"],"sourcesContent":["import { type Container } from 'stratal/di'\nimport { type InjectionToken } from 'stratal/module'\nimport type { TestingModuleBuilder } from '../testing-module-builder'\n\n/**\n * Provider override configuration\n */\nexport interface ProviderOverrideConfig<T = unknown> {\n token: InjectionToken<T>\n type: 'value' | 'class' | 'factory' | 'existing'\n implementation: T | (new (...args: unknown[]) => T) | ((container: Container) => T) | InjectionToken<T>\n}\n\n/**\n * Fluent builder for provider overrides\n *\n * Provides a NestJS-style API for overriding providers in tests.\n *\n * @example\n * ```typescript\n * const module = await Test.createTestingModule({\n * imports: [RegistrationModule],\n * })\n * .overrideProvider(EMAIL_TOKENS.EmailService)\n * .useValue(mockEmailService)\n * .compile()\n * ```\n */\nexport class ProviderOverrideBuilder<T> {\n constructor(\n private readonly parent: TestingModuleBuilder,\n private readonly token: InjectionToken<T>\n ) { }\n\n /**\n * Use a static value as the provider\n *\n * The value will be registered directly in the container.\n *\n * @param value - The value to use as the provider\n * @returns The parent TestingModuleBuilder for chaining\n */\n useValue(value: T): TestingModuleBuilder {\n return this.parent.addProviderOverride({\n token: this.token,\n type: 'value',\n implementation: value,\n })\n }\n\n /**\n * Use a class as the provider\n *\n * The class will be registered as a singleton.\n *\n * @param cls - The class constructor to use as the provider\n * @returns The parent TestingModuleBuilder for chaining\n */\n useClass(cls: new (...args: unknown[]) => T): TestingModuleBuilder {\n return this.parent.addProviderOverride({\n token: this.token,\n type: 'class',\n implementation: cls,\n })\n }\n\n /**\n * Use a factory function as the provider\n *\n * The factory receives the container and should return the provider instance.\n *\n * @param factory - Factory function that creates the provider\n * @returns The parent TestingModuleBuilder for chaining\n */\n useFactory(factory: (container: Container) => T): TestingModuleBuilder {\n return this.parent.addProviderOverride({\n token: this.token,\n type: 'factory',\n implementation: factory,\n })\n }\n\n /**\n * Use an existing token as the provider (alias)\n *\n * The override token will resolve to the same instance as the target token.\n *\n * @param existingToken - The token to alias\n * @returns The parent TestingModuleBuilder for chaining\n *\n * @example\n * ```typescript\n * const module = await Test.createTestingModule({\n * imports: [MyModule],\n * })\n * .overrideProvider(ABSTRACT_TOKEN)\n * .useExisting(ConcreteService)\n * .compile()\n * ```\n */\n useExisting(existingToken: InjectionToken<T>): TestingModuleBuilder {\n return this.parent.addProviderOverride({\n token: this.token,\n type: 'existing',\n implementation: existingToken,\n })\n }\n}\n","import type { DetectionStrategy, I18nModuleOptions } from 'stratal/i18n'\nimport { I18N_TOKENS } from 'stratal/i18n'\nimport type { TestingModule } from '../testing-module'\n\n/**\n * Resolve the configured detection strategy from the testing module's DI container.\n * Falls back to 'cookie' if I18n is not configured.\n */\nexport function resolveLocaleStrategy(module: TestingModule): DetectionStrategy {\n try {\n const options = module.get<I18nModuleOptions>(I18N_TOKENS.Options)\n const detection = options.detection\n if (detection && 'strategy' in detection && detection.strategy) {\n return detection.strategy\n }\n return 'cookie'\n } catch {\n return 'cookie'\n }\n}\n\n/**\n * Apply locale to request headers based on detection strategy.\n */\nexport function applyLocaleToHeaders(\n headers: Headers,\n locale: string,\n strategy: DetectionStrategy,\n): void {\n switch (strategy) {\n case 'cookie':\n headers.set('Cookie', `locale=${locale}`)\n break\n case 'header':\n headers.set('Accept-Language', locale)\n break\n }\n}\n\n/**\n * Apply locale to URL based on detection strategy.\n */\nexport function applyLocaleToUrl(\n url: URL,\n locale: string,\n strategy: DetectionStrategy,\n): void {\n if (strategy === 'querystring') {\n url.searchParams.set('locale', locale)\n }\n}\n","import type { AuthService } from '@stratal/framework/auth'\nimport { type GenericEndpointContext } from 'better-auth'\nimport { setSessionCookie } from 'better-auth/cookies'\nimport { convertSetCookieToCookie } from 'better-auth/test'\n\nasync function makeSignature(value: string, secret: string): Promise<string> {\n const algorithm = { name: 'HMAC', hash: 'SHA-256' }\n const secretBuf = new TextEncoder().encode(secret)\n const key = await crypto.subtle.importKey('raw', secretBuf, algorithm, false, ['sign'])\n const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value))\n return btoa(String.fromCharCode(...new Uint8Array(signature)))\n}\n\nfunction buildCookieString(name: string, value: string, options: Record<string, unknown> = {}): string {\n const encodedValue = encodeURIComponent(value)\n let str = `${name}=${encodedValue}`\n if (options.path) str += `; Path=${options.path as string}`\n if (options.httpOnly) str += '; HttpOnly'\n if (options.secure) str += '; Secure'\n if (options.sameSite) str += `; SameSite=${options.sameSite as string}`\n if (options.maxAge !== undefined) str += `; Max-Age=${Math.floor(options.maxAge as number)}`\n return str\n}\n\n/**\n * ActingAs\n *\n * Creates authentication sessions for testing.\n * Uses Better Auth's internalAdapter to create real database sessions.\n *\n * @example\n * ```typescript\n * const actingAs = new ActingAs(authService)\n * const headers = await actingAs.createSessionForUser({ id: 'user-123' })\n * ```\n */\nexport class ActingAs {\n constructor(private readonly authService: AuthService) { }\n\n async createSessionForUser(user: { id: string }): Promise<Headers> {\n const auth = this.authService.auth\n const ctx = await auth.$context\n\n const secret = ctx.secret\n\n const session = await ctx.internalAdapter.createSession(\n user.id,\n undefined,\n { ipAddress: '127.0.0.1', userAgent: 'test-client' }\n )\n\n const dbUser = await ctx.internalAdapter.findUserById(user.id)\n if (!dbUser) {\n throw new Error(`User not found: ${user.id}`)\n }\n\n const responseHeaders = new Headers()\n const mockCtx = {\n context: ctx,\n getSignedCookie: () => null,\n setSignedCookie: async (name: string, value: string, _secret: string, options: Record<string, unknown> = {}) => {\n const signature = await makeSignature(value, secret)\n const signedValue = `${value}.${signature}`\n responseHeaders.append('Set-Cookie', buildCookieString(name, signedValue, options))\n },\n setCookie: (name: string, value: string, options: Record<string, unknown> = {}) => {\n responseHeaders.append('Set-Cookie', buildCookieString(name, value, options))\n },\n }\n\n await setSessionCookie(mockCtx as unknown as GenericEndpointContext, { session, user: dbUser }, false)\n return convertSetCookieToCookie(responseHeaders)\n }\n}\n","/**\n * Get value at dot-notation path.\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 * Check if a path exists in the object (even if value is null/undefined).\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","import { expect } from 'vitest'\nimport { getValueAtPath, hasValueAtPath } from './path-utils'\n\n/**\n * TestResponse\n *\n * Wrapper around Response with assertion methods.\n *\n * @example\n * ```typescript\n * response\n * .assertOk()\n * .assertStatus(200)\n * .assertJsonPath('data.id', userId)\n * ```\n */\nexport class TestResponse {\n private jsonData: unknown = null\n private textData: string | null = null\n\n constructor(private readonly response: Response) {}\n\n /**\n * Get the raw Response object.\n */\n get raw(): Response {\n return this.response\n }\n\n /**\n * Get the response status code.\n */\n get status(): number {\n return this.response.status\n }\n\n /**\n * Get response headers.\n */\n get headers(): Headers {\n return this.response.headers\n }\n\n /**\n * Get the response body as JSON.\n */\n async json<T = unknown>(): Promise<T> {\n if (this.jsonData === null) {\n this.jsonData = await this.response.clone().json()\n }\n return this.jsonData as T\n }\n\n /**\n * Get the response body as text.\n */\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 /**\n * Assert response status is 200 OK.\n */\n assertOk(): this {\n return this.assertStatus(200)\n }\n\n /**\n * Assert response status is 201 Created.\n */\n assertCreated(): this {\n return this.assertStatus(201)\n }\n\n /**\n * Assert response status is 204 No Content.\n */\n assertNoContent(): this {\n return this.assertStatus(204)\n }\n\n /**\n * Assert response status is 400 Bad Request.\n */\n assertBadRequest(): this {\n return this.assertStatus(400)\n }\n\n /**\n * Assert response status is 401 Unauthorized.\n */\n assertUnauthorized(): this {\n return this.assertStatus(401)\n }\n\n /**\n * Assert response status is 403 Forbidden.\n */\n assertForbidden(): this {\n return this.assertStatus(403)\n }\n\n /**\n * Assert response status is 404 Not Found.\n */\n assertNotFound(): this {\n return this.assertStatus(404)\n }\n\n /**\n * Assert response status is 422 Unprocessable Entity.\n */\n assertUnprocessable(): this {\n return this.assertStatus(422)\n }\n\n /**\n * Assert response status is 500 Internal Server Error.\n */\n assertServerError(): this {\n return this.assertStatus(500)\n }\n\n /**\n * Assert response has the given status code.\n */\n assertStatus(expected: number): this {\n expect(\n this.response.status,\n `Expected status ${expected}, got ${this.response.status}`\n ).toBe(expected)\n return this\n }\n\n /**\n * Assert response status is in the 2xx success range.\n */\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 /**\n * Assert JSON response contains the given data.\n */\n async assertJson(expected: Record<string, unknown>): Promise<this> {\n const actual = await this.json<Record<string, unknown>>()\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 /**\n * Assert JSON response has value at the given path.\n *\n * @param path - Dot-notation path (e.g., 'data.user.id')\n * @param expected - Expected value at path\n */\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 /**\n * Assert JSON response structure matches the given schema.\n */\n async assertJsonStructure(structure: string[]): Promise<this> {\n const json = await this.json<Record<string, unknown>>()\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 /**\n * Assert a JSON path exists (value can be anything, including null).\n *\n * @param path - Dot-notation path (e.g., 'data.user.id')\n */\n async assertJsonPathExists(path: string): Promise<this> {\n const json = await this.json()\n const exists = hasValueAtPath(json, path)\n\n expect(\n exists,\n `Expected JSON path \"${path}\" to exist`\n ).toBe(true)\n\n return this\n }\n\n /**\n * Assert a JSON path does not exist.\n *\n * @param path - Dot-notation path (e.g., 'data.user.deletedAt')\n */\n async assertJsonPathMissing(path: string): Promise<this> {\n const json = await this.json()\n const exists = hasValueAtPath(json, path)\n\n expect(\n exists,\n `Expected JSON path \"${path}\" to not exist`\n ).toBe(false)\n\n return this\n }\n\n /**\n * Assert JSON value at path matches a custom predicate.\n *\n * @param path - Dot-notation path (e.g., 'data.items')\n * @param matcher - Predicate function to validate the value\n */\n async assertJsonPathMatches(\n path: string,\n matcher: (value: unknown) => boolean\n ): 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 /**\n * Assert string value at path contains a substring.\n *\n * @param path - Dot-notation path (e.g., 'data.message')\n * @param substring - Substring to search for\n */\n async assertJsonPathContains(path: string, substring: string): Promise<this> {\n const json = await this.json()\n const value = getValueAtPath(json, path)\n\n expect(\n typeof value === 'string',\n `Expected JSON path \"${path}\" to be a string, got ${typeof value}`\n ).toBe(true)\n\n expect(\n (value as string).includes(substring),\n `Expected JSON path \"${path}\" to contain \"${substring}\", got \"${String(value)}\"`\n ).toBe(true)\n\n return this\n }\n\n /**\n * Assert array value at path includes a specific item.\n *\n * @param path - Dot-notation path (e.g., 'data.tags')\n * @param item - Item to search for in the array\n */\n async assertJsonPathIncludes(path: string, item: unknown): Promise<this> {\n const json = await this.json()\n const value = getValueAtPath(json, path)\n\n expect(\n Array.isArray(value),\n `Expected JSON path \"${path}\" to be an array, got ${typeof value}`\n ).toBe(true)\n\n expect(\n (value as unknown[]).includes(item),\n `Expected JSON path \"${path}\" to include ${JSON.stringify(item)}`\n ).toBe(true)\n\n return this\n }\n\n /**\n * Assert array length at path equals the expected count.\n *\n * @param path - Dot-notation path (e.g., 'data.items')\n * @param count - Expected array length\n */\n async assertJsonPathCount(path: string, count: number): Promise<this> {\n const json = await this.json()\n const value = getValueAtPath(json, path)\n\n expect(\n Array.isArray(value),\n `Expected JSON path \"${path}\" to be an array, got ${typeof value}`\n ).toBe(true)\n\n expect(\n (value as unknown[]).length,\n `Expected JSON path \"${path}\" to have ${count} items, got ${(value as unknown[]).length}`\n ).toBe(count)\n\n return this\n }\n\n /**\n * Assert multiple JSON paths at once (batch assertion).\n *\n * @param expectations - Object mapping paths to expected values\n */\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 // ============================================================\n // Header Assertions\n // ============================================================\n\n /**\n * Assert response has the given header.\n */\n assertHeader(name: string, expected?: string): this {\n const actual = this.response.headers.get(name)\n\n expect(\n actual !== null,\n `Expected header \"${name}\" to be present`\n ).toBe(true)\n\n if (expected !== undefined) {\n expect(\n actual,\n `Expected header \"${name}\" to be \"${expected}\", got \"${actual}\"`\n ).toBe(expected)\n }\n\n return this\n }\n\n /**\n * Assert response does not have the given header.\n */\n assertHeaderMissing(name: string): this {\n const actual = this.response.headers.get(name)\n\n expect(\n actual,\n `Expected header \"${name}\" to be absent, but got \"${actual}\"`\n ).toBeNull()\n\n return this\n }\n\n}\n","import type { AuthService } from '@stratal/framework/auth'\nimport { AUTH_SERVICE } from '@stratal/framework/auth'\nimport type { DetectionStrategy } from 'stratal/i18n'\nimport { ActingAs } from '../../auth'\nimport type { TestingModule } from '../testing-module'\nimport { applyLocaleToHeaders, applyLocaleToUrl, resolveLocaleStrategy } from './locale-helper'\nimport { TestResponse } from './test-response'\n\n/**\n * TestHttpRequest\n *\n * Request builder with fluent API for configuring test HTTP requests.\n *\n * @example\n * ```typescript\n * const response = await module.http\n * .post('/api/v1/register')\n * .withBody({ name: 'Test School' })\n * .withHeaders({ 'X-Custom': 'value' })\n * .send()\n * ```\n *\n * @example Authenticated request\n * ```typescript\n * const response = await module.http\n * .get('/api/v1/profile')\n * .actingAs({ id: user.id })\n * .send()\n * ```\n */\nexport class TestHttpRequest {\n\tprivate body: unknown = null\n\tprivate requestHeaders: Headers\n\tprivate actingAsUser: { id: string } | null = null\n\tprivate localeConfig: { locale: string; strategy: DetectionStrategy } | null\n\n\tconstructor(\n\t\tprivate readonly method: string,\n\t\tprivate readonly path: string,\n\t\theaders: Headers,\n\t\tprivate readonly module: TestingModule,\n\t\tprivate readonly host: string | null = null,\n\t\tlocaleConfig: { locale: string; strategy: DetectionStrategy } | null = null,\n\t) {\n\t\tthis.requestHeaders = new Headers(headers)\n\t\tthis.localeConfig = localeConfig\n\t}\n\n\t/**\n\t * Set the request body\n\t */\n\twithBody(data: unknown): this {\n\t\tthis.body = data\n\t\treturn this\n\t}\n\n\t/**\n\t * Add headers to the request\n\t */\n\twithHeaders(headers: Record<string, string>): this {\n\t\tfor (const [key, value] of Object.entries(headers)) {\n\t\t\tthis.requestHeaders.set(key, value)\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Set the locale for this request.\n\t * If strategy is not provided, resolves from the module's I18n configuration.\n\t *\n\t * @param locale - Locale code (e.g., 'en', 'fr')\n\t * @param strategy - Detection strategy override\n\t */\n\twithLocale(locale: string, strategy?: DetectionStrategy): this {\n\t\tconst resolved = strategy ?? resolveLocaleStrategy(this.module)\n\t\tthis.localeConfig = { locale, strategy: resolved }\n\t\tapplyLocaleToHeaders(this.requestHeaders, locale, resolved)\n\t\treturn this\n\t}\n\n\t/**\n\t * Set Content-Type to application/json\n\t */\n\tasJson(): this {\n\t\tthis.requestHeaders.set('Content-Type', 'application/json')\n\t\treturn this\n\t}\n\n\t/**\n\t * Authenticate the request as a specific user\n\t */\n\tactingAs(user: { id: string }): this {\n\t\tthis.actingAsUser = user\n\t\treturn this\n\t}\n\n\t/**\n\t * Send the request and return response\n\t *\n\t * Calls module.fetch() - NOT SELF.fetch()\n\t */\n\tasync send(): Promise<TestResponse> {\n\t\tawait this.applyAuthentication()\n\n\t\t// Auto-set Content-Type for body\n\t\tif (this.body && !this.requestHeaders.has('Content-Type')) {\n\t\t\tthis.requestHeaders.set('Content-Type', 'application/json')\n\t\t}\n\n\t\t// Build request\n\t\tconst url = new URL(this.path, `http://${this.host ?? 'localhost'}`)\n\n\t\t// Apply locale to URL for querystring strategy\n\t\tif (this.localeConfig) {\n\t\t\tapplyLocaleToUrl(url, this.localeConfig.locale, this.localeConfig.strategy)\n\t\t}\n\n\t\tconst request = new Request(url.toString(), {\n\t\t\tmethod: this.method,\n\t\t\theaders: this.requestHeaders,\n\t\t\tbody: this.body ? JSON.stringify(this.body) : null,\n\t\t})\n\n\t\t// Call module.fetch() - NO SELF.fetch()\n\t\tconst response = await this.module.fetch(request)\n\t\treturn new TestResponse(response)\n\t}\n\n\tprivate async applyAuthentication(): Promise<void> {\n\t\tif (!this.actingAsUser) return\n\n\t\tawait this.module.runInRequestScope(async () => {\n\t\t\tconst authService = this.module.get<AuthService>(AUTH_SERVICE)\n\t\t\tconst actingAs = new ActingAs(authService)\n\t\t\tconst authHeaders = this.actingAsUser ? await actingAs.createSessionForUser(this.actingAsUser) : new Headers()\n\n\t\t\tfor (const [key, value] of authHeaders.entries()) {\n\t\t\t\tthis.requestHeaders.set(key, value)\n\t\t\t}\n\t\t})\n\t}\n}\n","import type { DetectionStrategy } from 'stratal/i18n'\nimport type { TestingModule } from '../testing-module'\nimport { applyLocaleToHeaders, resolveLocaleStrategy } from './locale-helper'\nimport { TestHttpRequest } from './test-http-request'\n\n/**\n * TestHttpClient\n *\n * Fluent HTTP client for making test requests.\n *\n * @example\n * ```typescript\n * const response = await module.http\n * .forHost('example.com')\n * .post('/api/v1/users')\n * .withBody({ name: 'Test' })\n * .send()\n *\n * response.assertCreated()\n * ```\n */\nexport class TestHttpClient {\n private defaultHeaders: Headers = new Headers()\n private host: string | null = null\n private localeConfig: { locale: string; strategy: DetectionStrategy } | null = null\n\n constructor(private readonly module: TestingModule) { }\n\n /**\n * Set the host for the request\n */\n forHost(host: string): this {\n this.host = host\n return this\n }\n\n /**\n * Set default headers for all requests\n */\n withHeaders(headers: Record<string, string>): this {\n for (const [key, value] of Object.entries(headers)) {\n this.defaultHeaders.set(key, value)\n }\n return this\n }\n\n /**\n * Set the locale for all requests from this client.\n * If strategy is not provided, resolves from the module's I18n configuration.\n *\n * @param locale - Locale code (e.g., 'en', 'fr')\n * @param strategy - Detection strategy override\n */\n withLocale(locale: string, strategy?: DetectionStrategy): this {\n const resolved = strategy ?? resolveLocaleStrategy(this.module)\n this.localeConfig = { locale, strategy: resolved }\n applyLocaleToHeaders(this.defaultHeaders, locale, resolved)\n return this\n }\n\n /**\n * Create a GET request\n */\n get(path: string): TestHttpRequest {\n return this.createRequest('GET', path)\n }\n\n /**\n * Create a POST request\n */\n post(path: string): TestHttpRequest {\n return this.createRequest('POST', path)\n }\n\n /**\n * Create a PUT request\n */\n put(path: string): TestHttpRequest {\n return this.createRequest('PUT', path)\n }\n\n /**\n * Create a PATCH request\n */\n patch(path: string): TestHttpRequest {\n return this.createRequest('PATCH', path)\n }\n\n /**\n * Create a DELETE request\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, this.localeConfig)\n }\n}\n","import type { CommandResult } from 'stratal/quarry'\nimport { expect } from 'vitest'\n\n/**\n * Fluent assertion wrapper for command results.\n *\n * @example\n * ```typescript\n * const result = await module\n * .quarry('users:create')\n * .withInput({ email: 'test@example.com' })\n * .run()\n *\n * result.assertSuccessful()\n * result.assertOutputContains('User created')\n * ```\n */\nexport class TestCommandResult {\n constructor(private readonly result: CommandResult) {}\n\n get exitCode(): number {\n return this.result.exitCode\n }\n\n get output(): string[] {\n return this.result.output\n }\n\n get errors(): string[] {\n return this.result.errors\n }\n\n assertSuccessful(): this {\n expect(this.result.exitCode, `Expected exit code 0, got ${this.result.exitCode}. Errors: ${this.result.errors.join(', ')}`).toBe(0)\n expect(this.result.errors, 'Expected no errors').toHaveLength(0)\n return this\n }\n\n assertFailed(exitCode?: number): this {\n if (exitCode !== undefined) {\n expect(this.result.exitCode, `Expected exit code ${exitCode}, got ${this.result.exitCode}`).toBe(exitCode)\n } else {\n expect(this.result.exitCode, 'Expected non-zero exit code').not.toBe(0)\n }\n return this\n }\n\n assertExitCode(code: number): this {\n expect(this.result.exitCode, `Expected exit code ${code}, got ${this.result.exitCode}`).toBe(code)\n return this\n }\n\n assertOutputContains(text: string): this {\n const joined = this.result.output.join('\\n')\n expect(joined, `Expected output to contain \"${text}\"`).toContain(text)\n return this\n }\n\n assertOutputMissing(text: string): this {\n const joined = this.result.output.join('\\n')\n expect(joined, `Expected output NOT to contain \"${text}\"`).not.toContain(text)\n return this\n }\n\n assertErrorContains(text: string): this {\n const joined = this.result.errors.join('\\n')\n expect(joined, `Expected errors to contain \"${text}\"`).toContain(text)\n return this\n }\n\n assertErrorMissing(text: string): this {\n const joined = this.result.errors.join('\\n')\n expect(joined, `Expected errors NOT to contain \"${text}\"`).not.toContain(text)\n return this\n }\n}\n","import type { CommandInput } from 'stratal/quarry'\nimport type { TestingModule } from '../testing-module'\nimport { TestCommandResult } from './test-command-result'\n\n/**\n * Fluent builder for testing Quarry commands.\n *\n * @example\n * ```typescript\n * const result = await module\n * .quarry('users:create')\n * .withInput({ email: 'test@example.com', admin: true })\n * .run()\n *\n * result.assertSuccessful()\n * result.assertOutputContains('User created')\n * ```\n */\nexport class TestCommandRequest {\n private _input: CommandInput = {}\n\n constructor(\n private readonly commandName: string,\n private readonly module: TestingModule,\n ) {}\n\n /**\n * Set the flat input for the command.\n */\n withInput(input: CommandInput): this {\n this._input = { ...input }\n return this\n }\n\n /**\n * Execute the command and return a TestCommandResult for assertions.\n */\n async run(): Promise<TestCommandResult> {\n const result = await this.module.application.handleCommand(this.commandName, this._input)\n return new TestCommandResult(result)\n }\n}\n","import { expect } from \"vitest\"\n\n/**\n * Represents a parsed SSE event\n */\nexport interface TestSseEvent {\n\tdata: string\n\tevent?: string\n\tid?: string\n\tretry?: number\n}\n\n/**\n * TestSseConnection\n *\n * Live SSE connection wrapper with assertion helpers for testing.\n *\n * @example\n * ```typescript\n * const sse = await module.sse('/streaming/sse').connect()\n * await sse.assertEvent({ event: 'message', data: 'hello', id: '1' })\n * await sse.waitForEnd()\n * ```\n */\nexport class TestSseConnection {\n\tprivate readonly eventQueue: TestSseEvent[] = []\n\tprivate eventWaiters: ((event: TestSseEvent) => void)[] = []\n\tprivate streamEnded = false\n\tprivate endWaiters: (() => void)[] = []\n\n\tconstructor(private readonly response: Response) {\n\t\tthis.startReading()\n\t}\n\n\t/**\n\t * Wait for the next SSE event\n\t */\n\tasync waitForEvent(timeout = 5000): Promise<TestSseEvent> {\n\t\tif (this.eventQueue.length > 0) {\n\t\t\treturn this.eventQueue.shift()!\n\t\t}\n\n\t\tif (this.streamEnded) {\n\t\t\tthrow new Error('SSE: stream has ended, no more events')\n\t\t}\n\n\t\treturn new Promise<TestSseEvent>((resolve, reject) => {\n\t\t\tconst waiter = (event: TestSseEvent) => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tresolve(event)\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tconst index = this.eventWaiters.indexOf(waiter)\n\t\t\t\tif (index !== -1) this.eventWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`SSE: no event received within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\tthis.eventWaiters.push(waiter)\n\t\t})\n\t}\n\n\t/**\n\t * Wait for the stream to end\n\t */\n\tasync waitForEnd(timeout = 5000): Promise<void> {\n\t\tif (this.streamEnded) return\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst waiter = () => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tresolve()\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tconst index = this.endWaiters.indexOf(waiter)\n\t\t\t\tif (index !== -1) this.endWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`SSE: stream did not end within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\tthis.endWaiters.push(waiter)\n\t\t})\n\t}\n\n\t/**\n\t * Collect all remaining events until the stream ends\n\t */\n\tasync collectEvents(timeout = 5000): Promise<TestSseEvent[]> {\n\t\tconst events: TestSseEvent[] = []\n\n\t\tif (this.streamEnded) {\n\t\t\treturn [...this.eventQueue.splice(0)]\n\t\t}\n\n\t\treturn new Promise<TestSseEvent[]>((resolve, reject) => {\n\t\t\t// Listen for new events until stream ends\n\t\t\tconst originalDispatch = this.dispatchEvent.bind(this)\n\t\t\tthis.dispatchEvent = (event: TestSseEvent) => {\n\t\t\t\tevents.push(event)\n\t\t\t\toriginalDispatch(event)\n\t\t\t}\n\n\t\t\tconst endWaiter = () => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tthis.dispatchEvent = originalDispatch\n\t\t\t\tresolve(events)\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.dispatchEvent = originalDispatch\n\t\t\t\tconst index = this.endWaiters.indexOf(endWaiter)\n\t\t\t\tif (index !== -1) this.endWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`SSE: stream did not end within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\t// Drain any queued events first\n\t\t\tevents.push(...this.eventQueue.splice(0))\n\n\t\t\tthis.endWaiters.push(endWaiter)\n\t\t})\n\t}\n\n\t/**\n\t * Assert that the next event matches the expected partial shape\n\t */\n\tasync assertEvent(expected: Partial<TestSseEvent>, timeout = 5000): Promise<void> {\n\t\tconst event = await this.waitForEvent(timeout)\n\t\texpect(event).toMatchObject(expected)\n\t}\n\n\t/**\n\t * Assert that the next event's data matches the expected string\n\t */\n\tasync assertEventData(expected: string, timeout = 5000): Promise<void> {\n\t\tconst event = await this.waitForEvent(timeout)\n\t\texpect(event.data, `Expected SSE data \"${expected}\", got \"${event.data}\"`).toBe(expected)\n\t}\n\n\t/**\n\t * Assert that the next event's data is JSON matching the expected value\n\t */\n\tasync assertJsonEventData<T>(expected: T, timeout = 5000): Promise<void> {\n\t\tconst event = await this.waitForEvent(timeout)\n\t\tconst parsed = JSON.parse(event.data) as unknown\n\t\texpect(parsed).toEqual(expected)\n\t}\n\n\t/**\n\t * Access the raw Response\n\t */\n\tget raw(): Response {\n\t\treturn this.response\n\t}\n\n\tprivate startReading(): void {\n\t\tconst body = this.response.body\n\t\tif (!body) {\n\t\t\tthis.streamEnded = true\n\t\t\treturn\n\t\t}\n\n\t\tconst reader = body.getReader() as ReadableStreamDefaultReader<Uint8Array>\n\t\tconst decoder = new TextDecoder()\n\t\tlet buffer = ''\n\n\t\tconst read = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read()\n\n\t\t\t\t\tif (done) {\n\t\t\t\t\t\t// Parse any remaining buffered data\n\t\t\t\t\t\tif (buffer.trim()) {\n\t\t\t\t\t\t\tconst event = this.parseEvent(buffer)\n\t\t\t\t\t\t\tif (event) this.dispatchEvent(event)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.streamEnded = true\n\t\t\t\t\t\tfor (const waiter of this.endWaiters) {\n\t\t\t\t\t\t\twaiter()\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.endWaiters = []\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true })\n\n\t\t\t\t\t// Split on double newlines (SSE event boundary)\n\t\t\t\t\tconst parts = buffer.split('\\n\\n')\n\t\t\t\t\t// Last part may be incomplete, keep it in the buffer\n\t\t\t\t\tbuffer = parts.pop()!\n\n\t\t\t\t\tfor (const part of parts) {\n\t\t\t\t\t\tif (!part.trim()) continue\n\t\t\t\t\t\tconst event = this.parseEvent(part)\n\t\t\t\t\t\tif (event) this.dispatchEvent(event)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tthis.streamEnded = true\n\t\t\t\tfor (const waiter of this.endWaiters) {\n\t\t\t\t\twaiter()\n\t\t\t\t}\n\t\t\t\tthis.endWaiters = []\n\t\t\t}\n\t\t}\n\n\t\tvoid read()\n\t}\n\n\tprivate parseEvent(raw: string): TestSseEvent | null {\n\t\tconst lines = raw.split('\\n')\n\t\tconst dataLines: string[] = []\n\t\tlet event: string | undefined\n\t\tlet id: string | undefined\n\t\tlet retry: number | undefined\n\n\t\tfor (const line of lines) {\n\t\t\tif (line.startsWith(':')) continue // comment line\n\n\t\t\tconst colonIndex = line.indexOf(':')\n\t\t\tif (colonIndex === -1) continue\n\n\t\t\tconst field = line.slice(0, colonIndex)\n\t\t\t// Strip optional leading space after colon\n\t\t\tconst value = line[colonIndex + 1] === ' ' ? line.slice(colonIndex + 2) : line.slice(colonIndex + 1)\n\n\t\t\tswitch (field) {\n\t\t\t\tcase 'data':\n\t\t\t\t\tdataLines.push(value)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'event':\n\t\t\t\t\tevent = value\n\t\t\t\t\tbreak\n\t\t\t\tcase 'id':\n\t\t\t\t\tid = value\n\t\t\t\t\tbreak\n\t\t\t\tcase 'retry': {\n\t\t\t\t\tconst parsed = parseInt(value, 10)\n\t\t\t\t\tif (!isNaN(parsed)) retry = parsed\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (dataLines.length === 0) return null\n\n\t\tconst result: TestSseEvent = { data: dataLines.join('\\n') }\n\t\tif (event !== undefined) result.event = event\n\t\tif (id !== undefined) result.id = id\n\t\tif (retry !== undefined) result.retry = retry\n\n\t\treturn result\n\t}\n\n\tprivate dispatchEvent(event: TestSseEvent): void {\n\t\tif (this.eventWaiters.length > 0) {\n\t\t\tthis.eventWaiters.shift()!(event)\n\t\t} else {\n\t\t\tthis.eventQueue.push(event)\n\t\t}\n\t}\n}\n","import type { AuthService } from '@stratal/framework/auth'\nimport { AUTH_SERVICE } from '@stratal/framework/auth'\nimport type { DetectionStrategy } from 'stratal/i18n'\nimport { expect } from 'vitest'\nimport { ActingAs } from '../../auth'\nimport type { TestingModule } from '../testing-module'\nimport { applyLocaleToHeaders, resolveLocaleStrategy } from '../http/locale-helper'\nimport { TestSseConnection } from './test-sse-connection'\n\n/**\n * TestSseRequest\n *\n * Builder for SSE connection requests. Follows the TestWsRequest pattern.\n *\n * @example\n * ```typescript\n * const sse = await module.sse('/streaming/sse').connect()\n * await sse.assertEvent({ event: 'message', data: 'hello' })\n * await sse.waitForEnd()\n * ```\n *\n * @example Authenticated SSE\n * ```typescript\n * const sse = await module.sse('/streaming/sse').actingAs({ id: user.id }).connect()\n * ```\n */\nexport class TestSseRequest {\n\tprivate requestHeaders: Headers = new Headers()\n\tprivate actingAsUser: { id: string } | null = null\n\n\tconstructor(\n\t\tprivate readonly path: string,\n\t\tprivate readonly module: TestingModule,\n\t) { }\n\n\t/**\n\t * Add custom headers to the request\n\t */\n\twithHeaders(headers: Record<string, string>): this {\n\t\tfor (const [key, value] of Object.entries(headers)) {\n\t\t\tthis.requestHeaders.set(key, value)\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Set the locale for this SSE connection.\n\t * If strategy is not provided, resolves from the module's I18n configuration.\n\t */\n\twithLocale(locale: string, strategy?: DetectionStrategy): this {\n\t\tconst resolved = strategy ?? resolveLocaleStrategy(this.module)\n\t\tapplyLocaleToHeaders(this.requestHeaders, locale, resolved)\n\t\treturn this\n\t}\n\n\t/**\n\t * Authenticate the SSE connection as a specific user\n\t */\n\tactingAs(user: { id: string }): this {\n\t\tthis.actingAsUser = user\n\t\treturn this\n\t}\n\n\t/**\n\t * Send the request and return a live SSE connection\n\t */\n\tasync connect(): Promise<TestSseConnection> {\n\t\tawait this.applyAuthentication()\n\n\t\tthis.requestHeaders.set('Accept', 'text/event-stream')\n\n\t\tconst url = new URL(this.path, 'http://localhost')\n\t\tconst request = new Request(url.toString(), {\n\t\t\theaders: this.requestHeaders,\n\t\t})\n\n\t\tconst response = await this.module.fetch(request)\n\n\t\texpect(\n\t\t\tresponse.status,\n\t\t\t`Expected status 200, got ${response.status}`,\n\t\t).toBe(200)\n\n\t\tconst contentType = response.headers.get('content-type') ?? ''\n\t\texpect(\n\t\t\tcontentType.includes('text/event-stream'),\n\t\t\t`Expected content-type \"text/event-stream\", got \"${contentType}\"`,\n\t\t).toBe(true)\n\n\t\treturn new TestSseConnection(response)\n\t}\n\n\tprivate async applyAuthentication(): Promise<void> {\n\t\tif (!this.actingAsUser) return\n\n\t\tawait this.module.runInRequestScope(async () => {\n\t\t\tconst authService = this.module.get<AuthService>(AUTH_SERVICE)\n\t\t\tconst actingAs = new ActingAs(authService)\n\t\t\tconst authHeaders = this.actingAsUser ? await actingAs.createSessionForUser(this.actingAsUser) : new Headers()\n\n\t\t\tfor (const [key, value] of authHeaders.entries()) {\n\t\t\t\tthis.requestHeaders.set(key, value)\n\t\t\t}\n\t\t})\n\t}\n}\n","import { expect } from \"vitest\";\n\n/**\n * TestWsConnection\n *\n * Live WebSocket connection wrapper with assertion helpers for testing.\n *\n * @example\n * ```typescript\n * const ws = await module.ws('/ws/chat').connect()\n * ws.send('hello')\n * await ws.assertMessage('echo:hello')\n * ws.close()\n * await ws.waitForClose()\n * ```\n */\nexport class TestWsConnection {\n\tprivate readonly messageQueue: (string | ArrayBuffer)[] = []\n\tprivate messageWaiters: ((data: string | ArrayBuffer) => void)[] = []\n\tprivate closeEvent: { code?: number; reason?: string } | null = null\n\tprivate closeWaiters: ((event: { code?: number; reason?: string }) => void)[] = []\n\n\tconstructor(private readonly ws: WebSocket) {\n\t\tthis.ws.addEventListener('message', (event: MessageEvent) => {\n\t\t\tconst data = event.data as string | ArrayBuffer\n\t\t\tif (this.messageWaiters.length > 0) {\n\t\t\t\tthis.messageWaiters.shift()!(data)\n\t\t\t} else {\n\t\t\t\tthis.messageQueue.push(data)\n\t\t\t}\n\t\t})\n\n\t\tthis.ws.addEventListener('close', (event: CloseEvent) => {\n\t\t\tthis.closeEvent = { code: event.code, reason: event.reason }\n\t\t\tfor (const waiter of this.closeWaiters) {\n\t\t\t\twaiter(this.closeEvent)\n\t\t\t}\n\t\t\tthis.closeWaiters = []\n\t\t})\n\t}\n\n\t/**\n\t * Send a message through the WebSocket\n\t */\n\tsend(data: string | ArrayBuffer | Uint8Array): void {\n\t\tthis.ws.send(data)\n\t}\n\n\t/**\n\t * Close the WebSocket connection\n\t */\n\tclose(code?: number, reason?: string): void {\n\t\tthis.ws.close(code, reason)\n\t}\n\n\t/**\n\t * Wait for the next message, returning its data\n\t */\n\tasync waitForMessage(timeout = 5000): Promise<string | ArrayBuffer> {\n\t\tif (this.messageQueue.length > 0) {\n\t\t\treturn this.messageQueue.shift()!\n\t\t}\n\n\t\treturn new Promise<string | ArrayBuffer>((resolve, reject) => {\n\t\t\tconst waiter = (data: string | ArrayBuffer) => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tresolve(data)\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tconst index = this.messageWaiters.indexOf(waiter)\n\t\t\t\tif (index !== -1) this.messageWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`WebSocket: no message received within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\tthis.messageWaiters.push(waiter)\n\t\t})\n\t}\n\n\t/**\n\t * Wait for the connection to close\n\t */\n\tasync waitForClose(timeout = 5000): Promise<{ code?: number; reason?: string }> {\n\t\tif (this.closeEvent) {\n\t\t\treturn this.closeEvent\n\t\t}\n\n\t\treturn new Promise<{ code?: number; reason?: string }>((resolve, reject) => {\n\t\t\tconst waiter = (event: { code?: number; reason?: string }) => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tresolve(event)\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tconst index = this.closeWaiters.indexOf(waiter)\n\t\t\t\tif (index !== -1) this.closeWaiters.splice(index, 1)\n\t\t\t\treject(new Error(`WebSocket: connection did not close within ${timeout}ms`))\n\t\t\t}, timeout)\n\n\t\t\tthis.closeWaiters.push(waiter)\n\t\t})\n\t}\n\n\t/**\n\t * Assert that the next message equals the expected value\n\t */\n\tasync assertMessage(expected: string, timeout = 5000): Promise<void> {\n\t\tconst data = await this.waitForMessage(timeout)\n\t\tconst message = typeof data === 'string' ? data : '[ArrayBuffer]'\n\t\texpect(message, `Expected WebSocket message \"${expected}\", got \"${message}\"`).toBe(expected)\n\t}\n\n\t/**\n\t * Assert that the connection closes, optionally with an expected code\n\t */\n\tasync assertClosed(expectedCode?: number, timeout = 5000): Promise<void> {\n\t\tconst event = await this.waitForClose(timeout)\n\t\tif (expectedCode !== undefined) {\n\t\t\texpect(event.code, `Expected close code ${expectedCode}, got ${event.code}`).toBe(expectedCode)\n\t\t}\n\t}\n\n\t/**\n\t * Access the raw Cloudflare WebSocket\n\t */\n\tget raw(): WebSocket {\n\t\treturn this.ws\n\t}\n}\n","import type { AuthService } from '@stratal/framework/auth'\nimport { AUTH_SERVICE } from '@stratal/framework/auth'\nimport type { DetectionStrategy } from 'stratal/i18n'\nimport { expect } from 'vitest'\nimport { ActingAs } from '../../auth'\nimport type { TestingModule } from '../testing-module'\nimport { applyLocaleToHeaders, resolveLocaleStrategy } from '../http/locale-helper'\nimport { TestWsConnection } from './test-ws-connection'\n\n/**\n * TestWsRequest\n *\n * Builder for WebSocket upgrade requests. Follows the TestHttpRequest pattern.\n *\n * @example\n * ```typescript\n * const ws = await module.ws('/ws/chat').connect()\n * ws.send('hello')\n * await ws.assertMessage('echo:hello')\n * ws.close()\n * ```\n *\n * @example Authenticated WebSocket\n * ```typescript\n * const ws = await module.ws('/ws/chat').actingAs({ id: user.id }).connect()\n * ```\n */\nexport class TestWsRequest {\n\tprivate requestHeaders: Headers = new Headers()\n\tprivate actingAsUser: { id: string } | null = null\n\n\tconstructor(\n\t\tprivate readonly path: string,\n\t\tprivate readonly module: TestingModule,\n\t) { }\n\n\t/**\n\t * Add custom headers to the upgrade request\n\t */\n\twithHeaders(headers: Record<string, string>): this {\n\t\tfor (const [key, value] of Object.entries(headers)) {\n\t\t\tthis.requestHeaders.set(key, value)\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * Set the locale for this WebSocket connection.\n\t * If strategy is not provided, resolves from the module's I18n configuration.\n\t */\n\twithLocale(locale: string, strategy?: DetectionStrategy): this {\n\t\tconst resolved = strategy ?? resolveLocaleStrategy(this.module)\n\t\tapplyLocaleToHeaders(this.requestHeaders, locale, resolved)\n\t\treturn this\n\t}\n\n\t/**\n\t * Authenticate the WebSocket connection as a specific user\n\t */\n\tactingAs(user: { id: string }): this {\n\t\tthis.actingAsUser = user\n\t\treturn this\n\t}\n\n\t/**\n\t * Send the upgrade request and return a live WebSocket connection\n\t */\n\tasync connect(): Promise<TestWsConnection> {\n\t\tawait this.applyAuthentication()\n\n\t\tthis.requestHeaders.set('Upgrade', 'websocket')\n\t\tthis.requestHeaders.set('Connection', 'Upgrade')\n\t\tthis.requestHeaders.set('Sec-WebSocket-Key', 'dGhlIHNhbXBsZSBub25jZQ==')\n\t\tthis.requestHeaders.set('Sec-WebSocket-Version', '13')\n\n\t\tconst url = new URL(this.path, 'http://localhost')\n\t\tconst request = new Request(url.toString(), {\n\t\t\theaders: this.requestHeaders,\n\t\t})\n\n\t\tconst response = await this.module.fetch(request)\n\n\t\texpect(\n\t\t\tresponse.status,\n\t\t\t`Expected status 101 (Switching Protocols), got ${response.status}`,\n\t\t).toBe(101)\n\n\t\tconst ws = (response as Response & { webSocket: WebSocket | null }).webSocket\n\t\tif (!ws) {\n\t\t\tthrow new Error('Response did not include a WebSocket connection')\n\t\t}\n\n\t\tws.accept()\n\n\t\treturn new TestWsConnection(ws)\n\t}\n\n\tprivate async applyAuthentication(): Promise<void> {\n\t\tif (!this.actingAsUser) return\n\n\t\tawait this.module.runInRequestScope(async () => {\n\t\t\tconst authService = this.module.get<AuthService>(AUTH_SERVICE)\n\t\t\tconst actingAs = new ActingAs(authService)\n\t\t\tconst authHeaders = this.actingAsUser ? await actingAs.createSessionForUser(this.actingAsUser) : new Headers()\n\n\t\t\tfor (const [key, value] of authHeaders.entries()) {\n\t\t\t\tthis.requestHeaders.set(key, value)\n\t\t\t}\n\t\t})\n\t}\n}\n","import type { ConnectionName, DatabaseService } from '@stratal/framework/database'\nimport { connectionSymbol } from '@stratal/framework/database'\nimport type { Application, Constructor, StratalEnv, StratalExecutionContext } from 'stratal'\nimport { DI_TOKENS, type Container } from 'stratal/di'\nimport { type InjectionToken } from 'stratal/module'\nimport { SEEDER_TOKENS, type Seeder, type SeederRegistry, SeederNotRegisteredError } from 'stratal/seeder'\nimport { STORAGE_TOKENS } from 'stratal/storage'\nimport { expect } from 'vitest'\nimport type { FakeStorageService } from '../storage'\nimport { TestHttpClient } from './http/test-http-client'\nimport { TestCommandRequest } from './quarry/test-command-request'\nimport { TestSseRequest } from './sse/test-sse-request'\nimport { TestWsRequest } from './ws/test-ws-request'\n\n/**\n * TestingModule\n *\n * Provides access to the test application, container, HTTP client, and utilities.\n *\n * @example\n * ```typescript\n * const module = await Test.createTestingModule({\n * modules: [RegistrationModule],\n * }).compile()\n *\n * // Make HTTP requests\n * const response = await module.http\n * .post('/api/v1/register')\n * .withBody({ ... })\n * .send()\n *\n * // Access services\n * const service = module.get(REGISTRATION_TOKENS.RegistrationService)\n *\n * // Database utilities\n * await module.truncateDb()\n * await module.seed(UserSeeder)\n * await module.assertDatabaseHas('user', { email: 'test@example.com' })\n *\n * // Cleanup\n * await module.close()\n * ```\n */\nexport class TestingModule {\n private _http: TestHttpClient | null = null\n private readonly _requestContainer: Container\n\n constructor(\n private readonly app: Application,\n private readonly env: StratalEnv,\n private readonly ctx: StratalExecutionContext,\n ) {\n const mockContext = this.app.createMockRouterContext()\n this._requestContainer = this.app.container.createRequestScope(mockContext)\n }\n\n /**\n * Resolve a service from the container\n */\n get<T>(token: InjectionToken<T>): T {\n return this._requestContainer.resolve(token)\n }\n\n /**\n * Get HTTP test client for making requests\n */\n get http(): TestHttpClient {\n this._http ??= new TestHttpClient(this)\n return this._http\n }\n\n /**\n * Get fake storage service for assertions\n */\n get storage(): FakeStorageService {\n return this.get<FakeStorageService>(STORAGE_TOKENS.StorageService)\n }\n\n /**\n * Create a WebSocket test request builder for the given path\n */\n ws(path: string): TestWsRequest {\n return new TestWsRequest(path, this)\n }\n\n /**\n * Create an SSE test request builder for the given path\n */\n sse(path: string): TestSseRequest {\n return new TestSseRequest(path, this)\n }\n\n /**\n * Create a Quarry command test request builder\n */\n quarry(name: string): TestCommandRequest {\n return new TestCommandRequest(name, this)\n }\n\n /**\n * Get Application instance\n */\n get application(): Application {\n return this.app\n }\n\n /**\n * Get DI Container (request-scoped)\n */\n get container(): Container {\n return this._requestContainer\n }\n\n /**\n * Execute an HTTP request through HonoApp\n */\n async fetch(request: Request): Promise<Response> {\n return this.app.hono.fetch(request, this.env, this.ctx as ExecutionContext)\n }\n\n /**\n * Run callback in request scope (for DB operations, service access)\n */\n async runInRequestScope<T>(callback: (container: Container) => T | Promise<T>): Promise<T> {\n const mockContext = this.app.createMockRouterContext()\n return this.app.container.runInRequestScope(mockContext, callback)\n }\n\n /**\n * Get database service instance (resolved in request scope)\n */\n getDb(): DatabaseService\n getDb<K extends ConnectionName>(name: K): DatabaseService<K>\n getDb(name?: string): unknown {\n const token = name ? connectionSymbol(name) : DI_TOKENS.Database\n return this._requestContainer.resolve(token)\n }\n\n /**\n * Truncate all non-prisma tables in the database\n */\n async truncateDb(name?: ConnectionName): Promise<void> {\n const db = this.getDb(name!)\n const tables = await db.$queryRaw<{ tablename: string }[]>`\n SELECT tablename::text as tablename FROM pg_tables\n WHERE schemaname = current_schema()\n AND tablename NOT LIKE '_prisma%'\n `\n if (tables.length === 0) return\n const tableList = tables.map((t) => `\"${t.tablename}\"`).join(', ')\n await db.$executeRawUnsafe(`TRUNCATE ${tableList} RESTART IDENTITY CASCADE`)\n }\n\n /**\n * Run seeders by class constructor in the request-scoped container\n */\n async seed(...SeederClasses: Constructor<Seeder>[]): Promise<void> {\n const registry = this._requestContainer.resolve<SeederRegistry>(SEEDER_TOKENS.SeederRegistry)\n for (const SeederClass of SeederClasses) {\n if (!registry.has(SeederClass)) {\n throw new SeederNotRegisteredError(SeederClass.name)\n }\n await registry.run(SeederClass, { container: this._requestContainer })\n }\n }\n\n /**\n * Assert that a record exists in the database\n */\n async assertDatabaseHas(table: string, data: Record<string, unknown>, name?: ConnectionName): Promise<void> {\n const db = this.getDb(name!)\n const model = (db as unknown as Record<string, unknown>)[table] as { findFirst: (opts: unknown) => Promise<unknown> }\n const result = await model.findFirst({ where: data })\n expect(result, `Expected ${table} with ${JSON.stringify(data)}`).not.toBeNull()\n }\n\n /**\n * Assert that a record does not exist in the database\n */\n async assertDatabaseMissing(table: string, data: Record<string, unknown>, name?: ConnectionName): Promise<void> {\n const db = this.getDb(name!)\n const model = (db as unknown as Record<string, unknown>)[table] as { findFirst: (opts: unknown) => Promise<unknown> }\n const result = await model.findFirst({ where: data })\n expect(result, `Expected ${table} NOT to have ${JSON.stringify(data)}`).toBeNull()\n }\n\n /**\n * Assert the number of records in a table\n */\n async assertDatabaseCount(table: string, expected: number, name?: ConnectionName): Promise<void> {\n const db = this.getDb(name!)\n const model = (db as unknown as Record<string, unknown>)[table] as { count: () => Promise<number> }\n const actual = await model.count()\n expect(actual, `Expected ${table} count ${expected}, got ${actual}`).toBe(expected)\n }\n\n /**\n * Cleanup - call in afterAll\n */\n async close(): Promise<void> {\n await this._requestContainer.dispose()\n await this.app.shutdown()\n }\n}\n","import {\n Application,\n type ApplicationConfig,\n type Constructor,\n type StratalEnv,\n type StratalExecutionContext,\n} from 'stratal'\nimport { type Container } from 'stratal/di'\nimport { LogLevel } from 'stratal/logger'\nimport { type InjectionToken, Module, type ModuleClass, type ModuleOptions } from 'stratal/module'\nimport { STORAGE_TOKENS } from 'stratal/storage'\nimport { FakeStorageService } from '../storage'\nimport { ProviderOverrideBuilder, type ProviderOverrideConfig } from './override'\nimport { Test } from './test'\nimport { TestingModule } from './testing-module'\n\n/**\n * Configuration for creating a testing module\n *\n * Extends ModuleOptions to support all module properties like NestJS.\n *\n * @example\n * ```typescript\n * const module = await Test.createTestingModule({\n * imports: [RegistrationModule, GeoModule],\n * providers: [{ provide: MOCK_TOKEN, useValue: mockValue }],\n * controllers: [TestController],\n * }).compile()\n * ```\n */\nexport interface TestingModuleConfig extends ModuleOptions {\n /** Optional environment overrides */\n env?: Partial<StratalEnv>\n /** Logging configuration. Defaults: level=ERROR, formatter='json' */\n logging?: ApplicationConfig['logging']\n}\n\n/**\n * Builder for creating test modules with provider overrides\n */\nexport class TestingModuleBuilder {\n private overrides: ProviderOverrideConfig<object>[] = []\n\n constructor(private config: TestingModuleConfig) { }\n\n /**\n * Override a provider with a custom implementation\n */\n overrideProvider<T>(token: InjectionToken<T>): ProviderOverrideBuilder<T> {\n return new ProviderOverrideBuilder(this, token)\n }\n\n /**\n * Add a provider override (internal use by ProviderOverrideBuilder)\n *\n * @internal\n */\n addProviderOverride<T>(override: ProviderOverrideConfig<T>): this {\n this.overrides.push(override as ProviderOverrideConfig<object>)\n return this\n }\n\n /**\n * Merge additional environment bindings\n */\n withEnv(env: Partial<StratalEnv>): this {\n this.config.env = { ...this.config.env, ...env }\n return this\n }\n\n private async getCloudflareWorkers() {\n try {\n return await import('cloudflare:workers')\n } catch {\n return null\n }\n }\n\n /**\n * Compile the testing module\n *\n * Creates the Application, applies overrides, initializes, and returns TestingModule.\n */\n async compile(): Promise<TestingModule> {\n const cf = await this.getCloudflareWorkers()\n\n const env = { ...cf?.env, ...this.config.env } as StratalEnv\n const ctx: StratalExecutionContext = {\n waitUntil: cf ? cf.waitUntil : (p) => {\n p.catch(() => {\n //\n })\n },\n }\n\n // Build root module from config\n const baseModules = Test.getBaseModules()\n const allImports = [...baseModules, ...(this.config.imports ?? [])]\n\n const rootModule = this.createTestRootModule({\n imports: allImports,\n providers: this.config.providers,\n controllers: this.config.controllers,\n consumers: this.config.consumers,\n jobs: this.config.jobs,\n })\n\n const app = new Application({\n module: rootModule,\n logging: {\n level: this.config.logging?.level ?? LogLevel.ERROR,\n formatter: this.config.logging?.formatter ?? 'pretty',\n },\n env,\n ctx,\n })\n\n // Auto-register FakeStorageService (can be overridden by user)\n app.container.registerSingleton(STORAGE_TOKENS.StorageService, FakeStorageService)\n\n await app.initialize()\n\n // Apply user overrides AFTER initialize so they replace module-registered providers\n for (const override of this.overrides) {\n switch (override.type) {\n case 'value':\n app.container.registerValue(override.token, override.implementation)\n break\n case 'class':\n app.container.registerSingleton(\n override.token,\n override.implementation as Constructor\n )\n break\n case 'factory':\n app.container.registerFactory(\n override.token,\n override.implementation as (c: Container) => object\n )\n break\n case 'existing':\n app.container.registerExisting(\n override.token,\n override.implementation as InjectionToken<object>\n )\n break\n }\n }\n\n return new TestingModule(app, env, ctx)\n }\n\n /**\n * Create a test root module with the given options\n */\n private createTestRootModule(options: ModuleOptions): ModuleClass {\n @Module(options)\n class TestRootModule { }\n return TestRootModule\n }\n}\n","import { type DynamicModule, type ModuleClass } from 'stratal/module'\nimport { TestingModuleBuilder, type TestingModuleConfig } from './testing-module-builder'\n\n/**\n * Test\n *\n * Static class for creating testing modules.\n * Provides a NestJS-style API for configuring test modules.\n *\n * @example\n * ```typescript\n * // In vitest.setup.ts:\n * Test.setBaseModules([CoreModule])\n *\n * // In test files:\n * const module = await Test.createTestingModule({\n * imports: [RegistrationModule, GeoModule],\n * })\n * .overrideProvider(EMAIL_TOKENS.EmailService)\n * .useValue(mockEmailService)\n * .compile()\n * ```\n */\nexport class Test {\n /**\n * Base modules to include in all test modules\n * Set once in vitest.setup.ts\n */\n private static baseModules: (ModuleClass | DynamicModule)[] = []\n\n /**\n * Set base modules to include in all test modules\n * Should be called once in vitest.setup.ts\n *\n * @param modules - Modules to include before test-specific modules (e.g., CoreModule)\n */\n static setBaseModules(modules: (ModuleClass | DynamicModule)[]): void {\n this.baseModules = modules\n }\n\n /**\n * Get base modules\n */\n static getBaseModules(): (ModuleClass | DynamicModule)[] {\n return this.baseModules\n }\n\n /**\n * Create a testing module builder\n *\n * @param config - Configuration with modules and optional env overrides\n * @returns TestingModuleBuilder for configuring and compiling the module\n */\n static createTestingModule(config: TestingModuleConfig): TestingModuleBuilder {\n return new TestingModuleBuilder(config)\n }\n}\n","import { http, HttpResponse, type RequestHandler } from 'msw'\nimport { setupServer, type SetupServer } from 'msw/node'\nimport type { MockErrorOptions, MockJsonOptions } from './fetch-mock.types'\n\ntype HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options'\n\n/**\n * MSW-based fetch mock for declarative HTTP mocking in tests.\n *\n * Replaces the old Cloudflare `fetchMock` (undici MockAgent) with MSW's `setupServer`.\n * Works in both Node.js and workerd test environments.\n *\n * @example\n * ```typescript\n * import { createMockFetch } from '@stratal/testing'\n *\n * const mock = createMockFetch()\n *\n * beforeAll(() => mock.listen())\n * afterEach(() => mock.reset())\n * afterAll(() => mock.close())\n *\n * it('should mock external API', async () => {\n * mock.mockJsonResponse('https://api.example.com/data', { success: true })\n *\n * const response = await fetch('https://api.example.com/data')\n * const json = await response.json()\n *\n * expect(json.success).toBe(true)\n * })\n * ```\n */\nexport class MockFetch {\n private server: SetupServer\n\n constructor(handlers: RequestHandler[] = []) {\n this.server = setupServer(...handlers)\n }\n\n /** Start intercepting. Call in beforeAll/beforeEach. */\n listen() {\n this.server.listen({ onUnhandledRequest: 'error' })\n }\n\n /** Reset runtime handlers. Call in afterEach. */\n reset() {\n this.server.resetHandlers()\n }\n\n /** Stop intercepting. Call in afterAll. */\n close() {\n this.server.close()\n }\n\n /** Add runtime handler(s) for a single test. */\n use(...handlers: RequestHandler[]) {\n this.server.use(...handlers)\n }\n\n /**\n * Mock a JSON response.\n *\n * @param url - Full URL to mock (e.g., 'https://api.example.com/users')\n * @param data - JSON data to return\n * @param options - HTTP method, status code, headers\n *\n * @example\n * ```typescript\n * mock.mockJsonResponse('https://api.example.com/users', { users: [] })\n * mock.mockJsonResponse('https://api.example.com/users', { created: true }, { method: 'POST', status: 201 })\n * ```\n */\n mockJsonResponse(url: string, data: Record<string, unknown> | unknown[], options: MockJsonOptions = {}) {\n const method = (options.method ?? 'GET').toLowerCase() as HttpMethod\n const handler = http[method](url, () =>\n HttpResponse.json(data, {\n status: options.status ?? 200,\n headers: options.headers,\n }),\n )\n this.server.use(handler)\n }\n\n /**\n * Mock an error response.\n *\n * @param url - Full URL to mock\n * @param status - HTTP error status code\n * @param message - Optional error message\n * @param options - HTTP method, headers\n *\n * @example\n * ```typescript\n * mock.mockError('https://api.example.com/fail', 401, 'Unauthorized')\n * mock.mockError('https://api.example.com/fail', 500, 'Server Error', { method: 'POST' })\n * ```\n */\n mockError(url: string, status: number, message?: string, options: MockErrorOptions = {}) {\n const method = (options.method ?? 'GET').toLowerCase() as HttpMethod\n const body = message ? { error: message } : undefined\n this.server.use(\n http[method](url, () =>\n HttpResponse.json(body, { status, headers: options.headers }),\n ),\n )\n }\n}\n\n/**\n * Factory function to create a new MockFetch instance\n *\n * @param handlers - Optional initial MSW request handlers\n * @returns A new MockFetch instance\n *\n * @example\n * ```typescript\n * import { createMockFetch } from '@stratal/testing'\n *\n * const mock = createMockFetch()\n *\n * beforeAll(() => mock.listen())\n * afterEach(() => mock.reset())\n * afterAll(() => mock.close())\n * ```\n */\nexport function createMockFetch(handlers?: RequestHandler[]): MockFetch {\n return new MockFetch(handlers)\n}\n","/**\n * Base error class for all test framework errors.\n * Extends from Error and allows easy identification via `instanceof`.\n */\nexport class TestError extends Error {\n constructor(\n message: string,\n public readonly cause?: Error\n ) {\n super(message)\n this.name = 'TestError'\n\n // Maintain proper stack trace\n Error.captureStackTrace(this, this.constructor)\n }\n}\n","import { TestError } from './test-error'\n\n/**\n * Error thrown when test setup fails.\n * Examples: schema creation failure, migration failure, application bootstrap failure.\n */\nexport class TestSetupError extends TestError {\n constructor(message: string, cause?: Error) {\n super(`Test setup failed: ${message}`, cause)\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { t as FakeStorageService } from "../storage-PcJUKxwp.mjs";
1
+ import { t as FakeStorageService } from "../storage-CGRm_2Nh.mjs";
2
2
  export { FakeStorageService };
@@ -1,19 +1,19 @@
1
- import { FileNotFoundError, STORAGE_TOKENS, StorageManagerService, StorageService } from "stratal/storage";
1
+ import { FileNotFoundError, STORAGE_TOKENS, StorageService } from "stratal/storage";
2
2
  import { Transient, inject } from "stratal/di";
3
3
  import { expect } from "vitest";
4
- //#region \0@oxc-project+runtime@0.115.0/helpers/decorateMetadata.js
4
+ //#region \0@oxc-project+runtime@0.122.0/helpers/decorateMetadata.js
5
5
  function __decorateMetadata(k, v) {
6
6
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
7
7
  }
8
8
  //#endregion
9
- //#region \0@oxc-project+runtime@0.115.0/helpers/decorateParam.js
9
+ //#region \0@oxc-project+runtime@0.122.0/helpers/decorateParam.js
10
10
  function __decorateParam(paramIndex, decorator) {
11
11
  return function(target, key) {
12
12
  decorator(target, key, paramIndex);
13
13
  };
14
14
  }
15
15
  //#endregion
16
- //#region \0@oxc-project+runtime@0.115.0/helpers/decorate.js
16
+ //#region \0@oxc-project+runtime@0.122.0/helpers/decorate.js
17
17
  function __decorate(decorators, target, key, desc) {
18
18
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
19
19
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -22,7 +22,6 @@ function __decorate(decorators, target, key, desc) {
22
22
  }
23
23
  //#endregion
24
24
  //#region src/storage/fake-storage.service.ts
25
- var _ref;
26
25
  let FakeStorageService = class FakeStorageService extends StorageService {
27
26
  files = /* @__PURE__ */ new Map();
28
27
  constructor(storageManager, options) {
@@ -198,9 +197,9 @@ FakeStorageService = __decorate([
198
197
  Transient(STORAGE_TOKENS.StorageService),
199
198
  __decorateParam(0, inject(STORAGE_TOKENS.StorageManager)),
200
199
  __decorateParam(1, inject(STORAGE_TOKENS.Options)),
201
- __decorateMetadata("design:paramtypes", [typeof (_ref = typeof StorageManagerService !== "undefined" && StorageManagerService) === "function" ? _ref : Object, Object])
200
+ __decorateMetadata("design:paramtypes", [Object, Object])
202
201
  ], FakeStorageService);
203
202
  //#endregion
204
203
  export { __decorate as n, FakeStorageService as t };
205
204
 
206
- //# sourceMappingURL=storage-PcJUKxwp.mjs.map
205
+ //# sourceMappingURL=storage-CGRm_2Nh.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-CGRm_2Nh.mjs","names":[],"sources":["../src/storage/fake-storage.service.ts"],"sourcesContent":["import { Transient, inject } from 'stratal/di'\nimport {\n FileNotFoundError,\n STORAGE_TOKENS,\n type StorageManagerService,\n StorageService,\n type StreamingBlobPayloadInputTypes,\n type DownloadResult,\n type PresignedUrlResult,\n type StorageConfig,\n type UploadOptions,\n type UploadResult,\n} from 'stratal/storage'\nimport { expect } from 'vitest'\n\n/**\n * Stored file representation in memory\n */\nexport interface StoredFile {\n content: Uint8Array\n mimeType: string\n size: number\n metadata?: Record<string, string>\n uploadedAt: Date\n}\n\n/**\n * FakeStorageService\n *\n * In-memory storage implementation for testing.\n * Registered by default in TestingModuleBuilder.\n *\n * Similar to Laravel's Storage::fake() - stores files in memory\n * and provides assertion helpers for testing.\n *\n * @example\n * ```typescript\n * // Access via TestingModule\n * module.storage.assertExists('path/to/file.pdf')\n * module.storage.assertMissing('deleted/file.pdf')\n * module.storage.clear() // Reset between tests\n * ```\n */\n@Transient(STORAGE_TOKENS.StorageService)\nexport class FakeStorageService extends StorageService {\n private files = new Map<string, StoredFile>()\n\n constructor(\n @inject(STORAGE_TOKENS.StorageManager)\n protected readonly storageManager: StorageManagerService,\n @inject(STORAGE_TOKENS.Options)\n protected readonly options: StorageConfig\n ) {\n super(storageManager, options)\n }\n\n /**\n * Upload content to fake storage\n */\n async upload(\n body: StreamingBlobPayloadInputTypes,\n relativePath: string,\n options: UploadOptions,\n disk?: string\n ): Promise<UploadResult> {\n const content = await this.bodyToUint8Array(body)\n const diskName = this.resolveDisk(disk)\n\n this.files.set(relativePath, {\n content,\n mimeType: options.mimeType ?? 'application/octet-stream',\n size: options.size,\n metadata: options.metadata,\n uploadedAt: new Date(),\n })\n\n return {\n path: relativePath,\n disk: diskName,\n fullPath: relativePath,\n size: options.size,\n mimeType: options.mimeType ?? 'application/octet-stream',\n uploadedAt: new Date(),\n }\n }\n\n /**\n * Download a file from fake storage\n */\n download(path: string): Promise<DownloadResult> {\n const file = this.files.get(path)\n\n if (!file) {\n return Promise.reject(new FileNotFoundError(path))\n }\n\n return Promise.resolve({\n toStream: () => new ReadableStream({\n start(controller) {\n controller.enqueue(file.content)\n controller.close()\n },\n }),\n toString: () => Promise.resolve(new TextDecoder().decode(file.content)),\n toArrayBuffer: () => Promise.resolve(file.content),\n contentType: file.mimeType,\n size: file.size,\n metadata: file.metadata,\n })\n }\n\n /**\n * Delete a file from fake storage\n */\n delete(path: string): Promise<void> {\n this.files.delete(path)\n return Promise.resolve()\n }\n\n /**\n * Check if a file exists in fake storage\n */\n exists(path: string): Promise<boolean> {\n return Promise.resolve(this.files.has(path))\n }\n\n /**\n * Generate a fake presigned download URL\n */\n getPresignedDownloadUrl(\n path: string,\n expiresIn?: number\n ): Promise<PresignedUrlResult> {\n return Promise.resolve(this.createPresignedUrl(path, 'GET', expiresIn))\n }\n\n /**\n * Generate a fake presigned upload URL\n */\n getPresignedUploadUrl(\n path: string,\n expiresIn?: number\n ): Promise<PresignedUrlResult> {\n return Promise.resolve(this.createPresignedUrl(path, 'PUT', expiresIn))\n }\n\n /**\n * Generate a fake presigned delete URL\n */\n getPresignedDeleteUrl(\n path: string,\n expiresIn?: number\n ): Promise<PresignedUrlResult> {\n return Promise.resolve(this.createPresignedUrl(path, 'DELETE', expiresIn))\n }\n\n /**\n * Chunked upload (same as regular upload for fake)\n */\n async chunkedUpload(\n body: StreamingBlobPayloadInputTypes,\n path: string,\n options: Omit<UploadOptions, 'size'> & { size?: number },\n disk?: string\n ): Promise<UploadResult> {\n const content = await this.bodyToUint8Array(body)\n const size = options.size ?? content.length\n\n return this.upload(body, path, { ...options, size }, disk)\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Test Assertion Helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Assert that a file exists at the given path\n *\n * @param path - Path to check\n * @throws AssertionError if file does not exist\n */\n assertExists(path: string): void {\n expect(\n this.files.has(path),\n `Expected file to exist at: ${path}\\nStored files: ${this.getStoredPaths().join(', ') || '(none)'}`\n ).toBe(true)\n }\n\n /**\n * Assert that a file does NOT exist at the given path\n *\n * @param path - Path to check\n * @throws AssertionError if file exists\n */\n assertMissing(path: string): void {\n expect(\n this.files.has(path),\n `Expected file NOT to exist at: ${path}`\n ).toBe(false)\n }\n\n /**\n * Assert storage is empty\n *\n * @throws AssertionError if any files exist\n */\n assertEmpty(): void {\n expect(\n this.files.size,\n `Expected storage to be empty but found ${this.files.size} files: ${this.getStoredPaths().join(', ')}`\n ).toBe(0)\n }\n\n /**\n * Assert storage has exactly N files\n *\n * @param count - Expected number of files\n * @throws AssertionError if count doesn't match\n */\n assertCount(count: number): void {\n expect(\n this.files.size,\n `Expected ${count} files in storage but found ${this.files.size}`\n ).toBe(count)\n }\n\n /**\n * Get all stored files (for inspection)\n */\n getStoredFiles(): Map<string, StoredFile> {\n return new Map(this.files)\n }\n\n /**\n * Get all stored file paths\n */\n getStoredPaths(): string[] {\n return Array.from(this.files.keys())\n }\n\n /**\n * Get a specific file by path\n */\n getFile(path: string): StoredFile | undefined {\n return this.files.get(path)\n }\n\n /**\n * Clear all stored files (call in beforeEach for test isolation)\n */\n clear(): void {\n this.files.clear()\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private createPresignedUrl(\n path: string,\n method: 'GET' | 'PUT' | 'DELETE' | 'HEAD',\n expiresIn = 300\n ): PresignedUrlResult {\n const expiresAt = new Date(Date.now() + expiresIn * 1000)\n\n return {\n url: `https://fake-storage.test/${path}?method=${method}&expires=${expiresAt.toISOString()}`,\n expiresIn,\n expiresAt,\n method,\n }\n }\n\n private async bodyToUint8Array(body: StreamingBlobPayloadInputTypes | null | undefined): Promise<Uint8Array> {\n if (!body) {\n return new Uint8Array(0)\n }\n\n if (body instanceof Uint8Array) {\n return body\n }\n\n if (body instanceof ArrayBuffer) {\n return new Uint8Array(body)\n }\n\n if (typeof body === 'string') {\n return new TextEncoder().encode(body)\n }\n\n if (body instanceof Blob) {\n const buffer = await body.arrayBuffer()\n return new Uint8Array(buffer)\n }\n\n if (body instanceof ReadableStream) {\n return new Uint8Array(await new Response(body).arrayBuffer())\n }\n\n // FormData or URLSearchParams - convert via Response\n if (body instanceof FormData || body instanceof URLSearchParams) {\n return new Uint8Array(await new Response(body).arrayBuffer())\n }\n\n return new Uint8Array(0)\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/vitest-plugin/stratal-test.ts"],"sourcesContent":["import { createRequire } from 'node:module'\nimport path from 'node:path'\n\nimport { cloudflareTest } from '@cloudflare/vitest-pool-workers'\nimport type { Plugin, UserConfig } from 'vite'\n\nconst require = createRequire(import.meta.url)\n\ntype CloudflareTestOptions = Parameters<typeof cloudflareTest>[0]\n\nconst pgCjsResolvers = new Map<string, () => string>([\n ['pg-protocol', () => require.resolve('pg-protocol')],\n ['pg-connection-string', () => require.resolve('pg-connection-string')],\n ['pg-pool', () => require.resolve('pg-pool')],\n ['pg-cloudflare', () => path.join(path.dirname(require.resolve('pg-cloudflare')), 'index.js')],\n])\n\n/**\n * Returns a Vite plugin that forces CJS resolution for `pg` sub-dependencies.\n *\n * `pg` is CJS but its dependencies (`pg-protocol`, `pg-connection-string`, `pg-pool`) ship\n * dual CJS/ESM exports. In workerd, the module fallback resolver prefers the ESM condition,\n * causing `SyntaxError: Cannot use import statement outside a module` when CJS `pg` does\n * `require()`. Additionally, `pg-cloudflare` uses a `workerd` export condition that the\n * root Vite instance doesn't resolve.\n *\n * Must be used at the **root** `defineConfig` level so that the\n * `@cloudflare/vitest-pool-workers` module fallback resolver (which uses the root Vite\n * instance) resolves pg sub-deps correctly.\n *\n * @example\n * ```ts\n * import { fixPgCjs, stratalTest } from '@stratal/testing/vitest-plugin'\n * import { defineConfig } from 'vitest/config'\n *\n * export default defineConfig({\n * plugins: [fixPgCjs()],\n * test: {\n * projects: [{\n * plugins: [stratalTest({ wrangler: { configPath: './wrangler.jsonc' } })],\n * test: { name: 'e2e', include: ['test/e2e/**\\/*.spec.ts'] },\n * }],\n * },\n * })\n * ```\n */\nexport const fixPgCjs = (): Plugin => ({\n name: 'stratal-pg-cjs',\n enforce: 'pre',\n resolveId(id) {\n const resolver = pgCjsResolvers.get(id)\n if (!resolver) return\n try {\n return resolver()\n } catch {\n return\n }\n },\n})\n\nconst stratalPlugin: Plugin = {\n name: 'stratal-test',\n config() {\n return {\n resolve: {\n alias: {\n tslib: 'tsyringe/node_modules/tslib/tslib.es6.js',\n '@zenstackhq/language/ast': '@stratal/testing/mocks/zenstack-language',\n '@zenstackhq/language/utils': '@stratal/testing/mocks/zenstack-language',\n '@zenstackhq/language': '@stratal/testing/mocks/zenstack-language',\n nodemailer: '@stratal/testing/mocks/nodemailer',\n },\n },\n ssr: {\n noExternal: ['@zenstackhq/better-auth'],\n },\n } satisfies UserConfig\n },\n}\n\n/**\n * Returns Vite plugins for Stratal tests running in the Cloudflare Workers (workerd) environment.\n *\n * Includes the cloudflare pool plugin and Stratal alias plugin.\n * Use inside a project-level `plugins` array.\n *\n * **Note:** `fixPgCjs()` must be registered separately at the root `defineConfig` level.\n *\n * @param options - Same options as `cloudflareTest()` from `@cloudflare/vitest-pool-workers`\n * @returns An array of Vite plugins\n */\nexport function stratalTest(options: CloudflareTestOptions = {}): Plugin[] {\n return [cloudflareTest(options), stratalPlugin]\n}\n"],"mappings":";;;;AAMA,MAAM,UAAU,cAAc,OAAO,KAAK,IAAI;AAI9C,MAAM,iBAAiB,IAAI,IAA0B;CACnD,CAAC,qBAAqB,QAAQ,QAAQ,cAAc,CAAC;CACrD,CAAC,8BAA8B,QAAQ,QAAQ,uBAAuB,CAAC;CACvE,CAAC,iBAAiB,QAAQ,QAAQ,UAAU,CAAC;CAC7C,CAAC,uBAAuB,KAAK,KAAK,KAAK,QAAQ,QAAQ,QAAQ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAC/F,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BF,MAAa,kBAA0B;CACrC,MAAM;CACN,SAAS;CACT,UAAU,IAAI;EACZ,MAAM,WAAW,eAAe,IAAI,GAAG;AACvC,MAAI,CAAC,SAAU;AACf,MAAI;AACF,UAAO,UAAU;UACX;AACN;;;CAGL;AAED,MAAM,gBAAwB;CAC5B,MAAM;CACN,SAAS;AACP,SAAO;GACL,SAAS,EACP,OAAO;IACL,OAAO;IACP,4BAA4B;IAC5B,8BAA8B;IAC9B,wBAAwB;IACxB,YAAY;IACb,EACF;GACD,KAAK,EACH,YAAY,CAAC,0BAA0B,EACxC;GACF;;CAEJ;;;;;;;;;;;;AAaD,SAAgB,YAAY,UAAiC,EAAE,EAAY;AACzE,QAAO,CAAC,eAAe,QAAQ,EAAE,cAAc"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/vitest-plugin/stratal-test.ts"],"sourcesContent":["import { createRequire } from 'node:module'\nimport path from 'node:path'\n\nimport { cloudflareTest } from '@cloudflare/vitest-pool-workers'\nimport type { Plugin, UserConfig } from 'vite'\n\nconst require = createRequire(import.meta.url)\n\ntype CloudflareTestOptions = Parameters<typeof cloudflareTest>[0]\n\nconst pgCjsResolvers = new Map<string, () => string>([\n ['pg-protocol', () => require.resolve('pg-protocol')],\n ['pg-connection-string', () => require.resolve('pg-connection-string')],\n ['pg-pool', () => require.resolve('pg-pool')],\n ['pg-cloudflare', () => path.join(path.dirname(require.resolve('pg-cloudflare')), 'index.js')],\n])\n\n/**\n * Returns a Vite plugin that forces CJS resolution for `pg` sub-dependencies.\n *\n * `pg` is CJS but its dependencies (`pg-protocol`, `pg-connection-string`, `pg-pool`) ship\n * dual CJS/ESM exports. In workerd, the module fallback resolver prefers the ESM condition,\n * causing `SyntaxError: Cannot use import statement outside a module` when CJS `pg` does\n * `require()`. Additionally, `pg-cloudflare` uses a `workerd` export condition that the\n * root Vite instance doesn't resolve.\n *\n * Must be used at the **root** `defineConfig` level so that the\n * `@cloudflare/vitest-pool-workers` module fallback resolver (which uses the root Vite\n * instance) resolves pg sub-deps correctly.\n *\n * @example\n * ```ts\n * import { fixPgCjs, stratalTest } from '@stratal/testing/vitest-plugin'\n * import { defineConfig } from 'vitest/config'\n *\n * export default defineConfig({\n * plugins: [fixPgCjs()],\n * test: {\n * projects: [{\n * plugins: [stratalTest({ wrangler: { configPath: './wrangler.jsonc' } })],\n * test: { name: 'e2e', include: ['test/e2e/**\\/*.spec.ts'] },\n * }],\n * },\n * })\n * ```\n */\nexport const fixPgCjs = (): Plugin => ({\n name: 'stratal-pg-cjs',\n enforce: 'pre',\n resolveId(id) {\n const resolver = pgCjsResolvers.get(id)\n if (!resolver) return\n try {\n return resolver()\n } catch {\n return\n }\n },\n})\n\nconst stratalPlugin: Plugin = {\n name: 'stratal-test',\n config() {\n return {\n resolve: {\n alias: {\n tslib: 'tsyringe/node_modules/tslib/tslib.es6.js',\n '@zenstackhq/language/ast': '@stratal/testing/mocks/zenstack-language',\n '@zenstackhq/language/utils': '@stratal/testing/mocks/zenstack-language',\n '@zenstackhq/language': '@stratal/testing/mocks/zenstack-language',\n nodemailer: '@stratal/testing/mocks/nodemailer',\n },\n },\n ssr: {\n noExternal: ['@zenstackhq/better-auth'],\n },\n } satisfies UserConfig\n },\n}\n\n/**\n * Returns Vite plugins for Stratal tests running in the Cloudflare Workers (workerd) environment.\n *\n * Includes the cloudflare pool plugin and Stratal alias plugin.\n * Use inside a project-level `plugins` array.\n *\n * **Note:** `fixPgCjs()` must be registered separately at the root `defineConfig` level.\n *\n * @param options - Same options as `cloudflareTest()` from `@cloudflare/vitest-pool-workers`\n * @returns An array of Vite plugins\n */\nexport function stratalTest(options: CloudflareTestOptions = {}): Plugin[] {\n return [cloudflareTest(options) as unknown as Plugin, stratalPlugin]\n}\n"],"mappings":";;;;AAMA,MAAM,UAAU,cAAc,OAAO,KAAK,IAAI;AAI9C,MAAM,iBAAiB,IAAI,IAA0B;CACnD,CAAC,qBAAqB,QAAQ,QAAQ,cAAc,CAAC;CACrD,CAAC,8BAA8B,QAAQ,QAAQ,uBAAuB,CAAC;CACvE,CAAC,iBAAiB,QAAQ,QAAQ,UAAU,CAAC;CAC7C,CAAC,uBAAuB,KAAK,KAAK,KAAK,QAAQ,QAAQ,QAAQ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAC/F,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BF,MAAa,kBAA0B;CACrC,MAAM;CACN,SAAS;CACT,UAAU,IAAI;EACZ,MAAM,WAAW,eAAe,IAAI,GAAG;AACvC,MAAI,CAAC,SAAU;AACf,MAAI;AACF,UAAO,UAAU;UACX;AACN;;;CAGL;AAED,MAAM,gBAAwB;CAC5B,MAAM;CACN,SAAS;AACP,SAAO;GACL,SAAS,EACP,OAAO;IACL,OAAO;IACP,4BAA4B;IAC5B,8BAA8B;IAC9B,wBAAwB;IACxB,YAAY;IACb,EACF;GACD,KAAK,EACH,YAAY,CAAC,0BAA0B,EACxC;GACF;;CAEJ;;;;;;;;;;;;AAaD,SAAgB,YAAY,UAAiC,EAAE,EAAY;AACzE,QAAO,CAAC,eAAe,QAAQ,EAAuB,cAAc"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratal/testing",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "Testing utilities and mocks for Stratal framework applications",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -62,19 +62,19 @@
62
62
  "scripts": {
63
63
  "build": "tsdown",
64
64
  "typecheck": "tsc --noEmit",
65
- "lint": "npx eslint .",
66
- "lint:fix": "npx eslint --fix ."
65
+ "lint": "npx oxlint .",
66
+ "lint:fix": "npx oxlint --fix ."
67
67
  },
68
68
  "dependencies": {
69
- "@cloudflare/vitest-pool-workers": "^0.13.2",
70
- "@golevelup/ts-vitest": "^3.0.0",
71
- "msw": "^2.12.13"
69
+ "@cloudflare/vitest-pool-workers": "^0.13.5",
70
+ "@golevelup/ts-vitest": "^4.0.0",
71
+ "msw": "^2.12.14"
72
72
  },
73
73
  "peerDependencies": {
74
- "@stratal/framework": "^0.0.16",
74
+ "@stratal/framework": "^0.0.18",
75
75
  "better-auth": "^1.4.9",
76
76
  "reflect-metadata": "^0.2.2",
77
- "stratal": "^0.0.16",
77
+ "stratal": "^0.0.18",
78
78
  "vitest": "^4.1.0"
79
79
  },
80
80
  "peerDependenciesMeta": {
@@ -89,13 +89,13 @@
89
89
  "@cloudflare/workers-types": "4.20260317.1",
90
90
  "@stratal/framework": "workspace:*",
91
91
  "@types/node": "^25.5.0",
92
- "@vitest/runner": "~4.1.0",
93
- "@vitest/snapshot": "~4.1.0",
94
- "better-auth": "^1.5.5",
92
+ "@vitest/runner": "~4.1.2",
93
+ "@vitest/snapshot": "~4.1.2",
94
+ "better-auth": "^1.5.6",
95
95
  "reflect-metadata": "^0.2.2",
96
96
  "stratal": "workspace:*",
97
- "tsdown": "^0.21.4",
98
- "typescript": "^5.9.3",
99
- "vitest": "~4.1.0"
97
+ "tsdown": "^0.21.6",
98
+ "typescript": "^6.0.2",
99
+ "vitest": "~4.1.2"
100
100
  }
101
101
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"storage-PcJUKxwp.mjs","names":[],"sources":["../src/storage/fake-storage.service.ts"],"sourcesContent":["import { Transient, inject } from 'stratal/di'\nimport {\n FileNotFoundError,\n STORAGE_TOKENS,\n StorageManagerService,\n StorageService,\n StreamingBlobPayloadInputTypes,\n type DownloadResult,\n type PresignedUrlResult,\n type StorageConfig,\n type UploadOptions,\n type UploadResult,\n} from 'stratal/storage'\nimport { expect } from 'vitest'\n\n/**\n * Stored file representation in memory\n */\nexport interface StoredFile {\n content: Uint8Array\n mimeType: string\n size: number\n metadata?: Record<string, string>\n uploadedAt: Date\n}\n\n/**\n * FakeStorageService\n *\n * In-memory storage implementation for testing.\n * Registered by default in TestingModuleBuilder.\n *\n * Similar to Laravel's Storage::fake() - stores files in memory\n * and provides assertion helpers for testing.\n *\n * @example\n * ```typescript\n * // Access via TestingModule\n * module.storage.assertExists('path/to/file.pdf')\n * module.storage.assertMissing('deleted/file.pdf')\n * module.storage.clear() // Reset between tests\n * ```\n */\n@Transient(STORAGE_TOKENS.StorageService)\nexport class FakeStorageService extends StorageService {\n private files = new Map<string, StoredFile>()\n\n constructor(\n @inject(STORAGE_TOKENS.StorageManager)\n protected readonly storageManager: StorageManagerService,\n @inject(STORAGE_TOKENS.Options)\n protected readonly options: StorageConfig\n ) {\n super(storageManager, options)\n }\n\n /**\n * Upload content to fake storage\n */\n async upload(\n body: StreamingBlobPayloadInputTypes,\n relativePath: string,\n options: UploadOptions,\n disk?: string\n ): Promise<UploadResult> {\n const content = await this.bodyToUint8Array(body)\n const diskName = this.resolveDisk(disk)\n\n this.files.set(relativePath, {\n content,\n mimeType: options.mimeType ?? 'application/octet-stream',\n size: options.size,\n metadata: options.metadata,\n uploadedAt: new Date(),\n })\n\n return {\n path: relativePath,\n disk: diskName,\n fullPath: relativePath,\n size: options.size,\n mimeType: options.mimeType ?? 'application/octet-stream',\n uploadedAt: new Date(),\n }\n }\n\n /**\n * Download a file from fake storage\n */\n download(path: string): Promise<DownloadResult> {\n const file = this.files.get(path)\n\n if (!file) {\n return Promise.reject(new FileNotFoundError(path))\n }\n\n return Promise.resolve({\n toStream: () => new ReadableStream({\n start(controller) {\n controller.enqueue(file.content)\n controller.close()\n },\n }),\n toString: () => Promise.resolve(new TextDecoder().decode(file.content)),\n toArrayBuffer: () => Promise.resolve(file.content),\n contentType: file.mimeType,\n size: file.size,\n metadata: file.metadata,\n })\n }\n\n /**\n * Delete a file from fake storage\n */\n delete(path: string): Promise<void> {\n this.files.delete(path)\n return Promise.resolve()\n }\n\n /**\n * Check if a file exists in fake storage\n */\n exists(path: string): Promise<boolean> {\n return Promise.resolve(this.files.has(path))\n }\n\n /**\n * Generate a fake presigned download URL\n */\n getPresignedDownloadUrl(\n path: string,\n expiresIn?: number\n ): Promise<PresignedUrlResult> {\n return Promise.resolve(this.createPresignedUrl(path, 'GET', expiresIn))\n }\n\n /**\n * Generate a fake presigned upload URL\n */\n getPresignedUploadUrl(\n path: string,\n expiresIn?: number\n ): Promise<PresignedUrlResult> {\n return Promise.resolve(this.createPresignedUrl(path, 'PUT', expiresIn))\n }\n\n /**\n * Generate a fake presigned delete URL\n */\n getPresignedDeleteUrl(\n path: string,\n expiresIn?: number\n ): Promise<PresignedUrlResult> {\n return Promise.resolve(this.createPresignedUrl(path, 'DELETE', expiresIn))\n }\n\n /**\n * Chunked upload (same as regular upload for fake)\n */\n async chunkedUpload(\n body: StreamingBlobPayloadInputTypes,\n path: string,\n options: Omit<UploadOptions, 'size'> & { size?: number },\n disk?: string\n ): Promise<UploadResult> {\n const content = await this.bodyToUint8Array(body)\n const size = options.size ?? content.length\n\n return this.upload(body, path, { ...options, size }, disk)\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Test Assertion Helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Assert that a file exists at the given path\n *\n * @param path - Path to check\n * @throws AssertionError if file does not exist\n */\n assertExists(path: string): void {\n expect(\n this.files.has(path),\n `Expected file to exist at: ${path}\\nStored files: ${this.getStoredPaths().join(', ') || '(none)'}`\n ).toBe(true)\n }\n\n /**\n * Assert that a file does NOT exist at the given path\n *\n * @param path - Path to check\n * @throws AssertionError if file exists\n */\n assertMissing(path: string): void {\n expect(\n this.files.has(path),\n `Expected file NOT to exist at: ${path}`\n ).toBe(false)\n }\n\n /**\n * Assert storage is empty\n *\n * @throws AssertionError if any files exist\n */\n assertEmpty(): void {\n expect(\n this.files.size,\n `Expected storage to be empty but found ${this.files.size} files: ${this.getStoredPaths().join(', ')}`\n ).toBe(0)\n }\n\n /**\n * Assert storage has exactly N files\n *\n * @param count - Expected number of files\n * @throws AssertionError if count doesn't match\n */\n assertCount(count: number): void {\n expect(\n this.files.size,\n `Expected ${count} files in storage but found ${this.files.size}`\n ).toBe(count)\n }\n\n /**\n * Get all stored files (for inspection)\n */\n getStoredFiles(): Map<string, StoredFile> {\n return new Map(this.files)\n }\n\n /**\n * Get all stored file paths\n */\n getStoredPaths(): string[] {\n return Array.from(this.files.keys())\n }\n\n /**\n * Get a specific file by path\n */\n getFile(path: string): StoredFile | undefined {\n return this.files.get(path)\n }\n\n /**\n * Clear all stored files (call in beforeEach for test isolation)\n */\n clear(): void {\n this.files.clear()\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Helpers\n // ─────────────────────────────────────────────────────────────────────────\n\n private createPresignedUrl(\n path: string,\n method: 'GET' | 'PUT' | 'DELETE' | 'HEAD',\n expiresIn = 300\n ): PresignedUrlResult {\n const expiresAt = new Date(Date.now() + expiresIn * 1000)\n\n return {\n url: `https://fake-storage.test/${path}?method=${method}&expires=${expiresAt.toISOString()}`,\n expiresIn,\n expiresAt,\n method,\n }\n }\n\n private async bodyToUint8Array(body: StreamingBlobPayloadInputTypes | null | undefined): Promise<Uint8Array> {\n if (!body) {\n return new Uint8Array(0)\n }\n\n if (body instanceof Uint8Array) {\n return body\n }\n\n if (body instanceof ArrayBuffer) {\n return new Uint8Array(body)\n }\n\n if (typeof body === 'string') {\n return new TextEncoder().encode(body)\n }\n\n if (body instanceof Blob) {\n const buffer = await body.arrayBuffer()\n return new Uint8Array(buffer)\n }\n\n if (body instanceof ReadableStream) {\n return new Uint8Array(await new Response(body).arrayBuffer())\n }\n\n // FormData or URLSearchParams - convert via Response\n if (body instanceof FormData || body instanceof URLSearchParams) {\n return new Uint8Array(await new Response(body).arrayBuffer())\n }\n\n return new Uint8Array(0)\n }\n}\n"],"mappings":"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"}