test(mcp): prove a report's lab link lands on that report's reading

Two draws of one analyte a year apart and two reports on the same day, against
Postgres: each report lists its own reading id, and fetching it returns that
reading with that report in its links, not the latest one.
This commit is contained in:
Marc-André Bombeck committed 2026-11-03 21:33:17 +01:00
1 parent ba27e728cf
commit 52e6bcd480
1 file changed
+136 -6
+136 -6
View File
@@ -74,12 +74,17 @@ async function makeDocument(
});
}
async function makeLab(userId: string, analyte: string, takenAt: string) {
async function makeLab(
userId: string,
analyte: string,
takenAt: string,
value = 120,
) {
return getPrismaClient().labResult.create({
data: {
userId,
analyte,
value: 120,
value,
unit: "mg/dL",
takenAt: new Date(takenAt),
},
@@ -206,8 +211,9 @@ describe("get_labs lists each reading's links", () => {
},
]);
expect(JSON.stringify(answer)).not.toContain("paperless.example.test");
// No raw lab result id is left on the wire.
expect(JSON.stringify(answer)).not.toContain(r.ldl.id);
// Each reading carries its own fetch id, and no bare lab result id is left.
expect((ldl as Reading & { id?: string }).id).toBe(`labresult:${r.ldl.id}`);
expect(JSON.stringify(answer).split(r.ldl.id)).toHaveLength(2);
});
it("lists the links of earlier values and of a trajectory, [] when none", async () => {
@@ -331,8 +337,8 @@ describe("fetch document lists the labs and medications filed against it", () =>
const r = await seedRecord(user.id);
const doc = await fetchId(user.id, `document:${r.report.id}`);
expect(doc.metadata.links?.map((l) => l.id)).toEqual([
"lab:LDL",
"lab:HDL",
`labresult:${r.ldl.id}`,
`labresult:${r.hdl.id}`,
`med:${r.med.id}`,
]);
expect(doc.text).toContain("Linked records: lab");
@@ -347,6 +353,130 @@ describe("fetch document lists the labs and medications filed against it", () =>
});
});
describe("a link to a lab result lands on the reading that record holds", () => {
/**
* Two reports a year apart with an LDL reading each, plus a second report
* dated the same day as the later one with its own LDL reading. `lab:LDL`
* answers with the latest; a link from a report must not.
*/
async function seedDraws(userId: string) {
const db = getPrismaClient();
const earlier = await makeDocument(userId, "Lipid panel 2025");
const later = await makeDocument(userId, "Lipid panel 2026");
const sameDay = await makeDocument(userId, "Second lab, same day");
const visit = await db.encounter.create({
data: {
userId,
occurredAt: new Date("2025-03-01T09:00:00Z"),
kind: "ROUTINE",
},
});
const early = await makeLab(userId, "LDL", "2025-03-01T09:00:00Z", 160);
const late = await makeLab(userId, "LDL", "2026-03-01T09:00:00Z", 110);
const twin = await makeLab(userId, "LDL", "2026-03-01T09:00:00Z", 115);
for (const [lab, doc] of [
[early, earlier],
[late, later],
[twin, sameDay],
] as const) {
await linkTargets(db, {
userId,
sourceKind: "labResult",
sourceId: lab.id,
targetKind: "document",
targetIds: [doc.id],
});
}
await linkTargets(db, {
userId,
sourceKind: "encounter",
sourceId: visit.id,
targetKind: "labResult",
targetIds: [early.id],
});
return { earlier, later, sameDay, visit, early, late, twin };
}
const linkIds = (answer: FetchAnswer) =>
(answer.metadata.links ?? []).map((l) => l.id);
it("from a report, a visit and get_labs, each id fetches that reading", async () => {
const user = await makeUser();
const d = await seedDraws(user.id);
const fromEarlier = await fetchId(user.id, `document:${d.earlier.id}`);
expect(linkIds(fromEarlier)).toEqual([`labresult:${d.early.id}`]);
const reading = await fetchId(user.id, `labresult:${d.early.id}`);
expect(reading.text).toContain("LDL: 160 mg/dL");
expect(reading.text).toContain("measured 2025-03-01");
expect(reading.metadata.value).toBe(160);
expect(linkIds(reading)).toEqual([
`document:${d.earlier.id}`,
`visit:${d.visit.id}`,
]);
expect(linkIds(reading)).not.toContain(`document:${d.later.id}`);
// The visit the earlier draw is filed against links the same reading.
const visit = await fetchId(user.id, `visit:${d.visit.id}`);
expect(linkIds(visit)).toContain(`labresult:${d.early.id}`);
// Two reports on one day: each lands on its own reading.
for (const [doc, lab, value] of [
[d.later, d.late, 110],
[d.sameDay, d.twin, 115],
] as const) {
const fromDoc = await fetchId(user.id, `document:${doc.id}`);
expect(linkIds(fromDoc)).toEqual([`labresult:${lab.id}`]);
const own = await fetchId(user.id, `labresult:${lab.id}`);
expect(own.metadata.value).toBe(value);
expect(linkIds(own)).toEqual([`document:${doc.id}`]);
}
// The analyte view is unchanged: the latest reading, named by its id.
const latest = await fetchId(user.id, "lab:LDL");
expect(latest.metadata.readingId).toMatch(/^labresult:/);
expect([`labresult:${d.late.id}`, `labresult:${d.twin.id}`]).toContain(
latest.metadata.readingId,
);
// Every reading get_labs lists carries an id that fetch takes back.
const trajectory = (await tool("get_labs").run(ctx(user.id), {
analyte: "LDL",
history: true,
})) as { readings: Array<{ id: string; value: number }> };
expect(trajectory.readings).toHaveLength(3);
for (const r of trajectory.readings) {
const back = await fetchId(user.id, r.id);
expect(back.metadata.value).toBe(r.value);
}
});
it("does not resolve another account's, a deleted or a switched-off reading", async () => {
const me = await makeUser();
const other = await makeUser();
const mine = await seedDraws(me.id);
const theirs = await seedDraws(other.id);
const foreign = await fetchId(me.id, `labresult:${theirs.early.id}`);
expect(foreign.title).toBe("Not found");
expect(JSON.stringify(foreign)).not.toContain(theirs.earlier.id);
await getPrismaClient().labResult.update({
where: { id: mine.early.id },
data: { deletedAt: new Date() },
});
const deleted = await fetchId(me.id, `labresult:${mine.early.id}`);
expect(deleted.title).toBe("Not found");
const report = await fetchId(me.id, `document:${mine.earlier.id}`);
expect(linkIds(report)).toEqual([]);
const off = await makeUser({ labs: false });
const offDraws = await seedDraws(off.id);
const gated = await fetchId(off.id, `labresult:${offDraws.late.id}`);
expect(gated.metadata.reason).toBe("module_disabled");
});
});
describe("list rows carry the id fetch takes", () => {
it("on visits, the medication schedule and conditions", async () => {
const user = await makeUser();