Teach the export test mocks about the recorded score days

Four suites hand-roll a Prisma stand-in for the backup builder, so a new
section reaches them as an undefined delegate rather than as an empty
result. Each gains the delegate, and the admin restore round trip gains an
assertion that the day comes back with its composition and per-pillar
scores, not just its headline: a row restored without those looks restored
and answers nothing.
This commit is contained in:
Marc-André Bombeck committed 2026-07-31 15:31:47 +02:00
1 parent ab8e9a164d
commit cd7c6f44c8
4 files changed
+65

No files matched your search

@@ -257,6 +257,29 @@ function sourceClient() {
},
]),
},
// The score as it was SHOWN on a local day. Not recomputable: today's
// number always comes from today's rows, so once the readings behind this
// day moved, nothing can reproduce what it said.
healthScoreRecord: {
findMany: vi.fn().mockResolvedValue([
{
id: "score-day-1",
userId: OWNER,
dayKey: "2026-07-18",
timezone: "Europe/Berlin",
composite: 76,
band: "yellow",
scoreVersion: 2,
composition: ["BLOOD_PRESSURE", "SLEEP", "ADIPOSITY"],
pillarScores: { BLOOD_PRESSURE: 86, SLEEP: 100, ADIPOSITY: 42 },
inputFingerprint: "a".repeat(64),
configVersion: null,
configChangedAt: null,
computedAt: new Date("2026-07-18T20:00:00.000Z"),
createdAt: new Date("2026-07-18T20:00:00.000Z"),
},
]),
},
customMetric: {
findMany: vi.fn().mockResolvedValue([
{
@@ -586,6 +609,44 @@ describe("backup round trip — export, wire schema, restore", () => {
expect(rows[0].hourlyCumulative).toEqual(DAY_CURVE);
});
it("writes the recorded score day back with the number it showed", async () => {
const { written } = await roundTrip();
const write = written.find(
(w) => w.model === "healthScoreRecord" && w.op === "createMany",
);
expect(
write,
"the recorded score day reached the file and nothing wrote it back",
).toBeDefined();
const rows = write!.data as unknown as Array<Record<string, unknown>>;
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({
id: "score-day-1",
userId: OWNER,
dayKey: "2026-07-18",
timezone: "Europe/Berlin",
composite: 76,
band: "yellow",
scoreVersion: 2,
inputFingerprint: "a".repeat(64),
});
// The composition and the per-pillar scores are what keep the stored day
// readable across a later change to what counts. A row that came back
// with only its headline would look restored and answer nothing.
expect(rows[0].composition).toEqual([
"BLOOD_PRESSURE",
"SLEEP",
"ADIPOSITY",
]);
expect(rows[0].pillarScores).toEqual({
BLOOD_PRESSURE: 86,
SLEEP: 100,
ADIPOSITY: 42,
});
});
it("writes the account's own cycle symptom back before resolving its links", async () => {
const { written, res } = await roundTrip();
@@ -27,6 +27,7 @@ vi.mock("@/lib/db", () => ({
// The hourly shape of a cumulative day, read by
// `buildIntradayProfileBackupSection`.
intradayCumulativeProfile: { findMany: vi.fn().mockResolvedValue([]) },
healthScoreRecord: { findMany: vi.fn().mockResolvedValue([]) },
// v1.15.0 — cycle tables read by the full-backup helper.
cycleProfile: { findUnique: vi.fn() },
menstrualCycle: { findMany: vi.fn() },
@@ -96,6 +97,7 @@ beforeEach(() => {
vi.mocked(prisma.intradayCumulativeProfile.findMany).mockResolvedValue(
[] as never,
);
vi.mocked(prisma.healthScoreRecord.findMany).mockResolvedValue([] as never);
});
afterEach(() => {
@@ -25,6 +25,7 @@ vi.mock("@/lib/db", () => ({
// The hourly shape of a cumulative day, read by
// `buildIntradayProfileBackupSection`.
intradayCumulativeProfile: { findMany: vi.fn().mockResolvedValue([]) },
healthScoreRecord: { findMany: vi.fn().mockResolvedValue([]) },
cycleProfile: { findUnique: vi.fn().mockResolvedValue(null) },
menstrualCycle: { findMany: vi.fn().mockResolvedValue([]) },
cycleDayLog: { findMany: vi.fn().mockResolvedValue([]) },
@@ -204,6 +204,7 @@ function makePrisma() {
},
]),
},
healthScoreRecord: { findMany: vi.fn().mockResolvedValue([]) },
// Left unmocked on purpose: `buildProfileBackupSection` runs for real
// against these, so the assertions below exercise the builder rather than
// a stand-in that would agree with whatever the payload happened to do.