@elench/testkit 0.1.83 → 0.1.85

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 (87) hide show
  1. package/lib/cli/agents/investigation-interpreter.mjs +320 -0
  2. package/lib/cli/agents/investigation-log.mjs +37 -0
  3. package/lib/cli/agents/providers/codex.mjs +1 -1
  4. package/lib/cli/presentation/tree-reporter.mjs +33 -1
  5. package/lib/cli/tui/run-session-app.mjs +73 -11
  6. package/lib/cli/tui/run-session-state.mjs +29 -5
  7. package/node_modules/@elench/next-analysis/package.json +1 -1
  8. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  9. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  10. package/node_modules/@elench/ts-analysis/package.json +1 -1
  11. package/package.json +7 -6
  12. package/lib/app/configs.test.mjs +0 -34
  13. package/lib/app/typecheck.test.mjs +0 -24
  14. package/lib/bundler/index.test.mjs +0 -164
  15. package/lib/cli/agents/investigation-context.test.mjs +0 -144
  16. package/lib/cli/agents/providers/claude.test.mjs +0 -95
  17. package/lib/cli/agents/providers/codex.test.mjs +0 -93
  18. package/lib/cli/args.test.mjs +0 -110
  19. package/lib/cli/command-helpers.test.mjs +0 -122
  20. package/lib/cli/commands/investigate.test.mjs +0 -83
  21. package/lib/cli/presentation/code-frames.test.mjs +0 -71
  22. package/lib/cli/presentation/events-reporter.test.mjs +0 -73
  23. package/lib/cli/presentation/run-reporter.test.mjs +0 -192
  24. package/lib/cli/presentation/summary-box.test.mjs +0 -60
  25. package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
  26. package/lib/cli/presentation/tree-reporter.test.mjs +0 -166
  27. package/lib/cli/tui/run-session-app.test.mjs +0 -50
  28. package/lib/cli/tui/run-tree-state.test.mjs +0 -324
  29. package/lib/config/database.test.mjs +0 -29
  30. package/lib/config/discovery.test.mjs +0 -276
  31. package/lib/config/env.test.mjs +0 -40
  32. package/lib/config/index.test.mjs +0 -44
  33. package/lib/config/paths.test.mjs +0 -27
  34. package/lib/config/runtime.test.mjs +0 -82
  35. package/lib/config/skip-config.test.mjs +0 -63
  36. package/lib/config-api/index.test.mjs +0 -398
  37. package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
  38. package/lib/coverage/backend-discovery.test.mjs +0 -61
  39. package/lib/coverage/evidence.test.mjs +0 -87
  40. package/lib/coverage/index.test.mjs +0 -715
  41. package/lib/coverage/routing.test.mjs +0 -36
  42. package/lib/coverage/shared.test.mjs +0 -72
  43. package/lib/database/fingerprint.test.mjs +0 -99
  44. package/lib/database/index.test.mjs +0 -95
  45. package/lib/database/naming.test.mjs +0 -39
  46. package/lib/database/state.test.mjs +0 -66
  47. package/lib/database/template-steps.test.mjs +0 -43
  48. package/lib/discovery/file-metadata.test.mjs +0 -51
  49. package/lib/discovery/index.test.mjs +0 -182
  50. package/lib/discovery/path-policy.test.mjs +0 -65
  51. package/lib/drizzle/index.test.mjs +0 -33
  52. package/lib/env/index.test.mjs +0 -82
  53. package/lib/history/index.test.mjs +0 -115
  54. package/lib/package.test.mjs +0 -59
  55. package/lib/playwright/index.test.mjs +0 -43
  56. package/lib/regressions/github.test.mjs +0 -324
  57. package/lib/regressions/index.test.mjs +0 -187
  58. package/lib/reporters/playwright.test.mjs +0 -167
  59. package/lib/runner/default-runtime-errors.test.mjs +0 -49
  60. package/lib/runner/execution-config.test.mjs +0 -67
  61. package/lib/runner/failure-details.test.mjs +0 -114
  62. package/lib/runner/formatting.test.mjs +0 -205
  63. package/lib/runner/metadata.test.mjs +0 -52
  64. package/lib/runner/planning.test.mjs +0 -371
  65. package/lib/runner/playwright-config.test.mjs +0 -78
  66. package/lib/runner/processes.test.mjs +0 -21
  67. package/lib/runner/regressions.test.mjs +0 -168
  68. package/lib/runner/reporting.test.mjs +0 -310
  69. package/lib/runner/results.test.mjs +0 -376
  70. package/lib/runner/runtime-manager.test.mjs +0 -252
  71. package/lib/runner/runtime-preparation.test.mjs +0 -141
  72. package/lib/runner/selection.test.mjs +0 -24
  73. package/lib/runner/setup-operations.test.mjs +0 -94
  74. package/lib/runner/state.test.mjs +0 -62
  75. package/lib/runner/suite-selection.test.mjs +0 -49
  76. package/lib/runner/template.test.mjs +0 -272
  77. package/lib/runtime-src/k6/http-checks.test.mjs +0 -120
  78. package/lib/runtime-src/k6/http.test.mjs +0 -205
  79. package/lib/runtime-src/shared/http-parsing.test.mjs +0 -69
  80. package/lib/shared/build-config.test.mjs +0 -132
  81. package/lib/shared/configured-steps.test.mjs +0 -102
  82. package/lib/shared/execution-schema.test.mjs +0 -26
  83. package/lib/shared/file-timeout.test.mjs +0 -64
  84. package/lib/shared/test-context.test.mjs +0 -43
  85. package/lib/timing/index.test.mjs +0 -64
  86. package/lib/toolchains/index.test.mjs +0 -168
  87. package/lib/vitest/index.test.mjs +0 -20
