fix(measurements): cut drain days in the zone the server publishes for the account

This commit is contained in:
Marc-André Bombeck committed 2026-10-16 05:48:13 +02:00
1 parent 3c44f3d167
commit 37645fc4bb
2 files changed
+37 -1

No files matched your search

@@ -1,5 +1,10 @@
import { describe, expect, it, vi } from "vitest";
const appSettings = vi.hoisted(() => ({
findUnique: vi.fn(async () => ({ defaultUserTimezone: "America/New_York" })),
}));
vi.mock("@/lib/db", () => ({ prisma: { appSettings } }));
import {
bucketRowsByDay,
iterateDayBuckets,
@@ -303,4 +308,29 @@ describe("runConsolidation — streamed walk", () => {
expect(result.stoppedEarly).toBe(true);
expect(findMany).toHaveBeenCalledTimes(1);
});
it("cuts days in the zone the server publishes for the account", async () => {
// A stored zone the server sets aside reads as the instance default, as
// it does for `/api/auth/me` and the hour-bucket writer; never as the
// drain's own fallback.
const findMany = buildPagingFindMany(hourlyRows(1));
const base = params(findMany, async () => ({
kind: "written" as const,
sourceRowsRemoved: 0,
}));
const zones: string[] = [];
await runConsolidation({
...base,
prismaClient: {
user: {
findMany: vi
.fn()
.mockResolvedValue([{ id: "user-2", timezone: null }]),
},
measurement: { findMany },
} as unknown as PrismaClient,
onUserStart: ({ tz }) => zones.push(tz),
});
expect(zones).toEqual(["America/New_York"]);
});
});
+7 -1
View File
@@ -44,6 +44,7 @@ import {
type PerSampleRow,
} from "./consolidation-tz";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
import { resolveStoredTimezone } from "@/lib/tz/resolver";
// Re-export the shared grace constant so the established
// `consolidation-base` import surface stays unchanged for callers.
@@ -454,7 +455,12 @@ export async function runConsolidation<TType extends MeasurementType>(
const users = await loadConsolidationUsers(prismaClient, options.userId);
walk: for (const user of users) {
const tz = resolveUserTimezone(user.timezone);
// The zone the server publishes for the account (`/api/auth/me`), which
// is the zone the phone cuts its `stats:` days and hour buckets in and
// the one the hour-bucket writer stores them under. A stored value the
// server sets aside falls back to the instance default here as it does
// there, never to a second fallback of the drain's own.
const tz = await resolveStoredTimezone(user.timezone);
// Aligned to the account's local day: a day is folded whole or not at
// all, never in two runs (see `foldBoundary`).
const cutoffAt =