From a104963dafae9f015e1bbe63dc53d231290af381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 14 Aug 2026 14:27:58 +0200 Subject: [PATCH] Register the skip and snooze admissions in the MANAGE handler matrix The two new measurement-reminder routes were admitted in the frozen fixture but never got their integration drivers, so the matrix count stayed at 75 and the executable proof was missing. Both routes now carry a full manageContract driver: the skip driver asserts the stamped column, exactly one SKIPPED ledger row, and that lastSatisfiedAt stays untouched; the snooze driver asserts the snoozed-until write under the same invariant. All three inventory pins move to 77. --- .../sharing-manage-handler-matrix.test.ts | 71 ++++++++++++++++++- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/tests/integration/sharing-manage-handler-matrix.test.ts b/tests/integration/sharing-manage-handler-matrix.test.ts index a44f1776a..cb4fea195 100644 --- a/tests/integration/sharing-manage-handler-matrix.test.ts +++ b/tests/integration/sharing-manage-handler-matrix.test.ts @@ -281,7 +281,7 @@ const ROUTE_IMPORTS = import.meta.glob>( describe("the complete MANAGE handler matrix", () => { it("starts with a non-empty, exact handler inventory", () => { - expect(ADMITTED_MUTATING_HANDLERS.length).toBe(75); + expect(ADMITTED_MUTATING_HANDLERS.length).toBe(77); }); }); @@ -737,6 +737,71 @@ manageContract("DELETE /api/measurement-reminders/[id]", { }, }); +manageContract("POST /api/measurement-reminders/[id]/skip", { + contract: ADMITTED_MUTATING_HANDLERS.find( + (entry) => + entry.route === "/api/measurement-reminders/[id]/skip" && + entry.action === "POST", + )!, + prepare: (ownerId) => makeReminder(ownerId), + act: async (row) => { + const { POST } = + await import("@/app/api/measurement-reminders/[id]/skip/route"); + return call( + POST as Handler, + "POST", + `/api/measurement-reminders/${row.id}/skip`, + undefined, + { id: row.id }, + ); + }, + ok: 200, + auditAction: "measurementReminder.skip", + applied: async (row) => { + const after = await getPrismaClient().measurementReminder.findUnique({ + where: { id: row.id }, + }); + const ledger = await getPrismaClient().measurementReminderEvent.count({ + where: { reminderId: row.id, kind: "SKIPPED" }, + }); + // A skip stamps its own column and ledger row and never completes the + // reminder: lastSatisfiedAt must stay exactly as prepared (null). + return ( + after?.lastSkippedAt !== null && + after?.lastSatisfiedAt === null && + ledger === 1 + ); + }, +}); + +manageContract("POST /api/measurement-reminders/[id]/snooze", { + contract: ADMITTED_MUTATING_HANDLERS.find( + (entry) => + entry.route === "/api/measurement-reminders/[id]/snooze" && + entry.action === "POST", + )!, + prepare: (ownerId) => makeReminder(ownerId), + act: async (row) => { + const { POST } = + await import("@/app/api/measurement-reminders/[id]/snooze/route"); + return call( + POST as Handler, + "POST", + `/api/measurement-reminders/${row.id}/snooze`, + { until: "2027-01-15" }, + { id: row.id }, + ); + }, + ok: 200, + auditAction: "measurementReminder.snooze", + applied: async (row) => { + const after = await getPrismaClient().measurementReminder.findUnique({ + where: { id: row.id }, + }); + return after?.snoozedUntil !== null && after?.lastSatisfiedAt === null; + }, +}); + /* -------------------------------------------------------------------------- */ /* 1a — measurements and their reminder schedules */ /* -------------------------------------------------------------------------- */ @@ -2515,13 +2580,13 @@ describe("the conditions the admissions were granted on", () => { describe("the complete MANAGE handler matrix", () => { it("registers one strict actor-and-effect driver for every admission", () => { const expected = ADMITTED_MUTATING_HANDLERS.map(matrixKey).sort(); - expect(STRICT_DRIVER_KEYS.size).toBe(75); + expect(STRICT_DRIVER_KEYS.size).toBe(77); expect([...STRICT_DRIVER_KEYS].sort()).toEqual(expected); }); it("executes every registered driver through its owned effect and actor audit", () => { const expected = ADMITTED_MUTATING_HANDLERS.map(matrixKey).sort(); - expect(REAL_EFFECT_KEYS.size).toBe(75); + expect(REAL_EFFECT_KEYS.size).toBe(77); expect([...REAL_EFFECT_KEYS].sort()).toEqual(expected); }); });