import { expect, test, type Page } from "@playwright/test"; const credential = { id: "fixture-credential", name: "Mailbox login", description: "Fixture metadata only", credential_kind: "username_password", scope_type: "tenant", scope_id: null, public_data: { username: "fixture@example.test" }, secret_configured: true, allowed_modules: ["mail"], allowed_server_refs: ["mail:smtp-server", "mail:imap-server", "mail:deleted-server"], inherit_to_lower_scopes: false, is_active: true, created_at: "2026-01-01T12:00:00Z", updated_at: "2026-01-01T12:00:00Z" }; async function installFixtures(page: Page, release: Promise, options: { failFirst?: boolean; paginated?: boolean; denied?: boolean; retrySave?: Promise } = {}) { let loads = 0; let savedCredential = credential; const saves: Record[] = []; const forbiddenRequests: string[] = []; await page.route((url) => url.pathname.startsWith("/api/"), async (route) => { const request = route.request(); const url = new URL(request.url()); if (request.method() === "GET" && url.pathname === "/api/v1/credentials") { await route.fulfill({ json: { credentials: [savedCredential] } }); return; } if (request.method() === "PATCH" && url.pathname === `/api/v1/credentials/${credential.id}` && options.retrySave) { const body = request.postDataJSON() as Record; saves.push(body); await options.retrySave; if (saves.length === 1) { await route.fulfill({ status: 503, json: { detail: "Fixture save failed; keep your draft and retry." } }); } else { savedCredential = { ...credential, name: String(body.name) }; await route.fulfill({ json: savedCredential }); } return; } if (request.method() === "GET" && url.pathname === "/api/v1/platform/modules") { await route.fulfill({ json: { modules: [{ id: "mail", label: "Mail", version: "1" }] } }); return; } if (request.method() === "GET" && url.pathname === "/api/v1/mail/settings/delta") { loads += 1; const number = loads; await release; if (options.denied || (options.failFirst && number === 1)) { await route.fulfill({ status: options.denied ? 403 : 503, json: { detail: "Fixture catalogue unavailable" } }); return; } expect(url.searchParams.get("scope_type")).toBe("tenant"); expect(url.searchParams.get("include_inactive")).toBe("true"); if (options.paginated && url.searchParams.get("since")) { await route.fulfill({ json: { full: false, has_more: false, watermark: "fixture-watermark-2", profiles: [], deleted: [{ id: "deleted-profile", resource_type: "mail_profile" }] } }); return; } await route.fulfill({ json: { full: true, has_more: Boolean(options.paginated), watermark: "fixture-watermark", deleted: [], profiles: [{ id: "fixture-profile", name: "Town hall", slug: "town-hall", servers: [ { id: "smtp-server", name: "Outgoing town hall", protocol: "smtp", is_active: true, scope_type: "tenant" }, { id: "imap-server", name: "Incoming town hall", protocol: "imap", is_active: false, scope_type: "tenant" } ] }, ...(options.paginated ? [{ id: "deleted-profile", name: "Removed profile", slug: "removed", servers: [ { id: "deleted-server", name: "Removed server", protocol: "smtp", is_active: true, scope_type: "tenant" } ] }] : [])] } }); return; } forbiddenRequests.push(`${request.method()} ${url.pathname}`); await route.fulfill({ status: 403, json: { detail: "Unexpected fixture request" } }); }); return { get loads() { return loads; }, forbiddenRequests, saves }; } test("credential server labels resolve on first opening despite StrictMode cleanup", async ({ page }) => { page.on("pageerror", (error) => { throw error; }); let release!: () => void; const fixture = await installFixtures(page, new Promise((resolve) => { release = resolve; })); await page.goto("/?credential-references&language=en"); await page.getByRole("button", { name: "Edit Mailbox login", exact: true }).click(); const dialog = page.getByRole("dialog", { name: "Edit reusable credential", exact: true }); await expect(dialog).toBeVisible(); await expect.poll(() => fixture.loads).toBeGreaterThan(0); release(); const servers = dialog.getByRole("list", { name: "Credential servers selected" }); await expect(servers).toContainText("Outgoing town hall"); await expect(servers).toContainText("Incoming town hall"); await expect(servers.locator(".is-inactive")).toContainText("Incoming town hall"); await expect(servers.locator(".is-unavailable")).toContainText("mail:deleted-server"); await expect(servers.locator(".is-unavailable")).toHaveCount(1); await dialog.locator('input[data-help-context-id="access.credentials.field.name"]').fill("Renamed metadata only"); await expect(servers).toContainText("Outgoing town hall"); expect(fixture.loads).toBe(1); expect(fixture.forbiddenRequests).toEqual([]); }); test("paginated server catalogues remove deleted profiles while retaining their selected references", async ({ page }) => { const fixture = await installFixtures(page, Promise.resolve(), { paginated: true }); await page.goto("/?credential-references&language=en"); await page.getByRole("button", { name: "Edit Mailbox login", exact: true }).click(); const servers = page.getByRole("list", { name: "Credential servers selected" }); await expect(servers).toContainText("Outgoing town hall"); await expect(servers.locator(".is-unavailable")).toHaveCount(1); await expect(servers.locator(".is-unavailable")).toContainText("mail:deleted-server"); await expect(servers).not.toContainText("Removed server"); expect(fixture.loads).toBe(2); expect(fixture.forbiddenRequests).toEqual([]); }); test("denied server metadata remains unavailable without dropping stored references", async ({ page }) => { const fixture = await installFixtures(page, Promise.resolve(), { denied: true }); await page.goto("/?credential-references&language=en"); await page.getByRole("button", { name: "Edit Mailbox login", exact: true }).click(); const servers = page.getByRole("list", { name: "Credential servers selected" }); await expect(servers.locator(".is-unavailable")).toHaveCount(3); for (const reference of credential.allowed_server_refs) await expect(servers).toContainText(reference); await expect(servers).not.toContainText("Outgoing town hall"); expect(fixture.forbiddenRequests).toEqual([]); }); test("temporary server-catalogue failures can recover by reopening the editor", async ({ page }) => { const fixture = await installFixtures(page, Promise.resolve(), { failFirst: true }); await page.goto("/?credential-references&language=en"); await page.getByRole("button", { name: "Edit Mailbox login", exact: true }).click(); await expect(page.getByRole("list", { name: "Credential servers selected" }).locator(".is-unavailable")).toHaveCount(3); await page.getByRole("dialog").getByRole("button", { name: "Cancel", exact: true }).click(); await page.getByRole("button", { name: "Edit Mailbox login", exact: true }).click(); await expect(page.getByRole("list", { name: "Credential servers selected" })).toContainText("Outgoing town hall"); expect(fixture.loads).toBe(2); expect(fixture.forbiddenRequests).toEqual([]); }); test("closing while servers load does not poison the next credential editor", async ({ page }) => { let release!: () => void; const fixture = await installFixtures(page, new Promise((resolve) => { release = resolve; })); await page.goto("/?credential-references&language=en"); await page.getByRole("button", { name: "Edit Mailbox login", exact: true }).click(); await expect.poll(() => fixture.loads).toBeGreaterThan(0); await page.getByRole("dialog").getByRole("button", { name: "Cancel", exact: true }).click(); await page.getByRole("button", { name: "Edit Mailbox login", exact: true }).click(); release(); const servers = page.getByRole("list", { name: "Credential servers selected" }); await expect(servers).toContainText("Outgoing town hall"); await expect(servers).toContainText("Incoming town hall"); expect(fixture.forbiddenRequests).toEqual([]); }); test("failed credential saves show their error inside the editor and retain the draft for explicit retry", async ({ page }) => { let finishSave!: () => void; const retrySave = new Promise((resolve) => { finishSave = resolve; }); const fixture = await installFixtures(page, Promise.resolve(), { retrySave }); await page.goto("/?credential-references&language=en"); await page.getByRole("button", { name: "Edit Mailbox login", exact: true }).click(); const dialog = page.getByRole("dialog", { name: "Edit reusable credential", exact: true }); const name = dialog.locator('input[data-help-context-id="access.credentials.field.name"]'); const secret = dialog.locator('input[type="password"]'); await name.fill("Renamed mailbox credential"); await secret.fill("synthetic-fixture-replacement"); await dialog.getByRole("button", { name: "Save credential", exact: true }).click(); await expect.poll(() => fixture.saves.length).toBe(1); await expect(name).toBeDisabled(); await expect(secret).toBeDisabled(); await expect(dialog.getByRole("button", { name: "Cancel", exact: true })).toBeDisabled(); await expect(dialog.locator(".dialog-close")).toBeDisabled(); await page.keyboard.press("Escape"); await expect(dialog).toBeVisible(); finishSave(); await expect(dialog.getByRole("alert")).toContainText("Fixture save failed; keep your draft and retry."); await expect(page.getByRole("alert")).toHaveCount(1); await expect(name).toHaveValue("Renamed mailbox credential"); await expect(secret).toHaveValue("synthetic-fixture-replacement"); expect(fixture.saves).toHaveLength(1); await dialog.getByRole("button", { name: "Save credential", exact: true }).click(); await expect(dialog).toHaveCount(0); await expect(page.getByRole("button", { name: "Edit Renamed mailbox credential", exact: true })).toBeVisible(); expect(fixture.saves).toHaveLength(2); expect(fixture.saves[1]).toEqual(fixture.saves[0]); expect(fixture.saves[1].allowed_server_refs).toEqual(credential.allowed_server_refs); expect(fixture.forbiddenRequests).toEqual([]); });