test: add unit tests for auto-dashboard, auto-recovery, crash-recovery (#526)
46 new tests covering 3 previously untested modules: - auto-dashboard.test.ts (18 tests): unitVerb, unitPhaseLabel, describeNextUnit phase mapping, formatAutoElapsed, formatWidgetTokens - crash-recovery.test.ts (10 tests): writeLock/readCrashLock round-trip, clearLock, isLockProcessAlive (own PID, dead PID, invalid PIDs), formatCrashInfo - auto-recovery.test.ts (18 tests): resolveExpectedArtifactPath for all unit types, diagnoseExpectedArtifact, buildLoopRemediationSteps, completed-unit key persistence (persist, load, remove, idempotency) Total test count: 123 → 169 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c09dcfc380
commit
f70ddea074
3 changed files with 559 additions and 0 deletions
153
src/resources/extensions/gsd/tests/auto-dashboard.test.ts
Normal file
153
src/resources/extensions/gsd/tests/auto-dashboard.test.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
unitVerb,
|
||||
unitPhaseLabel,
|
||||
describeNextUnit,
|
||||
formatAutoElapsed,
|
||||
formatWidgetTokens,
|
||||
} from "../auto-dashboard.ts";
|
||||
|
||||
// ─── unitVerb ─────────────────────────────────────────────────────────────
|
||||
|
||||
test("unitVerb maps known unit types to verbs", () => {
|
||||
assert.equal(unitVerb("research-milestone"), "researching");
|
||||
assert.equal(unitVerb("research-slice"), "researching");
|
||||
assert.equal(unitVerb("plan-milestone"), "planning");
|
||||
assert.equal(unitVerb("plan-slice"), "planning");
|
||||
assert.equal(unitVerb("execute-task"), "executing");
|
||||
assert.equal(unitVerb("complete-slice"), "completing");
|
||||
assert.equal(unitVerb("replan-slice"), "replanning");
|
||||
assert.equal(unitVerb("reassess-roadmap"), "reassessing");
|
||||
assert.equal(unitVerb("run-uat"), "running UAT");
|
||||
});
|
||||
|
||||
test("unitVerb returns raw type for unknown types", () => {
|
||||
assert.equal(unitVerb("custom-thing"), "custom-thing");
|
||||
});
|
||||
|
||||
test("unitVerb handles hook types", () => {
|
||||
assert.equal(unitVerb("hook/verify-code"), "hook: verify-code");
|
||||
assert.equal(unitVerb("hook/"), "hook: ");
|
||||
});
|
||||
|
||||
// ─── unitPhaseLabel ───────────────────────────────────────────────────────
|
||||
|
||||
test("unitPhaseLabel maps known types to labels", () => {
|
||||
assert.equal(unitPhaseLabel("research-milestone"), "RESEARCH");
|
||||
assert.equal(unitPhaseLabel("research-slice"), "RESEARCH");
|
||||
assert.equal(unitPhaseLabel("plan-milestone"), "PLAN");
|
||||
assert.equal(unitPhaseLabel("plan-slice"), "PLAN");
|
||||
assert.equal(unitPhaseLabel("execute-task"), "EXECUTE");
|
||||
assert.equal(unitPhaseLabel("complete-slice"), "COMPLETE");
|
||||
assert.equal(unitPhaseLabel("replan-slice"), "REPLAN");
|
||||
assert.equal(unitPhaseLabel("reassess-roadmap"), "REASSESS");
|
||||
assert.equal(unitPhaseLabel("run-uat"), "UAT");
|
||||
});
|
||||
|
||||
test("unitPhaseLabel uppercases unknown types", () => {
|
||||
assert.equal(unitPhaseLabel("custom-thing"), "CUSTOM-THING");
|
||||
});
|
||||
|
||||
test("unitPhaseLabel returns HOOK for hook types", () => {
|
||||
assert.equal(unitPhaseLabel("hook/verify"), "HOOK");
|
||||
});
|
||||
|
||||
// ─── describeNextUnit ─────────────────────────────────────────────────────
|
||||
|
||||
test("describeNextUnit handles pre-planning phase", () => {
|
||||
const result = describeNextUnit({
|
||||
phase: "pre-planning",
|
||||
activeMilestone: { id: "M001", title: "Test" },
|
||||
} as any);
|
||||
assert.equal(result.label, "Research & plan milestone");
|
||||
});
|
||||
|
||||
test("describeNextUnit handles executing phase", () => {
|
||||
const result = describeNextUnit({
|
||||
phase: "executing",
|
||||
activeMilestone: { id: "M001", title: "Test" },
|
||||
activeSlice: { id: "S01", title: "Slice" },
|
||||
activeTask: { id: "T01", title: "Task One" },
|
||||
} as any);
|
||||
assert.ok(result.label.includes("T01"));
|
||||
assert.ok(result.label.includes("Task One"));
|
||||
});
|
||||
|
||||
test("describeNextUnit handles summarizing phase", () => {
|
||||
const result = describeNextUnit({
|
||||
phase: "summarizing",
|
||||
activeMilestone: { id: "M001", title: "Test" },
|
||||
activeSlice: { id: "S01", title: "First Slice" },
|
||||
} as any);
|
||||
assert.ok(result.label.includes("S01"));
|
||||
});
|
||||
|
||||
test("describeNextUnit handles needs-discussion phase", () => {
|
||||
const result = describeNextUnit({
|
||||
phase: "needs-discussion",
|
||||
activeMilestone: { id: "M001", title: "Test" },
|
||||
} as any);
|
||||
assert.ok(
|
||||
result.label.toLowerCase().includes("discuss") || result.label.toLowerCase().includes("draft"),
|
||||
);
|
||||
});
|
||||
|
||||
test("describeNextUnit handles completing-milestone phase", () => {
|
||||
const result = describeNextUnit({
|
||||
phase: "completing-milestone",
|
||||
activeMilestone: { id: "M001", title: "Test" },
|
||||
} as any);
|
||||
assert.ok(result.label.toLowerCase().includes("milestone"));
|
||||
});
|
||||
|
||||
test("describeNextUnit returns fallback for unknown phase", () => {
|
||||
const result = describeNextUnit({
|
||||
phase: "some-future-phase" as any,
|
||||
activeMilestone: { id: "M001", title: "Test" },
|
||||
} as any);
|
||||
assert.equal(result.label, "Continue");
|
||||
});
|
||||
|
||||
// ─── formatAutoElapsed ────────────────────────────────────────────────────
|
||||
|
||||
test("formatAutoElapsed returns empty for zero startTime", () => {
|
||||
assert.equal(formatAutoElapsed(0), "");
|
||||
});
|
||||
|
||||
test("formatAutoElapsed formats seconds", () => {
|
||||
const result = formatAutoElapsed(Date.now() - 30_000);
|
||||
assert.match(result, /^\d+s$/);
|
||||
});
|
||||
|
||||
test("formatAutoElapsed formats minutes", () => {
|
||||
const result = formatAutoElapsed(Date.now() - 180_000); // 3 min
|
||||
assert.match(result, /^3m/);
|
||||
});
|
||||
|
||||
test("formatAutoElapsed formats hours", () => {
|
||||
const result = formatAutoElapsed(Date.now() - 3_700_000); // ~1h
|
||||
assert.match(result, /^1h/);
|
||||
});
|
||||
|
||||
// ─── formatWidgetTokens ──────────────────────────────────────────────────
|
||||
|
||||
test("formatWidgetTokens formats small numbers directly", () => {
|
||||
assert.equal(formatWidgetTokens(0), "0");
|
||||
assert.equal(formatWidgetTokens(500), "500");
|
||||
assert.equal(formatWidgetTokens(999), "999");
|
||||
});
|
||||
|
||||
test("formatWidgetTokens formats thousands with k", () => {
|
||||
assert.equal(formatWidgetTokens(1000), "1.0k");
|
||||
assert.equal(formatWidgetTokens(5500), "5.5k");
|
||||
assert.equal(formatWidgetTokens(10000), "10k");
|
||||
assert.equal(formatWidgetTokens(99999), "100k");
|
||||
});
|
||||
|
||||
test("formatWidgetTokens formats millions with M", () => {
|
||||
assert.equal(formatWidgetTokens(1_000_000), "1.0M");
|
||||
assert.equal(formatWidgetTokens(10_000_000), "10M");
|
||||
assert.equal(formatWidgetTokens(25_000_000), "25M");
|
||||
});
|
||||
272
src/resources/extensions/gsd/tests/auto-recovery.test.ts
Normal file
272
src/resources/extensions/gsd/tests/auto-recovery.test.ts
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdirSync, writeFileSync, existsSync, readFileSync, rmSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import {
|
||||
resolveExpectedArtifactPath,
|
||||
diagnoseExpectedArtifact,
|
||||
buildLoopRemediationSteps,
|
||||
completedKeysPath,
|
||||
persistCompletedKey,
|
||||
removePersistedKey,
|
||||
loadPersistedKeys,
|
||||
} from "../auto-recovery.ts";
|
||||
|
||||
function makeTmpBase(): string {
|
||||
const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
|
||||
// Create .gsd/milestones/M001/slices/S01/tasks/ structure
|
||||
mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
|
||||
return base;
|
||||
}
|
||||
|
||||
function cleanup(base: string): void {
|
||||
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
||||
}
|
||||
|
||||
// ─── resolveExpectedArtifactPath ──────────────────────────────────────────
|
||||
|
||||
test("resolveExpectedArtifactPath returns correct path for research-milestone", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const result = resolveExpectedArtifactPath("research-milestone", "M001", base);
|
||||
assert.ok(result);
|
||||
assert.ok(result!.includes("M001"));
|
||||
assert.ok(result!.includes("RESEARCH"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("resolveExpectedArtifactPath returns correct path for execute-task", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const result = resolveExpectedArtifactPath("execute-task", "M001/S01/T01", base);
|
||||
assert.ok(result);
|
||||
assert.ok(result!.includes("tasks"));
|
||||
assert.ok(result!.includes("SUMMARY"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("resolveExpectedArtifactPath returns correct path for complete-slice", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const result = resolveExpectedArtifactPath("complete-slice", "M001/S01", base);
|
||||
assert.ok(result);
|
||||
assert.ok(result!.includes("SUMMARY"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("resolveExpectedArtifactPath returns correct path for plan-slice", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const result = resolveExpectedArtifactPath("plan-slice", "M001/S01", base);
|
||||
assert.ok(result);
|
||||
assert.ok(result!.includes("PLAN"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("resolveExpectedArtifactPath returns null for unknown type", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const result = resolveExpectedArtifactPath("unknown-type", "M001", base);
|
||||
assert.equal(result, null);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("resolveExpectedArtifactPath returns correct path for all milestone-level types", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const planResult = resolveExpectedArtifactPath("plan-milestone", "M001", base);
|
||||
assert.ok(planResult);
|
||||
assert.ok(planResult!.includes("ROADMAP"));
|
||||
|
||||
const completeResult = resolveExpectedArtifactPath("complete-milestone", "M001", base);
|
||||
assert.ok(completeResult);
|
||||
assert.ok(completeResult!.includes("SUMMARY"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("resolveExpectedArtifactPath returns correct path for all slice-level types", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const researchResult = resolveExpectedArtifactPath("research-slice", "M001/S01", base);
|
||||
assert.ok(researchResult);
|
||||
assert.ok(researchResult!.includes("RESEARCH"));
|
||||
|
||||
const assessResult = resolveExpectedArtifactPath("reassess-roadmap", "M001/S01", base);
|
||||
assert.ok(assessResult);
|
||||
assert.ok(assessResult!.includes("ASSESSMENT"));
|
||||
|
||||
const uatResult = resolveExpectedArtifactPath("run-uat", "M001/S01", base);
|
||||
assert.ok(uatResult);
|
||||
assert.ok(uatResult!.includes("UAT-RESULT"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── diagnoseExpectedArtifact ─────────────────────────────────────────────
|
||||
|
||||
test("diagnoseExpectedArtifact returns description for known types", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const research = diagnoseExpectedArtifact("research-milestone", "M001", base);
|
||||
assert.ok(research);
|
||||
assert.ok(research!.includes("research"));
|
||||
|
||||
const plan = diagnoseExpectedArtifact("plan-slice", "M001/S01", base);
|
||||
assert.ok(plan);
|
||||
assert.ok(plan!.includes("plan"));
|
||||
|
||||
const task = diagnoseExpectedArtifact("execute-task", "M001/S01/T01", base);
|
||||
assert.ok(task);
|
||||
assert.ok(task!.includes("T01"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("diagnoseExpectedArtifact returns null for unknown type", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
assert.equal(diagnoseExpectedArtifact("unknown", "M001", base), null);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── buildLoopRemediationSteps ────────────────────────────────────────────
|
||||
|
||||
test("buildLoopRemediationSteps returns steps for execute-task", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const steps = buildLoopRemediationSteps("execute-task", "M001/S01/T01", base);
|
||||
assert.ok(steps);
|
||||
assert.ok(steps!.includes("T01"));
|
||||
assert.ok(steps!.includes("gsd doctor"));
|
||||
assert.ok(steps!.includes("[x]"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("buildLoopRemediationSteps returns steps for plan-slice", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const steps = buildLoopRemediationSteps("plan-slice", "M001/S01", base);
|
||||
assert.ok(steps);
|
||||
assert.ok(steps!.includes("PLAN"));
|
||||
assert.ok(steps!.includes("gsd doctor"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("buildLoopRemediationSteps returns steps for complete-slice", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const steps = buildLoopRemediationSteps("complete-slice", "M001/S01", base);
|
||||
assert.ok(steps);
|
||||
assert.ok(steps!.includes("S01"));
|
||||
assert.ok(steps!.includes("ROADMAP"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("buildLoopRemediationSteps returns null for unknown type", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
assert.equal(buildLoopRemediationSteps("unknown", "M001", base), null);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Completed-unit key persistence ───────────────────────────────────────
|
||||
|
||||
test("completedKeysPath returns path inside .gsd", () => {
|
||||
const path = completedKeysPath("/project");
|
||||
assert.ok(path.includes(".gsd"));
|
||||
assert.ok(path.includes("completed-units.json"));
|
||||
});
|
||||
|
||||
test("persistCompletedKey and loadPersistedKeys round-trip", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
persistCompletedKey(base, "execute-task/M001/S01/T01");
|
||||
persistCompletedKey(base, "plan-slice/M001/S02");
|
||||
|
||||
const keys = new Set<string>();
|
||||
loadPersistedKeys(base, keys);
|
||||
|
||||
assert.ok(keys.has("execute-task/M001/S01/T01"));
|
||||
assert.ok(keys.has("plan-slice/M001/S02"));
|
||||
assert.equal(keys.size, 2);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("persistCompletedKey is idempotent", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
persistCompletedKey(base, "execute-task/M001/S01/T01");
|
||||
persistCompletedKey(base, "execute-task/M001/S01/T01");
|
||||
|
||||
const keys = new Set<string>();
|
||||
loadPersistedKeys(base, keys);
|
||||
assert.equal(keys.size, 1);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("removePersistedKey removes a key", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
persistCompletedKey(base, "a");
|
||||
persistCompletedKey(base, "b");
|
||||
removePersistedKey(base, "a");
|
||||
|
||||
const keys = new Set<string>();
|
||||
loadPersistedKeys(base, keys);
|
||||
assert.ok(!keys.has("a"));
|
||||
assert.ok(keys.has("b"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("loadPersistedKeys handles missing file gracefully", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const keys = new Set<string>();
|
||||
assert.doesNotThrow(() => loadPersistedKeys(base, keys));
|
||||
assert.equal(keys.size, 0);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("removePersistedKey is safe when file doesn't exist", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
assert.doesNotThrow(() => removePersistedKey(base, "nonexistent"));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
134
src/resources/extensions/gsd/tests/crash-recovery.test.ts
Normal file
134
src/resources/extensions/gsd/tests/crash-recovery.test.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdirSync, existsSync, readFileSync, rmSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import {
|
||||
writeLock,
|
||||
clearLock,
|
||||
readCrashLock,
|
||||
isLockProcessAlive,
|
||||
formatCrashInfo,
|
||||
type LockData,
|
||||
} from "../crash-recovery.ts";
|
||||
|
||||
function makeTmpBase(): string {
|
||||
const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
|
||||
mkdirSync(join(base, ".gsd"), { recursive: true });
|
||||
return base;
|
||||
}
|
||||
|
||||
function cleanup(base: string): void {
|
||||
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
||||
}
|
||||
|
||||
// ─── writeLock / readCrashLock ────────────────────────────────────────────
|
||||
|
||||
test("writeLock creates lock file and readCrashLock reads it", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
writeLock(base, "execute-task", "M001/S01/T01", 3, "/tmp/session.jsonl");
|
||||
const lock = readCrashLock(base);
|
||||
assert.ok(lock, "lock should exist");
|
||||
assert.equal(lock!.unitType, "execute-task");
|
||||
assert.equal(lock!.unitId, "M001/S01/T01");
|
||||
assert.equal(lock!.completedUnits, 3);
|
||||
assert.equal(lock!.sessionFile, "/tmp/session.jsonl");
|
||||
assert.equal(lock!.pid, process.pid);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("readCrashLock returns null when no lock exists", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
const lock = readCrashLock(base);
|
||||
assert.equal(lock, null);
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── clearLock ────────────────────────────────────────────────────────────
|
||||
|
||||
test("clearLock removes existing lock file", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
writeLock(base, "plan-slice", "M001/S01", 0);
|
||||
assert.ok(readCrashLock(base), "lock should exist before clear");
|
||||
clearLock(base);
|
||||
assert.equal(readCrashLock(base), null, "lock should be gone after clear");
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test("clearLock is safe when no lock exists", () => {
|
||||
const base = makeTmpBase();
|
||||
try {
|
||||
assert.doesNotThrow(() => clearLock(base));
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── isLockProcessAlive ──────────────────────────────────────────────────
|
||||
|
||||
test("isLockProcessAlive returns true for current process (different pid)", () => {
|
||||
// Our own PID is explicitly excluded (recycled PID guard)
|
||||
const lock: LockData = {
|
||||
pid: process.pid,
|
||||
startedAt: new Date().toISOString(),
|
||||
unitType: "execute-task",
|
||||
unitId: "M001/S01/T01",
|
||||
unitStartedAt: new Date().toISOString(),
|
||||
completedUnits: 0,
|
||||
};
|
||||
assert.equal(isLockProcessAlive(lock), false, "own PID should return false");
|
||||
});
|
||||
|
||||
test("isLockProcessAlive returns false for dead PID", () => {
|
||||
const lock: LockData = {
|
||||
pid: 999999999, // almost certainly not running
|
||||
startedAt: new Date().toISOString(),
|
||||
unitType: "execute-task",
|
||||
unitId: "M001/S01/T01",
|
||||
unitStartedAt: new Date().toISOString(),
|
||||
completedUnits: 0,
|
||||
};
|
||||
assert.equal(isLockProcessAlive(lock), false);
|
||||
});
|
||||
|
||||
test("isLockProcessAlive returns false for invalid PIDs", () => {
|
||||
const base: Omit<LockData, "pid"> = {
|
||||
startedAt: new Date().toISOString(),
|
||||
unitType: "x",
|
||||
unitId: "x",
|
||||
unitStartedAt: new Date().toISOString(),
|
||||
completedUnits: 0,
|
||||
};
|
||||
assert.equal(isLockProcessAlive({ ...base, pid: 0 } as LockData), false);
|
||||
assert.equal(isLockProcessAlive({ ...base, pid: -1 } as LockData), false);
|
||||
assert.equal(isLockProcessAlive({ ...base, pid: 1.5 } as LockData), false);
|
||||
});
|
||||
|
||||
// ─── formatCrashInfo ─────────────────────────────────────────────────────
|
||||
|
||||
test("formatCrashInfo includes unit type, id, and PID", () => {
|
||||
const lock: LockData = {
|
||||
pid: 12345,
|
||||
startedAt: "2025-01-01T00:00:00.000Z",
|
||||
unitType: "complete-slice",
|
||||
unitId: "M002/S03",
|
||||
unitStartedAt: "2025-01-01T00:01:00.000Z",
|
||||
completedUnits: 7,
|
||||
};
|
||||
const info = formatCrashInfo(lock);
|
||||
assert.ok(info.includes("complete-slice"));
|
||||
assert.ok(info.includes("M002/S03"));
|
||||
assert.ok(info.includes("12345"));
|
||||
assert.ok(info.includes("7"));
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue