Files
HealthLog/src/lib/insights/__tests__/unit-change-refresh.test.ts
T
Marc-André Bombeck a7e6b89672 fix(insights): hand the briefing its figures in the reader's units
The daily briefing serialised the aggregated feature set as computed:
weight, grip strength and the weight target in kilograms, waist in
centimetres, glucose and the signals of the day in mg/dL (the glucose
signal carried no unit at all), workout distances in kilometres. A
reader on pounds or mmol/L got a briefing in units they do not use.

One step now converts the feature set after all canonical arithmetic,
on both the nightly and the on-demand path: absolute values through the
full transform, differences and slopes through the factor alone, every
converted block labelled, workout distances under unit-neutral keys.
The prompt, the stored fingerprint and the grounding check all read the
converted set, so a unit switch moves the fingerprint and the grounding
check compares the reply against the figures the model was shown. The
weight target sentence, the comparison block and the GLP-1 plateau
context quote weight in the reader's unit; the comparison block also
labelled sleep as hours while carrying minutes.

A unit switch now also re-warms a cached briefing alongside the status
notes and narratives.

Refs #1067
2026-10-01 13:23:17 +02:00

93 lines
2.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@/lib/db", () => ({
prisma: {
user: { findUnique: vi.fn() },
insightNarrative: { findMany: vi.fn() },
},
}));
vi.mock("@/lib/insights/status-invalidation", () => ({
enqueueStatusRefillForUser: vi.fn(),
}));
vi.mock("@/lib/jobs/period-narrative-shared", () => ({
enqueueNarrativeWarm: vi.fn(),
}));
vi.mock("@/lib/jobs/insight-pregenerate-shared", () => ({
enqueueForceWarm: vi.fn(),
}));
import { prisma } from "@/lib/db";
import { enqueueStatusRefillForUser } from "@/lib/insights/status-invalidation";
import { enqueueNarrativeWarm } from "@/lib/jobs/period-narrative-shared";
import { enqueueForceWarm } from "@/lib/jobs/insight-pregenerate-shared";
import { refreshTextsAfterUnitChange } from "../unit-change-refresh";
beforeEach(() => {
vi.resetAllMocks();
});
describe("refreshTextsAfterUnitChange", () => {
it("refills the status cards in the reader's locale and re-warms each stored narrative", async () => {
vi.mocked(prisma.user.findUnique).mockResolvedValue({
locale: "de",
} as never);
vi.mocked(enqueueStatusRefillForUser).mockResolvedValue(12);
vi.mocked(prisma.insightNarrative.findMany).mockResolvedValue([
{ period: "week", locale: "de" },
{ period: "month", locale: "en" },
{ period: "year", locale: "de" },
{ period: "week", locale: "xx" },
] as never);
await refreshTextsAfterUnitChange("user-1");
expect(enqueueStatusRefillForUser).toHaveBeenCalledWith("user-1", "de");
expect(enqueueNarrativeWarm).toHaveBeenCalledTimes(2);
expect(enqueueNarrativeWarm).toHaveBeenCalledWith({
userId: "user-1",
period: "week",
locale: "de",
});
expect(enqueueNarrativeWarm).toHaveBeenCalledWith({
userId: "user-1",
period: "month",
locale: "en",
});
});
it("re-warms a cached briefing in the language it was written in", async () => {
vi.mocked(prisma.user.findUnique).mockResolvedValue({
locale: "de",
insightsCachedAt: new Date(),
insightsCachedLocale: "en",
} as never);
vi.mocked(prisma.insightNarrative.findMany).mockResolvedValue([]);
await refreshTextsAfterUnitChange("user-1");
expect(enqueueForceWarm).toHaveBeenCalledWith({
userId: "user-1",
locale: "en",
});
});
it("leaves the briefing alone when none was ever written", async () => {
vi.mocked(prisma.user.findUnique).mockResolvedValue({
locale: "de",
insightsCachedAt: null,
} as never);
vi.mocked(prisma.insightNarrative.findMany).mockResolvedValue([]);
await refreshTextsAfterUnitChange("user-1");
expect(enqueueForceWarm).not.toHaveBeenCalled();
});
it("never throws into the preference write", async () => {
vi.mocked(prisma.user.findUnique).mockRejectedValue(new Error("db down"));
await expect(refreshTextsAfterUnitChange("user-1")).resolves.toBe(
undefined,
);
});
});