rpki/ui/rpki-explorer/tests/e2e/object-detail.spec.ts

89 lines
3.8 KiB
TypeScript

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");
});