src/features/reviews/commands.test.ts
Linked repository file from this build.
import { describe, expect, test } from "bun:test";
import { createSeed, completedReviewAnswers } from "@/programs/snap-negative/seed";
import {
activeStoredFindings,
captureReceivedSnapshot,
canAddNote,
canEdit,
canRefuseFinding,
canUpdateNote,
execute,
finalCurrent,
openFindingsBlockApproval,
requiredInputCurrent,
validationCurrent,
type ReviewCommand,
} from "./commands";
import type { Actor } from "./types";
import { transition } from "./workflow";
import { demoActor } from "@/lib/auth/roster";
const examiner: Actor = { ...demoActor("examiner"), name: "Test Examiner" };
const supervisor: Actor = { ...demoActor("supervisor"), name: "Sam Supervisor" };
const pec: Actor = { ...demoActor("pec"), name: "Pat Policy" };
const pm: Actor = { ...demoActor("program_manager"), name: "Morgan Manager" };
const meta = { id: "event-1", at: "2026-09-05T12:00:00Z" };
function readyReview(outcome: "clean" | "error" = "error", openFinding = false) {
let review = createSeed();
review = execute(
review,
{
type: "answers",
value: completedReviewAnswers({
finding: openFinding ? "1" : outcome === "error" ? "2" : "1",
recordedReasonAccuracy: openFinding || outcome === "error" ? "2" : "1",
}),
},
examiner,
meta,
);
review = execute(review, { type: "apply_final" }, examiner, meta);
review = execute(review, { type: "outcome", value: outcome }, examiner, meta);
return review;
}
describe("SNAP Negative review commands", () => {
test("submit stores edit check and allows awaiting review with open findings", () => {
let review = readyReview("error", true);
review = execute(review, { type: "transition", action: "submit", note: "" }, examiner, {
...meta,
id: "submit-1",
});
expect(review.state).toBe("awaiting_review");
expect(review.validation?.findings.length).toBeGreaterThan(0);
expect(openFindingsBlockApproval(review)).toBe(true);
expect(requiredInputCurrent(review)).toBe(true);
});
test("approval preserves explicit finding refusals but waits for the routing map", () => {
let review = execute(
readyReview("clean", true),
{ type: "transition", action: "submit", note: "" },
examiner,
meta,
);
expect(() =>
execute(review, { type: "transition", action: "approve", note: "" }, supervisor, meta),
).toThrow("Resolve or refuse");
review = execute(
review,
{
type: "refuse_finding",
findingId: review.validation!.findings[0]!.id,
reason: "Verified evidence",
},
supervisor,
meta,
);
expect(activeStoredFindings(review.validation!.findings)).toHaveLength(0);
expect(() =>
execute(review, { type: "transition", action: "approve", note: "" }, supervisor, meta),
).toThrow("defect map");
});
test("manager coverage returns facts to the examiner and resubmission captures a new copy", () => {
let review = execute(
readyReview(),
{ type: "transition", action: "submit", note: "" },
examiner,
meta,
);
const original = structuredClone(review.receivedSnapshots!.supervisor);
review = execute(
review,
{ type: "transition", action: "request_changes", note: "Check the office assignment." },
pm,
meta,
);
review = execute(
review,
{ type: "answers", value: { ...review.facts.answers, narrative: "Corrected evidence" } },
examiner,
meta,
);
review = execute(review, { type: "transition", action: "submit", note: "" }, examiner, meta);
expect(review.receivedSnapshots!.supervisor!.facts.answers.narrative).toBe(
"Corrected evidence",
);
expect(original!.facts.answers.narrative).not.toBe("Corrected evidence");
});
test("returned input edits invalidate Final Results and prevent resubmission", () => {
let review = execute(
readyReview(),
{ type: "transition", action: "submit", note: "" },
examiner,
meta,
);
review = execute(
review,
{ type: "transition", action: "request_changes", note: "Check household" },
supervisor,
meta,
);
review = execute(review, { type: "household", value: review.facts.members }, examiner, meta);
expect(finalCurrent(review)).toBe(false);
expect(validationCurrent(review)).toBe(false);
expect(() =>
execute(review, { type: "transition", action: "submit", note: "" }, examiner, meta),
).toThrow("Final Results");
});
test("note update and PEC ordinary notes follow increment 9 rules", () => {
let review = createSeed();
review = execute(review, { type: "note", body: "Initial narrative draft." }, examiner, meta);
const noteId = review.notes[0]!.id;
expect(canAddNote(pec)).toBe(true);
review = execute(review, { type: "note", body: "PEC working note." }, pec, {
...meta,
id: "pec-note",
});
expect(review.notes[1]?.kind).toBe("pec");
review = execute(
review,
{ type: "update_note", noteId, body: "Revised narrative draft." },
examiner,
{ ...meta, id: "note-edit" },
);
expect(review.notes[0]?.body).toBe("Revised narrative draft.");
expect(canUpdateNote(review, supervisor, noteId)).toBe(true);
expect(canRefuseFinding(examiner)).toBe(false);
expect(canRefuseFinding(supervisor)).toBe(true);
});
test("senior staff cannot edit federal facts", () => {
const review = { ...createSeed(), state: "awaiting_review" as const };
for (const actor of [supervisor, pm]) {
expect(canEdit(review, actor)).toBe(false);
expect(() => execute(review, { type: "outcome", value: "clean" }, actor, meta)).toThrow(
"examiner",
);
}
});
test("examiner and PEC edit restrictions apply in commands", () => {
const review = createSeed();
expect(() =>
execute(
{ ...review, state: "awaiting_review" },
{ type: "answers", value: review.facts.answers },
examiner,
meta,
),
).toThrow("examiner");
expect(() =>
execute(review, { type: "identity", value: { clientName: "Blocked" } }, pec, meta),
).toThrow("PEC");
});
});
test("every workflow state prevents senior fact edits", () => {
const states = [
"new_active",
"needs_changes",
"awaiting_review",
"awaiting_pec_approval",
"awaiting_manager",
"awaiting_cao_response",
"released_to_reporting",
"dropped",
] as const;
for (const state of states) {
for (const actor of [supervisor, pm]) {
const review = { ...createSeed(), state };
const commands: ReviewCommand[] = [
{ type: "identity", value: { clientName: "Senior edit" } },
{ type: "household", value: review.facts.members },
{ type: "negative_action", value: { actionDate: "2026-04-02" } },
{ type: "scenario", scenario: "compI", value: review.facts.scenarios.compI },
{ type: "answers", value: review.facts.answers },
{ type: "outcome", value: "clean" },
{ type: "note", body: "Synthetic senior note" },
{ type: "apply_final" },
];
for (const command of commands) {
if (command.type === "note")
expect(() => execute(review, command, actor, meta)).not.toThrow();
else expect(() => execute(review, command, actor, meta)).toThrow();
if (command.type !== "note") {
expect(() => execute(review, command, pec, meta)).toThrow();
}
}
for (const paCap of ["yes", "unknown"] as const) {
const leftover = { ...review, facts: { ...review.facts, paCap } };
expect(() => execute(leftover, { type: "apply_final" }, actor, meta)).toThrow();
}
}
}
});
test("transition approve uses required input not validation pass", () => {
expect(
transition(
{
state: "new_active",
outcome: "clean",
role: "examiner",
requiredInputCurrent: true,
computationCurrent: true,
openFindingsBlockApproval: true,
note: "",
},
"submit",
),
).toBe("awaiting_review");
});
test("command permissions reject desk and statistics note writes", () => {
for (const role of ["admin", "statistics"] as const) {
expect(() =>
execute(
createSeed(),
{ type: "note", body: "Unauthorized" },
{ ...demoActor(role), name: role },
meta,
),
).toThrow("cannot add");
}
});
test("scenario saves reject orphaned members and duplicate income IDs", () => {
const review = createSeed();
const value = structuredClone(review.facts.scenarios.compI);
value.income[0].memberId = "missing";
expect(() =>
execute(review, { type: "scenario", scenario: "compI", value }, examiner, meta),
).toThrow("household member");
value.income = [review.facts.scenarios.compI.income[0], review.facts.scenarios.compI.income[0]];
expect(() =>
execute(review, { type: "scenario", scenario: "compI", value }, examiner, meta),
).toThrow("unique");
});
test("action edits stay consistent between header and coded answers", () => {
let review = createSeed();
review = execute(
review,
{ type: "answers", value: { ...review.facts.answers, actionType: "3" } },
examiner,
meta,
);
expect(review.facts.negativeAction).toBe("Suspension");
review = execute(
review,
{ type: "negative_action", value: { negativeAction: "Termination" } },
examiner,
meta,
);
expect(review.facts.answers.actionType).toBe("2");
});
test("policy working notes belong to PEC and desk roles cannot edit old notes", () => {
const review = createSeed();
expect(() =>
execute(review, { type: "note", kind: "pec", body: "Policy" }, examiner, meta),
).toThrow("Only PEC");
const desk = { ...demoActor("admin"), name: "Old desk actor" };
review.notes.push({ id: "old", actor: desk, body: "Legacy note", createdAt: meta.at });
expect(() =>
execute(review, { type: "update_note", noteId: "old", body: "Changed" }, desk, meta),
).toThrow("cannot edit");
});
test("clearing a roster removes linked income but preserves scenario sizes and saved evidence", () => {
const review = readyReview();
const before = structuredClone(review);
const cleared = execute(review, { type: "clear_roster" }, examiner, meta);
expect(cleared.facts.members).toEqual([]);
for (const id of ["eligibility", "compI", "compII"] as const) {
expect(cleared.facts.scenarios[id].income).toEqual([]);
expect(cleared.facts.scenarios[id].householdSize).toBe(
before.facts.scenarios[id].householdSize,
);
}
expect(finalCurrent(cleared)).toBe(false);
expect(review).toEqual(before);
expect(() =>
execute(review, { type: "clear_roster" }, { ...demoActor("admin"), name: "Desk" }, meta),
).toThrow();
});
test("manager kickback clears stakeholder approvals", () => {
const review = {
...readyReview(),
state: "awaiting_manager" as const,
supervisorApproved: { actor: supervisor, at: meta.at },
pecApproved: { actor: pec, at: meta.at },
};
const next = execute(
review,
{ type: "transition", action: "request_changes", note: "Incorrect assignment" },
pm,
meta,
);
expect(next.state).toBe("needs_changes");
expect(next.supervisorApproved).toBeUndefined();
expect(next.pecApproved).toBeUndefined();
});
test("documents require a name and purpose and metadata edits preserve checked facts", () => {
let review = readyReview();
const document = {
id: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
name: "Notice",
filename: "notice.pdf",
description: "Supports notice accuracy.",
category: "Evidence" as const,
mimeType: "application/pdf" as const,
size: 20,
storageKey: "notice.pdf",
actor: examiner,
createdAt: meta.at,
};
expect(() =>
execute(
review,
{ type: "document", document: { ...document, description: " " } },
examiner,
meta,
),
).toThrow("purpose");
review = execute(review, { type: "document", document }, examiner, meta);
const revision = review.revision;
review = execute(
review,
{
type: "update_document",
documentId: document.id,
name: "Rejection notice",
description: "Explains the action reason.",
},
examiner,
meta,
);
expect(review.documents[0]!.name).toBe("Rejection notice");
expect(review.documents[0]!.description).toBe("Explains the action reason.");
expect(review.revision).toBe(revision);
expect(() =>
execute(
review,
{ type: "update_document", documentId: document.id, name: "", description: "Purpose" },
examiner,
meta,
),
).toThrow("name");
});
test("new answer edits allow only one variance pair and leave historic storage intact", () => {
const review = createSeed();
review.facts.answers.variances = [
{ element: "140", nature: "099" },
{ element: "111", nature: "001" },
];
expect(() =>
execute(review, { type: "answers", value: review.facts.answers }, examiner, meta),
).toThrow("one element");
expect(review.facts.answers.variances).toHaveLength(2);
const next = execute(
review,
{
type: "answers",
value: { ...review.facts.answers, variances: review.facts.answers.variances.slice(0, 1) },
},
examiner,
meta,
);
expect(next.facts.answers.variances).toHaveLength(1);
});
test("restore keeps stale Final Results stale and checks income ownership", () => {
let review = readyReview();
review = execute(
review,
{
type: "scenario",
scenario: "compI",
value: { ...review.facts.scenarios.compI, householdSize: 0, populated: false },
},
examiner,
meta,
);
const snapshot = captureReceivedSnapshot(review);
expect(finalCurrent(review)).toBe(false);
const restored = execute(review, { type: "restore_facts", ...snapshot }, examiner, meta);
expect(finalCurrent(restored)).toBe(false);
expect(restored.facts.scenarios).toEqual(snapshot.facts.scenarios);
expect(restored.facts.inputsRevision).toBe(review.facts.inputsRevision + 1);
expect(() =>
execute(
review,
{ type: "restore_facts", ...snapshot, facts: { ...snapshot.facts, members: [] } },
examiner,
meta,
),
).toThrow("household member");
});
test("restoring unrelated fields preserves unchanged historic variance pairs", () => {
const review = createSeed();
review.facts.answers.variances = [
{ element: "140", nature: "099" },
{ element: "111", nature: "001" },
];
const snapshot = captureReceivedSnapshot(review);
const restored = execute(
review,
{
type: "restore_facts",
...snapshot,
identity: { ...snapshot.identity, clientName: "Restored" },
},
examiner,
meta,
);
expect(restored.facts.answers.variances).toEqual(review.facts.answers.variances);
snapshot.facts.answers.variances.push({ element: "416", nature: "096" });
expect(() => execute(review, { type: "restore_facts", ...snapshot }, examiner, meta)).toThrow(
"one element",
);
});
test("note authors are matched by staff ID, not display name", () => {
const author: Actor = {
name: "Alex Examiner",
role: "examiner",
staffId: "101",
program: "SNAP",
};
const namesake: Actor = { ...author, staffId: "102" };
let review = { ...createSeed(), examiner: "101" };
review = execute(review, { type: "note", body: "Mine." }, author, meta);
const noteId = review.notes[0]!.id;
expect(canUpdateNote(review, author, noteId)).toBe(true);
expect(canUpdateNote(review, { ...author, name: "Alex Renamed" }, noteId)).toBe(true);
expect(canUpdateNote(review, namesake, noteId)).toBe(false);
});
test("records keep who acted, never session scope or user-editable preferences", () => {
const session = {
...demoActor("examiner"),
preferences: { autosave: true, big: "x".repeat(500) },
};
const review = execute(createSeed(), { type: "note", body: "Saved." }, session, meta);
const expected = { name: session.name, role: "examiner" as const, staffId: "101" };
expect(review.notes.at(-1)?.actor).toEqual(expected);
expect(review.activity.at(-1)?.actor).toEqual(expected);
});