import { expect, test } from "@playwright/test"; import { installMockApi } from "./mockApi"; test.beforeEach(async ({ page }) => { await installMockApi(page); }); test("object detail renders header metadata and parsed ROA projection", async ({ page }) => { await page.goto("/objects/obj-roa-0001"); await expect(page.getByText("ROA object")).toBeVisible(); await expect(page.getByText("rsync://repo-a.example/ca1/alice.roa").first()).toBeVisible(); // Parsed tab is the default and renders structured ROA content await expect(page.getByText("AS ID")).toBeVisible(); await expect(page.getByText("65001")).toBeVisible(); await expect(page.getByText("203.0.113.0/24")).toBeVisible(); await expect(page.getByText("2001:db8::/32")).toBeVisible(); }); test("tabs lazy-load: chain and validation requests fire only on activation", async ({ page }) => { const requests: string[] = []; page.on("request", (req) => requests.push(req.url())); await page.goto("/objects/obj-roa-0001"); await expect(page.getByText("65001")).toBeVisible(); expect(requests.some((u) => u.includes("/chain"))).toBe(false); expect(requests.some((u) => u.includes("/validation"))).toBe(false); await page.getByRole("tab", { name: "Chain" }).click(); await expect.poll(() => requests.some((u) => u.includes("/chain"))).toBe(true); await expect(page.getByText("issued_by")).toBeVisible(); await page.getByRole("tab", { name: "Validation" }).click(); await expect.poll(() => requests.some((u) => u.includes("/validation"))).toBe(true); await expect(page.getByText("Final status")).toBeVisible(); }); test("validation explain POSTs and renders the audit-projection notice", async ({ page }) => { await page.goto("/objects/obj-roa-0001?tab=validation"); const posts: string[] = []; page.on("request", (req) => { if (req.method() === "POST" && req.url().includes("/validation/explain")) posts.push(req.url()); }); await page.getByRole("button", { name: "Run explain" }).click(); await expect.poll(() => posts.length).toBe(1); await expect(page.getByText("audit_projection").first()).toBeVisible(); await expect(page.getByText("not a full revalidation")).toBeVisible(); }); test("manifest object renders the file list", async ({ page }) => { await page.goto("/objects/obj-mft-0001"); await expect(page.getByText("Manifest number")).toBeVisible(); await expect(page.getByText("0A2F")).toBeVisible(); await expect(page.getByRole("cell", { name: "alice.roa" })).toBeVisible(); }); test("object without projection shows the explicit unavailable state", async ({ page }) => { await page.goto("/objects/obj-asa-0001"); await expect(page.getByText("Parsed projection unavailable")).toBeVisible(); }); test("download raw triggers a browser download", async ({ page }) => { await page.goto("/objects/obj-roa-0001"); const downloadPromise = page.waitForEvent("download"); await page.getByRole("button", { name: "Download raw" }).click(); const download = await downloadPromise; expect(download.suggestedFilename()).toBe("alice.roa"); }); test("export object set starts a job and offers the tarball when complete", async ({ page }) => { await page.goto("/objects/obj-roa-0001"); const posts: string[] = []; page.on("request", (req) => { if (req.method() === "POST" && req.url().includes("/exports")) posts.push(req.url()); }); await page.getByRole("button", { name: "Export object set" }).click(); await expect.poll(() => posts.length).toBe(1); // Mock completes the job after the first poll. await expect(page.getByRole("link", { name: "Download tar" })).toBeVisible({ timeout: 10_000 }); }); test("missing object shows an error state", async ({ page }) => { await page.goto("/objects/obj-does-not-exist"); await expect(page.getByRole("alert")).toContainText("Failed to load object"); });