@stratal/testing 0.0.0-canary-ccb3f17 → 0.0.0-canary-e5681b8

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +603 -0
  2. package/README.md +137 -36
  3. package/dist/database/index.d.mts +2 -2
  4. package/dist/database/index.mjs +2 -2
  5. package/dist/{database-DeMI4Igi.mjs → database-Rojs69r3.mjs} +112 -60
  6. package/dist/database-Rojs69r3.mjs.map +1 -0
  7. package/dist/{decorate-ZDdbRmsv.mjs → decorate-RQD1h28J.mjs} +1 -1
  8. package/dist/feature-flags/index.mjs +1 -1
  9. package/dist/{feature-flags-BMYurC1B.mjs → feature-flags-CZ1a4M2g.mjs} +2 -2
  10. package/dist/{feature-flags-BMYurC1B.mjs.map → feature-flags-CZ1a4M2g.mjs.map} +1 -1
  11. package/dist/{index-DUScdrKC.d.mts → index-B8Mw1Dxk.d.mts} +46 -33
  12. package/dist/index-B8Mw1Dxk.d.mts.map +1 -0
  13. package/dist/{index-Dm6e2N61.d.mts → index-D7FM6EKp.d.mts} +21 -2
  14. package/dist/index-D7FM6EKp.d.mts.map +1 -0
  15. package/dist/index.d.mts +55 -28
  16. package/dist/index.d.mts.map +1 -1
  17. package/dist/index.mjs +67 -61
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/mocks/cloudflare-workers.d.mts +6 -7
  20. package/dist/mocks/cloudflare-workers.d.mts.map +1 -1
  21. package/dist/mocks/index.d.mts +1 -1
  22. package/dist/mocks/index.mjs +1 -1
  23. package/dist/mocks/noop-rate-limiter-store.d.mts +1 -2
  24. package/dist/mocks/noop-rate-limiter-store.d.mts.map +1 -1
  25. package/dist/mocks/zenstack-language.d.mts +21 -21
  26. package/dist/mocks/zenstack-language.d.mts.map +1 -1
  27. package/dist/mocks/zenstack-language.mjs +1 -1
  28. package/dist/mocks/zenstack-language.mjs.map +1 -1
  29. package/dist/storage/index.d.mts +1 -1
  30. package/dist/storage/index.mjs +1 -1
  31. package/dist/{storage-q8RFahgU.mjs → storage-BiGamMA8.mjs} +70 -3
  32. package/dist/storage-BiGamMA8.mjs.map +1 -0
  33. package/dist/{test-worker-exports-DMct0Egv.d.mts → test-worker-exports-dCRARYEc.d.mts} +8 -7
  34. package/dist/test-worker-exports-dCRARYEc.d.mts.map +1 -0
  35. package/dist/{test-workers-cache-eiGMw9Yj.mjs → test-workers-cache-D02Pt_dE.mjs} +8 -7
  36. package/dist/{test-workers-cache-eiGMw9Yj.mjs.map → test-workers-cache-D02Pt_dE.mjs.map} +1 -1
  37. package/dist/vitest-plugin/index.d.mts +9 -9
  38. package/dist/vitest-plugin/index.d.mts.map +1 -1
  39. package/dist/vitest-plugin/index.mjs +22 -15
  40. package/dist/vitest-plugin/index.mjs.map +1 -1
  41. package/package.json +22 -19
  42. package/dist/database-DeMI4Igi.mjs.map +0 -1
  43. package/dist/index-DUScdrKC.d.mts.map +0 -1
  44. package/dist/index-Dm6e2N61.d.mts.map +0 -1
  45. package/dist/storage-q8RFahgU.mjs.map +0 -1
  46. package/dist/test-worker-exports-DMct0Egv.d.mts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { d as deriveTemplateName, f as ensureWorkerDatabase, i as BINDING_ENV_VAR, l as deriveAdminConnectionString, n as buildTruncateSql, o as buildConnectionString, t as buildTableDiscoverySql, u as deriveFileDbName } from "./database-DeMI4Igi.mjs";
2
- import { t as FakeStorageService } from "./storage-q8RFahgU.mjs";
3
- import { t as __decorate } from "./decorate-ZDdbRmsv.mjs";
1
+ import { i as BINDING_ENV_VAR, n as buildTruncateSql, p as leaseWorkerDatabase, t as buildTableDiscoverySql } from "./database-Rojs69r3.mjs";
2
+ import { t as FakeStorageService } from "./storage-BiGamMA8.mjs";
3
+ import { t as __decorate } from "./decorate-RQD1h28J.mjs";
4
4
  import { NoopRateLimiterStore } from "./mocks/noop-rate-limiter-store.mjs";
