@jterrazz/test 6.3.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 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/utilities/grep.ts
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/utilities/reporter.ts
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/response-accessor.ts
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/specification-result.ts
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.
@@ -593,6 +593,7 @@ var SpecificationBuilder = class {
593
593
  config;
594
594
  fixtures = [];
595
595
  intercepts = [];
596
+ jobName = null;
596
597
  label;
597
598
  mocks = [];
598
599
  projectName = null;
@@ -752,6 +753,18 @@ var SpecificationBuilder = class {
752
753
  return this;
753
754
  }
754
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
+ /**
755
768
  * Execute the specification: run seeds, copy fixtures, then perform the
756
769
  * configured action (HTTP or CLI).
757
770
  *
@@ -763,8 +776,14 @@ var SpecificationBuilder = class {
763
776
  async run() {
764
777
  const hasHttpAction = this.request !== null;
765
778
  const hasCliAction = this.commandArgs !== null || this.spawnConfig !== null;
766
- if (!hasHttpAction && !hasCliAction) throw new Error(`Specification "${this.label}": no action defined. Call .get(), .post(), .exec(), etc. before .run()`);
767
- if (hasHttpAction && hasCliAction) throw new Error(`Specification "${this.label}": cannot mix HTTP (.get/.post) and CLI (.exec/.spawn) actions`);
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)`);
768
787
  let workDir = null;
769
788
  if (hasCliAction) workDir = this.prepareWorkDir();
770
789
  if (this.config.databases) for (const db of this.config.databases.values()) await db.reset();
@@ -782,11 +801,12 @@ var SpecificationBuilder = class {
782
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 });
783
802
  let cleanupIntercepts = null;
784
803
  if (this.intercepts.length > 0) {
785
- const { registerIntercepts } = await Promise.resolve().then(() => require("./server.cjs"));
804
+ const { registerIntercepts } = await Promise.resolve().then(() => require("./intercept2.cjs"));
786
805
  cleanupIntercepts = await registerIntercepts(this.intercepts);
787
806
  }
788
807
  try {
789
808
  if (hasHttpAction) return await this.runHttpAction();
809
+ if (hasJobAction) return await this.runJobAction();
790
810
  return await this.runCliAction(workDir);
791
811
  } finally {
792
812
  if (cleanupIntercepts) cleanupIntercepts();
@@ -828,6 +848,19 @@ var SpecificationBuilder = class {
828
848
  testDir: this.testDir
829
849
  });
830
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
+ }
831
864
  async runCliAction(workDir) {
832
865
  if (!this.config.command) throw new Error("CLI actions require a command adapter (use cli())");
833
866
  const env = this.resolveEnv(workDir);
@@ -861,7 +894,7 @@ function getCallerDir() {
861
894
  if (!match) continue;
862
895
  const filePath = match[1];
863
896
  if (filePath.includes("node_modules")) continue;
864
- if (filePath.includes("/src/builder/") || filePath.includes("/src/runner/")) continue;
897
+ if (filePath.includes("/src/builder/") || filePath.includes("/src/spec/")) continue;
865
898
  return (0, node_path.resolve)(filePath, "..");
866
899
  }
867
900
  throw new Error("Cannot detect caller directory from stack trace");
@@ -1053,7 +1086,7 @@ var DockerAssertion = class {
1053
1086
  }
1054
1087
  };
1055
1088
  //#endregion
1056
- //#region src/adapters/compose.adapter.ts
1089
+ //#region src/infra/adapters/compose.adapter.ts
1057
1090
  /**
1058
1091
  * Start the full compose stack and stop it all on cleanup.
1059
1092
  * Supports per-worker project names for parallel execution.
@@ -1100,7 +1133,7 @@ var ComposeStackAdapter = class {
1100
1133
  }
1101
1134
  };
1102
1135
  //#endregion
1103
- //#region src/adapters/testcontainers.adapter.ts
1136
+ //#region src/infra/adapters/testcontainers.adapter.ts
1104
1137
  /**
1105
1138
  * Container adapter using testcontainers.
1106
1139
  * Wraps a GenericContainer for programmatic container lifecycle.
@@ -1160,7 +1193,7 @@ var TestcontainersAdapter = class {
1160
1193
  }
1161
1194
  };
1162
1195
  //#endregion
1163
- //#region src/orchestrator/compose-parser.ts
1196
+ //#region src/infra/compose-parser.ts
1164
1197
  /**
1165
1198
  * Detect the service type from the image name.
1166
1199
  */
@@ -1233,7 +1266,7 @@ function parseComposeFile(filePath) {
1233
1266
  };
1234
1267
  }