@@ -1,60 +0,0 @@
1
- import { Writable } from "stream";
2
- import { describe, expect, it } from "vitest";
3
- import { renderSummaryBox } from "./summary-box.mjs";
4
- import { measureWidth } from "./terminal-layout.mjs";
5
-
6
- function createStream(columns) {
7
- const stream = new Writable({
8
- write(_chunk, _encoding, callback) {
9
- callback();
10
- },
11
- });
12
- stream.columns = columns;
13
- return stream;
14
- }
15
-
16
- describe("summary box", () => {
17
- it("renders key/value rows inside a bounded box", () => {
18
- const lines = renderSummaryBox(
19
- [
20
- ["Result", "FAILED"],
21
- ["Passed", "203"],
22
- ["Duration", "8m 56s"],
23
- ],
24
- { stdout: createStream(80) }
25
- );
26
-
27
- expect(lines[0]).toMatch(/^┌/);
28
- expect(lines.at(-1)).toMatch(/^└/);
29
- expect(lines.join("\n")).toContain("Result");
30
- expect(lines.join("\n")).toContain("FAILED");
31
- });
32
-
33
- it("wraps long values instead of widening to content length", () => {
34
- const lines = renderSummaryBox(
35
- [["Catalog sync", "Used stale GitHub cache while catalog validation was unavailable"]],
36
- { stdout: createStream(40) }
37
- );
38
-
39
- expect(lines.length).toBeGreaterThan(4);
40
- expect(lines.join("\n")).toContain("Catalog sync");
41
- expect(lines.join("\n")).toContain("stale");
42
- expect(lines.join("\n")).toContain("cache");
43
- });
44
-
45
- it("keeps long labels inside the box width", () => {
46
- const lines = renderSummaryBox(
47
- [
48
- ["Result", "FAILED"],
49
- ["New regressions", "1"],
50
- ],
51
- { stdout: createStream(80) }
52
- );
53
-
54
- const boxWidth = measureWidth(lines[0]);
55
- expect(lines.join("\n")).toContain("New regressions");
56
- for (const line of lines) {
57
- expect(measureWidth(line)).toBe(boxWidth);
58
- }
59
- });
60
- });
@@ -1,23 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { getTerminalWidth, renderIndentedBlock, wrapText } from "./terminal-layout.mjs";
3
-
4
- describe("terminal layout", () => {
5
- it("falls back to a default width when stream columns are unavailable", () => {
6
- expect(getTerminalWidth({}, 91)).toBe(91);
7
- });
8
-
9
- it("wraps indented blocks to the provided width", () => {
10
- const lines = renderIndentedBlock("response: abcdefghijklmnopqrstuvwxyz", {
11
- width: 20,
12
- indent: " ",
13
- });
14
-
15
- expect(lines[0].startsWith(" ")).toBe(true);
16
- expect(lines.length).toBeGreaterThan(1);
17
- });
18
-
19
- it("hard-wraps long unbroken values", () => {
20
- const lines = wrapText("abcdefghijklmnopqrstuvwxyz", 8);
21
- expect(lines.length).toBeGreaterThan(1);
22
- });
23
- });
@@ -1,166 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const renderMock = vi.fn();
4
- const startHostedInvestigationMock = vi.fn();
5
- let capturedElement = null;
6
- let finalizeResolve = null;
7
- let unmountSpy;
8
-
9
- vi.mock("ink", () => ({
10
- render: renderMock,
11
- }));
12
-
13
- vi.mock("../agents/investigate.mjs", () => ({
14
- startHostedInvestigation: startHostedInvestigationMock,
15
- }));
16
-
17
- function flush() {
18
- return new Promise((resolve) => setTimeout(resolve, 0));
19
- }
20
-
21
- beforeEach(() => {
22
- capturedElement = null;
23
- unmountSpy = vi.fn(() => finalizeResolve?.());
24
- renderMock.mockImplementation((element) => {
25
- capturedElement = element;
26
- return {
27
- waitUntilExit() {
28
- return new Promise((resolve) => {
29
- finalizeResolve = resolve;
30
- });
31
- },
32
- unmount: unmountSpy,
33
- };
34
- });
35
- });
36
-
37
- afterEach(() => {
38
- renderMock.mockReset();
39
- startHostedInvestigationMock.mockReset();
40
- finalizeResolve = null;
41
- });
42
-
43
- function getSessionState() {
44
- return capturedElement.props.sessionState;
45
- }
46
-
47
- function seedFailure(reporter) {
48
- reporter.setServicePlans([
49
- {
50
- config: { name: "api" },
51
- skipped: false,
52
- suites: [
53
- {
54
- name: "users",
55
- type: "integration",
56
- displayType: "int",
57
- framework: "k6",
58
- files: ["tests/api/users.int.testkit.ts"],
59
- },
60
- ],
61
- },
62
- ]);
63
- reporter.taskFinished(
64
- {
65
- serviceName: "api",
66
- type: "integration",
67
- displayType: "int",
68
- framework: "k6",
69
- suiteName: "users",
70
- file: "tests/api/users.int.testkit.ts",
71
- },
72
- { failed: true, error: "status 500", durationMs: 1000, failureDetails: [] }
73
- );
74
- }
75
-
76
- describe("tree reporter", () => {
77
- it("keeps the session mounted after run summary until close is requested", async () => {
78
- const { createTreeReporter } = await import("./tree-reporter.mjs");
79
- const tree = createTreeReporter({ productDir: "/tmp/project" });
80
-
81
- tree.reporter.runSummary([], 1000, null);
82
-
83
- let settled = false;
84
- tree.finalize.then(() => {
85
- settled = true;
86
- });
87
- await flush();
88
-
89
- expect(settled).toBe(false);
90
- tree.close();
91
- await tree.finalize;
92
- expect(unmountSpy).toHaveBeenCalled();
93
- });
94
-
95
- it("streams hosted investigation events into the session state", async () => {
96
- const { createTreeReporter } = await import("./tree-reporter.mjs");
97
- startHostedInvestigationMock.mockImplementation(({ onEvent }) => {
98
- onEvent({ type: "start" });
99
- onEvent({ type: "status", message: "Inspecting repository" });
100
- onEvent({ type: "delta", text: "Likely root cause." });
101
- return {
102
- cancel: vi.fn(),
103
- completion: Promise.resolve({
104
- provider: "codex",
105
- exitCode: 0,
106
- finalText: "Likely root cause.",
107
- cancelled: false,
108
- }),
109
- };
110
- });
111
-
112
- const tree = createTreeReporter({ productDir: "/tmp/project" });
113
- seedFailure(tree.reporter);
114
-
115
- await capturedElement.props.onInvestigate({
116
- provider: "codex",
117
- userMessage: "Investigate the selected failure",
118
- });
119
-
120
- const snapshot = getSessionState().getSnapshot();
121
- expect(startHostedInvestigationMock).toHaveBeenCalledWith(
122
- expect.objectContaining({
123
- productDir: "/tmp/project",
124
- serviceName: "api",
125
- filePath: "tests/api/users.int.testkit.ts",
126
- })
127
- );
128
- expect(snapshot.agentSession.status).toBe("complete");
129
- expect(snapshot.agentSession.entries.some((entry) => entry.kind === "assistant")).toBe(true);
130
- });
131
-
132
- it("cancels an active investigation and unmounts cleanly on close", async () => {
133
- const { createTreeReporter } = await import("./tree-reporter.mjs");
134
- const cancel = vi.fn();
135
- let resolveCompletion;
136
- startHostedInvestigationMock.mockReturnValue({
137
- cancel,
138
- completion: new Promise((resolve) => {
139
- resolveCompletion = resolve;
140
- }),
141
- });
142
-
143
- const tree = createTreeReporter({ productDir: "/tmp/project" });
144
- seedFailure(tree.reporter);
145
-
146
- const pending = capturedElement.props.onInvestigate({
147
- provider: "claude",
148
- userMessage: "Investigate the selected failure",
149
- });
150
- await flush();
151
- capturedElement.props.onCancelInvestigation();
152
- resolveCompletion({
153
- provider: "claude",
154
- exitCode: 130,
155
- finalText: "",
156
- cancelled: true,
157
- });
158
- await pending;
159
-
160
- expect(cancel).toHaveBeenCalled();
161
- expect(getSessionState().getSnapshot().notice).toContain("Cancelled");
162
- tree.close();
163
- await tree.finalize;
164
- expect(unmountSpy).toHaveBeenCalled();
165
- });
166
- });
@@ -1,50 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { buildFooterText, buildHeaderText, formatAgentEntry } from "./run-session-app.mjs";
3
-
4
- describe("run session app helpers", () => {
5
- it("renders header text for running and complete modes", () => {
6
- expect(
7
- buildHeaderText({
8
- totalCount: 3,
9
- completedCount: 1,
10
- phase: "setup",
11
- finished: false,
12
- mode: "running",
13
- })
14
- ).toContain("[1/3]");
15
-
16
- expect(
17
- buildHeaderText({
18
- totalCount: 3,
19
- completedCount: 3,
20
- phase: null,
21
- finished: true,
22
- mode: "complete",
23
- })
24
- ).toContain("interactive summary");
25
- });
26
-
27
- it("renders footer text for complete and investigating modes", () => {
28
- expect(
29
- buildFooterText({
30
- finished: true,
31
- mode: "complete",
32
- selectedFailure: { filePath: "tests/api/users.int.testkit.ts" },
33
- })
34
- ).toContain("investigate");
35
-
36
- expect(
37
- buildFooterText({
38
- finished: true,
39
- mode: "investigating",
40
- agentSession: { status: "running" },
41
- })
42
- ).toContain("cancel");
43
- });
44
-
45
- it("formats transcript entries by kind", () => {
46
- expect(formatAgentEntry({ kind: "status", text: "Inspecting repository" })).toContain("[status]");
47
- expect(formatAgentEntry({ kind: "tool", text: "Bash: rg failure" })).toContain("[tool]");
48
- expect(formatAgentEntry({ kind: "assistant", text: "Likely root cause." })).toBe("Likely root cause.");
49
- });
50
- });
@@ -1,324 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createRunTreeState } from "./run-tree-state.mjs";
3
-
4
- function makePlans(suites = []) {
5
- return [
6
- {
7
- config: { name: "api" },
8
- skipped: false,
9
- suites: suites.length > 0 ? suites : [
10
- {
11
- name: "users",
12
- type: "integration",
13
- displayType: "int",
14
- framework: "k6",
15
- files: ["tests/api/users.int.testkit.ts", "tests/api/users-create.int.testkit.ts"],
16
- },
17
- {
18
- name: "auth",
19
- type: "integration",
20
- displayType: "int",
21
- framework: "k6",
22
- files: ["tests/api/auth.int.testkit.ts"],
23
- },
24
- ],
25
- },
26
- ];
27
- }
28
-
29
- describe("run-tree-state", () => {
30
- it("builds tree hierarchy from service plans", () => {
31
- const state = createRunTreeState();
32
- state.initFromPlans(makePlans());
33
- const snap = state.getSnapshot();
34
-
35
- expect(snap.services).toHaveLength(1);
36
- expect(snap.services[0].name).toBe("api");
37
- expect(snap.services[0].skipped).toBe(false);
38
- expect(snap.services[0].types).toHaveLength(1);
39
- expect(snap.services[0].types[0].type).toBe("int");
40
- expect(snap.services[0].types[0].suites).toHaveLength(2);
41
- expect(snap.services[0].types[0].suites[0].groupLabel).toBe("Users");
42
- expect(snap.services[0].types[0].suites[0].visibleFiles).toHaveLength(2);
43
- expect(snap.services[0].types[0].suites[1].groupLabel).toBe("Auth");
44
- });
45
-
46
- it("marks file running", () => {
47
- const state = createRunTreeState();
48
- state.initFromPlans(makePlans());
49
- state.markFileRunning("api", "int:users", "tests/api/users.int.testkit.ts");
50
- const snap = state.getSnapshot();
51
- const file = snap.services[0].types[0].suites[0].visibleFiles.find(
52
- (f) => f.path === "tests/api/users.int.testkit.ts"
53
- );
54
- expect(file.status).toBe("running");
55
- });
56
-
57
- it("collapses suite when all files pass", () => {
58
- const state = createRunTreeState();
59
- state.initFromPlans(makePlans());
60
- state.setTotalFileCount(3);
61
-
62
- state.markFileFinished(
63
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users.int.testkit.ts" },
64
- { failed: false, durationMs: 1200 }
65
- );
66
- state.markFileFinished(
67
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users-create.int.testkit.ts" },
68
- { failed: false, durationMs: 800 }
69
- );
70
-
71
- const snap = state.getSnapshot();
72
- const suite = snap.services[0].types[0].suites[0];
73
- expect(suite.collapsed).toBe(true);
74
- expect(suite.collapseStatus).toBe("all_passed");
75
- expect(suite.visibleFiles).toHaveLength(0);
76
- expect(suite.totalDurationMs).toBe(2000);
77
- expect(snap.completedCount).toBe(2);
78
- });
79
-
80
- it("collapses suite when all files skipped", () => {
81
- const state = createRunTreeState();
82
- state.initFromPlans(makePlans());
83
-
84
- state.markFileFinished(
85
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users.int.testkit.ts" },
86
- { status: "skipped", reason: "ci skip", durationMs: 0 }
87
- );
88
- state.markFileFinished(
89
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users-create.int.testkit.ts" },
90
- { status: "skipped", reason: "ci skip", durationMs: 0 }
91
- );
92
-
93
- const snap = state.getSnapshot();
94
- const suite = snap.services[0].types[0].suites[0];
95
- expect(suite.collapsed).toBe(true);
96
- expect(suite.collapseStatus).toBe("all_skipped");
97
- });
98
-
99
- it("keeps suite expanded when a file fails", () => {
100
- const state = createRunTreeState();
101
- state.initFromPlans(makePlans());
102
-
103
- state.markFileFinished(
104
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users.int.testkit.ts" },
105
- { failed: false, durationMs: 1200 }
106
- );
107
- state.markFileFinished(
108
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users-create.int.testkit.ts" },
109
- { failed: true, error: "assertion failed", durationMs: 500, failureDetails: [] }
110
- );
111
-
112
- const snap = state.getSnapshot();
113
- const suite = snap.services[0].types[0].suites[0];
114
- expect(suite.collapsed).toBe(false);
115
- // Only failed files visible when no running/pending
116
- expect(suite.visibleFiles).toHaveLength(1);
117
- expect(suite.visibleFiles[0].status).toBe("failed");
118
- expect(suite.passedCount).toBe(1);
119
- });
120
-
121
- it("keeps suite expanded while files are running", () => {
122
- const state = createRunTreeState();
123
- state.initFromPlans(makePlans());
124
-
125
- state.markFileRunning("api", "int:users", "tests/api/users.int.testkit.ts");
126
- state.markFileFinished(
127
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users-create.int.testkit.ts" },
128
- { failed: false, durationMs: 800 }
129
- );
130
-
131
- const snap = state.getSnapshot();
132
- const suite = snap.services[0].types[0].suites[0];
133
- expect(suite.collapsed).toBe(false);
134
- expect(suite.visibleFiles).toHaveLength(2);
135
- });
136
-
137
- it("type collapses when all suites are collapsed", () => {
138
- const state = createRunTreeState();
139
- state.initFromPlans(makePlans());
140
-
141
- // Pass all files in both suites
142
- state.markFileFinished(
143
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users.int.testkit.ts" },
144
- { failed: false, durationMs: 1000 }
145
- );
146
- state.markFileFinished(
147
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users-create.int.testkit.ts" },
148
- { failed: false, durationMs: 500 }
149
- );
150
- state.markFileFinished(
151
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "auth", file: "tests/api/auth.int.testkit.ts" },
152
- { failed: false, durationMs: 2000 }
153
- );
154
-
155
- const snap = state.getSnapshot();
156
- const type = snap.services[0].types[0];
157
- expect(type.collapsed).toBe(true);
158
- });
159
-
160
- it("tracks completed and total counts", () => {
161
- const state = createRunTreeState();
162
- state.initFromPlans(makePlans());
163
- state.setTotalFileCount(3);
164
-
165
- expect(state.getSnapshot().totalCount).toBe(3);
166
- expect(state.getSnapshot().completedCount).toBe(0);
167
-
168
- state.markFileFinished(
169
- { serviceName: "api", type: "integration", displayType: "int", framework: "k6", suiteName: "users", file: "tests/api/users.int.testkit.ts" },
170
- { failed: false, durationMs: 100 }
171
- );
172
- expect(state.getSnapshot().completedCount).toBe(1);
173
- });
174
-
175
- it("handles skipped services", () => {
176
- const state = createRunTreeState();
177
- state.initFromPlans([{ config: { name: "web" }, skipped: true, suites: [] }]);
178
- const snap = state.getSnapshot();
179
- expect(snap.services[0].skipped).toBe(true);
180
- });
181
-
182
- it("markServiceSkipped updates existing service", () => {
183
- const state = createRunTreeState();
184
- state.initFromPlans(makePlans());
185
- state.markServiceSkipped("api", "no matching types");
186
- const snap = state.getSnapshot();
187
- expect(snap.services[0].skipped).toBe(true);
188
- expect(snap.services[0].skipReason).toBe("no matching types");
189
- });
190
-
191
- it("finish populates summaryData", () => {
192
- const state = createRunTreeState();
193
- state.initFromPlans(makePlans());
194
-
195
- const mockResults = [
196
- {
197
- name: "api",
198
- failed: false,
199
- skipped: false,
200
- suiteCount: 2,
201
- completedSuiteCount: 2,
202
- failedSuiteCount: 0,
203
- skippedSuiteCount: 0,
204
- totalFileCount: 3,
205
- passedFileCount: 3,
206
- failedFileCount: 0,
207
- skippedFileCount: 0,
208
- notRunFileCount: 0,
209
- suites: [],
210
- errors: [],
211
- },
212
- ];
213
-
214
- state.finish(mockResults, 5000, null);
215
- const snap = state.getSnapshot();
216
- expect(snap.finished).toBe(true);
217
- expect(snap.summaryData.result).toBe("PASSED");
218
- expect(snap.summaryData.rows.length).toBeGreaterThanOrEqual(7);
219
- });
220
-
221
- it("subscribe fires callback on state mutations", () => {
222
- const state = createRunTreeState();
223
- state.initFromPlans(makePlans());
224
- let callCount = 0;
225
- state.subscribe(() => { callCount += 1; });
226
-
227
- state.setTotalFileCount(3);
228
- state.setPhase("running");
229
- expect(callCount).toBe(2);
230
- });
231
-
232
- it("markPlannedSkip sets file status and increments completed", () => {
233
- const state = createRunTreeState();
234
- state.initFromPlans(makePlans());
235
- state.setTotalFileCount(3);
236
-
237
- state.markPlannedSkip({ serviceName: "api", file: "tests/api/users.int.testkit.ts", reason: "ci skip" });
238
- const snap = state.getSnapshot();
239
- const file = snap.services[0].types[0].suites[0].visibleFiles.find(
240
- (f) => f.path === "tests/api/users.int.testkit.ts"
241
- );
242
- expect(file.status).toBe("skipped");
243
- expect(snap.completedCount).toBe(1);
244
- });
245
-
246
- it("markRuntimeError marks file failed", () => {
247
- const state = createRunTreeState();
248
- state.initFromPlans(makePlans());
249
-
250
- state.markRuntimeError(
251
- { serviceName: "api", file: "tests/api/users.int.testkit.ts" },
252
- "process crashed"
253
- );
254
- const snap = state.getSnapshot();
255
- const file = snap.services[0].types[0].suites[0].visibleFiles.find(
256
- (f) => f.path === "tests/api/users.int.testkit.ts"
257
- );
258
- expect(file.status).toBe("failed");
259
- expect(file.error).toBe("process crashed");
260
- });
261
-
262
- it("tracks the selected failure after a file fails", () => {
263
- const state = createRunTreeState();
264
- state.initFromPlans(makePlans());
265
-
266
- state.markFileFinished(
267
- {
268
- serviceName: "api",
269
- type: "integration",
270
- displayType: "int",
271
- framework: "k6",
272
- suiteName: "users",
273
- file: "tests/api/users-create.int.testkit.ts",
274
- },
275
- { failed: true, error: "assertion failed", durationMs: 500, failureDetails: [] }
276
- );
277
-
278
- const snap = state.getSnapshot();
279
- expect(snap.failures).toHaveLength(1);
280
- expect(snap.selectedFailure.filePath).toBe("tests/api/users-create.int.testkit.ts");
281
- });
282
-
283
- it("cycles through failures", () => {
284
- const state = createRunTreeState();
285
- state.initFromPlans(makePlans());
286
-
287
- state.markRuntimeError(
288
- { serviceName: "api", file: "tests/api/users.int.testkit.ts" },
289
- "process crashed"
290
- );
291
- state.markRuntimeError(
292
- { serviceName: "api", file: "tests/api/auth.int.testkit.ts" },
293
- "another crash"
294
- );
295
-
296
- const first = state.getSnapshot().selectedFailure.filePath;
297
- state.selectNextFailure();
298
- const second = state.getSnapshot().selectedFailure.filePath;
299
- expect(second).not.toBe(first);
300
- state.selectPreviousFailure();
301
- expect(state.getSnapshot().selectedFailure.filePath).toBe(first);
302
- });
303
-
304
- it("records investigation transcript state", () => {
305
- const state = createRunTreeState();
306
- state.initFromPlans(makePlans());
307
- state.markRuntimeError(
308
- { serviceName: "api", file: "tests/api/users.int.testkit.ts" },
309
- "process crashed"
310
- );
311
-
312
- state.beginInvestigation({ provider: "codex", userMessage: "Investigate this failure" });
313
- state.appendAgentEvent({ type: "start" });
314
- state.appendAgentEvent({ type: "status", message: "Inspecting repository" });
315
- state.appendAgentEvent({ type: "delta", text: "Likely root cause." });
316
- state.completeAgentSession({ finalText: "Likely root cause.", exitCode: 0 });
317
-
318
- const snap = state.getSnapshot();
319
- expect(snap.mode).toBe("investigating");
320
- expect(snap.agentSession.status).toBe("complete");
321
- expect(snap.agentSession.entries.some((entry) => entry.kind === "assistant")).toBe(true);
322
- expect(snap.agentSession.finalText).toContain("Likely root cause");
323
- });
324
- });
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { normalizeDatabaseConfig } from "./database.mjs";
3
-
4
- describe("config database helpers", () => {
5
- it("normalizes local database config with template defaults", () => {
6
- expect(
7
- normalizeDatabaseConfig(
8
- {
9
- database: {
10
- template: {
11
- migrate: [{ kind: "sql-file", path: "sql/migrate.sql" }],
12
- },
13
- },
14
- },
15
- "web"
16
- )
17
- ).toMatchObject({
18
- provider: "local",
19
- selectedBackend: "local",
20
- reset: true,
21
- template: {
22
- inputs: [],
23
- migrate: [{ kind: "sql-file", path: "sql/migrate.sql" }],
24
- seed: [],
25
- verify: [],
26
- },
27
- });
28
- });
29
- });