@elench/testkit 0.1.83 → 0.1.84

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 (83) hide show
  1. package/lib/cli/agents/providers/codex.mjs +1 -1
  2. package/lib/cli/tui/run-session-app.mjs +1 -1
  3. package/node_modules/@elench/next-analysis/package.json +1 -1
  4. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  5. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  6. package/node_modules/@elench/ts-analysis/package.json +1 -1
  7. package/package.json +7 -6
  8. package/lib/app/configs.test.mjs +0 -34
  9. package/lib/app/typecheck.test.mjs +0 -24
  10. package/lib/bundler/index.test.mjs +0 -164
  11. package/lib/cli/agents/investigation-context.test.mjs +0 -144
  12. package/lib/cli/agents/providers/claude.test.mjs +0 -95
  13. package/lib/cli/agents/providers/codex.test.mjs +0 -93
  14. package/lib/cli/args.test.mjs +0 -110
  15. package/lib/cli/command-helpers.test.mjs +0 -122
  16. package/lib/cli/commands/investigate.test.mjs +0 -83
  17. package/lib/cli/presentation/code-frames.test.mjs +0 -71
  18. package/lib/cli/presentation/events-reporter.test.mjs +0 -73
  19. package/lib/cli/presentation/run-reporter.test.mjs +0 -192
  20. package/lib/cli/presentation/summary-box.test.mjs +0 -60
  21. package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
  22. package/lib/cli/presentation/tree-reporter.test.mjs +0 -166
  23. package/lib/cli/tui/run-session-app.test.mjs +0 -50
  24. package/lib/cli/tui/run-tree-state.test.mjs +0 -324
  25. package/lib/config/database.test.mjs +0 -29
  26. package/lib/config/discovery.test.mjs +0 -276
  27. package/lib/config/env.test.mjs +0 -40
  28. package/lib/config/index.test.mjs +0 -44
  29. package/lib/config/paths.test.mjs +0 -27
  30. package/lib/config/runtime.test.mjs +0 -82
  31. package/lib/config/skip-config.test.mjs +0 -63
  32. package/lib/config-api/index.test.mjs +0 -398
  33. package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
  34. package/lib/coverage/backend-discovery.test.mjs +0 -61
  35. package/lib/coverage/evidence.test.mjs +0 -87
  36. package/lib/coverage/index.test.mjs +0 -715
  37. package/lib/coverage/routing.test.mjs +0 -36
  38. package/lib/coverage/shared.test.mjs +0 -72
  39. package/lib/database/fingerprint.test.mjs +0 -99
  40. package/lib/database/index.test.mjs +0 -95
  41. package/lib/database/naming.test.mjs +0 -39
  42. package/lib/database/state.test.mjs +0 -66
  43. package/lib/database/template-steps.test.mjs +0 -43
  44. package/lib/discovery/file-metadata.test.mjs +0 -51
  45. package/lib/discovery/index.test.mjs +0 -182
  46. package/lib/discovery/path-policy.test.mjs +0 -65
  47. package/lib/drizzle/index.test.mjs +0 -33
  48. package/lib/env/index.test.mjs +0 -82
  49. package/lib/history/index.test.mjs +0 -115
  50. package/lib/package.test.mjs +0 -59
  51. package/lib/playwright/index.test.mjs +0 -43
  52. package/lib/regressions/github.test.mjs +0 -324
  53. package/lib/regressions/index.test.mjs +0 -187
  54. package/lib/reporters/playwright.test.mjs +0 -167
  55. package/lib/runner/default-runtime-errors.test.mjs +0 -49
  56. package/lib/runner/execution-config.test.mjs +0 -67
  57. package/lib/runner/failure-details.test.mjs +0 -114
  58. package/lib/runner/formatting.test.mjs +0 -205
  59. package/lib/runner/metadata.test.mjs +0 -52
  60. package/lib/runner/planning.test.mjs +0 -371
  61. package/lib/runner/playwright-config.test.mjs +0 -78
  62. package/lib/runner/processes.test.mjs +0 -21
  63. package/lib/runner/regressions.test.mjs +0 -168
  64. package/lib/runner/reporting.test.mjs +0 -310
  65. package/lib/runner/results.test.mjs +0 -376
  66. package/lib/runner/runtime-manager.test.mjs +0 -252
  67. package/lib/runner/runtime-preparation.test.mjs +0 -141
  68. package/lib/runner/selection.test.mjs +0 -24
  69. package/lib/runner/setup-operations.test.mjs +0 -94
  70. package/lib/runner/state.test.mjs +0 -62
  71. package/lib/runner/suite-selection.test.mjs +0 -49
  72. package/lib/runner/template.test.mjs +0 -272
  73. package/lib/runtime-src/k6/http-checks.test.mjs +0 -120
  74. package/lib/runtime-src/k6/http.test.mjs +0 -205
  75. package/lib/runtime-src/shared/http-parsing.test.mjs +0 -69
  76. package/lib/shared/build-config.test.mjs +0 -132
  77. package/lib/shared/configured-steps.test.mjs +0 -102
  78. package/lib/shared/execution-schema.test.mjs +0 -26
  79. package/lib/shared/file-timeout.test.mjs +0 -64
  80. package/lib/shared/test-context.test.mjs +0 -43
  81. package/lib/timing/index.test.mjs +0 -64
  82. package/lib/toolchains/index.test.mjs +0 -168
  83. package/lib/vitest/index.test.mjs +0 -20
