Files
HealthLog/tests/integration/document-undated-lab-value.test.ts
Marc-André Bombeck b6da360856 refactor(db): drop the runtime schema mirror
ensureDbCompatibility ran ALTER TABLE / CREATE TABLE IF NOT EXISTS on the
login, register, token and session paths, mirroring columns every
migration under prisma/migrations already creates. The entrypoint applies
those migrations before the server listens and stops the boot when one
fails, so the mirror never had work to do, and DDL on a request path
needs the table's owner: a connection that may only read and write rows
fails every login on it. Drop the module, its call sites and the mocks
that stubbed it out.
2026-11-03 20:32:35 +01:00

219 lines
6.5 KiB
TypeScript

/**
* A lab value without its own date, confirmed from a stored document, against
* a real Postgres and through the real confirm route.
*
* A lab report usually prints the collection date once, in its header. The
* extraction puts that date on the document (`reportDate`) and leaves each
* value's own date empty, so the confirm step has to date the value by its
* document. It used to file every such reading under the moment it was
* confirmed, so a report from years ago showed up as today's results.
*
* The route reads the document's two dates once and hands them to the commit
* step: the report date first, else the filing date (`documentDate`), the order
* the day view places a document by. A value's own date still wins, but only
* when it names a real calendar day.
*/
import { NextRequest } from "next/server";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { cookieJar } from "./mock-next-headers";
import { getPrismaClient, truncateAllTables } from "./setup";
const USER_ID = "user-undated-lab-value";
vi.mock("next/headers", async () => {
const { cookieJar, headerJar } = await import("./mock-next-headers");
return {
headers: vi.fn(async () => ({
get: (name: string) => headerJar.get(name.toLowerCase()) ?? null,
})),
cookies: vi.fn(async () => ({
get: (name: string) => {
const value = cookieJar.get(name);
return value ? { name, value } : undefined;
},
set: (name: string, value: string) => {
cookieJar.set(name, value);
},
delete: (name: string) => {
cookieJar.delete(name);
},
})),
};
});
beforeEach(async () => {
await truncateAllTables(getPrismaClient());
cookieJar.clear();
const prisma = getPrismaClient();
await prisma.user.create({
data: {
id: USER_ID,
username: "undated-lab-value",
email: "undated-lab-value@example.test",
modulePreferencesJson: { inboundDocuments: true, labs: true },
},
});
const session = await prisma.session.create({
data: { userId: USER_ID, expiresAt: new Date(Date.now() + 3_600_000) },
});
cookieJar.set("healthlog_session", session.id);
for (const [name, unit] of [
["Ferritin", "ng/mL"],
["Glucose", "mmol/L"],
["Haemoglobin", "g/L"],
]) {
await prisma.biomarker.create({ data: { userId: USER_ID, name, unit } });
}
});
const noon = (day: string) => new Date(`${day}T12:00:00.000Z`);
async function storedDocument(dates: {
reportDate: string | null;
documentDate: string | null;
}) {
return getPrismaClient().inboundDocument.create({
data: {
userId: USER_ID,
filename: "befund.pdf",
mimeType: "application/pdf",
byteSize: 1024,
contentEncrypted: Buffer.from("stand-in for the stored bytes"),
status: "EXTRACTED",
reportDate: dates.reportDate ? noon(dates.reportDate) : null,
documentDate: dates.documentDate ? noon(dates.documentDate) : null,
},
});
}
async function stagedValue(
documentId: string,
label: string,
value: number,
unit: string,
effectiveDate: string | null,
) {
const { encryptFactData, encryptFactProvenance } =
await import("@/lib/documents/store");
return getPrismaClient().extractedFact.create({
data: {
documentId,
userId: USER_ID,
factType: "OBSERVATION",
status: "PENDING",
confidence: 0.95,
needsReview: false,
dataEncrypted: encryptFactData({
label,
code: null,
codeSystem: null,
value,
valueText: null,
unit,
referenceLow: null,
referenceHigh: null,
referenceText: null,
effectiveDate,
}),
provenanceEncrypted: encryptFactProvenance({
sourceText: `${label} ${value} ${unit}`,
anchored: true,
sourceOffset: 0,
page: 0,
confidence: 0.95,
}),
},
});
}
async function confirm(documentId: string, factIds: string[]) {
const { POST } =
await import("@/app/api/documents/inbound/[id]/confirm/route");
const res = await POST(
new NextRequest(
`http://localhost/api/documents/inbound/${documentId}/confirm`,
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
decisions: factIds.map((factId) => ({ factId, action: "approve" })),
}),
},
),
{ params: Promise.resolve({ id: documentId }) },
);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.data.failed).toEqual([]);
expect(body.data.approved).toHaveLength(factIds.length);
return body.data.approved as { factId: string; recordId: string }[];
}
async function takenAtOf(recordId: string) {
const row = await getPrismaClient().labResult.findUniqueOrThrow({
where: { id: recordId },
});
return row.takenAt.toISOString();
}
describe("an undated lab value is dated by its document", () => {
it("takes the report date over the filing date, keeps a value's own date, and ignores an impossible one", async () => {
const today = new Date().toISOString().slice(0, 10);
// Collection date printed once in the header; filed today (the upload-day
// default for documentDate).
const doc = await storedDocument({
reportDate: "2021-03-01",
documentDate: today,
});
const undated = await stagedValue(doc.id, "Ferritin", 80, "ng/mL", null);
const ownDate = await stagedValue(
doc.id,
"Glucose",
5.1,
"mmol/L",
"2021-02-20",
);
const impossible = await stagedValue(
doc.id,
"Haemoglobin",
142,
"g/L",
"2021-02-30",
);
const approved = await confirm(doc.id, [
undated.id,
ownDate.id,
impossible.id,
]);
const recordOf = (factId: string) =>
approved.find((a) => a.factId === factId)!.recordId;
expect(await takenAtOf(recordOf(undated.id))).toBe(
"2021-03-01T12:00:00.000Z",
);
expect(await takenAtOf(recordOf(ownDate.id))).toBe(
"2021-02-20T12:00:00.000Z",
);
// Not 2 March: a day the month does not have is no date at all.
expect(await takenAtOf(recordOf(impossible.id))).toBe(
"2021-03-01T12:00:00.000Z",
);
});
it("takes the filing date when the document has no report date", async () => {
const doc = await storedDocument({
reportDate: null,
documentDate: "2021-02-15",
});
const undated = await stagedValue(doc.id, "Ferritin", 80, "ng/mL", null);
const [approved] = await confirm(doc.id, [undated.id]);
expect(await takenAtOf(approved!.recordId)).toBe(
"2021-02-15T12:00:00.000Z",
);
});
});