101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { createRunRegistry } from "./registry.js";
|
|
|
|
type RunRegistry = ReturnType<typeof createRunRegistry>;
|
|
|
|
function addRunningRecord(
|
|
registry: RunRegistry,
|
|
params: {
|
|
runId: string;
|
|
sessionId: string;
|
|
startedAtMs: number;
|
|
scopeKey?: string;
|
|
backendId?: string;
|
|
},
|
|
) {
|
|
registry.add({
|
|
runId: params.runId,
|
|
sessionId: params.sessionId,
|
|
backendId: params.backendId ?? "b1",
|
|
scopeKey: params.scopeKey,
|
|
state: "running",
|
|
startedAtMs: params.startedAtMs,
|
|
lastOutputAtMs: params.startedAtMs,
|
|
createdAtMs: params.startedAtMs,
|
|
updatedAtMs: params.startedAtMs,
|
|
});
|
|
}
|
|
|
|
describe("process supervisor run registry", () => {
|
|
it("finalize is idempotent and preserves first terminal metadata", () => {
|
|
const registry = createRunRegistry();
|
|
addRunningRecord(registry, { runId: "r1", sessionId: "s1", startedAtMs: 1 });
|
|
|
|
const first = registry.finalize("r1", {
|
|
reason: "overall-timeout",
|
|
exitCode: null,
|
|
exitSignal: "SIGKILL",
|
|
});
|
|
const second = registry.finalize("r1", {
|
|
reason: "manual-cancel",
|
|
exitCode: 0,
|
|
exitSignal: null,
|
|
});
|
|
|
|
expect(first).not.toBeNull();
|
|
expect(first?.firstFinalize).toBe(true);
|
|
expect(first?.record.terminationReason).toBe("overall-timeout");
|
|
expect(first?.record.exitCode).toBeNull();
|
|
expect(first?.record.exitSignal).toBe("SIGKILL");
|
|
|
|
expect(second).not.toBeNull();
|
|
expect(second?.firstFinalize).toBe(false);
|
|
expect(second?.record.terminationReason).toBe("overall-timeout");
|
|
expect(second?.record.exitCode).toBeNull();
|
|
expect(second?.record.exitSignal).toBe("SIGKILL");
|
|
});
|
|
|
|
it("prunes oldest exited records once retention cap is exceeded", () => {
|
|
const registry = createRunRegistry({ maxExitedRecords: 2 });
|
|
addRunningRecord(registry, { runId: "r1", sessionId: "s1", startedAtMs: 1 });
|
|
addRunningRecord(registry, { runId: "r2", sessionId: "s2", startedAtMs: 2 });
|
|
addRunningRecord(registry, { runId: "r3", sessionId: "s3", startedAtMs: 3 });
|
|
|
|
registry.finalize("r1", { reason: "exit", exitCode: 0, exitSignal: null });
|
|
registry.finalize("r2", { reason: "exit", exitCode: 0, exitSignal: null });
|
|
registry.finalize("r3", { reason: "exit", exitCode: 0, exitSignal: null });
|
|
|
|
expect(registry.get("r1")).toBeUndefined();
|
|
expect(registry.get("r2")?.state).toBe("exited");
|
|
expect(registry.get("r3")?.state).toBe("exited");
|
|
});
|
|
|
|
it("filters listByScope and returns detached copies", () => {
|
|
const registry = createRunRegistry();
|
|
addRunningRecord(registry, {
|
|
runId: "r1",
|
|
sessionId: "s1",
|
|
scopeKey: "scope:a",
|
|
startedAtMs: 1,
|
|
});
|
|
addRunningRecord(registry, {
|
|
runId: "r2",
|
|
sessionId: "s2",
|
|
scopeKey: "scope:b",
|
|
startedAtMs: 2,
|
|
});
|
|
|
|
expect(registry.listByScope(" ")).toEqual([]);
|
|
const scoped = registry.listByScope("scope:a");
|
|
expect(scoped).toHaveLength(1);
|
|
const [firstScoped] = scoped;
|
|
expect(firstScoped?.runId).toBe("r1");
|
|
|
|
if (!firstScoped) {
|
|
throw new Error("missing scoped record");
|
|
}
|
|
firstScoped.state = "exited";
|
|
expect(registry.get("r1")?.state).toBe("running");
|
|
});
|
|
});
|