mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
realisedFamily probed for the closest two slots up to range.to only, so the answer moved with the range. A Saturday + Sunday plan started the weekend before the autumn DST change, read around its Saturday dose: the ±24 h range ends at 06:00 UTC on the Sunday, whose 08:00 slot is at 07:00 UTC after the change. Cut off, the closest slots were six days apart (weekly); the wide re-mint saw the 25 h gap (daily). The two passes disagreed, no band survived, and the dose was filed as unscheduled, so a second backdated dose within a minute came back as the first one (200). Probe the same reach on both sides of the range's middle.
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
/**
|
|
* A Saturday + Sunday plan files each of its doses on its own slot in the
|
|
* week of the autumn DST change.
|
|
*
|
|
* The plan starts the Sunday before the change, so its realised slots are
|
|
* that Sunday, the Saturday before the change and the Sunday of the change.
|
|
* Read around the Saturday dose, the ±24 h range ended at 06:00 UTC on the
|
|
* Sunday while that Sunday's 08:00 slot is 07:00 UTC after the change. The
|
|
* family probe ended at the range's end, so one pass called the plan weekly
|
|
* and the wider one daily; the dose fell between them, was filed as
|
|
* unscheduled, and the 60-second duplicate guard answered it with the
|
|
* previous Sunday's row (200 instead of 201). The family is now read from a
|
|
* window centred on the range, the same for both passes.
|
|
*/
|
|
import {
|
|
afterAll,
|
|
afterEach,
|
|
beforeAll,
|
|
describe,
|
|
expect,
|
|
it,
|
|
vi,
|
|
} from "vitest";
|
|
|
|
import { POST as createMedication } from "@/app/api/medications/route";
|
|
import { POST as recordIntake } from "@/app/api/medications/[id]/intake/route";
|
|
|
|
import { callRoute } from "../kit/call-route";
|
|
import { makeSession, makeUser } from "../kit/factories";
|
|
import { getPrismaClient, truncateAllTables } from "./setup";
|
|
|
|
const prisma = getPrismaClient();
|
|
|
|
beforeAll(async () => {
|
|
await truncateAllTables(prisma);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await truncateAllTables(prisma);
|
|
});
|
|
|
|
describe("backdated doses on a weekend plan across the autumn DST change", () => {
|
|
it.each(["FREQ=WEEKLY;BYDAY=SA,SU", "FREQ=DAILY"])(
|
|
"%s: every dose lands on its own slot",
|
|
async (rrule) => {
|
|
const user = await makeUser({ timezone: "Europe/Berlin" });
|
|
const auth = await makeSession(user);
|
|
vi.useFakeTimers({ toFake: ["Date"] });
|
|
// 00:25 on Monday 2026-10-26 in Berlin.
|
|
vi.setSystemTime(new Date("2026-10-25T23:25:00.000Z"));
|
|
|
|
const created = await callRoute<{ id: string }>(createMedication, {
|
|
method: "POST",
|
|
route: "/api/medications",
|
|
body: {
|
|
name: `weekend ${rrule}`,
|
|
dose: "5 mg",
|
|
startsOn: "2026-10-18",
|
|
schedules: [
|
|
{
|
|
windowStart: "08:00",
|
|
windowEnd: "09:00",
|
|
timesOfDay: ["08:00"],
|
|
rrule,
|
|
},
|
|
],
|
|
},
|
|
auth,
|
|
});
|
|
expect(created.status).toBe(201);
|
|
const id = created.data.id;
|
|
await prisma.medication.update({
|
|
where: { id },
|
|
data: { createdAt: new Date("2026-10-17T22:25:00.000Z") },
|
|
});
|
|
|
|
// 08:00 local on Sunday 18th (CEST), Saturday 24th (CEST) and Sunday
|
|
// 25th (CET, after the change).
|
|
for (const slot of [
|
|
"2026-10-18T06:00:00.000Z",
|
|
"2026-10-24T06:00:00.000Z",
|
|
"2026-10-25T07:00:00.000Z",
|
|
]) {
|
|
const res = await callRoute<{ scheduledFor: string }>(recordIntake, {
|
|
method: "POST",
|
|
route: "/api/medications/{id}/intake",
|
|
params: { id },
|
|
body: { skipped: false, scheduledFor: slot, takenAt: slot },
|
|
auth,
|
|
});
|
|
expect(res.status, `the dose at ${slot} is written`).toBe(201);
|
|
expect(res.data.scheduledFor).toBe(slot);
|
|
}
|
|
},
|
|
);
|
|
});
|