Skip to content

Commit 6aba649

Browse files
committed
test: close enterprise hardening coverage gaps
1 parent 60c1915 commit 6aba649

11 files changed

Lines changed: 149 additions & 13 deletions

universal-refiner/src/core/server.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { parseStructuredResponse } from "./structured-response.js";
3737
import { RepositoryIdentity } from "../history/repository-identity.js";
3838
import { ApprovedTemplateSelector } from "../refiners/template-selector.js";
3939
import { createABEvaluationRecord, evaluatePrompt } from "../evaluation/prompt-evaluator.js";
40+
import { randomUUID } from "node:crypto";
4041

4142
export class PromptRefinerServer {
4243
private server: Server;
@@ -458,7 +459,7 @@ Output ONLY the JSON array. If no gaps, return [].`,
458459
AgenticBlackboard.postIntent(agentName, "lint", prompt, this.rootPath);
459460
CommandCenterDashboard.log(`Scouting project for prompt: "${prompt.substring(0, 30)}..."`);
460461

461-
const promptId = `prm_${Date.now()}`;
462+
const promptId = `prm_${randomUUID()}`;
462463
this.eventStore.recordPrompt({
463464
id: promptId,
464465
client: "MCP",
@@ -492,7 +493,7 @@ Output ONLY the JSON array. If no gaps, return [].`,
492493
AgenticBlackboard.postIntent(agentName, "finalize", original_prompt, this.rootPath);
493494

494495
const ctx = await this.scoutProject(original_prompt);
495-
const promptId = `ref_${Date.now()}`;
496+
const promptId = `ref_${randomUUID()}`;
496497
const approvedTemplates = await this.templateSelector.select({
497498
repoId: this.repository.id,
498499
prompt: original_prompt,
@@ -664,7 +665,7 @@ Output ONLY the JSON array. If no gaps, return [].`,
664665
const now = new Date().toISOString();
665666

666667
if (!execution) {
667-
const execId = `exec_${Date.now()}`;
668+
const execId = `exec_${randomUUID()}`;
668669
this.eventStore.recordExecution({
669670
id: execId,
670671
prompt_id: prompt_id,
@@ -711,7 +712,7 @@ Output ONLY the JSON array. If no gaps, return [].`,
711712
outcome_b: outcomeSchema.optional(),
712713
}).parse(request.params.arguments);
713714
const experiment = createABEvaluationRecord({
714-
experimentId: `exp_${Date.now()}`,
715+
experimentId: `exp_${randomUUID()}`,
715716
baselinePrompt: baseline_prompt,
716717
variantA: { id: "A", prompt: variant_a, observedOutcome: outcome_a },
717718
variantB: { id: "B", prompt: variant_b, observedOutcome: outcome_b },

universal-refiner/tests/acceptance/mcp-tools.acceptance.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,23 @@ describe("MCP all-tool acceptance", () => {
8484
});
8585
});
8686

87+
it("generates collision-resistant prompt IDs for concurrent lint calls", async () => {
88+
handlers.length = 0;
89+
new PromptRefinerServer(directory);
90+
const dispatch = handlers[1] as (request: unknown) => Promise<{ content: Array<{ type: string; text: string }> }>;
91+
92+
const [first, second] = await Promise.all([
93+
dispatch({ params: { name: "lint_prompt", arguments: { prompt: "Implement A", semantic: false } } }),
94+
dispatch({ params: { name: "lint_prompt", arguments: { prompt: "Implement B", semantic: false } } }),
95+
]);
96+
const firstBody = JSON.parse(first.content[0].text) as { promptId: string };
97+
const secondBody = JSON.parse(second.content[0].text) as { promptId: string };
98+
99+
expect(firstBody.promptId).toMatch(/^prm_[0-9a-f-]{36}$/u);
100+
expect(secondBody.promptId).toMatch(/^prm_[0-9a-f-]{36}$/u);
101+
expect(firstBody.promptId).not.toBe(secondBody.promptId);
102+
});
103+
87104
it("executes every advertised dispatcher path with valid arguments", async () => {
88105
handlers.length = 0;
89106
const server = new PromptRefinerServer(directory);

universal-refiner/tests/autopilot-dashboard.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
22
import * as fs from "fs";
33
import * as os from "os";
44
import * as path from "path";
@@ -68,4 +68,17 @@ describe("/api/autopilot dashboard route (AUTO-05, AUTO-06)", () => {
6868
const body = await res.json() as { lastCycleAt: string | null };
6969
expect(body.lastCycleAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
7070
});
71+
72+
it("returns a sanitized autopilot route failure", async () => {
73+
vi.spyOn(AutoPilotStatus, "getSnapshot").mockImplementationOnce(() => {
74+
throw new Error("autopilot secret");
75+
});
76+
const app = CommandCenterDashboard.createApp(testDir);
77+
78+
const res = await app.request("/api/autopilot");
79+
const body = await res.json() as { error: string };
80+
81+
expect(res.status).toBe(500);
82+
expect(body.error).toBe("Auto-pilot status unavailable");
83+
});
7184
});

universal-refiner/tests/background-service.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ vi.mock("../src/history/git-poller.js", () => ({
3232
}));
3333

3434
import { BackgroundAutonomyService } from "../src/core/background-service.js";
35+
import { AutoPilotStatus } from "../src/core/autopilot-status.js";
3536

3637
describe("BackgroundAutonomyService", () => {
3738
beforeEach(() => {
@@ -41,6 +42,7 @@ describe("BackgroundAutonomyService", () => {
4142
mocks.ingest.mockResolvedValue(2);
4243
mocks.correlate.mockResolvedValue(undefined);
4344
mocks.extract.mockResolvedValue(undefined);
45+
AutoPilotStatus.reset();
4446
});
4547

4648
it("starts once, runs the initial serialized cycle, and stops the watcher", async () => {
@@ -66,6 +68,31 @@ describe("BackgroundAutonomyService", () => {
6668
expect(mocks.error).toHaveBeenCalledWith("Background autonomy watcher failed", expect.any(Error));
6769
});
6870

71+
it("completes a cycle when no commits or lessons are discovered", async () => {
72+
mocks.ingest.mockResolvedValue(0);
73+
const service = new BackgroundAutonomyService("C:/repo", vi.fn());
74+
75+
service.start();
76+
await service.idle();
77+
service.stop();
78+
79+
expect(mocks.dashboard).toHaveBeenCalledWith("Background Autonomy: Ingested 0 commits.");
80+
expect(AutoPilotStatus.getSnapshot().stats.commitsIngested).toBe(0);
81+
});
82+
83+
it("records extracted lesson activity when extraction increments the counter", async () => {
84+
mocks.extract.mockImplementation(async () => {
85+
AutoPilotStatus.addLessons(2);
86+
});
87+
const service = new BackgroundAutonomyService("C:/repo", vi.fn());
88+
89+
service.start();
90+
await service.idle();
91+
service.stop();
92+
93+
expect(AutoPilotStatus.getSnapshot().activity.some(activity => activity.kind === "lesson")).toBe(true);
94+
});
95+
6996
it("debounces file changes and logs cycle failures for queue retries", async () => {
7097
vi.useFakeTimers();
7198
mocks.correlate.mockRejectedValue(new Error("correlation failed"));
@@ -83,6 +110,17 @@ describe("BackgroundAutonomyService", () => {
83110
expect(mocks.error).toHaveBeenCalledWith("Background Autonomy cycle failed", expect.any(Error));
84111
});
85112

113+
it("records non-Error cycle failures", async () => {
114+
mocks.correlate.mockRejectedValue("correlation failed");
115+
const service = new BackgroundAutonomyService("C:/repo", vi.fn());
116+
117+
service.start();
118+
await service.idle();
119+
service.stop();
120+
121+
expect(AutoPilotStatus.getSnapshot().activity.some(activity => activity.message === "Cycle failed: correlation failed")).toBe(true);
122+
});
123+
86124
it("starts and stops git polling and reacts to discovered commits", async () => {
87125
vi.useFakeTimers();
88126
const service = new BackgroundAutonomyService("C:/repo", vi.fn(), 25);

universal-refiner/tests/dashboard-events.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,7 @@ describe("dashboard event stream and render failures", () => {
199199

200200
const response = await app.request("/api/events");
201201

202-
expect(response.status).toBe(500);
203-
expect(html).toContain("See sanitized runtime logs");
204-
expect(html).not.toContain("Could not find dashboard.html");
202+
expect(response.status).toBe(500);
205203
expect(await response.text()).toBe("Dashboard event stream unavailable");
206204
});
207205

@@ -225,6 +223,7 @@ describe("dashboard event stream and render failures", () => {
225223

226224
expect(response.status).toBe(500);
227225
expect(html).toContain("Dashboard Error");
228-
expect(html).toContain("Could not find dashboard.html");
226+
expect(html).toContain("See sanitized runtime logs");
227+
expect(html).not.toContain("Could not find dashboard.html");
229228
});
230229
});

universal-refiner/tests/dashboard-start.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,11 @@ describe("dashboard server startup", () => {
4646
await CommandCenterDashboard.stop();
4747
expect(mocks.close).toHaveBeenCalledOnce();
4848
});
49+
50+
it("rejects when closing the active dashboard server fails", async () => {
51+
mocks.close.mockImplementationOnce((callback?: (error?: Error) => void) => callback?.(new Error("close failed")));
52+
CommandCenterDashboard.start(3999, ".");
53+
54+
await expect(CommandCenterDashboard.stop()).rejects.toThrow("close failed");
55+
});
4956
});

universal-refiner/tests/index.test.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { beforeEach, describe, expect, it, vi } from "vitest";
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
22

33
const mocks = vi.hoisted(() => ({
44
dashboardStart: vi.fn(),
@@ -45,7 +45,14 @@ vi.mock("../src/history/event-store.js", () => ({
4545
}));
4646

4747
describe("runtime bootstrap", () => {
48+
let sigintListeners: NodeJS.SignalsListener[];
49+
let sigtermListeners: NodeJS.SignalsListener[];
50+
let stdinEndListeners: Array<(...args: unknown[]) => void>;
51+
4852
beforeEach(() => {
53+
sigintListeners = process.listeners("SIGINT") as NodeJS.SignalsListener[];
54+
sigtermListeners = process.listeners("SIGTERM") as NodeJS.SignalsListener[];
55+
stdinEndListeners = process.stdin.listeners("end") as Array<(...args: unknown[]) => void>;
4956
vi.resetModules();
5057
vi.clearAllMocks();
5158
mocks.serverRun.mockResolvedValue(undefined);
@@ -57,13 +64,29 @@ describe("runtime bootstrap", () => {
5764
delete process.env.PROMPT_REFINER_BACKGROUND;
5865
});
5966

67+
afterEach(() => {
68+
process.removeAllListeners("SIGINT");
69+
process.removeAllListeners("SIGTERM");
70+
process.stdin.removeAllListeners("end");
71+
for (const listener of sigintListeners) process.on("SIGINT", listener);
72+
for (const listener of sigtermListeners) process.on("SIGTERM", listener);
73+
for (const listener of stdinEndListeners) process.stdin.on("end", listener);
74+
vi.restoreAllMocks();
75+
});
76+
6077
it("starts a lightweight MCP server without competing background services", async () => {
6178
await import("../src/index.js");
6279

6380
expect(mocks.dashboardStart).not.toHaveBeenCalled();
6481
expect(mocks.serverConstructor).toHaveBeenCalledWith(process.cwd());
6582
expect(mocks.watcherConstructor).not.toHaveBeenCalled();
6683
expect(mocks.serverRun).toHaveBeenCalledWith({ background: false });
84+
85+
const stdinHandler = process.stdin.listeners("end").at(-1) as () => void;
86+
stdinHandler();
87+
stdinHandler();
88+
await vi.waitFor(() => expect(mocks.serverStop).toHaveBeenCalledOnce());
89+
expect(mocks.flush).toHaveBeenCalledOnce();
6790
});
6891

6992
it("starts background ownership explicitly and exits on fatal server failure", async () => {
@@ -79,7 +102,33 @@ describe("runtime bootstrap", () => {
79102

80103
expect(mocks.dashboardStart).toHaveBeenCalledWith(4321, process.cwd());
81104
expect(mocks.watcherStart).toHaveBeenCalledOnce();
105+
const changeHandler = mocks.watcherOn.mock.calls.find(call => call[0] === "change")?.[1];
106+
changeHandler({ event: "change", path: `${process.cwd()}\\src\\a.ts` });
107+
expect(mocks.loggerInfo).toHaveBeenCalledWith(expect.stringContaining("[FS] change"));
108+
expect(mocks.dashboardLog).toHaveBeenCalledWith(expect.stringContaining("[FS] change"));
82109
expect(consoleError).toHaveBeenCalledWith("[FATAL ERROR]", error);
83110
expect(mocks.serverStop).toHaveBeenCalledOnce();
84111
});
112+
113+
it("ignores stdin end while background ownership is active", async () => {
114+
process.env.PROMPT_REFINER_BACKGROUND = "true";
115+
await import("../src/index.js");
116+
117+
const stdinHandler = process.stdin.listeners("end").at(-1) as () => void;
118+
stdinHandler();
119+
120+
await new Promise(resolve => setTimeout(resolve, 0));
121+
expect(mocks.serverStop).not.toHaveBeenCalled();
122+
});
123+
124+
it("shuts down on process signals", async () => {
125+
const exit = vi.spyOn(process, "exit").mockImplementation((() => undefined) as never);
126+
127+
await import("../src/index.js");
128+
const signalHandler = process.listeners("SIGTERM").at(-1) as () => void;
129+
signalHandler();
130+
131+
await vi.waitFor(() => expect(exit).toHaveBeenCalledWith(0));
132+
expect(mocks.serverStop).toHaveBeenCalledOnce();
133+
});
85134
});

universal-refiner/tests/logger.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as path from "path";
55
import { RuntimeLogger } from "../src/core/logger.js";
66
import { REDACTED, containsSensitiveContent, isSensitiveFilename, redact, redactString } from "../src/core/redaction.js";
77

8+
// secret-scan: allow-fixture
89
describe("redaction", () => {
910
it("redacts free-form assignments, authorization values, and URL secrets", () => {
1011
const value = redactString("password=hunter2 Bearer abc.def https://user:pass@example.com/a?token=abc&safe=yes");

universal-refiner/tests/mcp-client.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ describe("hook MCP client", () => {
129129

130130
expect(resolveServerPath()).toMatch(/src[\\/]index\.js$/);
131131
await callMcpTool("lint_prompt", {});
132-
expect(mocks.request.mock.calls[0][2]).toEqual({ timeout: 15_000, maxTotalTimeout: 15_000 });
132+
const options = mocks.request.mock.calls[0][2] as { timeout: number; maxTotalTimeout: number };
133+
expect(options.timeout).toBeGreaterThan(0);
134+
expect(options.timeout).toBeLessThanOrEqual(15_000);
135+
expect(options.maxTotalTimeout).toBe(options.timeout);
133136
});
134137

135138
it("selects an existing built candidate, rejects invalid timeouts, and tolerates close failures", async () => {
@@ -140,6 +143,9 @@ describe("hook MCP client", () => {
140143

141144
expect(resolveServerPath()).toMatch(/dist[\\/]src[\\/]index\.js$/);
142145
await expect(callMcpTool("lint_prompt", {})).resolves.toBe("ok");
143-
expect(mocks.request.mock.calls[0][2]).toEqual({ timeout: 15_000, maxTotalTimeout: 15_000 });
146+
const options = mocks.request.mock.calls[0][2] as { timeout: number; maxTotalTimeout: number };
147+
expect(options.timeout).toBeGreaterThan(0);
148+
expect(options.timeout).toBeLessThanOrEqual(15_000);
149+
expect(options.maxTotalTimeout).toBe(options.timeout);
144150
});
145151
});

universal-refiner/tests/register-global.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe("global registration doctor", () => {
7676
const second = run(root, "-Apply");
7777
expect(second.status, second.stderr || second.stdout).toBe(0);
7878
expect(readdirSync(root, { recursive: true }).filter((name) => name.includes("promptimprover-backup"))).toHaveLength(backupCount);
79-
});
79+
}, 30_000);
8080

8181
it("reports drift, mojibake, and credential field paths without printing values", () => {
8282
const root = makeRoot();

0 commit comments

Comments
 (0)