Files
govoplan-core/webui/conformance/tests/credential-references.spec.ts
T

178 lines
10 KiB
TypeScript

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<void>, options: { failFirst?: boolean; paginated?: boolean; denied?: boolean; retrySave?: Promise<void> } = {}) {
let loads = 0;
let savedCredential = credential;
const saves: Record<string, unknown>[] = [];
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<string, unknown>;
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<void>((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<void>((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<void>((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([]);
});