import { test, expect } from "@playwright/test"; // API responses mocked in the SAME envelope shapes the real backend returns // (verified against /api/v1/data/dashboard/*). The smoke asserts each panel // consumes the contract and renders DATA — and that interactions (verify) work — // not just that containers exist. const EVENT_ID = "f6a042ca-efbf-4c4d-b99c-dac41373eb63"; const MOCKS = { "/api/dashboard/whoami": { username: "dev", modules: ["core", "archive"] }, "/api/dashboard/summary": { apiUsage: { core: 2005, audit: 2002, total: 4007 }, apiKeys: 2, endpoints: 2, storage: { onDisk: { core: 353712, audit: 1321330, total: 1675042 }, logical: { core: 349712, audit: 1311320, total: 1661032 }, }, }, "/api/dashboard/usage-series": { series: [ { date: "2026-06-20", core: 76, audit: 0, total: 76 }, { date: "2026-06-21", core: 64, audit: 0, total: 64 }, ], }, "/api/dashboard/storage-series": { series: [ { date: "2026-06-20", coreDisk: 322617, auditDisk: 0, totalDisk: 322617 }, { date: "2026-06-21", coreDisk: 333904, auditDisk: 0, totalDisk: 333904 }, ], }, "/api/dashboard/keys": [], }; const coreRow = { eventUuid: EVENT_ID, sender: "did:jlinc:sender", receiver: "did:jlinc:receiver", date: "2026-06-23T11:37:40.664Z", agreementUuid: "9ab250b9-b3e3-48aa-1111-222233334444", size: 655, }; // Two audit records for ONE event (produce + process): same eventUuid, distinct // auditId. On the audit tab it is auditId — not eventUuid — that identifies a row. const auditRows = [ { ...coreRow, auditId: 9, auditDigest: "572b052a11aa" }, { ...coreRow, auditId: 10, auditDigest: "4029d4a6da22" }, ]; test.beforeEach(async ({ page }) => { await page.route("**/api/dashboard/**", async (route) => { const url = new URL(route.request().url()); const path = url.pathname; if (path === "/api/dashboard/transactions") { const audit = url.searchParams.get("type") === "audit"; return route.fulfill({ json: audit ? { rows: auditRows, total: 2, limit: 50, offset: 0 } : { rows: [coreRow], total: 1, limit: 50, offset: 0 }, }); } if (path === "/api/dashboard/verify") { const list = (k) => (url.searchParams.get(k) || "").split(",").filter(Boolean); const pass = (extra) => ({ status: "verified", verified: true, signatureCount: 1, auditCount: 1, checks: { validId: true }, ...extra }); return route.fulfill({ json: { results: [ ...list("ids").map((eventUuid) => pass({ eventUuid, auditId: null })), // Audit 9 passes, audit 10 fails. Two rows of the SAME event getting // different verdicts is only expressible if each row is its own // verification target. ...list("auditIds").map(Number).map((auditId) => auditId === 9 ? pass({ eventUuid: EVENT_ID, auditId }) : { eventUuid: EVENT_ID, auditId, status: "invalid", verified: false, signatureCount: 1, auditCount: 1, checks: { validAuditHash: false } }), ], }, }); } if (Object.prototype.hasOwnProperty.call(MOCKS, path)) return route.fulfill({ json: MOCKS[path] }); return route.fulfill({ json: {} }); }); }); test("dashboard panels render real data from the API contract", async ({ page }) => { await page.goto("/"); await expect(page.locator("#api-usage-total")).toHaveText(/\d/, { timeout: 10_000 }); await expect(page.locator("#storage-total")).toHaveText(/\d/); await expect(page.locator("#usage-chart svg")).toBeAttached({ timeout: 10_000 }); await expect(page.locator("#transaction-rows tr[data-event-id]")).toHaveCount(1); }); test("Audit tab: verifying one row badges ONLY that row", async ({ page }) => { await page.goto("/"); await page.locator("#tab-audit").click(); // Two audit records share one event UUID; each is its own row. const rows = page.locator("#transaction-rows tr[data-event-id]"); await expect(rows).toHaveCount(2); // The bug: clicking Verify on one row badged BOTH, because rows were keyed by // the event UUID they share. The request must target the row's audit id. const [request] = await Promise.all([ page.waitForRequest((r) => r.url().includes("/api/dashboard/verify")), rows.nth(0).locator(".btn-verify").click(), ]); expect(new URL(request.url()).searchParams.get("auditIds")).toBe("9"); await expect(rows.nth(0).locator(".verify-badge")).toContainText("Verified"); await expect(rows.nth(1).locator(".verify-badge")).toBeHidden(); }); test("Audit tab: Validate All gives each audit row its own verdict", async ({ page }) => { await page.goto("/"); await page.locator("#tab-audit").click(); await expect(page.locator("#transaction-rows tr[data-event-id]")).toHaveCount(2); await page.locator("#validate-all").click(); // Same event, different audit records -> independent results. A per-event // verification could not produce two different badges here. const rows = page.locator("#transaction-rows tr[data-event-id]"); await expect(rows.nth(0).locator(".verify-badge")).toContainText("Verified"); await expect(rows.nth(1).locator(".verify-badge")).toContainText("Invalid"); }); test("sidebar switches to the API Keys view", async ({ page }) => { await page.goto("/"); await expect(page.locator("#api-keys-view")).toBeAttached(); await page.locator("#nav-api-keys").click(); await expect(page.locator("#api-keys-view")).toBeVisible(); });