1235
1268
  //#endregion
1236
- //#region src/orchestrator/orchestrator.ts
1269
+ //#region src/infra/orchestrator.ts
1237
1270
  /**
1238
1271
  * Orchestrator for test infrastructure.
1239
1272
  * Integration: starts services via testcontainers.
@@ -1442,7 +1475,7 @@ var Orchestrator = class {
1442
1475
  }
1443
1476
  };
1444
1477
  //#endregion
1445
- //#region src/runner/resolve.ts
1478
+ //#region src/spec/resolve.ts
1446
1479
  /**
1447
1480
  * Resolve root — if relative, resolves from the caller's directory.
1448
1481
  */
@@ -1456,7 +1489,7 @@ function resolveProjectRoot(root) {
1456
1489
  const match = line.match(/at\s+(?:.*?\()?(?:file:\/\/)?([^:)]+):\d+:\d+/);
1457
1490
  if (!match) continue;
1458
1491
  const filePath = match[1];
1459
- if (filePath.includes("node_modules") || filePath.includes("/src/runner/")) continue;
1492
+ if (filePath.includes("node_modules") || filePath.includes("/src/spec/")) continue;
1460
1493
  return (0, node_path.resolve)(filePath, "..", root);
1461
1494
  }
1462
1495
  }
@@ -1474,7 +1507,7 @@ function resolveCommand(command, root) {
1474
1507
  return command;
1475
1508
  }
1476
1509
  //#endregion
1477
- //#region src/runner/spec.ts
1510
+ //#region src/spec/spec.ts
1478
1511
  /**
1479
1512
  * Create a specification runner for the given target.
1480
1513
  *
@@ -1522,12 +1555,15 @@ async function startApp(target, options) {
1522
1555
  const key = svc.composeName ?? svc.type;
1523
1556
  servicesMap[key] = svc;
1524
1557
  }
1525
- const honoApp = target.factory(servicesMap);
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;
1526
1561
  const database = orchestrator.getDatabase() ?? void 0;
1527
1562
  const databases = orchestrator.getDatabases();
1528
1563
  const runner = createSpecificationRunner({
1529
1564
  database,
1530
1565
  databases: databases.size > 0 ? databases : void 0,
1566
+ jobs,
1531
1567
  server: new HonoAdapter(honoApp)
1532
1568
  });
1533
1569
  runner.cleanup = async () => {
@@ -1596,7 +1632,7 @@ async function startCommand(target, options) {
1596
1632
  return runner;
1597
1633
  }
1598
1634
  //#endregion
1599
- //#region src/runner/targets.ts
1635
+ //#region src/spec/targets.ts
1600
1636
  /**
1601
1637
  * Test against an in-process Hono app. The factory receives started services
1602
1638
  * so you can wire connection strings into your app/DI container.
@@ -1646,7 +1682,7 @@ function command(bin) {
1646
1682
  };
1647
1683
  }
1648
1684
  //#endregion
1649
- //#region src/runner/cli.ts
1685
+ //#region src/spec/legacy-cli.ts
1650
1686
  /**
1651
1687
  * Create a CLI specification runner.
1652
1688
  * Runs CLI commands against fixture projects. Optionally starts infrastructure.
@@ -1681,7 +1717,7 @@ async function cli(options) {
1681
1717
  return runner;
1682
1718
  }
1683
1719
  //#endregion
1684
- //#region src/runner/e2e.ts
1720
+ //#region src/spec/legacy-e2e.ts
1685
1721
  /**
1686
1722
  * Create an E2E specification runner.
1687
1723
  * Starts full docker compose stack. App URL and database auto-detected.
@@ -1707,7 +1743,7 @@ async function e2e(options = {}) {
1707
1743
  return runner;
1708
1744
  }
1709
1745
  //#endregion
1710
- //#region src/runner/integration.ts
1746
+ //#region src/spec/legacy-integration.ts
1711
1747
  /**
1712
1748
  * Create an integration specification runner.
1713
1749
  * Starts infra containers via testcontainers, app runs in-process.