mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
148 lines
4.9 KiB
TypeScript
148 lines
4.9 KiB
TypeScript
/**
|
|
* v1.25.1 — commit-path unit-integrity tests.
|
|
*
|
|
* Committing a numeric observation against an EXISTING biomarker must not
|
|
* silently rewrite the document's stated unit with the catalog's. When the
|
|
* units disagree the fact is rejected (fail closed) so the user reconciles on
|
|
* the review screen, exactly like the `unitRequired` guard — never a corrupt
|
|
* "6.1 mmol/L written as 6.1 mg/dL" reading.
|
|
*/
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("@/lib/db", () => ({
|
|
prisma: {
|
|
labResult: { create: vi.fn() },
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/lib/labs/biomarker-store", () => ({
|
|
resolveOrMintBiomarker: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/lib/documents/store", () => ({
|
|
decryptFactData: vi.fn(),
|
|
}));
|
|
|
|
import { commitApprovedFact, FactCommitError } from "@/lib/documents/commit";
|
|
import { prisma } from "@/lib/db";
|
|
import { resolveOrMintBiomarker } from "@/lib/labs/biomarker-store";
|
|
import { decryptFactData } from "@/lib/documents/store";
|
|
import type { ExtractedFact } from "@/generated/prisma/client";
|
|
import { userDayKey } from "@/lib/tz/format";
|
|
|
|
function observationFact(): ExtractedFact {
|
|
return {
|
|
id: "fact-1",
|
|
factType: "OBSERVATION",
|
|
dataEncrypted: new Uint8Array(),
|
|
} as unknown as ExtractedFact;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.resetAllMocks();
|
|
});
|
|
|
|
describe("commitApprovedFact — OBSERVATION unit integrity", () => {
|
|
it("rejects a numeric observation whose stated unit differs from an existing marker's unit", async () => {
|
|
vi.mocked(decryptFactData).mockReturnValue({
|
|
label: "Glucose",
|
|
value: 6.1,
|
|
unit: "mmol/L",
|
|
referenceLow: null,
|
|
referenceHigh: null,
|
|
effectiveDate: "2025-01-01",
|
|
} as never);
|
|
// Existing marker stored in mg/dL — the resolver returns it as-is.
|
|
vi.mocked(resolveOrMintBiomarker).mockResolvedValue({
|
|
id: "bm-1",
|
|
name: "Glucose",
|
|
unit: "mg/dL",
|
|
lowerBound: 70,
|
|
upperBound: 100,
|
|
panel: null,
|
|
});
|
|
|
|
await expect(
|
|
commitApprovedFact("user-1", observationFact()),
|
|
).rejects.toMatchObject({ code: "observation.unitMismatch" });
|
|
await expect(
|
|
commitApprovedFact("user-1", observationFact()),
|
|
).rejects.toBeInstanceOf(FactCommitError);
|
|
expect(prisma.labResult.create).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("commits when the stated unit is the marker's unit spelled another way", async () => {
|
|
vi.mocked(decryptFactData).mockReturnValue({
|
|
label: "Glucose",
|
|
value: 95,
|
|
unit: " mg/DL ",
|
|
referenceLow: null,
|
|
referenceHigh: null,
|
|
effectiveDate: "2025-01-01",
|
|
} as never);
|
|
vi.mocked(resolveOrMintBiomarker).mockResolvedValue({
|
|
id: "bm-1",
|
|
name: "Glucose",
|
|
unit: "mg/dL",
|
|
lowerBound: 70,
|
|
upperBound: 100,
|
|
panel: null,
|
|
});
|
|
vi.mocked(prisma.labResult.create).mockResolvedValue({
|
|
id: "lr-1",
|
|
} as never);
|
|
|
|
const ref = await commitApprovedFact("user-1", observationFact());
|
|
expect(ref).toEqual({ recordType: "labResult", recordId: "lr-1" });
|
|
expect(prisma.labResult.create).toHaveBeenCalledOnce();
|
|
// The stated date is stored at noon UTC: it reads back on the stated day
|
|
// as a UTC label and formatted in a zone west of UTC alike. UTC midnight
|
|
// showed the reading a day early there.
|
|
const takenAt = vi.mocked(prisma.labResult.create).mock.calls[0]![0].data
|
|
.takenAt as Date;
|
|
expect(takenAt.toISOString()).toBe("2025-01-01T12:00:00.000Z");
|
|
expect(userDayKey(takenAt, "America/Los_Angeles")).toBe("2025-01-01");
|
|
});
|
|
|
|
// The comparison is the shared spelling normaliser every other lab write
|
|
// path uses, not a case fold. Mutation check: put the lower-cased equality
|
|
// back → the first case commits (milli read as mega) and the second refuses
|
|
// a µg/L reading the other paths accept.
|
|
function observation(unit: string, markerUnit: string) {
|
|
vi.mocked(decryptFactData).mockReturnValue({
|
|
label: "TSH",
|
|
value: 2.1,
|
|
unit,
|
|
referenceLow: null,
|
|
referenceHigh: null,
|
|
effectiveDate: "2025-01-01",
|
|
} as never);
|
|
vi.mocked(resolveOrMintBiomarker).mockResolvedValue({
|
|
id: "bm-2",
|
|
name: "TSH",
|
|
unit: markerUnit,
|
|
lowerBound: null,
|
|
upperBound: null,
|
|
panel: null,
|
|
});
|
|
vi.mocked(prisma.labResult.create).mockResolvedValue({
|
|
id: "lr-2",
|
|
} as never);
|
|
}
|
|
|
|
it("refuses MIU/L against a marker in mIU/L: the capital is mega, not milli", async () => {
|
|
observation("MIU/L", "mIU/L");
|
|
await expect(
|
|
commitApprovedFact("user-1", observationFact()),
|
|
).rejects.toMatchObject({ code: "observation.unitMismatch" });
|
|
expect(prisma.labResult.create).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("accepts ug/L against a marker in µg/L, as the other lab write paths do", async () => {
|
|
observation("ug/L", "µg/L");
|
|
await expect(
|
|
commitApprovedFact("user-1", observationFact()),
|
|
).resolves.toEqual({ recordType: "labResult", recordId: "lr-2" });
|
|
});
|
|
});
|