mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
Report every restored section, disclose export decrypt losses, re-fold on zone change
- The restore summary counts practitioners, encounters, the three link tables, and vaccinations — restored in full since 08-01 but absent from the report, so a file carrying visits or an Impfpass read as if those sections were empty. - A portable export that cannot open a field's ciphertext still nulls it fail-soft, but now names the loss in the file's own decryptFailures manifest — an export with a lost emergency note is no longer byte-identical to one where the note was never written. - A timezone change enqueues the medication-compliance re-fold: the rollup keys its rows by the local day minted under the OLD zone, and the coverage probe compares counts, not keys, so nothing else could notice the stale bucketing.
This commit is contained in:
1 parent
ac44b35867
commit
97cb63a77f
7 files changed
+237
No files matched your search
@@ -1532,6 +1532,11 @@ const handler = apiHandler(
|
||||
customMetrics: summary.customMetrics,
|
||||
customMetricEntries: summary.customMetricEntries,
|
||||
correlationPatterns: summary.correlationPatterns,
|
||||
practitioners: summary.practitioners,
|
||||
encounters: summary.encounters,
|
||||
encounterLinks: summary.encounterLinks,
|
||||
vaccinations: summary.vaccinations,
|
||||
vaccinationLinks: summary.vaccinationLinks,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,6 +23,14 @@ vi.mock("@/lib/db-compat", () => ({
|
||||
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
// v1.37.19 (A6-12) — the zone change enqueues a compliance re-fold.
|
||||
const refoldMock = vi.hoisted(() =>
|
||||
vi.fn(async () => ({ enqueued: true, error: null })),
|
||||
);
|
||||
vi.mock("@/lib/rollups/medication-compliance-rollups", () => ({
|
||||
enqueueUserMedicationComplianceBackfill: refoldMock,
|
||||
}));
|
||||
|
||||
vi.mock("next/headers", () => ({
|
||||
headers: vi.fn(async () => ({ get: () => null })),
|
||||
cookies: vi.fn(async () => ({
|
||||
@@ -96,6 +104,34 @@ describe("PUT /api/auth/me/timezone", () => {
|
||||
expect(res.status).toBe(422);
|
||||
});
|
||||
|
||||
// Watched red: with the re-fold enqueue removed from the route (the
|
||||
// pre-v1.37.19 write), the changed-zone case fails — the compliance
|
||||
// rollup's day keys were minted under the old zone and nothing ever
|
||||
// re-bucketed them (the coverage probe compares counts, not keys).
|
||||
it("enqueues a compliance re-fold when the zone actually changes", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
timezone: "Europe/Berlin",
|
||||
} as never);
|
||||
vi.mocked(prisma.user.update).mockResolvedValue({} as never);
|
||||
|
||||
const res = await PUT(mkReq({ timezone: "America/New_York" }));
|
||||
expect(res.status).toBe(200);
|
||||
expect(refoldMock).toHaveBeenCalledWith("user-1");
|
||||
});
|
||||
|
||||
it("does not re-fold when the zone is unchanged", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
timezone: "Europe/Berlin",
|
||||
} as never);
|
||||
vi.mocked(prisma.user.update).mockResolvedValue({} as never);
|
||||
|
||||
const res = await PUT(mkReq({ timezone: "Europe/Berlin" }));
|
||||
expect(res.status).toBe(200);
|
||||
expect(refoldMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("trims whitespace before validating", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { invalidateUserTimezone, isValidTimezone } from "@/lib/tz/resolver";
|
||||
import { enqueueUserMedicationComplianceBackfill } from "@/lib/rollups/medication-compliance-rollups";
|
||||
|
||||
export const PUT = apiHandler(async (request: NextRequest) => {
|
||||
const { user } = await requireAuth();
|
||||
@@ -60,6 +61,24 @@ export const PUT = apiHandler(async (request: NextRequest) => {
|
||||
|
||||
invalidateUserTimezone(user.id);
|
||||
|
||||
// v1.37.19 (A6-12) — the medication-compliance rollup keys its rows by
|
||||
// the LOCAL day the dose fell on, minted under the timezone in force at
|
||||
// write time. A zone change re-buckets those days, so the stored keys go
|
||||
// stale (and the coverage probe compares counts, not keys — it cannot
|
||||
// notice). Re-fold the trailing window through the ordinary backfill
|
||||
// job; best-effort, the singleton key collapses rapid re-picks.
|
||||
if ((before?.timezone ?? null) !== tz) {
|
||||
const refold = await enqueueUserMedicationComplianceBackfill(user.id);
|
||||
annotate({
|
||||
meta: {
|
||||
timezone_compliance_refold_enqueued: refold.enqueued,
|
||||
...(refold.error
|
||||
? { timezone_compliance_refold_error: refold.error }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await auditLog("user.timezone.update", {
|
||||
userId: user.id,
|
||||
ipAddress: getClientIp(request),
|
||||
|
||||
Reference in new issue
Block a user