132 lines
5.4 KiB
JavaScript
132 lines
5.4 KiB
JavaScript
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();
|
|
});
|