mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
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:
1 parent
ba27e728cf
commit
52e6bcd480
1 file changed
+136
-6
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user