mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
180 lines
5.1 KiB
TypeScript
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("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("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);
|
|
});
|
|
});
|