feat(FN-4578): complete Step 2 — persist milestone acceptance criteria
Fusion-Task-Id: FN-4578 Fusion-Task-Lineage: 1be0fa08-4d83-4988-a218-326225dfe4b5
This commit is contained in:
@@ -790,6 +790,69 @@ describe("MissionStore", () => {
|
||||
expect(updated.status).toBe("active");
|
||||
});
|
||||
|
||||
it("persists milestone acceptance criteria on create", () => {
|
||||
const mission = store.createMission({ title: "Parent" });
|
||||
const milestone = store.addMilestone(mission.id, {
|
||||
title: "Original",
|
||||
acceptanceCriteria: "Ship all phase outputs",
|
||||
});
|
||||
|
||||
const fetched = store.getMilestone(milestone.id);
|
||||
expect(fetched?.acceptanceCriteria).toBe("Ship all phase outputs");
|
||||
});
|
||||
|
||||
it("updates milestone acceptance criteria and persists across reopen", () => {
|
||||
const fileDb = new Database(fusionDir);
|
||||
fileDb.init();
|
||||
const fileStore = new MissionStore(fusionDir, fileDb);
|
||||
|
||||
const mission = fileStore.createMission({ title: "Parent" });
|
||||
const milestone = fileStore.addMilestone(mission.id, { title: "Original" });
|
||||
fileStore.updateMilestone(milestone.id, { acceptanceCriteria: "All validators pass" });
|
||||
|
||||
fileDb.close();
|
||||
|
||||
const reopenedDb = new Database(fusionDir);
|
||||
reopenedDb.init();
|
||||
const reopenedStore = new MissionStore(fusionDir, reopenedDb);
|
||||
const reopened = reopenedStore.getMilestone(milestone.id);
|
||||
|
||||
expect(reopened?.acceptanceCriteria).toBe("All validators pass");
|
||||
reopenedDb.close();
|
||||
});
|
||||
|
||||
it("partial milestone acceptance criteria update preserves other fields", () => {
|
||||
const mission = store.createMission({ title: "Parent" });
|
||||
const milestone = store.addMilestone(mission.id, {
|
||||
title: "Original",
|
||||
description: "Phase 1",
|
||||
verification: "Run smoke tests",
|
||||
});
|
||||
|
||||
const updated = store.updateMilestone(milestone.id, {
|
||||
acceptanceCriteria: "Phase complete when smoke tests pass",
|
||||
});
|
||||
|
||||
expect(updated.title).toBe("Original");
|
||||
expect(updated.description).toBe("Phase 1");
|
||||
expect(updated.verification).toBe("Run smoke tests");
|
||||
expect(updated.acceptanceCriteria).toBe("Phase complete when smoke tests pass");
|
||||
});
|
||||
|
||||
it("clears milestone acceptance criteria when updated with undefined", () => {
|
||||
const mission = store.createMission({ title: "Parent" });
|
||||
const milestone = store.addMilestone(mission.id, {
|
||||
title: "Original",
|
||||
acceptanceCriteria: "Initial criteria",
|
||||
});
|
||||
|
||||
const updated = store.updateMilestone(milestone.id, { acceptanceCriteria: undefined });
|
||||
const fetched = store.getMilestone(milestone.id);
|
||||
|
||||
expect(updated.acceptanceCriteria).toBeUndefined();
|
||||
expect(fetched?.acceptanceCriteria).toBeUndefined();
|
||||
});
|
||||
|
||||
it("deletes a milestone", () => {
|
||||
const mission = store.createMission({ title: "Parent" });
|
||||
const milestone = store.addMilestone(mission.id, { title: "To Delete" });
|
||||
|
||||
@@ -335,6 +335,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
|
||||
dependencies: fromJson<string[]>(row.dependencies) || [],
|
||||
planningNotes: row.planningNotes || undefined,
|
||||
verification: row.verification || undefined,
|
||||
acceptanceCriteria: row.acceptanceCriteria || undefined,
|
||||
validationState: (row.validationState as MilestoneValidationState) || "not_started",
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
@@ -1158,14 +1159,15 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
|
||||
dependencies: input.dependencies || [],
|
||||
planningNotes: input.planningNotes,
|
||||
verification: input.verification,
|
||||
acceptanceCriteria: input.acceptanceCriteria,
|
||||
validationState: "not_started",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
this.db.prepare(`
|
||||
INSERT INTO milestones (id, missionId, title, description, status, orderIndex, interviewState, dependencies, planningNotes, verification, validationState, createdAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO milestones (id, missionId, title, description, status, orderIndex, interviewState, dependencies, planningNotes, verification, acceptanceCriteria, validationState, createdAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
milestone.id,
|
||||
milestone.missionId,
|
||||
@@ -1177,6 +1179,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
|
||||
toJson(milestone.dependencies),
|
||||
milestone.planningNotes ?? null,
|
||||
milestone.verification ?? null,
|
||||
milestone.acceptanceCriteria ?? null,
|
||||
milestone.validationState as string,
|
||||
milestone.createdAt,
|
||||
milestone.updatedAt,
|
||||
@@ -1245,6 +1248,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
|
||||
dependencies = ?,
|
||||
planningNotes = ?,
|
||||
verification = ?,
|
||||
acceptanceCriteria = ?,
|
||||
validationState = ?,
|
||||
updatedAt = ?
|
||||
WHERE id = ?
|
||||
@@ -1257,6 +1261,7 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
|
||||
toJson(updated.dependencies),
|
||||
updated.planningNotes ?? null,
|
||||
updated.verification ?? null,
|
||||
updated.acceptanceCriteria ?? null,
|
||||
updated.validationState || "not_started",
|
||||
updated.updatedAt,
|
||||
updated.id,
|
||||
@@ -3340,14 +3345,14 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
|
||||
}
|
||||
|
||||
for (const milestone of snapshot.payload.milestones) {
|
||||
this.db.prepare(`INSERT INTO milestones (id, missionId, title, description, status, orderIndex, interviewState, dependencies, planningNotes, verification, validationState, createdAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
this.db.prepare(`INSERT INTO milestones (id, missionId, title, description, status, orderIndex, interviewState, dependencies, planningNotes, verification, acceptanceCriteria, validationState, createdAt, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET title=excluded.title, description=excluded.description, status=excluded.status, orderIndex=excluded.orderIndex,
|
||||
interviewState=excluded.interviewState, dependencies=excluded.dependencies, planningNotes=excluded.planningNotes, verification=excluded.verification,
|
||||
validationState=excluded.validationState, updatedAt=excluded.updatedAt`).run(
|
||||
acceptanceCriteria=excluded.acceptanceCriteria, validationState=excluded.validationState, updatedAt=excluded.updatedAt`).run(
|
||||
milestone.id, milestone.missionId, milestone.title, milestone.description ?? null, milestone.status, milestone.orderIndex,
|
||||
milestone.interviewState, toJsonNullable(milestone.dependencies), milestone.planningNotes ?? null, milestone.verification ?? null,
|
||||
milestone.validationState ?? null, milestone.createdAt, milestone.updatedAt,
|
||||
milestone.acceptanceCriteria ?? null, milestone.validationState ?? null, milestone.createdAt, milestone.updatedAt,
|
||||
);
|
||||
applied++;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user