/** * User stories covered: Features 5 & 6 — Export gate and approver as signatory * - Export buttons (PDF/XLSX) NOT shown on DRAFT/SUBMITTED/MGR_REVIEW POs * - Export buttons ARE shown on MGR_APPROVED or later statuses * - API returns 403 for DRAFT PO export attempts * - XLSX export returns correct content-type for approved POs (Feature 6) * * Note on Feature 5 export gate: * The po-detail.tsx source shows export buttons only render for statuses: * ["MGR_APPROVED", "SENT_FOR_PAYMENT", "PAID_DELIVERED", "PARTIALLY_CLOSED", "CLOSED"] * This supersedes the old po-export.spec.ts which expected buttons on DRAFT — those tests * were written before the export gate fix. * * Created: 2026-05-17 */ import { test, expect, type BrowserContext } from "@playwright/test"; import { login, USERS, createDraftPo, submitPo } from "./helpers/login"; // Helper: Create a PO and fully approve it (tech → manager approval) async function createApprovedPo( page: import("@playwright/test").Page, context: BrowserContext, title: string ): Promise { await login(page, USERS.TECH); const poUrl = await submitPo(page, title); await context.clearCookies(); await login(page, USERS.MANAGER); await page.goto(poUrl); await page.getByRole("button", { name: /^approve$/i }).click(); await expect(page.getByText(/approved/i)).toBeVisible({ timeout: 10_000 }); return poUrl; } test.describe("Feature 5 — Export gate: only approved POs can be exported", () => { test("US-5a: Export PDF button is NOT shown on a DRAFT PO", async ({ page }) => { await login(page, USERS.TECH); const poUrl = await createDraftPo(page, `E2E_GATE_DRAFT_${Date.now()}`); await page.goto(poUrl); await expect( page.getByRole("link", { name: /export pdf/i }) ).not.toBeVisible(); console.log("✓ Export PDF button correctly absent on DRAFT PO"); }); test("US-5a: Export XLSX button is NOT shown on a DRAFT PO", async ({ page }) => { await login(page, USERS.TECH); const poUrl = await createDraftPo(page, `E2E_GATE_DRAFTX_${Date.now()}`); await page.goto(poUrl); await expect( page.getByRole("link", { name: /export xlsx/i }) ).not.toBeVisible(); console.log("✓ Export XLSX button correctly absent on DRAFT PO"); }); test("US-5a: Export buttons are NOT shown on a SUBMITTED PO", async ({ page }) => { await login(page, USERS.TECH); const poUrl = await submitPo(page, `E2E_GATE_SUB_${Date.now()}`); await page.goto(poUrl); await expect( page.getByRole("link", { name: /export pdf/i }) ).not.toBeVisible(); await expect( page.getByRole("link", { name: /export xlsx/i }) ).not.toBeVisible(); console.log("✓ Export buttons correctly absent on SUBMITTED PO"); }); test("US-5b: Export buttons ARE shown on a MGR_APPROVED PO", async ({ page, context, }: { page: import("@playwright/test").Page; context: BrowserContext; }) => { const poUrl = await createApprovedPo( page, context, `E2E_GATE_APPROVED_${Date.now()}` ); // Still logged in as manager; view the approved PO await page.goto(poUrl); await expect(page.getByRole("link", { name: /export pdf/i })).toBeVisible({ timeout: 10_000, }); await expect( page.getByRole("link", { name: /export xlsx/i }) ).toBeVisible(); console.log("✓ Export buttons visible on MGR_APPROVED PO"); }); test("US-5c: GET /api/po/[id]/export?format=pdf returns 403 for a DRAFT PO", async ({ page, }) => { await login(page, USERS.TECH); const poUrl = await createDraftPo(page, `E2E_GATE_API_${Date.now()}`); const poId = poUrl.split("/po/")[1].replace(/\/$/, ""); const response = await page.request.get( `/api/po/${poId}/export?format=pdf` ); expect(response.status()).toBe(403); console.log(`✓ API returns 403 for DRAFT PO export (id: ${poId})`); }); test("US-5c: GET /api/po/[id]/export?format=xlsx returns 403 for a DRAFT PO", async ({ page, }) => { await login(page, USERS.TECH); const poUrl = await createDraftPo(page, `E2E_GATE_APIX_${Date.now()}`); const poId = poUrl.split("/po/")[1].replace(/\/$/, ""); const response = await page.request.get( `/api/po/${poId}/export?format=xlsx` ); expect(response.status()).toBe(403); console.log(`✓ API returns 403 for DRAFT PO XLSX export (id: ${poId})`); }); }); test.describe("Feature 6 — Approver as signatory: export content-types", () => { test("US-6a: ACCOUNTS user — XLSX export returns HTTP 200 with spreadsheet content-type", async ({ page, context, }: { page: import("@playwright/test").Page; context: BrowserContext; }) => { // Create and approve a PO first const poUrl = await createApprovedPo( page, context, `E2E_XLSX_ACCT_${Date.now()}` ); const poId = poUrl.split("/po/")[1].replace(/\/$/, ""); // Switch to accounts user await context.clearCookies(); await login(page, USERS.ACCOUNTS); const response = await page.request.get( `/api/po/${poId}/export?format=xlsx` ); expect(response.status()).toBe(200); const ct = response.headers()["content-type"] ?? ""; expect(ct).toContain("spreadsheetml"); console.log(`✓ XLSX export HTTP 200 with content-type: ${ct}`); }); test("US-6a: ACCOUNTS user — PDF export returns HTTP 200", async ({ page, context, }: { page: import("@playwright/test").Page; context: BrowserContext; }) => { const poUrl = await createApprovedPo( page, context, `E2E_PDF_ACCT_${Date.now()}` ); const poId = poUrl.split("/po/")[1].replace(/\/$/, ""); await context.clearCookies(); await login(page, USERS.ACCOUNTS); const response = await page.request.get( `/api/po/${poId}/export?format=pdf` ); expect(response.status()).toBe(200); console.log("✓ PDF export HTTP 200 for ACCOUNTS user on approved PO"); }); });