/** * Integration test for the feature-flagged closed-PO attachment remediation * (NEXT_PUBLIC_CLOSED_PO_ATTACHMENTS_ENABLED). With the flag ON, a CLOSED PO's own * submitter — plus Accounts / Manager / SuperUser — may attach documents; everyone * else is refused. (The flag-OFF case lives in po-document-upload.test.ts.) */ import { vi, describe, it, expect, beforeAll, afterEach } from "vitest"; vi.mock("@/auth", () => ({ auth: vi.fn() })); vi.mock("next/cache", () => ({ revalidatePath: vi.fn() })); vi.mock("@/lib/storage", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, uploadBuffer: vi.fn().mockResolvedValue(undefined) }; }); // Flip ONLY the remediation flag on; everything else stays real. vi.mock("@/lib/feature-flags", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, CLOSED_PO_ATTACHMENTS_ENABLED: true }; }); import { auth } from "@/auth"; import { db } from "@/lib/db"; import { uploadBuffer } from "@/lib/storage"; import type { Role } from "@prisma/client"; import { createPo } from "@/app/(portal)/po/new/actions"; import { uploadPoDocuments } from "@/app/actions/upload-po-documents"; import { makeSession, getSeedUser, getSeedVessel, getSeedAccount, makePoForm, deletePosByTitle } from "./helpers"; const PREFIX = "INTTEST_CLOSEDPO_"; let techId: string; // the PO's submitter let vesselId: string; let accountId: string; const userIds: Record = {}; beforeAll(async () => { const [tech, accounts, manager, superuser, manning, auditor, vessel, account] = await Promise.all([ getSeedUser("tech@pelagia.local"), getSeedUser("accounts@pelagia.local"), getSeedUser("manager@pelagia.local"), getSeedUser("superuser@pelagia.local"), getSeedUser("manning@pelagia.local"), getSeedUser("auditor@pelagia.local"), getSeedVessel("MV Pelagia Star"), getSeedAccount("700201"), ]); techId = tech.id; vesselId = vessel.id; accountId = account.id; userIds.ACCOUNTS = accounts.id; userIds.MANAGER = manager.id; userIds.SUPERUSER = superuser.id; userIds.MANNING = manning.id; userIds.AUDITOR = auditor.id; }); afterEach(async () => { await deletePosByTitle(PREFIX); vi.clearAllMocks(); }); function as(userId: string, role: Role) { vi.mocked(auth as unknown as () => Promise).mockResolvedValue(makeSession(userId, role)); } function pdf(name: string): File { return new File(["%PDF-1.4 hello"], name, { type: "application/pdf" }); } // A CLOSED PO submitted by the TECHNICAL user. async function makeClosedPo(title: string): Promise { as(techId, "TECHNICAL"); const result = await createPo(makePoForm({ title, vesselId, accountId, intent: "draft" })); expect(result).not.toHaveProperty("error"); const poId = (result as { id: string }).id; await db.purchaseOrder.update({ where: { id: poId }, data: { status: "CLOSED" } }); return poId; } describe("closed-PO attachments (flag on)", () => { it("lets the PO's own submitter attach to their closed PO", async () => { const poId = await makeClosedPo(`${PREFIX}Submitter`); as(techId, "TECHNICAL"); const err = await uploadPoDocuments(poId, [pdf("missing-invoice.pdf")]); expect(err).toBeNull(); expect(await db.pODocument.count({ where: { poId } })).toBe(1); }); it.each<[string, Role]>([ ["ACCOUNTS", "ACCOUNTS"], ["MANAGER", "MANAGER"], ["SUPERUSER", "SUPERUSER"], ])("lets %s attach to a closed PO they did not submit", async (key, role) => { const poId = await makeClosedPo(`${PREFIX}${key}`); as(userIds[key], role); const err = await uploadPoDocuments(poId, [pdf("doc.pdf")]); expect(err).toBeNull(); expect(await db.pODocument.count({ where: { poId } })).toBe(1); }); it("refuses a submitter-role user who is not this PO's submitter", async () => { const poId = await makeClosedPo(`${PREFIX}OtherSubmitter`); as(userIds.MANNING, "MANNING"); // a submitter role, but not the PO's submitter const err = await uploadPoDocuments(poId, [pdf("doc.pdf")]); expect(err).toEqual({ error: "Adding attachments to a closed purchase order isn't allowed." }); expect(uploadBuffer).not.toHaveBeenCalled(); expect(await db.pODocument.count({ where: { poId } })).toBe(0); }); it("refuses a role outside the allow-list (auditor)", async () => { const poId = await makeClosedPo(`${PREFIX}Auditor`); as(userIds.AUDITOR, "AUDITOR"); const err = await uploadPoDocuments(poId, [pdf("doc.pdf")]); expect(err).toEqual({ error: "Adding attachments to a closed purchase order isn't allowed." }); expect(await db.pODocument.count({ where: { poId } })).toBe(0); }); it("still allows uploads to a non-closed PO (normal flow unaffected)", async () => { as(techId, "TECHNICAL"); const result = await createPo(makePoForm({ title: `${PREFIX}Draft`, vesselId, accountId, intent: "draft" })); const poId = (result as { id: string }).id; // stays DRAFT as(techId, "TECHNICAL"); const err = await uploadPoDocuments(poId, [pdf("draft-doc.pdf")]); expect(err).toBeNull(); expect(await db.pODocument.count({ where: { poId } })).toBe(1); }); });