5
- import { n as TestWorkerExports, r as TestEmailProvider, t as TestWorkersCache } from "./test-workers-cache-eiGMw9Yj.mjs";
6
- import { n as FakeFeatureFlagService, t as FEATURE_FLAG_SERVICE_TOKEN } from "./feature-flags-BMYurC1B.mjs";
5
+ import { n as TestWorkerExports, r as TestEmailProvider, t as TestWorkersCache } from "./test-workers-cache-D02Pt_dE.mjs";
6
+ import { n as FakeFeatureFlagService, t as FEATURE_FLAG_SERVICE_TOKEN } from "./feature-flags-CZ1a4M2g.mjs";
7
7
  import { Application } from "stratal";
8
8
  import { LogLevel } from "stratal/logger";
9
9
  import { markGatewayMode } from "stratal/response-cache";
@@ -166,9 +166,7 @@ function applyLocaleToHeaders(headers, locale, strategy) {
166
166
  case "cookie":
167
167
  headers.set("Cookie", `locale=${locale}`);
168
168
  break;
169
- case "header":
170
- headers.set("Accept-Language", locale);
171
- break;
169
+ case "header": headers.set("Accept-Language", locale);
172
170
  }
173
171
  }
174
172
  /**
@@ -427,7 +425,8 @@ var TestResponse = class extends Macroable {
427
425
  * @param path - Dot-notation path (e.g., 'data.user.id')
428
426
  */
429
427
  async assertJsonPathExists(path) {
430
- expect(hasValueAtPath(await this.json(), path), `Expected JSON path "${path}" to exist`).toBe(true);
428
+ const exists = hasValueAtPath(await this.json(), path);
429
+ expect(exists, `Expected JSON path "${path}" to exist`).toBe(true);
431
430
  return this;
432
431
  }
433
432
  /**
@@ -436,7 +435,8 @@ var TestResponse = class extends Macroable {
436
435
  * @param path - Dot-notation path (e.g., 'data.user.deletedAt')
437
436
  */
438
437
  async assertJsonPathMissing(path) {
439
- expect(hasValueAtPath(await this.json(), path), `Expected JSON path "${path}" to not exist`).toBe(false);
438
+ const exists = hasValueAtPath(await this.json(), path);
439
+ expect(exists, `Expected JSON path "${path}" to not exist`).toBe(false);
440
440
  return this;
441
441
  }
442
442
  /**
@@ -777,19 +777,23 @@ var TestCommandResult = class {
777
777
  return this;
778
778
  }
779
779
  assertOutputContains(text) {
780
- expect(this.result.output.join("\n"), `Expected output to contain "${text}"`).toContain(text);
780
+ const joined = this.result.output.join("\n");
781
+ expect(joined, `Expected output to contain "${text}"`).toContain(text);
781
782
  return this;
782
783
  }
783
784
  assertOutputMissing(text) {
784
- expect(this.result.output.join("\n"), `Expected output NOT to contain "${text}"`).not.toContain(text);
785
+ const joined = this.result.output.join("\n");
786
+ expect(joined, `Expected output NOT to contain "${text}"`).not.toContain(text);
785
787
  return this;
786
788
  }
787
789
  assertErrorContains(text) {
788
- expect(this.result.errors.join("\n"), `Expected errors to contain "${text}"`).toContain(text);
790
+ const joined = this.result.errors.join("\n");
791
+ expect(joined, `Expected errors to contain "${text}"`).toContain(text);
789
792
  return this;
790
793
  }
791
794
  assertErrorMissing(text) {
792
- expect(this.result.errors.join("\n"), `Expected errors NOT to contain "${text}"`).not.toContain(text);
795
+ const joined = this.result.errors.join("\n");
796
+ expect(joined, `Expected errors NOT to contain "${text}"`).not.toContain(text);
793
797
  return this;
794
798
  }
795
799
  };
@@ -923,7 +927,8 @@ var TestSseConnection = class {
923
927
  * Assert that the next event matches the expected partial shape
924
928
  */
925
929
  async assertEvent(expected, timeout = 5e3) {
926
- expect(await this.waitForEvent(timeout)).toMatchObject(expected);
930
+ const event = await this.waitForEvent(timeout);
931
+ expect(event).toMatchObject(expected);
927
932
  }
928
933
  /**
929
934
  * Assert that the next event's data matches the expected string
@@ -937,7 +942,8 @@ var TestSseConnection = class {
937
942
  */
938
943
  async assertJsonEventData(expected, timeout = 5e3) {
939
944
  const event = await this.waitForEvent(timeout);
940
- expect(JSON.parse(event.data)).toEqual(expected);
945
+ const parsed = JSON.parse(event.data);
946
+ expect(parsed).toEqual(expected);
941
947
  }
