@jterrazz/test 6.2.0 → 6.4.1
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/dist/index.cjs +108 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -20
- package/dist/index.d.ts +68 -20
- package/dist/index.js +108 -41
- package/dist/index.js.map +1 -1
- package/dist/intercept.cjs +240 -0
- package/dist/intercept.cjs.map +1 -0
- package/dist/intercept.d.cts +89 -0
- package/dist/intercept.d.ts +89 -0
- package/dist/intercept.js +237 -0
- package/dist/intercept.js.map +1 -0
- package/dist/intercept2.cjs +79 -0
- package/dist/intercept2.cjs.map +1 -0
- package/dist/intercept2.js +79 -0
- package/dist/intercept2.js.map +1 -0
- package/dist/mock-of.cjs +2 -2
- package/dist/mock-of.cjs.map +1 -1
- package/dist/mock-of.d.cts +2 -2
- package/dist/mock-of.d.ts +2 -2
- package/dist/mock-of.js +2 -2
- package/dist/mock-of.js.map +1 -1
- package/dist/sqlite.adapter.cjs.map +1 -1
- package/dist/sqlite.adapter.d.cts +3 -3
- package/dist/sqlite.adapter.d.ts +3 -3
- package/dist/sqlite.adapter.js.map +1 -1
- package/dist/types.d.cts +35 -0
- package/dist/types.d.ts +35 -0
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -8,7 +8,7 @@ let node_os = require("node:os");
|
|
|
8
8
|
let node_path = require("node:path");
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
10
10
|
let yaml = require("yaml");
|
|
11
|
-
//#region src/adapters/exec.adapter.ts
|
|
11
|
+
//#region src/builder/cli/adapters/exec.adapter.ts
|
|
12
12
|
/**
|
|
13
13
|
* Build a child-process env from the parent env plus user overrides.
|
|
14
14
|
* `null` overrides delete keys (e.g. `INIT_CWD: null`).
|
|
@@ -105,7 +105,7 @@ var ExecAdapter = class {
|
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
107
|
//#endregion
|
|
108
|
-
//#region src/adapters/fetch.adapter.ts
|
|
108
|
+
//#region src/builder/http/adapters/fetch.adapter.ts
|
|
109
109
|
/**
|
|
110
110
|
* Server adapter that sends real HTTP requests via the Fetch API.
|
|
111
111
|
* Used by the `e2e()` specification runner to hit a live server.
|
|
@@ -138,7 +138,7 @@ var FetchAdapter = class {
|
|
|
138
138
|
}
|
|
139
139
|
};
|
|
140
140
|
//#endregion
|
|
141
|
-
//#region src/adapters/hono.adapter.ts
|
|
141
|
+
//#region src/builder/http/adapters/hono.adapter.ts
|
|
142
142
|
/**
|
|
143
143
|
* Server adapter that dispatches requests in-process through a Hono app instance.
|
|
144
144
|
* Used by the `integration()` specification runner -- no network overhead.
|
|
@@ -171,21 +171,7 @@ var HonoAdapter = class {
|
|
|
171
171
|
}
|
|
172
172
|
};
|
|
173
173
|
//#endregion
|
|
174
|
-
//#region src/
|
|
175
|
-
/**
|
|
176
|
-
* Extract text blocks from output that contain a pattern.
|
|
177
|
-
* Splits by blank lines (how linter/compiler output is structured),
|
|
178
|
-
* returns only blocks matching the pattern.
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* expect(grep(result.stdout, "unused-var.ts")).toContain("no-unused-vars")
|
|
182
|
-
* expect(grep(result.stdout, "valid/sorted.ts")).not.toContain("sort-imports")
|
|
183
|
-
*/
|
|
184
|
-
function grep(output, pattern) {
|
|
185
|
-
return output.replace(/\x1b\[[0-9;]*m/g, "").split(/\n\s*\n/).filter((block) => block.includes(pattern)).join("\n\n");
|
|
186
|
-
}
|
|
187
|
-
//#endregion
|
|
188
|
-
//#region src/utilities/directory.ts
|
|
174
|
+
//#region src/builder/common/directory.ts
|
|
189
175
|
/**
|
|
190
176
|
* Default ignore patterns — paths that should never appear in a tracked snapshot.
|
|
191
177
|
* Each entry is matched against any path segment OR a path prefix.
|
|
@@ -254,7 +240,7 @@ async function diffDirectories(expectedRoot, actualRoot, options = {}) {
|
|
|
254
240
|
};
|
|
255
241
|
}
|
|
256
242
|
//#endregion
|
|
257
|
-
//#region src/
|
|
243
|
+
//#region src/builder/common/reporter.ts
|
|
258
244
|
const GREEN = "\x1B[32m";
|
|
259
245
|
const RED = "\x1B[31m";
|
|
260
246
|
const DIM = "\x1B[2m";
|
|
@@ -392,7 +378,7 @@ function normalizeOutput(str) {
|
|
|
392
378
|
return stripAnsi(str).replace(/localhost:\d+/g, "localhost:PORT").replace(/\d+ms/g, "Xms").replace(/\d+\.\d+s/g, "X.Xs").trim();
|
|
393
379
|
}
|
|
394
380
|
//#endregion
|
|
395
|
-
//#region src/builder/directory-accessor.ts
|
|
381
|
+
//#region src/builder/common/directory-accessor.ts
|
|
396
382
|
/**
|
|
397
383
|
* Detect whether the user wants to update snapshots — `true` for any of:
|
|
398
384
|
* - vitest run with `-u` / `--update`
|
|
@@ -446,7 +432,21 @@ var DirectoryAccessor = class {
|
|
|
446
432
|
}
|
|
447
433
|
};
|
|
448
434
|
//#endregion
|
|
449
|
-
//#region src/builder/
|
|
435
|
+
//#region src/builder/common/grep.ts
|
|
436
|
+
/**
|
|
437
|
+
* Extract text blocks from output that contain a pattern.
|
|
438
|
+
* Splits by blank lines (how linter/compiler output is structured),
|
|
439
|
+
* returns only blocks matching the pattern.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* expect(grep(result.stdout, "unused-var.ts")).toContain("no-unused-vars")
|
|
443
|
+
* expect(grep(result.stdout, "valid/sorted.ts")).not.toContain("sort-imports")
|
|
444
|
+
*/
|
|
445
|
+
function grep(output, pattern) {
|
|
446
|
+
return output.replace(/\x1b\[[0-9;]*m/g, "").split(/\n\s*\n/).filter((block) => block.includes(pattern)).join("\n\n");
|
|
447
|
+
}
|
|
448
|
+
//#endregion
|
|
449
|
+
//#region src/builder/common/response-accessor.ts
|
|
450
450
|
/** Accessor for an HTTP response body with file-based assertion support. */
|
|
451
451
|
var ResponseAccessor = class {
|
|
452
452
|
body;
|
|
@@ -467,7 +467,7 @@ var ResponseAccessor = class {
|
|
|
467
467
|
}
|
|
468
468
|
};
|
|
469
469
|
//#endregion
|
|
470
|
-
//#region src/builder/table-assertion.ts
|
|
470
|
+
//#region src/builder/common/table-assertion.ts
|
|
471
471
|
/** Assertion helper for verifying database table contents after a specification run. */
|
|
472
472
|
var TableAssertion = class {
|
|
473
473
|
tableName;
|
|
@@ -496,7 +496,7 @@ var TableAssertion = class {
|
|
|
496
496
|
}
|
|
497
497
|
};
|
|
498
498
|
//#endregion
|
|
499
|
-
//#region src/builder/
|
|
499
|
+
//#region src/builder/common/result.ts
|
|
500
500
|
/**
|
|
501
501
|
* The outcome of a single specification run.
|
|
502
502
|
* Provides accessors for CLI output, HTTP responses, files, directories, and database tables.
|
|
@@ -592,6 +592,8 @@ var SpecificationBuilder = class {
|
|
|
592
592
|
commandEnv = {};
|
|
593
593
|
config;
|
|
594
594
|
fixtures = [];
|
|
595
|
+
intercepts = [];
|
|
596
|
+
jobName = null;
|
|
595
597
|
label;
|
|
596
598
|
mocks = [];
|
|
597
599
|
projectName = null;
|
|
@@ -664,6 +666,28 @@ var SpecificationBuilder = class {
|
|
|
664
666
|
return this;
|
|
665
667
|
}
|
|
666
668
|
/**
|
|
669
|
+
* Intercept an outgoing HTTP request and return a controlled response.
|
|
670
|
+
* Uses MSW under the hood. Intercepts are queued — multiple calls with the
|
|
671
|
+
* same trigger fire sequentially (first match consumed first).
|
|
672
|
+
*
|
|
673
|
+
* @param trigger - What to match (use openai.chat(), anthropic.messages(), http.get(), etc.)
|
|
674
|
+
* @param response - What to return (use openai.response(), http.json(), etc.)
|
|
675
|
+
*
|
|
676
|
+
* @example
|
|
677
|
+
* spec('pipeline')
|
|
678
|
+
* .intercept(openai.chat(), openai.response({ categories: ['TECH'] }))
|
|
679
|
+
* .intercept(openai.chat(), openai.response({ headline: 'AI News' }))
|
|
680
|
+
* .exec('process')
|
|
681
|
+
* .run();
|
|
682
|
+
*/
|
|
683
|
+
intercept(trigger, response) {
|
|
684
|
+
this.intercepts.push({
|
|
685
|
+
trigger,
|
|
686
|
+
response
|
|
687
|
+
});
|
|
688
|
+
return this;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
667
691
|
* Send a GET request to the server adapter.
|
|
668
692
|
*
|
|
669
693
|
* @example
|
|
@@ -729,6 +753,18 @@ var SpecificationBuilder = class {
|
|
|
729
753
|
return this;
|
|
730
754
|
}
|
|
731
755
|
/**
|
|
756
|
+
* Execute a named job registered via the app() factory.
|
|
757
|
+
*
|
|
758
|
+
* @param name - The job name to trigger (must match a registered JobHandle.name).
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* spec('pipeline').intercept(openai.chat(), openai.response({...})).job('report-refresh').run();
|
|
762
|
+
*/
|
|
763
|
+
job(name) {
|
|
764
|
+
this.jobName = name;
|
|
765
|
+
return this;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
732
768
|
* Execute the specification: run seeds, copy fixtures, then perform the
|
|
733
769
|
* configured action (HTTP or CLI).
|
|
734
770
|
*
|
|
@@ -740,8 +776,14 @@ var SpecificationBuilder = class {
|
|
|
740
776
|
async run() {
|
|
741
777
|
const hasHttpAction = this.request !== null;
|
|
742
778
|
const hasCliAction = this.commandArgs !== null || this.spawnConfig !== null;
|
|
743
|
-
|
|
744
|
-
|
|
779
|
+
const hasJobAction = this.jobName !== null;
|
|
780
|
+
const actionCount = [
|
|
781
|
+
hasHttpAction,
|
|
782
|
+
hasCliAction,
|
|
783
|
+
hasJobAction
|
|
784
|
+
].filter(Boolean).length;
|
|
785
|
+
if (actionCount === 0) throw new Error(`Specification "${this.label}": no action defined. Call .get(), .post(), .exec(), .job(), etc. before .run()`);
|
|
786
|
+
if (actionCount > 1) throw new Error(`Specification "${this.label}": cannot mix action types (.get/.post, .exec/.spawn, .job)`);
|
|
745
787
|
let workDir = null;
|
|
746
788
|
if (hasCliAction) workDir = this.prepareWorkDir();
|
|
747
789
|
if (this.config.databases) for (const db of this.config.databases.values()) await db.reset();
|
|
@@ -757,9 +799,18 @@ var SpecificationBuilder = class {
|
|
|
757
799
|
await db.seed(sql);
|
|
758
800
|
}
|
|
759
801
|
if (this.fixtures.length > 0 && workDir) for (const entry of this.fixtures) (0, node_fs.cpSync)((0, node_path.resolve)(this.testDir, "fixtures", entry.file), (0, node_path.resolve)(workDir, entry.file), { recursive: true });
|
|
760
|
-
|
|
761
|
-
if (
|
|
762
|
-
|
|
802
|
+
let cleanupIntercepts = null;
|
|
803
|
+
if (this.intercepts.length > 0) {
|
|
804
|
+
const { registerIntercepts } = await Promise.resolve().then(() => require("./intercept2.cjs"));
|
|
805
|
+
cleanupIntercepts = await registerIntercepts(this.intercepts);
|
|
806
|
+
}
|
|
807
|
+
try {
|
|
808
|
+
if (hasHttpAction) return await this.runHttpAction();
|
|
809
|
+
if (hasJobAction) return await this.runJobAction();
|
|
810
|
+
return await this.runCliAction(workDir);
|
|
811
|
+
} finally {
|
|
812
|
+
if (cleanupIntercepts) cleanupIntercepts();
|
|
813
|
+
}
|
|
763
814
|
}
|
|
764
815
|
resolveEnv(workDir) {
|
|
765
816
|
const keys = Object.keys(this.commandEnv);
|
|
@@ -797,6 +848,19 @@ var SpecificationBuilder = class {
|
|
|
797
848
|
testDir: this.testDir
|
|
798
849
|
});
|
|
799
850
|
}
|
|
851
|
+
async runJobAction() {
|
|
852
|
+
if (!this.config.jobs?.length) throw new Error("Job actions require jobs registered via app(() => ({ server, jobs }))");
|
|
853
|
+
const job = this.config.jobs.find((j) => j.name === this.jobName);
|
|
854
|
+
if (!job) {
|
|
855
|
+
const available = this.config.jobs.map((j) => j.name).join(", ");
|
|
856
|
+
throw new Error(`job("${this.jobName}"): not found. Available: ${available}`);
|
|
857
|
+
}
|
|
858
|
+
await job.execute();
|
|
859
|
+
return new SpecificationResult({
|
|
860
|
+
config: this.config,
|
|
861
|
+
testDir: this.testDir
|
|
862
|
+
});
|
|
863
|
+
}
|
|
800
864
|
async runCliAction(workDir) {
|
|
801
865
|
if (!this.config.command) throw new Error("CLI actions require a command adapter (use cli())");
|
|
802
866
|
const env = this.resolveEnv(workDir);
|
|
@@ -830,7 +894,7 @@ function getCallerDir() {
|
|
|
830
894
|
if (!match) continue;
|
|
831
895
|
const filePath = match[1];
|
|
832
896
|
if (filePath.includes("node_modules")) continue;
|
|
833
|
-
if (filePath.includes("/src/builder/") || filePath.includes("/src/
|
|
897
|
+
if (filePath.includes("/src/builder/") || filePath.includes("/src/spec/")) continue;
|
|
834
898
|
return (0, node_path.resolve)(filePath, "..");
|
|
835
899
|
}
|
|
836
900
|
throw new Error("Cannot detect caller directory from stack trace");
|
|
@@ -1022,7 +1086,7 @@ var DockerAssertion = class {
|
|
|
1022
1086
|
}
|
|
1023
1087
|
};
|
|
1024
1088
|
//#endregion
|
|
1025
|
-
//#region src/adapters/compose.adapter.ts
|
|
1089
|
+
//#region src/infra/adapters/compose.adapter.ts
|
|
1026
1090
|
/**
|
|
1027
1091
|
* Start the full compose stack and stop it all on cleanup.
|
|
1028
1092
|
* Supports per-worker project names for parallel execution.
|
|
@@ -1069,7 +1133,7 @@ var ComposeStackAdapter = class {
|
|
|
1069
1133
|
}
|
|
1070
1134
|
};
|
|
1071
1135
|
//#endregion
|
|
1072
|
-
//#region src/adapters/testcontainers.adapter.ts
|
|
1136
|
+
//#region src/infra/adapters/testcontainers.adapter.ts
|
|
1073
1137
|
/**
|
|
1074
1138
|
* Container adapter using testcontainers.
|
|
1075
1139
|
* Wraps a GenericContainer for programmatic container lifecycle.
|
|
@@ -1129,7 +1193,7 @@ var TestcontainersAdapter = class {
|
|
|
1129
1193
|
}
|
|
1130
1194
|
};
|
|
1131
1195
|
//#endregion
|
|
1132
|
-
//#region src/
|
|
1196
|
+
//#region src/infra/compose-parser.ts
|
|
1133
1197
|
/**
|
|
1134
1198
|
* Detect the service type from the image name.
|
|
1135
1199
|
*/
|
|
@@ -1202,7 +1266,7 @@ function parseComposeFile(filePath) {
|
|
|
1202
1266
|
};
|
|
1203
1267
|
}
|
|
1204
1268
|
//#endregion
|
|
1205
|
-
//#region src/
|
|
1269
|
+
//#region src/infra/orchestrator.ts
|
|
1206
1270
|
/**
|
|
1207
1271
|
* Orchestrator for test infrastructure.
|
|
1208
1272
|
* Integration: starts services via testcontainers.
|
|
@@ -1411,7 +1475,7 @@ var Orchestrator = class {
|
|
|
1411
1475
|
}
|
|
1412
1476
|
};
|
|
1413
1477
|
//#endregion
|
|
1414
|
-
//#region src/
|
|
1478
|
+
//#region src/spec/resolve.ts
|
|
1415
1479
|
/**
|
|
1416
1480
|
* Resolve root — if relative, resolves from the caller's directory.
|
|
1417
1481
|
*/
|
|
@@ -1425,7 +1489,7 @@ function resolveProjectRoot(root) {
|
|
|
1425
1489
|
const match = line.match(/at\s+(?:.*?\()?(?:file:\/\/)?([^:)]+):\d+:\d+/);
|
|
1426
1490
|
if (!match) continue;
|
|
1427
1491
|
const filePath = match[1];
|
|
1428
|
-
if (filePath.includes("node_modules") || filePath.includes("/src/
|
|
1492
|
+
if (filePath.includes("node_modules") || filePath.includes("/src/spec/")) continue;
|
|
1429
1493
|
return (0, node_path.resolve)(filePath, "..", root);
|
|
1430
1494
|
}
|
|
1431
1495
|
}
|
|
@@ -1443,7 +1507,7 @@ function resolveCommand(command, root) {
|
|
|
1443
1507
|
return command;
|
|
1444
1508
|
}
|
|
1445
1509
|
//#endregion
|
|
1446
|
-
//#region src/
|
|
1510
|
+
//#region src/spec/spec.ts
|
|
1447
1511
|
/**
|
|
1448
1512
|
* Create a specification runner for the given target.
|
|
1449
1513
|
*
|
|
@@ -1491,12 +1555,15 @@ async function startApp(target, options) {
|
|
|
1491
1555
|
const key = svc.composeName ?? svc.type;
|
|
1492
1556
|
servicesMap[key] = svc;
|
|
1493
1557
|
}
|
|
1494
|
-
const
|
|
1558
|
+
const factoryResult = target.factory(servicesMap);
|
|
1559
|
+
const honoApp = "server" in factoryResult ? factoryResult.server : factoryResult;
|
|
1560
|
+
const jobs = "jobs" in factoryResult ? factoryResult.jobs : void 0;
|
|
1495
1561
|
const database = orchestrator.getDatabase() ?? void 0;
|
|
1496
1562
|
const databases = orchestrator.getDatabases();
|
|
1497
1563
|
const runner = createSpecificationRunner({
|
|
1498
1564
|
database,
|
|
1499
1565
|
databases: databases.size > 0 ? databases : void 0,
|
|
1566
|
+
jobs,
|
|
1500
1567
|
server: new HonoAdapter(honoApp)
|
|
1501
1568
|
});
|
|
1502
1569
|
runner.cleanup = async () => {
|
|
@@ -1565,7 +1632,7 @@ async function startCommand(target, options) {
|
|
|
1565
1632
|
return runner;
|
|
1566
1633
|
}
|
|
1567
1634
|
//#endregion
|
|
1568
|
-
//#region src/
|
|
1635
|
+
//#region src/spec/targets.ts
|
|
1569
1636
|
/**
|
|
1570
1637
|
* Test against an in-process Hono app. The factory receives started services
|
|
1571
1638
|
* so you can wire connection strings into your app/DI container.
|
|
@@ -1615,7 +1682,7 @@ function command(bin) {
|
|
|
1615
1682
|
};
|
|
1616
1683
|
}
|
|
1617
1684
|
//#endregion
|
|
1618
|
-
//#region src/
|
|
1685
|
+
//#region src/spec/legacy-cli.ts
|
|
1619
1686
|
/**
|
|
1620
1687
|
* Create a CLI specification runner.
|
|
1621
1688
|
* Runs CLI commands against fixture projects. Optionally starts infrastructure.
|
|
@@ -1650,7 +1717,7 @@ async function cli(options) {
|
|
|
1650
1717
|
return runner;
|
|
1651
1718
|
}
|
|
1652
1719
|
//#endregion
|
|
1653
|
-
//#region src/
|
|
1720
|
+
//#region src/spec/legacy-e2e.ts
|
|
1654
1721
|
/**
|
|
1655
1722
|
* Create an E2E specification runner.
|
|
1656
1723
|
* Starts full docker compose stack. App URL and database auto-detected.
|
|
@@ -1676,7 +1743,7 @@ async function e2e(options = {}) {
|
|
|
1676
1743
|
return runner;
|
|
1677
1744
|
}
|
|
1678
1745
|
//#endregion
|
|
1679
|
-
//#region src/
|
|
1746
|
+
//#region src/spec/legacy-integration.ts
|
|
1680
1747
|
/**
|
|
1681
1748
|
* Create an integration specification runner.
|
|
1682
1749
|
* Starts infra containers via testcontainers, app runs in-process.
|