mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
fix(backup): keep a medication's creation date through a portable restore
A portable export left the medication's creation instant out, so a restore stamped the restore time instead. The creation instant is where a medication's expected doses start, so the whole restored history then read as predating the medication: its missed doses dropped out of every adherence rate. The portable file now carries the creation instant as the disaster-recovery file already did, and the restore, which already honoured it, brings it back. A round trip pins both file purposes. Refs #1028
This commit is contained in:
1 parent
a6b8c61a82
commit
8642d63b82
2 files changed
+64
-1
No files matched your search
@@ -998,13 +998,17 @@ export async function buildFullBackupPayload(
|
||||
reorderLeadDays: m.reorderLeadDays,
|
||||
externalSource: m.externalSource,
|
||||
externalId: m.externalId,
|
||||
createdAt: m.createdAt.toISOString(),
|
||||
updatedAt: m.updatedAt.toISOString(),
|
||||
}
|
||||
: {}),
|
||||
name: m.name,
|
||||
dose: m.dose,
|
||||
active: m.active,
|
||||
// Both purposes carry the creation instant: it is the floor of the
|
||||
// medication's expected slots, so a restore stamped with the restore
|
||||
// time would read the whole restored history as predating the
|
||||
// medication and drop its misses from every rate (#1028).
|
||||
createdAt: m.createdAt.toISOString(),
|
||||
schedules: m.schedules.map((s) => ({
|
||||
...(disasterRecovery
|
||||
? {
|
||||
|
||||
@@ -2890,4 +2890,63 @@ describe("every model the plan claims two-ended survives a real restore", () =>
|
||||
UNREADABLE_EXPORT_MARKER,
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* #1028 — a medication's creation instant is the floor of its expected
|
||||
* slots: every adherence surface counts a slot before it only when a
|
||||
* recorded dose claims it. A restore that stamped the restore time as the
|
||||
* creation turned the whole restored history into "before the medication
|
||||
* existed", so its misses dropped out of the rate. Both file purposes must
|
||||
* bring the creation instant back.
|
||||
*/
|
||||
for (const purpose of ["portable-export", "disaster-recovery"] as const) {
|
||||
it(`restores a medication's creation instant from a ${purpose} file`, async () => {
|
||||
const prisma = getPrismaClient();
|
||||
await seedAdminSession(prisma);
|
||||
await createOwner(prisma);
|
||||
const createdAt = AT("2025-11-03T16:05:00.000Z");
|
||||
await prisma.medication.create({
|
||||
data: {
|
||||
userId: OWNER_ID,
|
||||
name: "Metformin",
|
||||
dose: "500mg",
|
||||
createdAt,
|
||||
schedules: {
|
||||
create: {
|
||||
windowStart: "08:00",
|
||||
windowEnd: "08:00",
|
||||
timesOfDay: ["08:00"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { payload } = await buildFullBackupPayload(prisma, OWNER_ID, {
|
||||
purpose,
|
||||
});
|
||||
await prisma.user.delete({ where: { id: OWNER_ID } });
|
||||
await createOwner(prisma);
|
||||
const backup = await prisma.dataBackup.create({
|
||||
data: {
|
||||
userId: OWNER_ID,
|
||||
type: "TWO_ENDED_ROUND_TRIP",
|
||||
data: encrypt(JSON.stringify(payload)),
|
||||
},
|
||||
});
|
||||
const response = await POST(
|
||||
new Request(`http://localhost/api/admin/backups/${backup.id}/restore`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ confirm: "RESTORE" }),
|
||||
}) as never,
|
||||
{ params: Promise.resolve({ id: backup.id }) },
|
||||
);
|
||||
expect(response.status, JSON.stringify(await response.json())).toBe(200);
|
||||
|
||||
const restored = await prisma.medication.findFirstOrThrow({
|
||||
where: { userId: OWNER_ID, name: "Metformin" },
|
||||
});
|
||||
expect(restored.createdAt.toISOString()).toBe(createdAt.toISOString());
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user