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:
Marc-André Bombeck committed 2026-06-08 20:09:17 +02:00
1 parent dfb6a7f298
commit 050ddbecb2
4 files changed
+32 -6

No files matched your search

@@ -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);