feat: consolidate shared UI and harden browser authority for release
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
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([]);
|
||||
});
|
||||
Reference in New Issue
Block a user