test(insights): cover the adherence-card screen and the hero-line envelope guard

Compliance card: a dose-change imperative and a fabricated risk figure are
withheld to the deterministic fallback (no model text persisted, updatedAt
stays null), while a clean grounded summary still renders and persists.

Hero lines: a well-formed, truncated, or fenced JSON envelope resolves to the
deterministic lead/template for both the arrival reaction and the coach nudge;
a plain-text line still ships verbatim.
This commit is contained in:
Marc-André Bombeck committed 2026-07-24 15:10:24 +02:00
1 parent 0baffe04cd
commit f251ae13f8
2 files changed
+162

No files matched your search

@@ -26,6 +26,7 @@ vi.mock("@/lib/medication-category", () => ({
import { prisma } from "@/lib/db";
import { runStatusCompletion } from "@/lib/insights/status-provider";
import { getMedicationCategories } from "@/lib/medication-category";
import { getNoKeyMedicationComplianceStatusText } from "@/lib/insights/no-key-fallbacks";
import { generateMedicationComplianceStatusForUser } from "../medication-compliance-status";
const dayMs = 24 * 60 * 60 * 1000;
@@ -218,6 +219,100 @@ describe("generateMedicationComplianceStatusForUser — cache-read skips a stub"
});
});
describe("generateMedicationComplianceStatusForUser — outbound screen (v1.32.20)", () => {
/**
* The medication-adherence card is the highest-leverage surface for the
* outbound screen — a dose-change imperative or a fabricated figure lands
* exactly here. v1.32.20 routes it through `finalizeStatusSummary`, so it now
* WITHHOLDS the same dose / risk / causal violations its seven sibling status
* cards do (previously it only withheld unparseable envelopes). WITHHOLD =
* serve the deterministic no-key line, persist no model text, `updatedAt`
* stays null; the per-medication placeholder rows still render.
*/
function setupCard(now: Date) {
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(null);
vi.mocked(prisma.medication.findMany).mockResolvedValue([
medFixture(now),
] as never);
vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValue(
[] as never,
);
vi.mocked(prisma.auditLog.create).mockResolvedValue({
createdAt: now,
} as never);
}
it("withholds a dose-change imperative instead of rendering the raw prose", async () => {
const now = new Date();
setupCard(now);
stubCompletion('{"summary":"Increase your dose to 10 mg next week."}');
const result = await generateMedicationComplianceStatusForUser("user-1", {
locale: "en",
});
expect(result.summary).toBe(getNoKeyMedicationComplianceStatusText("en"));
expect(result.summary).not.toContain("10 mg");
expect(result.summary).not.toContain("Increase");
expect(result.updatedAt).toBeNull();
// The per-medication placeholder rows still surface for the active med.
expect(result.medications).toHaveLength(1);
// No cache row ever persisted the screened model text.
for (const call of vi.mocked(prisma.auditLog.create).mock.calls) {
const details = JSON.parse(
(call[0] as { data: { details: string } }).data.details,
) as { summary?: string };
expect(details.summary ?? "").not.toContain("10 mg");
}
});
it("withholds a fabricated risk figure instead of rendering the raw prose", async () => {
const now = new Date();
setupCard(now);
stubCompletion(
'{"summary":"Your 10-year cardiovascular risk is about 14%."}',
);
const result = await generateMedicationComplianceStatusForUser("user-1", {
locale: "en",
});
expect(result.summary).toBe(getNoKeyMedicationComplianceStatusText("en"));
expect(result.summary).not.toContain("14%");
expect(result.updatedAt).toBeNull();
expect(result.medications).toHaveLength(1);
});
it("still renders a clean grounded compliance summary", async () => {
const now = new Date();
setupCard(now);
stubCompletion(
'{"summary":"Adherence held steady at 96% across the last 30 days."}',
);
const result = await generateMedicationComplianceStatusForUser("user-1", {
locale: "en",
});
expect(result.summary).toBe(
"Adherence held steady at 96% across the last 30 days.",
);
expect(result.updatedAt).not.toBeNull();
expect(result.cached).toBe(false);
// The clean assessment was persisted (not the negative stub).
const persisted = vi
.mocked(prisma.auditLog.create)
.mock.calls.map(
(call) =>
JSON.parse(
(call[0] as { data: { details: string } }).data.details,
) as { summary?: string },
)
.find((details) => (details.summary ?? "").includes("96%"));
expect(persisted).toBeTruthy();
});
});
describe("generateMedicationComplianceStatusForUser — token-leak hardening (v1.4.27 F16)", () => {
it("strips metric: tokens out of the cached summary", async () => {
const now = new Date();
@@ -0,0 +1,67 @@
import { describe, expect, it, vi } from "vitest";
// The sanitisers are pure, but their modules import the db client at load time.
// Stub it so the unit under test never reaches for a connection.
vi.mock("@/lib/db", () => ({ prisma: {} }));
import { sanitiseReactionLine } from "@/lib/jobs/reaction-line";
import { sanitiseAiBody } from "@/lib/jobs/coach-nudge-ai";
/**
* v1.32.20 — both background hero lines (the arrival reaction and the coach
* nudge body) now run the status cards' structured-envelope detection
* (`extractAssessmentSummary`). A hero line is one plain sentence; a brace-led
* or fenced `{"line":"…"}` reply, or a truncated envelope under the char cap,
* is a broken contract and must resolve to the deterministic lead/template
* rather than render raw JSON. Genuine prose still ships.
*/
describe("sanitiseReactionLine — JSON-envelope detection", () => {
it("rejects a well-formed JSON envelope so the deterministic lead stands", () => {
expect(
sanitiseReactionLine(
'{"line":"Nice work today, your numbers are holding steady."}',
"en",
),
).toBeNull();
});
it("rejects a truncated brace-led envelope", () => {
expect(sanitiseReactionLine('{"line":"Nice work today', "en")).toBeNull();
});
it("rejects a fenced JSON envelope", () => {
expect(
sanitiseReactionLine('```json\n{"line":"Steady week."}\n```', "en"),
).toBeNull();
});
it("still renders a plain-text line verbatim", () => {
expect(
sanitiseReactionLine(
"Nice work today, your numbers are holding steady.",
"en",
),
).toBe("Nice work today, your numbers are holding steady.");
});
});
describe("sanitiseAiBody — JSON-envelope detection", () => {
it("rejects a well-formed JSON envelope so the template stands", () => {
expect(
sanitiseAiBody(
'{"body":"A gentle check-in on your routine this week."}',
"en",
),
).toBeNull();
});
it("rejects a truncated brace-led envelope", () => {
expect(sanitiseAiBody('{"body":"A gentle check-in', "en")).toBeNull();
});
it("still renders a plain-text body verbatim", () => {
expect(
sanitiseAiBody("A gentle check-in on your routine this week.", "en"),
).toBe("A gentle check-in on your routine this week.");
});
});