Files
HealthLog/tests/integration/sharing-import-attribution.test.ts
Marc-André Bombeck c043a77429 test: wire every release journey to the tests that carry it
The eight journeys were enumerated and one of them was wired. The other
seven existed as claims in a fixture file, which is the gap the enumeration
was written to close: a browser suite reports what it ran, never what it did
not, and a journey nobody wired reads exactly like a journey that passed.

All eight are wired now, and the list is checked in both directions — an
enumerated journey missing from the wiring fails, not just a wiring entry
naming a journey nobody declared. Before this, a subset was legal, so a
dropped journey and a not-yet-written one looked the same.

A journey names more than one file where more than one file carries it. J1
claims a grant runs through invite, accept, switch, expiry, revoke and a
direct URL; the browser proves the switch, what each level opens and the
direct-URL refusal, and the transitions either side are database facts
proved against real Postgres. Folding that into one file would mean a
browser spec driving expiry through the clock or an integration file
asserting what a page renders, and both are worse tests than the two that
exist.

Three journeys ride Playwright and five ride Vitest, so a green run of this
guard means all eight are claimed and here is which have executed in the
ordinary gate — never that all eight passed. The runner is derived from the
path rather than trusted, so it cannot become a label set to whichever value
made the split read better.

The extractor now reads `it(` as well as `test(`, and the parameterised
`it.each(table)("title")` form alongside the single-call one. It saw neither
before: five of the journeys are proved by Vitest files, every per-file leg
is over a matched set, and an empty match set satisfied them all. The
synthetic spec that proves the parse grew a Vitest half, and it is what
caught the parameterised form being invisible. What the matcher still cannot
see is written into its own doc comment.
2026-08-07 06:11:36 +02:00

180 lines
5.1 KiB
TypeScript

import { NextRequest } from "next/server";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { GET as getImportStatus } from "@/app/api/medications/[id]/intake/import/[jobId]/status/route";
import { ACCOUNT_SELECTOR_HEADER } from "@/lib/api-handler";
import { hashToken } from "@/lib/auth/hmac";
import { toJson } from "@/lib/db";
import {
_setMedicationIntakeImportPrismaForTests,
processMedicationIntakeImportJob,
type MedicationImportProgress,
} from "@/lib/jobs/medication-intake-import";
import { getPrismaClient, truncateAllTables } from "./setup";
import { cookieJar, headerJar } from "./mock-next-headers";
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;
},
})),
};
});
const EMPTY_PROGRESS: MedicationImportProgress = {
processed: 0,
total: 0,
imported: 0,
skippedByReason: {},
touchedDays: [],
rollupProcessed: 0,
};
beforeEach(async () => {
const prisma = getPrismaClient();
_setMedicationIntakeImportPrismaForTests(prisma);
await truncateAllTables(prisma);
cookieJar.clear();
headerJar.clear();
});
describe("shared intake import attribution", () => {
it("[J7-durable-import-actor] retains the record and manager through one durable completion audit", async () => {
const prisma = getPrismaClient();
await prisma.user.createMany({
data: [
{
id: "import-record",
username: "import-record",
email: "import-record@example.test",
role: "USER",
},
{
id: "import-manager",
username: "import-manager",
email: "import-manager@example.test",
role: "USER",
},
],
});
const job = await prisma.medicationIntakeImportJob.create({
data: {
recordUserId: "import-record",
actorUserId: "import-manager",
medicationId: null,
payload: toJson({ entries: [] }),
progress: toJson(EMPTY_PROGRESS),
} as never,
});
await processMedicationIntakeImportJob(job.id);
await processMedicationIntakeImportJob(job.id);
const [completed, audits] = await Promise.all([
prisma.medicationIntakeImportJob.findUniqueOrThrow({
where: { id: job.id },
}),
prisma.auditLog.findMany({
where: {
userId: "import-record",
action: "medication.intake.import",
},
}),
]);
expect(completed).toMatchObject({
recordUserId: "import-record",
actorUserId: "import-manager",
status: "done",
});
expect(audits).toHaveLength(1);
expect(audits[0]).toMatchObject({
userId: "import-record",
actorUserId: "import-manager",
});
expect(JSON.parse(audits[0]!.details!)).toEqual({
jobId: job.id,
recordUserId: "import-record",
actorUserId: "import-manager",
imported: 0,
skipped: 0,
total: 0,
});
});
it("[J7-durable-import-actor] lets the initiating manager poll the record-scoped job", async () => {
const prisma = getPrismaClient();
await prisma.user.createMany({
data: [
{
id: "status-record",
username: "status-record",
email: "status-record@example.test",
role: "USER",
},
{
id: "status-manager",
username: "status-manager",
email: "status-manager@example.test",
role: "USER",
},
],
});
const medication = await prisma.medication.create({
data: {
userId: "status-record",
name: "Status medication",
dose: "1 unit",
},
});
await prisma.accountGrant.create({
data: {
id: "status-manage-grant",
grantorId: "status-record",
granteeId: "status-manager",
access: "MANAGE",
acceptedAt: new Date(),
},
});
const token = "shared-import-status-token".padEnd(28, "0");
await prisma.apiToken.create({
data: {
userId: "status-manager",
name: "shared import status",
tokenHash: hashToken(token),
permissions: ["*"],
},
});
headerJar.set("authorization", `Bearer ${token}`);
headerJar.set(ACCOUNT_SELECTOR_HEADER, "status-record");
const job = await prisma.medicationIntakeImportJob.create({
data: {
recordUserId: "status-record",
actorUserId: "status-manager",
medicationId: medication.id,
payload: toJson({ entries: [] }),
progress: toJson(EMPTY_PROGRESS),
},
});
const response = await getImportStatus(
new NextRequest(
`http://localhost/api/medications/${medication.id}/intake/import/${job.id}/status`,
{},
),
{ params: Promise.resolve({ id: medication.id, jobId: job.id }) },
);
expect(response.status).toBe(200);
expect((await response.json()).data.jobId).toBe(job.id);
});
});