pelagia-portal/App/tests/e2e/export-gate.spec.ts
2026-05-18 23:18:58 +05:30

178 lines
5.9 KiB
TypeScript

/**
* 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<string> {
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");
});
});