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:
parent
fb68b12902
commit
59fbaf4b0f
2 changed files with 73 additions and 87 deletions
|
|
@ -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"],
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"]],
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue