@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.
- package/CHANGELOG.md +603 -0
- package/README.md +137 -36
- package/dist/database/index.d.mts +2 -2
- package/dist/database/index.mjs +2 -2
- package/dist/{database-DeMI4Igi.mjs → database-Rojs69r3.mjs} +112 -60
- package/dist/database-Rojs69r3.mjs.map +1 -0
- package/dist/{decorate-ZDdbRmsv.mjs → decorate-RQD1h28J.mjs} +1 -1
- package/dist/feature-flags/index.mjs +1 -1
- package/dist/{feature-flags-BMYurC1B.mjs → feature-flags-CZ1a4M2g.mjs} +2 -2
- package/dist/{feature-flags-BMYurC1B.mjs.map → feature-flags-CZ1a4M2g.mjs.map} +1 -1
- package/dist/{index-DUScdrKC.d.mts → index-B8Mw1Dxk.d.mts} +46 -33
- package/dist/index-B8Mw1Dxk.d.mts.map +1 -0
- package/dist/{index-Dm6e2N61.d.mts → index-D7FM6EKp.d.mts} +21 -2
- package/dist/index-D7FM6EKp.d.mts.map +1 -0
- package/dist/index.d.mts +55 -28
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +67 -61
- package/dist/index.mjs.map +1 -1
- package/dist/mocks/cloudflare-workers.d.mts +6 -7
- package/dist/mocks/cloudflare-workers.d.mts.map +1 -1
- package/dist/mocks/index.d.mts +1 -1
- package/dist/mocks/index.mjs +1 -1
- package/dist/mocks/noop-rate-limiter-store.d.mts +1 -2
- package/dist/mocks/noop-rate-limiter-store.d.mts.map +1 -1
- package/dist/mocks/zenstack-language.d.mts +21 -21
- package/dist/mocks/zenstack-language.d.mts.map +1 -1
- package/dist/mocks/zenstack-language.mjs +1 -1
- package/dist/mocks/zenstack-language.mjs.map +1 -1
- package/dist/storage/index.d.mts +1 -1
- package/dist/storage/index.mjs +1 -1
- package/dist/{storage-q8RFahgU.mjs → storage-BiGamMA8.mjs} +70 -3
- package/dist/storage-BiGamMA8.mjs.map +1 -0
- package/dist/{test-worker-exports-DMct0Egv.d.mts → test-worker-exports-dCRARYEc.d.mts} +8 -7
- package/dist/test-worker-exports-dCRARYEc.d.mts.map +1 -0
- package/dist/{test-workers-cache-eiGMw9Yj.mjs → test-workers-cache-D02Pt_dE.mjs} +8 -7
- package/dist/{test-workers-cache-eiGMw9Yj.mjs.map → test-workers-cache-D02Pt_dE.mjs.map} +1 -1
- package/dist/vitest-plugin/index.d.mts +9 -9
- package/dist/vitest-plugin/index.d.mts.map +1 -1
- package/dist/vitest-plugin/index.mjs +22 -15
- package/dist/vitest-plugin/index.mjs.map +1 -1
- package/package.json +22 -19
- package/dist/database-DeMI4Igi.mjs.map +0 -1
- package/dist/index-DUScdrKC.d.mts.map +0 -1
- package/dist/index-Dm6e2N61.d.mts.map +0 -1
- package/dist/storage-q8RFahgU.mjs.map +0 -1
- package/dist/test-worker-exports-DMct0Egv.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as FakeStorageService } from "./storage-
|
|
3
|
-
import { t as __decorate } from "./decorate-
|
|
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-
|
|
6
|
-
import { n as FakeFeatureFlagService, t as FEATURE_FLAG_SERVICE_TOKEN } from "./feature-flags-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
1528
|
-
*
|
|
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
|
|
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:
|
|
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
|
|
1664
|
-
*
|
|
1665
|
-
*
|
|
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
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
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
|
-
|
|
1690
|
+
workerDatabases.set(base, lease);
|
|
1686
1691
|
}
|
|
1687
|
-
await
|
|
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:
|
|
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
|
|
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
|
}));
|