mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
test(medications): make compliance and analytics tests timezone-stable
These tests passed in Europe/Berlin but failed under UTC (or against the shipped engine) because they leant on the host clock: - intake route test: the mocked user had no timezone, so slot resolution fell back to the host TZ and the 07:00 schedule slot only matched in Berlin. Pin the user to Europe/Berlin. - health-score: 30 daily takes seeded at an unaligned now-i*DAY shifted their Berlin hour with the runner TZ, dropping them out of the 08:00-10:00 on-time band. Anchor each take to a fixed 07:30 UTC. - sleep-stages: the night sat on the trailing-30-day window edge for a fixed calendar instant. Anchor it to now-5d. - weekly-compliance: not a timezone issue. The dose-window engine treats a weekly dose inside the 4-day overdue tail as still-takeable, not missed, so skipping the most-recent Monday no longer lowers the rate. Skip an older, settled-miss Monday instead; the gap assertion stands.
This commit is contained in:
1 parent
dfb6a7f298
commit
050ddbecb2
4 files changed
+32
-6
No files matched your search
@@ -96,7 +96,14 @@ import { getSession } from "@/lib/auth/session";
|
||||
|
||||
const SESSION_OK = {
|
||||
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
|
||||
user: { id: "user-1", username: "marc", role: "USER" as const },
|
||||
user: {
|
||||
id: "user-1",
|
||||
username: "marc",
|
||||
role: "USER" as const,
|
||||
// Pin the user timezone so slot-instant resolution is deterministic
|
||||
// regardless of the host TZ (CI runs in UTC, local in Europe/Berlin).
|
||||
timezone: "Europe/Berlin",
|
||||
},
|
||||
};
|
||||
|
||||
const MED_OK = { id: "med-1", userId: "user-1" };
|
||||
|
||||
@@ -163,7 +163,12 @@ describe("GET /api/analytics — Health Score", () => {
|
||||
},
|
||||
});
|
||||
for (let i = 0; i < 30; i++) {
|
||||
// Anchor the take to a fixed wall-clock instant that lands inside the
|
||||
// 08:00–10:00 Berlin window (07:30 UTC ≈ 09:30 Berlin) so the slot
|
||||
// matcher reads "on-time" deterministically regardless of host TZ —
|
||||
// an unaligned `now - i*DAY` shifts its Berlin hour with the runner TZ.
|
||||
const scheduledFor = new Date(now - i * DAY);
|
||||
scheduledFor.setUTCHours(7, 30, 0, 0);
|
||||
await prisma.medicationIntakeEvent.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
@@ -283,7 +288,12 @@ describe("GET /api/analytics — Health Score", () => {
|
||||
},
|
||||
});
|
||||
for (let i = 0; i < 30; i++) {
|
||||
// Anchor the take to a fixed wall-clock instant that lands inside the
|
||||
// 08:00–10:00 Berlin window (07:30 UTC ≈ 09:30 Berlin) so the slot
|
||||
// matcher reads "on-time" deterministically regardless of host TZ —
|
||||
// an unaligned `now - i*DAY` shifts its Berlin hour with the runner TZ.
|
||||
const scheduledFor = new Date(now - i * DAY);
|
||||
scheduledFor.setUTCHours(7, 30, 0, 0);
|
||||
await prisma.medicationIntakeEvent.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
|
||||
@@ -100,7 +100,11 @@ describe("GET /api/analytics — sleep-stage aggregation", () => {
|
||||
// One night with the four iOS-16+ stages tagged. The analytics
|
||||
// route should surface a single Measurement-summary datapoint
|
||||
// (~480 minutes total) plus a per-stage breakdown.
|
||||
const baseDate = new Date("2026-05-09T01:00:00.000Z");
|
||||
// Anchor the night comfortably inside the route's trailing 30-day
|
||||
// window (relative to `now`) so the test never drifts onto the
|
||||
// window edge as the calendar advances or the host TZ shifts.
|
||||
const baseDate = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000);
|
||||
baseDate.setUTCHours(1, 0, 0, 0);
|
||||
const stages: Array<{
|
||||
stage: "IN_BED" | "CORE" | "DEEP" | "REM";
|
||||
min: number;
|
||||
|
||||
@@ -212,15 +212,20 @@ describe("weekly injectable compliance (Mounjaro 0% regression)", () => {
|
||||
rrule: "FREQ=WEEKLY;BYDAY=MO",
|
||||
windowStart: "07:30",
|
||||
});
|
||||
// Log only 12 of the trailing 13 Mondays (skip the most recent).
|
||||
// Take the most-recent Monday on time, then skip ONE older Monday
|
||||
// (`lastMonday − 7d`, which sits 9 days before the pinned now — past
|
||||
// the weekly 4-day overdue tail, so it counts as definitively missed
|
||||
// rather than still-takeable) and take the 11 Mondays before it. The
|
||||
// gap is a real, settled miss, not a head-of-window overdue slot.
|
||||
await logWeeklyIntakes(id, lastMonday, 1);
|
||||
await logWeeklyIntakes(
|
||||
id,
|
||||
new Date(lastMonday.getTime() - 7 * DAY_MS),
|
||||
12,
|
||||
new Date(lastMonday.getTime() - 14 * DAY_MS),
|
||||
11,
|
||||
);
|
||||
|
||||
const { compliance90 } = await readCompliance(id);
|
||||
// 12 of 13 expected → ~92%, definitively non-zero and below 100.
|
||||
// 12 taken of 13 expected → ~92%, definitively non-zero and below 100.
|
||||
expect(compliance90.rate).toBeGreaterThan(80);
|
||||
expect(compliance90.rate).toBeLessThan(100);
|
||||
expect(compliance90.missed).toBeGreaterThanOrEqual(1);
|
||||
|
||||
Reference in new issue
Block a user