942
948
  /**
943
949
  * Access the raw Response
@@ -1361,12 +1367,18 @@ var TestingModule = class {
1361
1367
  }
1362
1368
  /**
1363
1369
  * Get Inertia test client for making Inertia requests
1370
+ *
1371
+ * Sends no `X-Inertia-Version`. It used to send `'1'`, which meant any app configuring a real
1372
+ * asset version had every request from this client read as a stale client and answered with a
1373
+ * version-mismatch 409 — an entire Inertia suite failing on a value the tests never chose. The
1374
+ * check needs a version from BOTH sides, so omitting it here leaves the app under test in charge
1375
+ * rather than this header.
1376
+ *
1377
+ * A test that wants the mismatch path asks for it:
1378
+ * `module.inertia.withHeaders({ 'X-Inertia-Version': 'stale' })`.
1364
1379
  */
1365
1380
  get inertia() {
1366
- return this.http.withHeaders({
1367
- "X-Inertia": "true",
1368
- "X-Inertia-Version": "1"
1369
- });
1381
+ return this.http.withHeaders({ "X-Inertia": "true" });
1370
1382
  }
1371
1383
  /**
1372
1384
  * Get fake storage service for assertions
@@ -1477,7 +1489,8 @@ var TestingModule = class {
1477
1489
  */
