// Every guard is tested twice: once as built, and once with the guard switched off // (the "negative control"). The negative control must show the failure the guard // prevents; if it did not, the first test would prove nothing. import { describe, expect, it } from "vitest"; import { EXAMPLE_LETTER, composeMorningSummary, emptyCheckState, markNotified, planTriggers, runCheck, runPass, type CheckConfig, type Guard, type PassConfig, } from "../src/core"; import { ManualClock, MemoryJournal, MemoryLock, MemoryMail, MemorySheet } from "../src/core/memory"; const CHECK: CheckConfig = { staleAfterMinutes: 40, pendingAfterMinutes: 60, contacts: { technicalOwner: "Technical owner", hiringCoordinator: "Hiring coordinator" }, }; function world(rows: string[][] = [], opts: { mode?: "dry-run" | "live"; off?: Guard[]; scope?: string } = {}) { const clock = new ManualClock(new Date("2026-09-21T13:00:00Z")); const sheet = new MemorySheet(rows); const mail = new MemoryMail(clock); const journal = new MemoryJournal(); const lock = new MemoryLock(); const config: PassConfig = { mode: opts.mode ?? "live", scopeFromStartDate: opts.scope ?? "2026-09-01", sendingPaused: false, letter: EXAMPLE_LETTER, unsafeDisabledGuards: opts.off, }; let n = 0; const deps = { source: sheet, sender: mail, journal, clock, lock, newPassId: () => `P${++n}` }; const pass = (over: Partial = {}) => runPass(deps, { ...config, ...over }); const check = (over: Partial = {}) => runCheck({ readHiring: () => sheet.readHiring(), readJournal: () => journal.read() }, { ...CHECK, ...over, unsafeDisabledGuards: opts.off }, clock.now()); const to = () => mail.outbox.map((m) => m.to); return { clock, sheet, mail, journal, lock, deps, config, pass, check, to }; } // [Arrival ID, Therapist, Status, Start date, Welcome email, Approved by] const MAYA = ["", "Maya Okafor", "Hired", "2026-10-05", "maya.okafor@example.org", "KR"]; const DANIEL = ["", "Daniel Reyes", "Hired", "2026-10-12", "daniel.reyes@example.org", "KR"]; describe("normal arrival", () => { it("sends one letter to the approved address, with the name and start date merged", () => { const w = world([MAYA]); const r = w.pass(); expect(r.outcome).toBe("complete"); expect(w.to()).toEqual(["maya.okafor@example.org"]); expect(w.mail.outbox[0].subject).toBe("Welcome to Mentella Health, Maya"); expect(w.mail.outbox[0].body).toContain("Monday, October 5"); expect(w.journal.events.map((e) => e.type)).toEqual(["id-assigned", "approval", "attempt", "sent", "pass"]); w.pass(); expect(w.to()).toHaveLength(1); }); it("does not send before the team approves", () => { const w = world([[...MAYA.slice(0, 5), ""]]); w.pass(); expect(w.to()).toEqual([]); expect(w.pass().views[0].reason).toBe("not-approved"); }); it("negative control: without the approval rule, an unapproved arrival is sent", () => { const w = world([[...MAYA.slice(0, 5), ""]], { off: ["approval-required"] }); w.pass(); expect(w.to()).toEqual(["maya.okafor@example.org"]); }); it('reads "hired " as Hired and never reads "Offer accepted" as Hired', () => { const w = world([ ["", "Maya Okafor", "hired ", "2026-10-05", "maya.okafor@example.org", "KR"], ["", "Tom Beaulieu", "Offer accepted", "2026-10-05", "tom.b@example.org", "KR"], ]); w.pass(); expect(w.to()).toEqual(["maya.okafor@example.org"]); }); it('negative control: without normalisation, "hired " is missed', () => { const w = world([["", "Maya Okafor", "hired ", "2026-10-05", "maya.okafor@example.org", "KR"]], { off: ["status-normalized"] }); w.pass(); expect(w.to()).toEqual([]); }); it("holds a letter whose address changed after approval, until the team approves the new one", () => { const w = world([[...MAYA.slice(0, 5), ""]]); w.pass(); // waiting for approval w.sheet.set(2, "Approved by", "KR"); w.pass({ sendingPaused: true }); // approval recorded for maya.okafor@..., sending paused w.sheet.set(2, "Welcome email", "maya.personal@example.net"); const r = w.pass(); expect(w.to()).toEqual([]); expect(r.views[0].reason).toBe("address-changed"); w.sheet.addDecision(w.sheet.get(2, "Arrival ID"), "Approve new address", "KR"); w.pass(); expect(w.to()).toEqual(["maya.personal@example.net"]); }); }); describe("copied and sorted rows keep their identity", () => { it("a copied row does not produce a second letter; sorting sends the right people", () => { const w = world([MAYA]); w.pass(); w.sheet.copyRow(2); // same Arrival ID w.sheet.addRow({ therapist: "Daniel Reyes", status: "Hired", startDate: "2026-10-12", welcomeEmail: "daniel.reyes@example.org", approvedBy: "KR" }); w.sheet.sortBy("Therapist"); // Daniel now above Maya w.pass(); w.pass(); expect(w.to()).toEqual(["maya.okafor@example.org", "daniel.reyes@example.org"]); }); it("negative control: with identity by row number, sorting gives Maya a second letter and Daniel none", () => { const w = world([MAYA], { off: ["stable-arrival-id"] }); w.pass(); w.sheet.addRow({ therapist: "Daniel Reyes", status: "Hired", startDate: "2026-10-12", welcomeEmail: "daniel.reyes@example.org", approvedBy: "KR" }); w.sheet.sortBy("Therapist"); w.pass(); expect(w.to()).toEqual(["maya.okafor@example.org", "maya.okafor@example.org"]); }); it("negative control: a copy is caught by two separate guards; only with both off is it sent twice", () => { const one = world([MAYA], { off: ["stable-arrival-id"] }); one.pass(); one.sheet.copyRow(2); one.pass(); expect(one.to()).toHaveLength(1); // the same-address-same-date hold still catches it const both = world([MAYA], { off: ["stable-arrival-id", "possible-duplicate-hold"] }); both.pass(); both.sheet.copyRow(2); both.pass(); expect(both.to()).toHaveLength(2); }); }); describe("two passes at the same time", () => { function race(off?: Guard[]) { const w = world([MAYA], { off }); w.pass({ mode: "dry-run" }); // IDs and approval recorded, nothing sent // Pass B starts while pass A has read the journal but not yet written anything. w.journal.onRead = () => { runPass(w.deps, { ...w.config, unsafeDisabledGuards: off }); }; w.pass(); return w; } it("the second pass waits for the first: one letter", () => { expect(race().to()).toHaveLength(1); }); it("negative control: without the exclusive pass, two letters", () => { expect(race(["exclusive-pass"]).to()).toHaveLength(2); }); }); describe("the execution stops after the mail service accepted the letter", () => { function interrupted(off?: Guard[]) { const w = world([MAYA], { off }); w.pass({ mode: "dry-run" }); w.mail.afterSend = () => { w.journal.broken = true; // the run dies before it can record "sent" }; expect(() => w.pass()).toThrow(); w.mail.afterSend = null; w.journal.broken = false; w.clock.advance(15); const next = w.pass(); return { w, next }; } it("marks the letter as uncertain and never resends on its own", () => { const { w, next } = interrupted(); expect(w.to()).toHaveLength(1); expect(next.views[0].reason).toBe("uncertain-send"); const c = w.check(); expect(c.alerts.map((a) => a.kind)).toContain("uncertain-send"); // The team checks the Sent folder and records what it found. w.sheet.addDecision(next.views[0].key, "Confirm sent", "JV"); expect(w.pass().views[0].state).toBe("sent"); expect(w.to()).toHaveLength(1); }); it('"Send again" from the team sends exactly one more letter', () => { const { w, next } = interrupted(); w.sheet.addDecision(next.views[0].key, "Send again", "JV"); w.pass(); w.pass(); expect(w.to()).toHaveLength(2); }); it("negative control: recording only after the send, the next pass sends it again", () => { const { w } = interrupted(["attempt-before-send"]); expect(w.to()).toHaveLength(2); }); }); describe("a required column goes missing", () => { it("the pass stops before sending and the check reports it cannot check", () => { const w = world([MAYA]); w.sheet.renameColumn("Welcome email", "Email (personal)"); const r = w.pass(); expect(r.outcome).toBe("failed"); expect(w.to()).toEqual([]); const c = w.check(); expect(c.status).toBe("check-impossible"); expect(c.alerts.find((a) => a.kind === "column-missing")?.what).toContain('"Welcome email"'); }); it("negative control: without the column check, nothing is sent and the check says all clear", () => { const w = world([MAYA], { off: ["schema-check"] }); w.sheet.renameColumn("Welcome email", "Email (personal)"); const r = w.pass(); expect(r.outcome).toBe("complete"); expect(w.to()).toEqual([]); expect(w.check().status).toBe("all-clear"); }); it("an unreadable sheet is never reported as zero problems", () => { const w = world([MAYA]); w.pass(); w.sheet.unreadable = true; expect(w.check().status).toBe("check-impossible"); }); }); describe("the sending routine stops (its trigger is deleted)", () => { function stopped(off?: Guard[]) { const w = world([MAYA], { off }); for (let i = 0; i < 3; i++) { w.pass(); w.clock.advance(15); } // Trigger deleted: no more passes. Time goes on. w.clock.advance(45); return w.check(); } it("the check raises one alert saying since when, who acts and what to do", () => { const c = stopped(); const a = c.alerts.find((x) => x.kind === "routine-stopped")!; expect(a).toBeDefined(); expect(a.since).toBe("2026-09-21T13:30:00.000Z"); expect(a.who).toBe("Technical owner"); expect(a.action).toContain("installTriggers"); expect(c.toNotify).toHaveLength(1); }); it("negative control: without the heartbeat check, all clear", () => { expect(stopped(["heartbeat-check"]).status).toBe("all-clear"); }); it("an alert whose note was accepted is not sent again at the next check", () => { const w = world([MAYA]); w.pass(); w.clock.advance(60); const first = w.check(); const second = runCheck({ readHiring: () => w.sheet.readHiring(), readJournal: () => w.journal.read() }, CHECK, w.clock.now(), markNotified({ firstSeen: first.firstSeen, notified: first.notified }, first.toNotify.map((a) => a.key), first.checkedAt)); expect(first.toNotify).toHaveLength(1); expect(second.toNotify).toHaveLength(0); expect(second.alerts).toHaveLength(1); }); }); describe("go-live: the first read never mails the history", () => { const history = Array.from({ length: 30 }, (_, i) => ["", `Past Therapist ${i}`, "Hired", "2025-0" + ((i % 9) + 1) + "-15", `past${i}@example.org`, "KR"]); it("dry run records what it would send; live mode sends only arrivals from the chosen start date", () => { const w = world([...history, MAYA], { scope: "2026-09-01" }); const dry = w.pass({ mode: "dry-run" }); expect(w.to()).toEqual([]); expect(dry.baselined).toHaveLength(30); expect(dry.wouldSend.map((x) => x.to)).toEqual(["maya.okafor@example.org"]); w.pass(); expect(w.to()).toEqual(["maya.okafor@example.org"]); }); it("negative control: without the go-live scope, thirty old hires get a welcome letter", () => { const w = world([...history, MAYA], { off: ["go-live-scope"] }); w.pass(); expect(w.to()).toHaveLength(31); }); }); describe("installing twice", () => { const desired = [ { handler: "runPass", kind: "every-minutes" as const, value: 15 }, ]; function install(off?: Guard[]) { const triggers: { id: string; handler: string }[] = []; for (let i = 0; i < 2; i++) { const plan = planTriggers(triggers, desired, ["runPass"], off); for (const c of plan.create) triggers.push({ id: `t${triggers.length}`, handler: c.handler }); } return triggers; } it("leaves exactly one trigger", () => { expect(install()).toHaveLength(1); }); it("negative control: without deduplication, two triggers", () => { expect(install(["trigger-dedup"])).toHaveLength(2); }); it("removes extra copies left by an older installer", () => { const plan = planTriggers([{ id: "a", handler: "runPass" }, { id: "b", handler: "runPass" }], desired, ["runPass"]); expect(plan).toEqual({ create: [], remove: ["b"] }); }); }); describe("withdrawal", () => { it("cancels a letter still waiting (here: daily mail limit reached) and keeps the history", () => { const w = world([MAYA]); w.mail.quota = 0; w.pass(); // approved, not sent: limit reached w.sheet.set(2, "Status", "Withdrawn"); w.mail.quota = 100; const r = w.pass(); expect(w.to()).toEqual([]); expect(r.cancelled).toHaveLength(1); expect(w.journal.events.some((e) => e.type === "approval")).toBe(true); }); it("negative control: without the cancellation, the withdrawn therapist is welcomed", () => { const w = world([MAYA], { off: ["withdrawal-cancels"] }); w.mail.quota = 0; w.pass(); w.sheet.set(2, "Status", "Withdrawn"); w.mail.quota = 100; w.pass(); expect(w.to()).toEqual(["maya.okafor@example.org"]); }); it("after the letter went out, a withdrawal changes nothing sent and is noted", () => { const w = world([MAYA]); w.pass(); w.sheet.set(2, "Status", "Withdrawn"); const r = w.pass(); expect(r.views[0].reason).toBe("withdrawn-after-sent"); expect(w.check().notes.join(" ")).toContain("withdrew after"); }); }); describe("rehire", () => { function rehire(off?: Guard[]) { const w = world([MAYA], { off }); w.pass(); w.clock.set("2028-03-01T14:00:00Z"); w.sheet.set(2, "Status", "Left"); w.sheet.addRow({ therapist: "Maya Okafor", status: "Hired", startDate: "2028-03-16", welcomeEmail: "maya.okafor@example.org", approvedBy: "KR" }); w.pass(); return w; } it("is a new arrival with its own letter", () => { expect(rehire().to()).toHaveLength(2); }); it("negative control: deduplicating by person, the returning therapist gets no letter", () => { expect(rehire(["arrival-not-person"]).to()).toHaveLength(1); }); }); describe("the check looks at results, not only at the routine", () => { it("an approved arrival still unsent after an hour is reported, with the reason where to look", () => { const w = world([MAYA]); w.mail.quota = 0; w.pass(); w.clock.advance(75); w.pass(); const c = w.check(); expect(c.alerts.map((a) => a.kind)).toEqual(["pending-too-long"]); }); it("a refused send is shown for review and not retried on its own", () => { const w = world([MAYA]); w.mail.failWith = "Invalid email: maya.okafor@example.org"; w.pass(); w.mail.failWith = null; w.pass(); expect(w.to()).toEqual([]); expect(w.check().alerts.map((a) => a.kind)).toContain("send-error"); }); it("sending paused: letters wait, the check says so", () => { const w = world([MAYA]); w.pass({ sendingPaused: true }); expect(w.to()).toEqual([]); expect(w.check().notes.join(" ")).toContain("paused"); }); it("a Decisions row that does not apply is recorded as ignored and never used later", () => { const w = world([MAYA]); w.pass(); const id = w.sheet.get(2, "Arrival ID"); w.sheet.addDecision(id, "Send again", "JV"); w.pass(); w.pass(); expect(w.to()).toHaveLength(1); expect(w.journal.events.filter((e) => e.type === "decision").map((e) => (e as { applied: boolean }).applied)).toEqual([false]); }); it("an Arrival ID is written only if the row still holds the same person", () => { const w = world([[...DANIEL]]); const original = w.sheet.writeArrivalId.bind(w.sheet); w.sheet.writeArrivalId = (row, expected, id) => { w.sheet.set(2, "Therapist", "Someone Else"); // a colleague edits the row mid-pass return original(row, expected, id); }; const r = w.pass(); expect(r.idsAssigned).toEqual([]); expect(w.to()).toEqual([]); }); }); // ---------- Reproductions from the review of September 25, 2026 (A01-A03, B01-B14) ---------- describe("A01/A02: an alert is never lost because its note failed", () => { function stoppedWithState(off?: Guard[]) { const w = world([MAYA], { off }); w.pass(); w.clock.advance(60); const cfg = { ...CHECK, unsafeDisabledGuards: off }; const input = { readHiring: () => w.sheet.readHiring(), readJournal: () => w.journal.read() }; const first = runCheck(input, cfg, w.clock.now(), emptyCheckState()); // The note for the first alert FAILS: the adapter does not call markNotified. const state = { firstSeen: first.firstSeen, notified: first.notified }; w.clock.advance(15); // The morning summary runs in between (A02): it never marks anything as notified. const summary = runCheck(input, cfg, w.clock.now(), state); composeMorningSummary(summary, (s) => s); const state2 = { firstSeen: summary.firstSeen, notified: summary.notified }; w.clock.advance(15); return { first, second: runCheck(input, cfg, w.clock.now(), state2) }; } it("the next check still has the alert to notify", () => { const { first, second } = stoppedWithState(); expect(first.toNotify.map((a) => a.kind)).toEqual(["routine-stopped"]); expect(second.toNotify.map((a) => a.kind)).toEqual(["routine-stopped"]); }); it("negative control: counting first detection as notified, the alert is never sent again", () => { const { second } = stoppedWithState(["notify-after-accept"]); expect(second.alerts.map((a) => a.kind)).toEqual(["routine-stopped"]); expect(second.toNotify).toEqual([]); }); }); describe("B06: a row whose reference could not be written stays in the check", () => { function unwritable(off?: Guard[]) { const w = world([MAYA], { off }); w.sheet.writeArrivalId = () => false; // the write keeps failing (protection, edit clash) w.pass(); const once = w.check(); w.clock.advance(15); w.pass(); w.clock.advance(1); return { once, twice: w.check(), w }; } it("counted at once, reported after two readings, never all clear", () => { const { once, twice, w } = unwritable(); expect(w.to()).toEqual([]); expect(once.status).toBe("attention"); expect(once.unregistered).toEqual(["Maya Okafor"]); expect(once.alerts).toEqual([]); expect(twice.alerts.map((a) => a.kind)).toEqual(["row-not-registered"]); }); it("negative control: without counting, the check says all clear with the row in the sheet", () => { expect(unwritable(["count-unregistered"]).twice.status).toBe("all-clear"); }); }); describe("B07: the summary separates the automation from work left for people", () => { const summaryFor = (rows: string[][]) => { const w = world(rows); w.pass(); return composeMorningSummary(w.check(), (s) => s).body; }; it("with a person waiting for an address, it never says nothing needs a person", () => { const body = summaryFor([["", "Daniel Reyes", "Hired", "2026-10-12", "", ""]]); expect(body).toMatch(/^The automation is running normally\.\nFor your team: 1 thing to do/); expect(body).not.toContain("Nothing needs a person"); }); it("witness: with nobody waiting, the same summary says there is nothing to do", () => { expect(summaryFor([MAYA])).toContain("For your team: nothing to do today."); }); }); describe("B03: approval binds the name, the address and the start date", () => { function changedWhileWaiting(off?: Guard[]) { const w = world([MAYA], { off }); w.mail.quota = 0; w.pass(); // approval recorded for Maya Okafor, 2026-10-05; letter waits for quota w.sheet.set(2, "Therapist", "Mia Okafor-Lee"); w.sheet.set(2, "Start date", "2026-11-02"); w.mail.quota = 100; return { r: w.pass(), w }; } it("a change of name or date after approval holds the letter", () => { const { r, w } = changedWhileWaiting(); expect(w.to()).toEqual([]); expect(r.views[0].reason).toBe("approval-changed"); }); it("negative control: without the snapshot, the letter goes out with the new details", () => { const { w } = changedWhileWaiting(["approval-snapshot"]); expect(w.mail.outbox.map((m) => m.subject)).toEqual(["Welcome to Mentella Health, Mia"]); }); }); describe("B01: what an approval covers (documented behaviour)", () => { it("initials are bound to the values seen at the first reading after they appear", () => { const w = world([[...MAYA.slice(0, 4), "maya.a@example.org", "KR"]]); w.sheet.set(2, "Welcome email", "maya.b@example.org"); // changed before any reading saw the initials w.pass(); // The routine cannot know an earlier value it never saw: the page says so instead of promising it. expect(w.to()).toEqual(["maya.b@example.org"]); expect(w.journal.events.find((e) => e.type === "approval")).toMatchObject({ address: "maya.b@example.org", therapist: "Maya Okafor", startDate: "2026-10-05" }); }); }); describe("B02: the row is read again just before each letter", () => { function withdrawnMidReading(off?: Guard[]) { const w = world([MAYA], { off }); w.pass({ mode: "dry-run" }); const real = w.sheet.readHiring.bind(w.sheet); let reads = 0; w.sheet.readHiring = () => { if (++reads === 2) w.sheet.set(2, "Status", "Withdrawn"); // changed by a person after the first read return real(); }; w.pass(); return w; } it("a withdrawal made during the reading stops the letter", () => { expect(withdrawnMidReading().to()).toEqual([]); }); it("negative control: without the second read, the letter goes out", () => { expect(withdrawnMidReading(["reread-before-send"]).to()).toEqual(["maya.okafor@example.org"]); }); }); describe("B04: a cleared reference does not produce a second letter", () => { function cleared(off?: Guard[]) { const w = world([MAYA], { off }); w.pass(); w.sheet.set(2, "Arrival ID", ""); // someone clears the cell w.pass(); w.pass(); return w; } it("the row is held for review", () => { const w = cleared(); expect(w.to()).toHaveLength(1); expect(w.check().alerts.map((a) => a.kind)).toContain("reference-removed"); }); it("negative control: without the hold, Maya gets a second letter", () => { expect(cleared(["orphan-hold"]).to()).toHaveLength(2); }); it("a rehire (new start date) is still welcomed", () => { const w = world([MAYA]); w.pass(); w.sheet.set(2, "Arrival ID", ""); w.sheet.set(2, "Start date", "2028-03-16"); w.pass(); expect(w.to()).toHaveLength(2); }); }); describe("B05: a reference is never handed out twice", () => { function partialWrite(off?: Guard[]) { const w = world([MAYA], { off }); const realWrite = w.sheet.writeArrivalId.bind(w.sheet); // The cell write succeeds, then the execution dies before (old order) or the journal refuses (new order). w.sheet.writeArrivalId = (row, exp, id) => { const ok = realWrite(row, exp, id); w.journal.broken = true; return ok; }; try { w.pass(); } catch { /* the reading died */ } w.sheet.writeArrivalId = realWrite; w.journal.broken = false; w.sheet.addRow({ therapist: "Daniel Reyes", status: "Hired", startDate: "2026-10-12", welcomeEmail: "daniel.reyes@example.org", approvedBy: "KR" }); w.pass(); return w.sheet.readHiring().slice(1).map((r) => r[0]); } it("Maya and Daniel end up with different references", () => { const ids = partialWrite(); expect(new Set(ids).size).toBe(ids.length); }); it("negative control: counting journal entries, Daniel gets Maya's reference", () => { const ids = partialWrite(["no-id-recycling"]); expect(ids[0]).toBe(ids[1]); }); }); describe("B08/B09: a missing or impossible start date holds the letter", () => { for (const [label, date] of [["missing", ""], ["impossible", "2026-10-32"]]) { it(`${label} date: nothing sent, reported`, () => { const w = world([["", "Maya Okafor", "Hired", date, "maya.okafor@example.org", "KR"]]); w.pass(); expect(w.to()).toEqual([]); expect(w.check().alerts.map((a) => a.kind)).toEqual(["invalid-date"]); }); it(`negative control, ${label} date: without the check, the letter goes out with a wrong or empty date`, () => { const w = world([["", "Maya Okafor", "Hired", date, "maya.okafor@example.org", "KR"]], { off: ["date-check"] }); w.pass(); expect(w.to()).toHaveLength(1); expect(w.mail.outbox[0].body).toMatch(date ? /Sunday, November 1/ : /start on \./); }); } }); describe("B10/B11: decisions must be signed and must not contradict each other", () => { function uncertain(off?: Guard[]) { const w = world([MAYA], { off }); w.pass({ mode: "dry-run" }); w.mail.afterSend = () => { w.journal.broken = true; }; try { w.pass(); } catch { /* interrupted */ } w.mail.afterSend = null; w.journal.broken = false; return { w, id: w.sheet.get(2, "Arrival ID") }; } it("B10: an unsigned Send again is ignored", () => { const { w, id } = uncertain(); w.sheet.addDecision(id, "Send again", ""); w.pass(); expect(w.to()).toHaveLength(1); }); it("B10 negative control: without the rule, the unsigned decision sends a second letter", () => { const { w, id } = uncertain(["signed-decisions"]); w.sheet.addDecision(id, "Send again", ""); w.pass(); expect(w.to()).toHaveLength(2); }); it("B11: Send again and Confirm sent together are both ignored", () => { const { w, id } = uncertain(); w.sheet.addDecision(id, "Send again", "JV"); w.sheet.addDecision(id, "Confirm sent", "KR"); const r = w.pass(); expect(w.to()).toHaveLength(1); expect(r.views[0].reason).toBe("uncertain-send"); }); it("B11 negative control: without the rule, both are recorded as applied", () => { const { w, id } = uncertain(["signed-decisions"]); w.sheet.addDecision(id, "Send again", "JV"); w.sheet.addDecision(id, "Confirm sent", "KR"); w.pass(); const applied = w.journal.events.filter((e) => e.type === "decision").map((e) => (e as { applied: boolean }).applied); expect(applied).toEqual([true, true]); }); }); describe("B12: the summary counts letters from the journal", () => { it("a letter still counts after its row is deleted", () => { const w = world([MAYA]); w.pass(); w.sheet.hiring.splice(1, 1); // row deleted const body = composeMorningSummary(w.check(), (s) => s).body; expect(body).toContain("Letters sent in the last 24 hours: Maya Okafor"); }); it("witness: with no letter in the journal, it says none", () => { const w = world([[...MAYA.slice(0, 5), ""]]); w.pass(); expect(composeMorningSummary(w.check(), (s) => s).body).toContain("Letters sent in the last 24 hours: none"); }); }); describe("B13: a reading records when it finished, not only when it started", () => { it("completedAt is the end of a slow reading, and the check uses it", () => { const w = world([MAYA]); w.mail.afterSend = () => w.clock.advance(5); w.pass(); const p = w.journal.events.find((e) => e.type === "pass") as { at: string; completedAt: string }; expect(Date.parse(p.completedAt) - Date.parse(p.at)).toBe(5 * 60000); expect(w.check().lastCompletePassAt).toBe(p.completedAt); }); }); describe("B14: reinstalling with a new cadence replaces the trigger", () => { const every = (value: number) => [{ handler: "runPass", kind: "every-minutes" as const, value }]; it("15 then 5 minutes: the old trigger is removed and one new one created", () => { expect(planTriggers([{ id: "a", handler: "runPass" }], every(5), ["runPass"], undefined, every(15))).toEqual({ create: every(5), remove: ["a"] }); }); it("witness: same cadence, nothing changes", () => { expect(planTriggers([{ id: "a", handler: "runPass" }], every(15), ["runPass"], undefined, every(15))).toEqual({ create: [], remove: [] }); }); });