test(01-11): pin rolling overdue consumer parity

- compare persisted weekly occurrence across list, dashboard, GLP-1, and take-all
- protect exact intake retries and unrelated occurrence isolation
This commit is contained in:
Marc-André Bombeck committed 2026-07-29 14:23:52 +02:00
1 parent 65ca1aae42
commit 25612294d1
2 files changed
+350

No files matched your search

@@ -579,6 +579,55 @@ describe("POST /api/medications/[id]/intake — v1.8.2 reconcile (M2 inventory)"
expect.objectContaining({ where: { id: "row-raced" } }),
);
});
it("does not treat a different occurrence, sibling, or archived row as an already-taken exact slot", async () => {
vi.mocked(prisma.medication.findFirst).mockResolvedValueOnce(
SCHEDULED_MED as never,
);
// The shared write probe must be scoped to the exact medication,
// canonical occurrence, and live row. An action at 07:00, on a sibling
// medication, or in an archived/tombstoned era is therefore not a
// candidate for this 19:00 write.
vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValueOnce(
[] as never,
);
vi.mocked(prisma.medicationIntakeEvent.create).mockResolvedValueOnce({
id: "row-evening-distinct",
takenAt: new Date("2026-06-15T17:02:00Z"),
skipped: false,
idempotencyKey: "evening-distinct",
scheduledFor: new Date("2026-06-15T17:00:00Z"),
source: "WEB",
createdAt: new Date("2026-06-15T17:02:00Z"),
} as never);
vi.mocked(prisma.medication.findUnique).mockResolvedValue({
oneShot: false,
active: true,
} as never);
const res = await POST(
postReq({
scheduledFor: "2026-06-15T17:00:30.000Z",
takenAt: "2026-06-15T17:02:00.000Z",
idempotencyKey: "evening-distinct",
}),
ROUTE_PARAMS,
);
expect(res.status).toBe(201);
expect(prisma.medicationIntakeEvent.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: {
userId: "user-1",
medicationId: "med-1",
scheduledFor: new Date("2026-06-15T17:00:00.000Z"),
deletedAt: null,
},
}),
);
expect(prisma.medicationIntakeEvent.create).toHaveBeenCalledTimes(1);
expect(prisma.medicationIntakeEvent.update).not.toHaveBeenCalled();
});
});
// ────────────────────────────────────────────────────────────────────
@@ -0,0 +1,301 @@
/**
* v1.34.1 (#664 follow-up) — one persisted rolling occurrence must have
* one identity across every medication consumer.
*
* This is intentionally a RED contract for the diagnosed Mounjaro defect:
* the weekly occurrence anchored on 2026-07-28 remains actionable after the
* user's local day changes. The list and Dashboard are the authoritative
* server reads; the GLP-1 card and take-all derivations consume the list
* payload exactly as production does.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { resolveDisplayedSlotInstant } from "@/components/medications/card-parts/displayed-slot-instant";
import {
deriveDueMedications,
type DueDerivationMedication,
} from "@/components/medications/take-all-due";
import { buildMedsTodayBlock } from "@/lib/dashboard/meds-today";
import { buildMedicationsList } from "@/lib/medications/list-read";
import { applyCanonicalSlotWrite } from "@/lib/medications/scheduling/slot-upsert";
import { getPrismaClient, truncateAllTables } from "./setup";
const USER_ID = "user-rolling-overdue-consumers";
const USER_TZ = "Europe/Berlin";
const NOW = new Date("2026-07-29T10:00:00.000Z");
const LAST_TAKEN = new Date("2026-07-21T06:00:00.000Z");
const OVERDUE_OCCURRENCE = new Date("2026-07-28T06:00:00.000Z");
const NEXT_OCCURRENCE = new Date("2026-08-04T06:00:00.000Z");
vi.mock("@/lib/db-compat", () => ({
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
}));
beforeEach(async () => {
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(NOW);
const prisma = getPrismaClient();
await truncateAllTables(prisma);
await prisma.user.create({
data: {
id: USER_ID,
username: "rolling-overdue-consumers",
email: "rolling-overdue-consumers@example.test",
timezone: USER_TZ,
},
});
});
afterEach(() => {
vi.useRealTimers();
});
async function createMounjaro(): Promise<string> {
const prisma = getPrismaClient();
const medication = await prisma.medication.create({
data: {
userId: USER_ID,
name: "Mounjaro",
dose: "5mg",
active: true,
treatmentClass: "GLP1",
deliveryForm: "INJECTION",
startsOn: new Date("2026-01-01T00:00:00.000Z"),
createdAt: new Date("2026-01-01T00:00:00.000Z"),
schedules: {
create: {
windowStart: "08:00",
windowEnd: "08:00",
timesOfDay: ["08:00"],
daysOfWeek: null,
rrule: null,
rollingIntervalDays: 7,
scheduleType: "SCHEDULED",
},
},
},
});
await prisma.medicationIntakeEvent.create({
data: {
userId: USER_ID,
medicationId: medication.id,
scheduledFor: LAST_TAKEN,
takenAt: LAST_TAKEN,
skipped: false,
source: "WEB",
},
});
return medication.id;
}
async function readConsumerOccurrence(
medicationId: string,
complianceStatus: "overdue" | "on_track" = "overdue",
) {
const prisma = getPrismaClient();
const list = await buildMedicationsList(USER_ID, USER_TZ);
const raw = list.find((row) => row.id === medicationId);
expect(raw).toBeDefined();
const medication = raw as unknown as DueDerivationMedication;
const listOccurrence = medication.nextDueAt ?? null;
const dashboard = await buildMedsTodayBlock(
prisma,
USER_ID,
USER_TZ,
NOW,
);
// A prior-day rolling slot is outside today's clock window. The GLP-1
// card therefore displays the server's canonical nextDueAt verbatim.
const glp1Occurrence = resolveDisplayedSlotInstant({
currentWindowStatus: {
status: null,
schedule: null,
window: null,
},
nextDueAt: listOccurrence,
now: NOW,
timeZone: USER_TZ,
})?.toISOString() ?? null;
const takeAll = deriveDueMedications([medication], {
now: NOW,
tz: USER_TZ,
// Mirrors the compliance row that keeps the card action enabled for an
// unresolved weekly dose after its same-day window has passed.
doseStatusById: new Map([[medicationId, complianceStatus]]),
});
return {
list: {
occurrence: listOccurrence,
overdue: medication.nextDueOverdue === true,
},
dashboard: {
occurrence:
dashboard.dueCandidates?.find(
(candidate) => candidate.medicationId === medicationId,
)?.dueAt ?? null,
overdue:
dashboard.dueCandidates?.find(
(candidate) => candidate.medicationId === medicationId,
)?.overdue ?? false,
},
glp1Card: glp1Occurrence,
takeAllDue: takeAll.find((entry) => entry.id === medicationId)
?.scheduledFor?.toISOString() ?? null,
};
}
describe("rolling weekly overdue occurrence — shared consumers", () => {
it("keeps the prior-day occurrence authoritative in list, Dashboard, GLP-1, and take-all", async () => {
const medicationId = await createMounjaro();
await expect(readConsumerOccurrence(medicationId)).resolves.toEqual({
list: {
occurrence: OVERDUE_OCCURRENCE.toISOString(),
overdue: true,
},
dashboard: {
occurrence: OVERDUE_OCCURRENCE.toISOString(),
overdue: true,
},
glp1Card: OVERDUE_OCCURRENCE.toISOString(),
takeAllDue: OVERDUE_OCCURRENCE.toISOString(),
});
});
it.each(["taken", "skipped", "autoMissed"] as const)(
"an exact %s action suppresses the overdue occurrence everywhere and advances",
async (status) => {
const prisma = getPrismaClient();
const medicationId = await createMounjaro();
await prisma.medicationIntakeEvent.create({
data: {
userId: USER_ID,
medicationId,
scheduledFor: OVERDUE_OCCURRENCE,
takenAt:
status === "taken"
? new Date("2026-07-28T06:10:00.000Z")
: null,
skipped: status === "skipped",
autoMissed: status === "autoMissed",
source: "WEB",
},
});
const state = await readConsumerOccurrence(medicationId, "on_track");
expect(state.list).toEqual({
occurrence: NEXT_OCCURRENCE.toISOString(),
overdue: false,
});
expect(state.dashboard).toEqual({
occurrence: NEXT_OCCURRENCE.toISOString(),
overdue: false,
});
expect(state.glp1Card).toBe(NEXT_OCCURRENCE.toISOString());
expect(state.takeAllDue).toBeNull();
},
);
it("concurrent and retried writes converge onto one live exact-occurrence row", async () => {
const prisma = getPrismaClient();
const medicationId = await createMounjaro();
const write = (idempotencyKey: string) =>
applyCanonicalSlotWrite({
client: prisma,
userId: USER_ID,
medicationId,
canonicalSlot: OVERDUE_OCCURRENCE,
takenAt: new Date("2026-07-29T10:01:00.000Z"),
skipped: false,
isExplicitTaken: true,
isExplicitSkip: false,
idempotencyKey,
createSource: "WEB",
});
const results = await Promise.all([
write("rolling-concurrent-a"),
write("rolling-concurrent-b"),
]);
await write("rolling-concurrent-retry");
expect(results).toHaveLength(2);
const rows = await prisma.medicationIntakeEvent.findMany({
where: {
userId: USER_ID,
medicationId,
scheduledFor: OVERDUE_OCCURRENCE,
deletedAt: null,
},
});
expect(rows).toHaveLength(1);
expect(rows[0]?.takenAt).not.toBeNull();
});
it("different occurrence, archived row, and sibling medication never block the exact write", async () => {
const prisma = getPrismaClient();
const medicationId = await createMounjaro();
const siblingId = await createMounjaro();
await prisma.medicationIntakeEvent.createMany({
data: [
{
userId: USER_ID,
medicationId,
scheduledFor: new Date("2026-07-27T06:00:00.000Z"),
takenAt: new Date("2026-07-27T06:05:00.000Z"),
skipped: false,
source: "WEB",
},
{
userId: USER_ID,
medicationId,
scheduledFor: OVERDUE_OCCURRENCE,
takenAt: new Date("2026-07-27T06:05:00.000Z"),
skipped: false,
source: "REMINDER",
deletedAt: new Date("2026-07-27T12:00:00.000Z"),
},
{
userId: USER_ID,
medicationId: siblingId,
scheduledFor: OVERDUE_OCCURRENCE,
takenAt: new Date("2026-07-28T06:05:00.000Z"),
skipped: false,
source: "WEB",
},
],
});
const result = await applyCanonicalSlotWrite({
client: prisma,
userId: USER_ID,
medicationId,
canonicalSlot: OVERDUE_OCCURRENCE,
takenAt: new Date("2026-07-29T10:02:00.000Z"),
skipped: false,
isExplicitTaken: true,
isExplicitSkip: false,
idempotencyKey: "rolling-isolation-target",
createSource: "WEB",
});
expect(result.outcome).toBe("inserted");
expect(result.row.scheduledFor).toEqual(OVERDUE_OCCURRENCE);
expect(
await prisma.medicationIntakeEvent.count({
where: {
userId: USER_ID,
medicationId,
scheduledFor: OVERDUE_OCCURRENCE,
deletedAt: null,
},
}),
).toBe(1);
});
});