@@ -1,36 +0,0 @@
1
- import path from "path";
2
- import { describe, expect, it } from "vitest";
3
- import {
4
- inferApiRoutesFromTestFile,
5
- inferOwnerDirectoryFromTestFile,
6
- inferPageRouteFromTestFile,
7
- pathMatchesOwner,
8
- } from "./routing.mjs";
9
-
10
- describe("coverage routing helpers", () => {
11
- it("infers page ownership routes from colocated __testkit__ files", () => {
12
- const serviceRoot = "/repo";
13
- const nextAppRoot = path.join(serviceRoot, "src", "app");
14
-
15
- expect(
16
- inferPageRouteFromTestFile("src/app/campaigns/[campaignId]/__testkit__/details.pw.testkit.ts", nextAppRoot, serviceRoot)
17
- ).toBe("/campaigns/[campaignId]");
18
- });
19
-
20
- it("infers API routes from colocated __testkit__ files", () => {
21
- const serviceRoot = "/repo";
22
- const nextAppRoot = path.join(serviceRoot, "src", "app");
23
-
24
- expect(
25
- inferApiRoutesFromTestFile("src/app/api/campaigns/__testkit__/create.int.testkit.ts", nextAppRoot, serviceRoot)
26
- ).toEqual(["/campaigns"]);
27
- });
28
-
29
- it("matches nested ownership directories for DAL evidence", () => {
30
- expect(inferOwnerDirectoryFromTestFile("src/backend/data/campaigns/__testkit__/save.dal.testkit.ts")).toBe(
31
- "src/backend/data/campaigns"
32
- );
33
- expect(pathMatchesOwner("src/backend/data/campaigns/index.ts", "src/backend/data/campaigns")).toBe(true);
34
- expect(pathMatchesOwner("src/backend/data/other/index.ts", "src/backend/data/campaigns")).toBe(false);
35
- });
36
- });
@@ -1,72 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- DYNAMIC_SEGMENT_TOKEN,
4
- dedupeTargets,
5
- findMatchingApiRouteEntry,
6
- findMatchingRouteValue,
7
- modulePathKey,
8
- pageLabelFromRoute,
9
- routePatternMatchesCandidate,
10
- toApiRequestPath,
11
- toSelectionType,
12
- } from "./shared.mjs";
13
-
14
- describe("coverage shared helpers", () => {
15
- it("builds readable page labels from routes", () => {
16
- expect(pageLabelFromRoute("/")).toBe("Home");
17
- expect(pageLabelFromRoute("/campaigns/[campaignId]")).toBe("Campaigns CampaignId");
18
- });
19
-
20
- it("collapses index modules into stable module path keys", () => {
21
- expect(modulePathKey("src/backend/server/campaigns/index.ts")).toBe("src/backend/server/campaigns");
22
- expect(modulePathKey("src/backend/server/campaigns.ts")).toBe("src/backend/server/campaigns");
23
- });
24
-
25
- it("dedupes overlay targets by kind and value", () => {
26
- expect(
27
- dedupeTargets([
28
- { kind: "testId", value: "save-button", confidence: "high" },
29
- { kind: "testId", value: "save-button", confidence: "medium" },
30
- { kind: "text", value: "Save", confidence: "medium" },
31
- ])
32
- ).toEqual([
33
- { kind: "testId", value: "save-button", confidence: "high" },
34
- { kind: "text", value: "Save", confidence: "medium" },
35
- ]);
36
- });
37
-
38
- it("normalizes request paths and selection types", () => {
39
- expect(toApiRequestPath("/campaigns")).toBe("/api/campaigns");
40
- expect(toSelectionType("integration", "http")).toBe("int");
41
- expect(toSelectionType("ui", "playwright")).toBe("pw");
42
- });
43
-
44
- it("matches dynamic API route patterns against concrete and template-like request paths", () => {
45
- expect(routePatternMatchesCandidate("/api/projects/[projectId]/overview", "/api/projects/123/overview")).toBe(true);
46
- expect(
47
- routePatternMatchesCandidate(
48
- "/api/projects/[projectId]/events",
49
- `/api/projects/${DYNAMIC_SEGMENT_TOKEN}/events`
50
- )
51
- ).toBe(true);
52
- expect(routePatternMatchesCandidate("/api/projects/[projectId]/events", "/api/projects/123/stats")).toBe(false);
53
- });
54
-
55
- it("prefers the most specific matching API route entry", () => {
56
- const routeEntry = findMatchingApiRouteEntry("GET", "/api/projects/123/events", [
57
- { method: "GET", requestPath: "/api/projects/[projectId]" },
58
- { method: "GET", requestPath: "/api/projects/[projectId]/events" },
59
- ]);
60
-
61
- expect(routeEntry).toEqual({
62
- method: "GET",
63
- requestPath: "/api/projects/[projectId]/events",
64
- });
65
- });
66
-
67
- it("matches dynamic page routes against discovered route patterns", () => {
68
- expect(
69
- findMatchingRouteValue(`/projects/${DYNAMIC_SEGMENT_TOKEN}`, ["/projects/[projectId]", "/projects"])
70
- ).toBe("/projects/[projectId]");
71
- });
72
- });
@@ -1,99 +0,0 @@
1
- import crypto from "crypto";
2
- import fs from "fs";
3
- import os from "os";
4
- import path from "path";
5
- import { afterEach, describe, expect, it } from "vitest";
6
- import {
7
- appendFileToHash,
8
- appendInputToHash,
9
- computeTemplateFingerprint,
10
- } from "./fingerprint.mjs";
11
-
12
- const tempDirs = [];
13
-
14
- afterEach(() => {
15
- for (const dir of tempDirs.splice(0)) {
16
- fs.rmSync(dir, { recursive: true, force: true });
17
- }
18
- });
19
-
20
- function mkTempDir() {
21
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-db-fingerprint-"));
22
- tempDirs.push(dir);
23
- return dir;
24
- }
25
-
26
- describe("database-fingerprint", () => {
27
- it("hashes files, directories, and missing paths deterministically", () => {
28
- const productDir = mkTempDir();
29
- fs.mkdirSync(path.join(productDir, "schema"), { recursive: true });
30
- fs.writeFileSync(path.join(productDir, "schema", "001.sql"), "select 1;\n");
31
- fs.mkdirSync(path.join(productDir, ".testkit", "ignored"), { recursive: true });
32
- fs.writeFileSync(path.join(productDir, ".testkit", "ignored", "skip.sql"), "skip");
33
-
34
- const hashA = crypto.createHash("sha256");
35
- appendInputToHash(hashA, productDir, "schema");
36
- appendInputToHash(hashA, productDir, "missing");
37
- const digestA = hashA.digest("hex");
38
-
39
- const hashB = crypto.createHash("sha256");
40
- appendInputToHash(hashB, productDir, "schema");
41
- appendInputToHash(hashB, productDir, "missing");
42
- const digestB = hashB.digest("hex");
43
-
44
- expect(digestA).toBe(digestB);
45
- });
46
-
47
- it("hashes file contents and missing files", () => {
48
- const productDir = mkTempDir();
49
- const filePath = path.join(productDir, "a.txt");
50
- fs.writeFileSync(filePath, "alpha");
51
-
52
- const present = crypto.createHash("sha256");
53
- appendFileToHash(present, productDir, filePath);
54
- const presentDigest = present.digest("hex");
55
-
56
- const missing = crypto.createHash("sha256");
57
- appendFileToHash(missing, productDir, path.join(productDir, "missing.txt"));
58
- const missingDigest = missing.digest("hex");
59
-
60
- expect(presentDigest).not.toBe(missingDigest);
61
- });
62
-
63
- it("computes template fingerprints from config inputs and env files", async () => {
64
- const productDir = mkTempDir();
65
- fs.mkdirSync(path.join(productDir, "schema"), { recursive: true });
66
- fs.writeFileSync(path.join(productDir, "schema", "001.sql"), "select 1;\n");
67
- fs.writeFileSync(path.join(productDir, ".env.testkit"), "TOKEN=alpha\n");
68
-
69
- const config = {
70
- productDir,
71
- testkit: {
72
- database: {
73
- provider: "local",
74
- selectedBackend: "local",
75
- template: {
76
- inputs: ["schema"],
77
- migrate: [
78
- {
79
- kind: "command",
80
- cmd: "npm run migrate",
81
- cwd: ".",
82
- inputs: [],
83
- },
84
- ],
85
- seed: [],
86
- verify: [],
87
- },
88
- },
89
- envFiles: [".env.testkit"],
90
- },
91
- };
92
-
93
- const first = await computeTemplateFingerprint(config);
94
- fs.writeFileSync(path.join(productDir, ".env.testkit"), "TOKEN=beta\n");
95
- const second = await computeTemplateFingerprint(config);
96
-
97
- expect(first).not.toBe(second);
98
- });
99
- });
@@ -1,95 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- dropDatabaseWithForceOrDrain,
4
- waitForDatabaseConnectionsToDrain,
5
- } from "./index.mjs";
6
-
7
- describe("database lifecycle helpers", () => {
8
- it("uses DROP DATABASE ... WITH (FORCE) when supported", async () => {
9
- const calls = [];
10
-
11
- await dropDatabaseWithForceOrDrain(
12
- { containerName: "pg", password: "pw", user: "user" },
13
- "demo",
14
- {
15
- async runAdminQuery(_infra, args) {
16
- calls.push(args);
17
- return "";
18
- },
19
- }
20
- );
21
-
22
- expect(calls).toEqual([["-c", 'DROP DATABASE IF EXISTS "demo" WITH (FORCE)']]);
23
- });
24
-
25
- it("falls back to draining connections when forced drop is unsupported", async () => {
26
- const calls = [];
27
- const counts = ["2", "0"];
28
-
29
- await dropDatabaseWithForceOrDrain(
30
- { containerName: "pg", password: "pw", user: "user" },
31
- "demo",
32
- {
33
- async runAdminQuery(_infra, args) {
34
- calls.push(args);
35
- if (args[1] === 'DROP DATABASE IF EXISTS "demo" WITH (FORCE)') {
36
- const error = new Error('syntax error at or near "WITH"');
37
- error.stderr = 'ERROR: syntax error at or near "WITH"';
38
- throw error;
39
- }
40
- if (args[0] === "-tAc") {
41
- return counts.shift() || "0";
42
- }
43
- return "";
44
- },
45
- async databaseExists() {
46
- return true;
47
- },
48
- async sleep() {},
49
- }
50
- );
51
-
52
- expect(calls).toEqual([
53
- ["-c", 'DROP DATABASE IF EXISTS "demo" WITH (FORCE)'],
54
- ["-c", 'ALTER DATABASE "demo" WITH ALLOW_CONNECTIONS false'],
55
- [
56
- "-c",
57
- "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'demo' AND pid <> pg_backend_pid();",
58
- ],
59
- [
60
- "-tAc",
61
- "SELECT COUNT(*) FROM pg_stat_activity WHERE datname = 'demo' AND pid <> pg_backend_pid();",
62
- ],
63
- [
64
- "-c",
65
- "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'demo' AND pid <> pg_backend_pid();",
66
- ],
67
- [
68
- "-tAc",
69
- "SELECT COUNT(*) FROM pg_stat_activity WHERE datname = 'demo' AND pid <> pg_backend_pid();",
70
- ],
71
- ["-c", 'DROP DATABASE IF EXISTS "demo"'],
72
- ]);
73
- });
74
-
75
- it("times out clearly while waiting for lingering connections to drain", async () => {
76
- let now = 0;
77
- await expect(() =>
78
- waitForDatabaseConnectionsToDrain(
79
- { containerName: "pg", password: "pw", user: "user" },
80
- "demo",
81
- {
82
- async runAdminQuery(_infra, args) {
83
- if (args[0] === "-tAc") return "1";
84
- return "";
85
- },
86
- async sleep() {
87
- now += 10;
88
- },
89
- now: () => now,
90
- timeoutMs: 20,
91
- }
92
- )
93
- ).rejects.toThrow(/Timed out waiting for database "demo" connections to close/);
94
- });
95
- });
@@ -1,39 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- buildContainerName,
4
- buildDatabaseUrl,
5
- buildRuntimeDatabaseName,
6
- buildTemplateDatabaseName,
7
- escapeIdentifier,
8
- escapeSqlLiteral,
9
- slugSegment,
10
- } from "./naming.mjs";
11
-
12
- describe("database-naming", () => {
13
- it("builds deterministic names and URLs", () => {
14
- expect(buildContainerName("/tmp/My Product")).toMatch(/^testkit_pg_my_product_/);
15
- expect(buildTemplateDatabaseName("api", "1234567890abcdef1234")).toBe(
16
- "tk_tpl_api_1234567890abcdef"
17
- );
18
- expect(buildRuntimeDatabaseName("api", "/tmp/state", "abcdef1234567890")).toMatch(
19
- /^tk_api_[a-f0-9]{10}_abcdef123456$/
20
- );
21
- expect(
22
- buildDatabaseUrl(
23
- {
24
- user: "a user",
25
- password: "p@ss word",
26
- host: "127.0.0.1",
27
- port: 5432,
28
- },
29
- "dbname"
30
- )
31
- ).toContain("postgresql://a%20user:p%40ss%20word@127.0.0.1:5432/dbname");
32
- });
33
-
34
- it("escapes identifiers and literals", () => {
35
- expect(slugSegment("My API")).toBe("my_api");
36
- expect(escapeIdentifier('bad"name')).toBe('bad""name');
37
- expect(escapeSqlLiteral("it's")).toBe("it''s");
38
- });
39
- });
@@ -1,66 +0,0 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import {
6
- getLocalInfraDir,
7
- getLocalLocksDir,
8
- getLocalServiceCacheDir,
9
- hasRemainingLocalArtifacts,
10
- readStateValue,
11
- visitDirs,
12
- } from "./state.mjs";
13
-
14
- const tempDirs = [];
15
-
16
- afterEach(() => {
17
- for (const dir of tempDirs.splice(0)) {
18
- fs.rmSync(dir, { recursive: true, force: true });
19
- }
20
- });
21
-
22
- function mkTempDir() {
23
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-db-state-"));
24
- tempDirs.push(dir);
25
- return dir;
26
- }
27
-
28
- describe("database-state", () => {
29
- it("builds local state paths", () => {
30
- expect(getLocalInfraDir("/tmp/product")).toBe("/tmp/product/.testkit/_infra/local-postgres");
31
- expect(getLocalLocksDir("/tmp/product")).toBe("/tmp/product/.testkit/_infra/local-postgres/locks");
32
- expect(getLocalServiceCacheDir("/tmp/product", "api")).toBe("/tmp/product/.testkit/_dbcache/api");
33
- });
34
-
35
- it("reads state values and visits directories", () => {
36
- const productDir = mkTempDir();
37
- const aDir = path.join(productDir, "a");
38
- const bDir = path.join(aDir, "b");
39
- fs.mkdirSync(bDir, { recursive: true });
40
- fs.writeFileSync(path.join(aDir, "value"), " hello \n");
41
-
42
- const visited = [];
43
- visitDirs(productDir, (dir) => visited.push(path.relative(productDir, dir)));
44
-
45
- expect(visited).toEqual(["a", path.join("a", "b")]);
46
- expect(readStateValue(path.join(aDir, "value"))).toBe("hello");
47
- expect(readStateValue(path.join(aDir, "missing"))).toBeNull();
48
- });
49
-
50
- it("detects remaining local artifacts", () => {
51
- const productDir = mkTempDir();
52
- const runtimeDir = path.join(productDir, ".testkit", "api");
53
- fs.mkdirSync(runtimeDir, { recursive: true });
54
- fs.writeFileSync(path.join(runtimeDir, "database_backend"), "local");
55
-
56
- expect(hasRemainingLocalArtifacts(productDir, readStateValue)).toBe(true);
57
-
58
- fs.rmSync(path.join(productDir, ".testkit"), { recursive: true, force: true });
59
- fs.mkdirSync(path.join(productDir, ".testkit", "_dbcache", "api"), { recursive: true });
60
-
61
- expect(hasRemainingLocalArtifacts(productDir, readStateValue)).toBe(true);
62
-
63
- fs.rmSync(path.join(productDir, ".testkit"), { recursive: true, force: true });
64
- expect(hasRemainingLocalArtifacts(productDir, readStateValue)).toBe(false);
65
- });
66
- });
@@ -1,43 +0,0 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { sanitizeSnapshotFile } from "./template-steps.mjs";
6
-
7
- const tempDirs = [];
8
-
9
- afterEach(() => {
10
- while (tempDirs.length > 0) {
11
- fs.rmSync(tempDirs.pop(), { recursive: true, force: true });
12
- }
13
- });
14
-
15
- function makeTempDir(prefix) {
16
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
17
- tempDirs.push(dir);
18
- return dir;
19
- }
20
-
21
- describe("template snapshot sanitization", () => {
22
- it("removes volatile pg_dump control lines", () => {
23
- const dir = makeTempDir("testkit-template-snapshot-");
24
- const filePath = path.join(dir, "schema.sql");
25
- fs.writeFileSync(
26
- filePath,
27
- [
28
- "SET statement_timeout = 0;",
29
- "SET transaction_timeout = 0;",
30
- "\\restrict abc123",
31
- "CREATE TABLE public.widgets (id integer);",
32
- "\\unrestrict abc123",
33
- "",
34
- ].join("\n")
35
- );
36
-
37
- sanitizeSnapshotFile(filePath);
38
-
39
- expect(fs.readFileSync(filePath, "utf8")).toBe(
40
- ["SET statement_timeout = 0;", "CREATE TABLE public.widgets (id integer);", ""].join("\n")
41
- );
42
- });
43
- });
@@ -1,51 +0,0 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { readTestFileMetadata } from "./file-metadata.mjs";
6
-
7
- const tempDirs = [];
8
-
9
- afterEach(() => {
10
- for (const dir of tempDirs.splice(0)) {
11
- fs.rmSync(dir, { recursive: true, force: true });
12
- }
13
- });
14
-
15
- describe("test file metadata", () => {
16
- it("reads exported testkit metadata from object literals and helper calls", () => {
17
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-file-meta-"));
18
- tempDirs.push(productDir);
19
- fs.mkdirSync(path.join(productDir, "__testkit__"), { recursive: true });
20
- fs.writeFileSync(
21
- path.join(productDir, "__testkit__", "billing.int.testkit.ts"),
22
- [
23
- 'import { defineFile } from "@elench/testkit/config";',
24
- 'export const testkit = defineFile({',
25
- ' skip: "Billing is stubbed locally",',
26
- ' locks: ["background-workers", "background-workers"],',
27
- "});",
28
- ].join("\n")
29
- );
30
-
31
- expect(readTestFileMetadata(productDir, "__testkit__/billing.int.testkit.ts")).toEqual({
32
- skipReason: "Billing is stubbed locally",
33
- locks: ["background-workers"],
34
- });
35
- });
36
-
37
- it("returns empty metadata when no export is present", () => {
38
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-file-meta-"));
39
- tempDirs.push(productDir);
40
- fs.mkdirSync(path.join(productDir, "__testkit__"), { recursive: true });
41
- fs.writeFileSync(
42
- path.join(productDir, "__testkit__", "health.int.testkit.ts"),
43
- "export default {};\n"
44
- );
45
-
46
- expect(readTestFileMetadata(productDir, "__testkit__/health.int.testkit.ts")).toEqual({
47
- skipReason: null,
48
- locks: [],
49
- });
50
- });
51
- });
@@ -1,182 +0,0 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { discoverTests } from "./index.mjs";
6
- import { buildHistoryTestId, saveHistory } from "../history/index.mjs";
7
-
8
- const cleanups = [];
9
-
10
- afterEach(() => {
11
- while (cleanups.length > 0) {
12
- cleanups.pop()();
13
- }
14
- });
15
-
16
- describe("public discovery", () => {
17
- it("returns resolved suites/files with human labels and persisted history", async () => {
18
- const productDir = createProduct();
19
- writeFile(
20
- productDir,
21
- "testkit.config.ts",
22
- `
23
- import { defineConfig } from "@elench/testkit/config";
24
-
25
- export default defineConfig({
26
- services: {
27
- api: {
28
- local: {
29
- cwd: ".",
30
- start: "node server.js",
31
- baseUrl: "http://127.0.0.1:3000",
32
- readyUrl: "http://127.0.0.1:3000"
33
- }
34
- },
35
- frontend: {
36
- local: {
37
- cwd: "frontend",
38
- start: "node server.js",
39
- baseUrl: "http://127.0.0.1:3001",
40
- readyUrl: "http://127.0.0.1:3001"
41
- },
42
- dependsOn: ["api"]
43
- }
44
- }
45
- });
46
- `
47
- );
48
- writeFile(
49
- productDir,
50
- "src/api/routes/__testkit__/agent-configs-auth-gate.int.testkit.ts",
51
- [
52
- 'import { defineFile } from "@elench/testkit/config";',
53
- 'export const testkit = defineFile({ locks: ["route-lock"] });',
54
- "export {};",
55
- ].join("\n")
56
- );
57
- writeFile(
58
- productDir,
59
- "frontend/src/app/login/__testkit__/auth.pw.testkit.ts",
60
- [
61
- 'import { defineFile } from "@elench/testkit/config";',
62
- 'export const testkit = defineFile({ skip: "Auth is stubbed locally" });',
63
- "export {};",
64
- ].join("\n")
65
- );
66
-
67
- saveHistory(productDir, {
68
- version: 1,
69
- tests: {
70
- [buildHistoryTestId("api", "int", "src/api/routes/__testkit__/agent-configs-auth-gate.int.testkit.ts")]: {
71
- id: buildHistoryTestId("api", "int", "src/api/routes/__testkit__/agent-configs-auth-gate.int.testkit.ts"),
72
- path: "src/api/routes/__testkit__/agent-configs-auth-gate.int.testkit.ts",
73
- service: "api",
74
- suiteName: "routes",
75
- selectionType: "int",
76
- framework: "k6",
77
- firstSeenAt: "2026-04-01T00:00:00.000Z",
78
- lastSeenAt: "2026-04-02T00:00:00.000Z",
79
- lastRunAt: "2026-04-02T00:00:00.000Z",
80
- runCount: 4,
81
- passCount: 3,
82
- failCount: 1,
83
- skipCount: 0,
84
- notRunCount: 0,
85
- avgDurationMs: 2500,
86
- durationCount: 4,
87
- lastStatus: "passed",
88
- },
89
- },
90
- });
91
-
92
- const result = await discoverTests({ dir: productDir });
93
- expect(result.summary).toMatchObject({
94
- services: 2,
95
- suites: 2,
96
- files: 2,
97
- activeFiles: 1,
98
- skippedFiles: 1,
99
- byType: {
100
- int: 1,
101
- pw: 1,
102
- },
103
- });
104
- expect(result.history.available).toBe(true);
105
- expect(result.services.map((entry) => entry.name)).toEqual(["api", "frontend"]);
106
-
107
- const apiFile = result.files.find((entry) => entry.service === "api");
108
- expect(apiFile).toMatchObject({
109
- displayName: "Agent Configs Auth Gate",
110
- suiteName: "routes",
111
- groupLabel: "Routes",
112
- selectionType: "int",
113
- skipped: false,
114
- locks: ["route-lock"],
115
- });
116
- expect(apiFile.history).toMatchObject({
117
- firstSeenAt: "2026-04-01T00:00:00.000Z",
118
- runCount: 4,
119
- passCount: 3,
120
- failCount: 1,
121
- avgDurationMs: 2500,
122
- });
123
-
124
- const frontendFile = result.files.find((entry) => entry.service === "frontend");
125
- expect(frontendFile).toMatchObject({
126
- displayName: "Auth",
127
- selectionType: "pw",
128
- skipped: true,
129
- skipReason: "Auth is stubbed locally",
130
- dependsOn: ["api"],
131
- });
132
- });
133
-
134
- it("reports legacy discovery diagnostics without failing in report mode", async () => {
135
- const productDir = createProduct();
136
- writeFile(
137
- productDir,
138
- "testkit.config.ts",
139
- `
140
- import { defineConfig } from "@elench/testkit/config";
141
-
142
- export default defineConfig({
143
- services: {
144
- api: {
145
- local: {
146
- cwd: ".",
147
- start: "node server.js",
148
- baseUrl: "http://127.0.0.1:3000",
149
- readyUrl: "http://127.0.0.1:3000"
150
- }
151
- }
152
- }
153
- });
154
- `
155
- );
156
- writeFile(productDir, "src/api/routes/__testkit__/health/health.int.testkit.ts");
157
- writeFile(productDir, "tests/api/integration/legacy.int.testkit.ts");
158
-
159
- const result = await discoverTests({ dir: productDir, diagnostics: "report" });
160
- expect(result.files).toHaveLength(1);
161
- expect(result.diagnostics).toEqual(
162
- expect.arrayContaining([
163
- expect.objectContaining({
164
- code: "legacy_path",
165
- path: "tests/api/integration/legacy.int.testkit.ts",
166
- }),
167
- ])
168
- );
169
- });
170
- });
171
-
172
- function createProduct() {
173
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-public-"));
174
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
175
- return productDir;
176
- }
177
-
178
- function writeFile(productDir, relativePath, contents = "export {};\n") {
179
- const absolutePath = path.join(productDir, relativePath);
180
- fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
181
- fs.writeFileSync(absolutePath, contents);
182
- }