test(doctor): refactor purpose-gate test to use insertMilestone/insertSlice helpers

Cleans up doctor-purpose-gate.test.mjs:
- Uses insertMilestone/insertSlice helpers instead of raw SQL
- Removes redundant test from doctor-plan-dir-normalization.test.mjs
- Adds module-level JSDoc purpose comment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Mikael Hugo 2026-05-15 18:44:09 +02:00
parent fb68b12902
commit 59fbaf4b0f
2 changed files with 73 additions and 87 deletions

View file

@ -13,12 +13,7 @@ import {
checkEngineHealth,
normalizeLegacyPlanSlugDirectories,
} from "../doctor-engine-checks.js";
import {
closeDatabase,
insertMilestone,
insertSlice,
openDatabase,
} from "../sf-db.js";
import { closeDatabase, openDatabase } from "../sf-db.js";
const tmpDirs = [];
@ -133,43 +128,4 @@ describe("doctor plan directory normalization", () => {
false,
);
});
test("checkEngineHealth_when_planning_records_lack_purpose_reports_db_purpose_errors", async () => {
const project = makeProject();
const dbPath = join(project, ".sf", "sf.db");
assert.equal(openDatabase(dbPath), true);
insertMilestone({
id: "M001",
title: "Missing purpose milestone",
status: "active",
planning: { vision: " " },
});
insertSlice({
milestoneId: "M001",
id: "S01",
title: "Missing purpose slice",
status: "active",
sequence: 1,
planning: { goal: " " },
});
closeDatabase();
const issues = [];
await checkEngineHealth(project, issues, [], () => false);
assert.deepEqual(
issues
.filter((issue) =>
[
"db_milestone_missing_vision",
"db_slice_missing_goal",
].includes(issue.code),
)
.map((issue) => [issue.code, issue.scope, issue.unitId]),
[
["db_milestone_missing_vision", "milestone", "M001"],
["db_slice_missing_goal", "slice", "M001/S01"],
],
);
});
});

View file

@ -1,56 +1,86 @@
/**
* doctor-purpose-gate.test.mjs doctor catches DB planning records that lack purpose.
*
* Purpose: prove that DB-first planning cannot advance through autonomous
* health checks when milestone vision or slice goal fields are blank.
*/
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";
import { checkEngineHealth } from "../doctor-engine-checks.js";
import { closeDatabase, openDatabase } from "../sf-db.js";
import {
closeDatabase,
insertMilestone,
insertSlice,
openDatabase,
} from "../sf-db.js";
const tmpDirs = [];
afterEach(() => {
closeDatabase();
while (tmpDirs.length > 0) {
const dir = tmpDirs.pop();
if (dir) rmSync(dir, { recursive: true, force: true });
}
});
function makeProject() {
const dir = mkdtempSync(join(tmpdir(), "sf-purpose-gate-"));
tmpDirs.push(dir);
mkdirSync(join(dir, ".sf"), { recursive: true });
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "singularity-forge" }));
openDatabase(join(dir, ".sf", "sf.db"));
return dir;
const dir = mkdtempSync(join(tmpdir(), "sf-purpose-gate-"));
tmpDirs.push(dir);
mkdirSync(join(dir, ".sf"), { recursive: true });
openDatabase(join(dir, ".sf", "sf.db"));
return dir;
}
test("checkEngineHealth_flags_milestones_without_vision", async () => {
const dir = makeProject();
const db = new (await import("node:sqlite")).DatabaseSync(join(dir, ".sf", "sf.db"));
db.prepare('INSERT INTO milestones (id, title, status, depends_on, vision, success_criteria, key_risks, proof_strategy, verification_contract, verification_integration, verification_operational, verification_uat, definition_of_done, requirement_coverage, boundary_map_markdown, vision_meeting_json, product_research_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
.run("M001", "Test", "active", "", "", "", "", "", "", "", "", "", "", "", "", "", "");
const issues = [];
await checkEngineHealth(dir, issues, [], () => false);
const missingVision = issues.find(i => i.code === "db_milestone_missing_vision");
assert.ok(missingVision, "expected db_milestone_missing_vision issue");
assert.equal(missingVision.scope, "milestone");
afterEach(() => {
closeDatabase();
while (tmpDirs.length > 0) {
const dir = tmpDirs.pop();
if (dir) rmSync(dir, { recursive: true, force: true });
}
});
test("checkEngineHealth_flags_slices_without_goal", async () => {
const dir = makeProject();
const db = new (await import("node:sqlite")).DatabaseSync(join(dir, ".sf", "sf.db"));
db.prepare('INSERT INTO milestones (id, title, status, depends_on, vision, success_criteria, key_risks, proof_strategy, verification_contract, verification_integration, verification_operational, verification_uat, definition_of_done, requirement_coverage, boundary_map_markdown, vision_meeting_json, product_research_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
.run("M001", "Test", "active", "", "vision text", "", "", "", "", "", "", "", "", "", "", "", "");
db.prepare('INSERT INTO slices (milestone_id, id, title, status, goal, sequence) VALUES (?, ?, ?, ?, ?, ?)')
.run("M001", "S01", "Slice", "pending", "", 1);
const issues = [];
await checkEngineHealth(dir, issues, [], () => false);
const missingGoal = issues.find(i => i.code === "db_slice_missing_goal");
assert.ok(missingGoal, "expected db_slice_missing_goal issue");
assert.equal(missingGoal.scope, "slice");
test("checkEngineHealth_when_milestone_vision_is_blank_reports_purpose_error", async () => {
const dir = makeProject();
insertMilestone({
id: "M001",
title: "Missing purpose milestone",
status: "active",
planning: { vision: " " },
});
closeDatabase();
const issues = [];
await checkEngineHealth(dir, issues, [], () => false);
assert.deepEqual(
issues
.filter((issue) => issue.code === "db_milestone_missing_vision")
.map((issue) => [issue.scope, issue.unitId]),
[["milestone", "M001"]],
);
});
test("checkEngineHealth_when_slice_goal_is_blank_reports_purpose_error", async () => {
const dir = makeProject();
insertMilestone({
id: "M001",
title: "Purposeful milestone",
status: "active",
planning: { vision: "Protect DB-backed planning purpose gates." },
});
insertSlice({
milestoneId: "M001",
id: "S01",
title: "Missing purpose slice",
status: "active",
sequence: 1,
planning: { goal: " " },
});
closeDatabase();
const issues = [];
await checkEngineHealth(dir, issues, [], () => false);
assert.deepEqual(
issues
.filter((issue) => issue.code === "db_slice_missing_goal")
.map((issue) => [issue.scope, issue.unitId]),
[["slice", "M001/S01"]],
);
});