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