/** * Integration tests for prepareVendorEmail (issue #14) — the "Email to vendor" * action that renders the PO PDF, stores it, and returns an Outlook mailto draft * with a download link. PdfService + storage are mocked (no Chromium / R2). */ import { vi, describe, it, expect, beforeAll, afterEach, afterAll } from "vitest"; vi.mock("@/auth", () => ({ auth: vi.fn() })); vi.mock("@/lib/pdf-service", () => ({ renderPoPdf: vi.fn(async () => Buffer.from("%PDF-1.4 fake")), isPdfServiceConfigured: vi.fn(() => true), PdfServiceError: class PdfServiceError extends Error {}, })); vi.mock("@/lib/storage", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, uploadBuffer: vi.fn(async () => {}), generateDownloadUrl: vi.fn(async () => "https://files.example/po.pdf?sig=abc"), }; }); import { auth } from "@/auth"; import { db } from "@/lib/db"; import { prepareVendorEmail } from "@/app/(portal)/po/[id]/email-actions"; import { isPdfServiceConfigured } from "@/lib/pdf-service"; import { makeSession, getSeedUser, getSeedVessel, getSeedAccount } from "./helpers"; const PREFIX = "INTTEST_EMAILVENDOR_"; let techId: string; let vesselId: string; let accountId: string; let vendorWithEmailId: string; let vendorNoEmailId: string; const as = (userId: string, role: "TECHNICAL" | "MANAGER") => vi.mocked(auth as unknown as () => Promise).mockResolvedValue(makeSession(userId, role)); async function makePo(status: string, vendorId: string): Promise { const po = await db.purchaseOrder.create({ data: { poNumber: `${PREFIX}${status}-${Date.now()}-${Math.round(Math.random() * 1e6)}`, title: `${PREFIX}PO`, status: status as never, totalAmount: 1000, currency: "INR", vesselId, accountId, submitterId: techId, vendorId, }, }); return po.id; } beforeAll(async () => { const [tech, vessel, account] = await Promise.all([ getSeedUser("tech@pelagia.local"), getSeedVessel("MV Poseidon"), getSeedAccount("700201"), ]); techId = tech.id; vesselId = vessel.id; accountId = account.id; const withEmail = await db.vendor.create({ data: { name: `${PREFIX}WithEmail`, contacts: { create: { name: "Vinod", email: "vinod@vendor.test", isPrimary: true } } }, }); vendorWithEmailId = withEmail.id; const noEmail = await db.vendor.create({ data: { name: `${PREFIX}NoEmail` } }); vendorNoEmailId = noEmail.id; }); afterEach(() => { vi.mocked(isPdfServiceConfigured).mockReturnValue(true); vi.clearAllMocks(); }); afterAll(async () => { await db.purchaseOrder.deleteMany({ where: { title: { startsWith: PREFIX } } }); await db.vendorContact.deleteMany({ where: { vendor: { name: { startsWith: PREFIX } } } }); await db.vendor.deleteMany({ where: { name: { startsWith: PREFIX } } }); }); describe("prepareVendorEmail", () => { it("builds a mailto draft to the vendor's primary contact with the PDF link", async () => { as(techId, "TECHNICAL"); const poId = await makePo("MGR_APPROVED", vendorWithEmailId); const result = await prepareVendorEmail(poId); expect("ok" in result && result.ok).toBe(true); if (!("ok" in result)) throw new Error(result.error); expect(result.to).toBe("vinod@vendor.test"); expect(result.mailto.startsWith("mailto:vinod%40vendor.test?")).toBe(true); // Subject is the PO number; body carries the (mocked) download link. expect(decodeURIComponent(result.mailto)).toContain("Purchase Order"); expect(decodeURIComponent(result.mailto)).toContain("https://files.example/po.pdf?sig=abc"); }); it("is available once payment is recorded too (PARTIALLY_PAID)", async () => { as(techId, "TECHNICAL"); const poId = await makePo("PARTIALLY_PAID", vendorWithEmailId); expect("ok" in (await prepareVendorEmail(poId))).toBe(true); }); it("refuses a PO that is not yet approved (DRAFT)", async () => { as(techId, "TECHNICAL"); const poId = await makePo("DRAFT", vendorWithEmailId); const result = await prepareVendorEmail(poId); expect("error" in result).toBe(true); }); it("errors when the vendor has no primary contact email", async () => { as(techId, "TECHNICAL"); const poId = await makePo("MGR_APPROVED", vendorNoEmailId); const result = await prepareVendorEmail(poId); expect("error" in result).toBe(true); }); it("errors when the PDF service is not configured", async () => { vi.mocked(isPdfServiceConfigured).mockReturnValue(false); as(techId, "TECHNICAL"); const poId = await makePo("MGR_APPROVED", vendorWithEmailId); const result = await prepareVendorEmail(poId); expect("error" in result).toBe(true); }); it("rejects an unauthenticated caller", async () => { vi.mocked(auth as unknown as () => Promise).mockResolvedValue(null); const poId = await makePo("MGR_APPROVED", vendorWithEmailId); expect(await prepareVendorEmail(poId)).toEqual({ error: "Unauthorized" }); }); });