1478
1490
  async truncateDb(name, opts = {}) {
1479
1491
  const db = this.getDb(name);
1480
- const sql = buildTruncateSql((await db.$queryRawUnsafe(buildTableDiscoverySql(opts.schemas ?? [], opts.preserve ?? []))).map((t) => ({
1492
+ const rows = await db.$queryRawUnsafe(buildTableDiscoverySql(opts.schemas ?? [], opts.preserve ?? []));
1493
+ const sql = buildTruncateSql(rows.map((t) => ({
1481
1494
  schema: t.schemaname,
1482
1495
  table: t.tablename
1483
1496
  })));
@@ -1497,13 +1510,15 @@ var TestingModule = class {
1497
1510
  * Assert that a record exists in the database
1498
1511
  */
1499
1512
  async assertDatabaseHas(table, data, name) {
1500
- expect(await this.getDb(name)[table].findFirst({ where: data }), `Expected ${table} with ${JSON.stringify(data)}`).not.toBeNull();
1513
+ const result = await this.getDb(name)[table].findFirst({ where: data });
1514
+ expect(result, `Expected ${table} with ${JSON.stringify(data)}`).not.toBeNull();
1501
1515
  }
1502
1516
  /**
1503
1517
  * Assert that a record does not exist in the database
1504
1518
  */
1505
1519
  async assertDatabaseMissing(table, data, name) {
1506
- expect(await this.getDb(name)[table].findFirst({ where: data }), `Expected ${table} NOT to have ${JSON.stringify(data)}`).toBeNull();
1520
+ const result = await this.getDb(name)[table].findFirst({ where: data });
1521
+ expect(result, `Expected ${table} NOT to have ${JSON.stringify(data)}`).toBeNull();
1507
1522
  }
1508
1523
  /**
1509
1524
  * Assert the number of records in a table
@@ -1524,20 +1539,12 @@ var TestingModule = class {
1524
1539
  //#endregion
1525
1540
  //#region src/core/testing-module-builder.ts
1526
1541
  /**
1527
- * Per-file guard: the first `compile()` in a file clones its database; later
1528
- * compiles in the same file skip straight to retargeting.
1542
+ * This isolate's worker-database lease, per base connection string. The pool
1543
+ * runs each test file in its own isolate (`isolate: true`), so the first
1544
+ * `compile()` in a file leases and clones a database and later compiles in the
1545
+ * same file reuse it. The lease ends when the pool disposes the isolate.
1529
1546
  */
1530
- const ensuredWorkerDbs = /* @__PURE__ */ new Map();
1531
- /**
1532
- * A crypto-random token generated ONCE per test-file isolate. The pool runs one
1533
- * isolate per file (`isolate: true`), so this is stable across a file's compiles
1534
- * and unique across files — giving every file its own database. Lazily created so
1535
- * DB-less test files never touch `crypto`.
1536
- */
1537
- let fileToken;
1538
- function currentFileToken() {
1539
- return fileToken ??= crypto.randomUUID().replace(/-/g, "").slice(0, 16);
1540
- }
1547
+ const workerDatabases = /* @__PURE__ */ new Map();
1541
1548
  /**
1542
1549
  * Builder for creating test modules with provider overrides
1543
1550
  */
@@ -1606,21 +1613,24 @@ var TestingModuleBuilder = class {
1606
1613
  let app = null;
1607
1614
  try {
1608
1615
  const allImports = [...Test.getBaseModules(), ...this.config.imports ?? []];
1616
+ const rootModule = this.createTestRootModule({
1617
+ imports: allImports,
1618
+ providers: this.config.providers,
1619
+ controllers: this.config.controllers,
1620
+ consumers: this.config.consumers,
1621
+ jobs: this.config.jobs
1622
+ });
1609
1623
  app = new Application({
1610
- module: this.createTestRootModule({
1611
- imports: allImports,
1612
- providers: this.config.providers,
1613
- controllers: this.config.controllers,
1614
- consumers: this.config.consumers,
1615
- jobs: this.config.jobs
1616
- }),
1624
+ module: rootModule,
1617
1625
  logging: {
1618
1626
  level: this.config.logging?.level ?? LogLevel.ERROR,
1619
1627
  formatter: this.config.logging?.formatter ?? "pretty"
1620
1628
  },
1621
1629
  env,
1622
1630
  ctx,
1623
- exceptionHandler: this.config.exceptionHandler
1631
+ exceptionHandler: this.config.exceptionHandler,
1632
+ trailingSlash: this.config.trailingSlash,
1633
+ versioning: this.config.versioning
1624
1634
  });
1625
1635
  await app.initialize();
1626
1636
  app.container.registerSingleton(STORAGE_TOKENS.StorageService, FakeStorageService);
@@ -1638,9 +1648,7 @@ var TestingModuleBuilder = class {
1638
1648
  case "factory":
1639
1649
  app.container.registerFactory(override.token, override.implementation);
1640
1650
  break;
1641
- case "existing":
1642
- app.container.registerExisting(override.token, override.implementation);
1643
- break;
1651
+ case "existing": app.container.registerExisting(override.token, override.implementation);
1644
1652
  }
1645
1653
  await app.ensureHono();
1646
1654
  testWorkerExports?.setHandler(async (request, props) => {
@@ -1660,10 +1668,9 @@ var TestingModuleBuilder = class {
1660
1668
  }
1661
1669
  }
1662
1670
  /**
1663
- * Point this app's Hyperdrive binding at the current worker's database,
1664
- * creating that database from the template on the slot's first compile. Returns
1665
- * nothing to clean up — worker databases live for the whole run and are reset
1666
- * between tests, not dropped per file. No-op when the consumer didn't opt into
1671
+ * Point this app's Hyperdrive binding at the database this file leased,
1672
+ * leasing it on the file's first compile. Returns nothing to clean up — the
1673
+ * lease ends with the isolate. No-op when the consumer didn't opt into
1667
1674
  * `stratalTest({ database })`; throws if they did but no connection string is
1668
1675
  * configured (a misconfiguration that would otherwise silently skip isolation).
1669
1676
  */
@@ -1674,20 +1681,18 @@ var TestingModuleBuilder = class {
1674
1681
  const db = bindings[bindingName];
1675
1682
  const base = db?.connectionString;
1676
1683
  if (!base) throw new Error(`[stratal-testing] stratalTest({ database }) is enabled but no \`${bindingName}\` Hyperdrive binding with a connectionString was found. Provide it via miniflare.hyperdrives (e.g. { ${bindingName}: DATABASE_URL }), or drop the \`database\` option.`);
1677
- const name = deriveFileDbName(base, currentFileToken());
1678
- const adminConnectionString = deriveAdminConnectionString(base);
1679
- let ensure = ensuredWorkerDbs.get(name);
1680
- if (!ensure) {
1681
- ensure = ensureWorkerDatabase(adminConnectionString, name, deriveTemplateName(base)).catch((error) => {
1682
- ensuredWorkerDbs.delete(name);
1684
+ let lease = workerDatabases.get(base);
1685
+ if (!lease) {
1686
+ lease = leaseWorkerDatabase(base).catch((error) => {
1687
+ workerDatabases.delete(base);
1683
1688
  throw error;
1684
1689
  });
1685
- ensuredWorkerDbs.set(name, ensure);
1690
+ workerDatabases.set(base, lease);
1686
1691
  }
1687
- await ensure;
1692
+ const { connectionString } = await lease;
1688
1693
  const isolated = Object.create(Object.getPrototypeOf(db), Object.getOwnPropertyDescriptors(db));
1689
1694
  Object.defineProperty(isolated, "connectionString", {
1690
- value: buildConnectionString(base, name),
1695
+ value: connectionString,
1691
1696
  enumerable: true,
1692
1697
  configurable: true,
1693
1698
  writable: true
@@ -1819,7 +1824,8 @@ var MockFetch = class {
1819
1824
  * ```
1820
1825
  */
1821
1826
  mockJsonResponse(url, data, options = {}) {
1822
- const handler = http$1[(options.method ?? "GET").toLowerCase()](url, () => HttpResponse$1.json(data, {
1827
+ const method = (options.method ?? "GET").toLowerCase();
1828
+ const handler = http$1[method](url, () => HttpResponse$1.json(data, {
1823
1829
  status: options.status ?? 200,
1824
1830
  headers: options.headers
1825
1831
  }));