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

216 lines
12 KiB
TypeScript

import { expect, test, type Page } from "@playwright/test";
type Policy = {
smtp_credentials?: { inherit?: boolean | null };
imap_credentials?: { inherit?: boolean | null };
allow_lower_level_limits?: Record<string, boolean>;
[key: string]: unknown;
};
async function installPolicy(page: Page, options: { parent?: Policy | null; local?: Policy; failFirst?: boolean; failReadOnce?: boolean; holdSave?: Promise<void> } = {}) {
let current = options.local ?? {};
const parent = options.parent === null ? null : { smtp_credentials: { inherit: false }, imap_credentials: { inherit: true }, ...options.parent };
const writes: Policy[] = [];
const unexpected: string[] = [];
let reads = 0;
function response() {
const effective: Policy = { allow_user_profiles: true, allow_group_profiles: true, allow_campaign_profiles: true, ...parent, ...current };
for (const protocol of ["smtp", "imap"] as const) {
const key = `${protocol}_credentials` as const;
const locked = parent?.allow_lower_level_limits?.[`${key}.inherit`] === false;
effective[key] = { inherit: (!locked ? current[key]?.inherit : null) ?? parent?.[key]?.inherit ?? true };
}
return { policy: current, parent_policy: parent, effective_policy: effective, effective_policy_sources: [
{ scope_type: "system", label: "System", path: "system", applied_fields: [], policy: parent ?? {} },
{ scope_type: "tenant", label: "Tenant", path: "tenant", applied_fields: [], policy: current }
] };
}
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
const request = route.request();
if (new URL(request.url()).pathname.startsWith("/api/v1/mail/policies/")) {
if (request.method() === "GET") {
reads += 1;
if (options.failReadOnce && reads === 1) {
await route.fulfill({ status: 503, json: { detail: "Synthetic policy load failed" } }); return;
}
await route.fulfill({ json: response() }); return;
}
if (request.method() === "PUT") {
const submitted = request.postDataJSON().policy as Policy;
writes.push(submitted);
if (options.holdSave) await options.holdSave;
if (options.failFirst && writes.length === 1) {
await route.fulfill({ status: 503, json: { detail: "Synthetic policy write failed" } }); return;
}
current = submitted;
await route.fulfill({ json: response() }); return;
}
}
unexpected.push(`${request.method()} ${new URL(request.url()).pathname}`);
await route.fulfill({ status: 403, json: { detail: "Unexpected fixture request" } });
});
return { writes, unexpected, get reads() { return reads; } };
}
function credentialRows(page: Page) {
const section = page.getByTestId("mail-credential-policy");
return {
section,
smtp: section.locator(".policy-row").filter({ has: page.locator('select[aria-label="SMTP credential selection"]') }),
imap: section.locator(".policy-row").filter({ has: page.locator('select[aria-label="IMAP credential selection"]') })
};
}
test("Mail credential policy exposes inherited/effective choices and persists stable override keys", async ({ page }) => {
page.on("pageerror", (error) => { throw error; });
const fixture = await installPolicy(page);
await page.goto("/?mail-credential-policy&language=en");
const { smtp, imap } = credentialRows(page);
await expect(smtp.locator("select")).toHaveValue("inherit");
await expect(smtp.locator(".policy-effective-value")).toContainText("Require explicit Mail credential");
// An inherited false does not itself lock the child: only the separate
// lower-level limit may prevent choosing an inherited default credential.
await smtp.locator("select").selectOption("profile");
await imap.locator("select").selectOption("explicit");
await smtp.getByRole("checkbox", { name: "Allow override" }).focus();
await page.keyboard.press("Space");
const wildcard = page.locator(".mail-policy-pattern-row").filter({ hasText: "SMTP hostnames" });
await wildcard.getByRole("checkbox", { name: "Whitelist", exact: true }).focus();
await page.keyboard.press("Space");
const campaignRow = page.locator(".mail-policy-row").filter({ hasText: "Campaign-scoped profiles" });
await campaignRow.getByRole("checkbox", { name: "Allow override" }).focus();
await page.keyboard.press("Space");
await page.getByRole("button", { name: "Save policy", exact: true }).click();
await expect.poll(() => fixture.writes.length).toBe(1);
expect(fixture.writes[0].smtp_credentials).toEqual({ inherit: true });
expect(fixture.writes[0].imap_credentials).toEqual({ inherit: false });
expect(fixture.writes[0].allow_lower_level_limits).toMatchObject({
"smtp_credentials.inherit": false, "whitelist.smtp_hosts": false, "allow_campaign_profiles": false
});
expect(Object.keys(fixture.writes[0].allow_lower_level_limits ?? {}).some((key) => key.startsWith("i18n:"))).toBe(false);
await expect(page.getByRole("button", { name: "Save policy", exact: true })).toBeDisabled();
expect(fixture.unexpected).toEqual([]);
});
test("ancestor credential locks cannot be bypassed or re-enabled and stale local overrides are cleared on save", async ({ page }) => {
const fixture = await installPolicy(page, {
parent: { allow_lower_level_limits: { "smtp_credentials.inherit": false } },
local: { smtp_credentials: { inherit: true }, allow_lower_level_limits: { "smtp_credentials.inherit": true } }
});
await page.goto("/?mail-credential-policy&language=en");
const { smtp, imap, section } = credentialRows(page);
await expect(smtp.locator("select")).toHaveValue("inherit");
await expect(smtp.locator("select")).toBeDisabled();
await expect(smtp.getByRole("checkbox", { name: "Allow override" })).toBeDisabled();
await expect(smtp.getByRole("checkbox", { name: "Allow override" })).not.toBeChecked();
await expect(section).toContainText("An ancestor has locked credential selection.");
await imap.locator("select").selectOption("explicit");
await page.getByRole("button", { name: "Save policy", exact: true }).click();
await expect.poll(() => fixture.writes.length).toBe(1);
expect(fixture.writes[0].smtp_credentials).toEqual({ inherit: null });
expect(fixture.writes[0].allow_lower_level_limits?.["smtp_credentials.inherit"]).toBeUndefined();
expect(fixture.unexpected).toEqual([]);
});
test("system defaults are concrete and campaign policy has no lower-level override controls", async ({ page }) => {
const fixture = await installPolicy(page, { parent: null });
await page.goto("/?mail-credential-policy&scope=system&language=en");
let { smtp, imap } = credentialRows(page);
await expect(smtp.locator("select")).toHaveValue("profile");
await expect(smtp.locator('option[value="inherit"]')).toHaveCount(0);
await smtp.locator("select").selectOption("explicit");
await page.getByRole("button", { name: "Save policy", exact: true }).click();
await expect.poll(() => fixture.writes.length).toBe(1);
expect(fixture.writes[0].smtp_credentials).toEqual({ inherit: false });
expect(fixture.writes[0].imap_credentials).toEqual({ inherit: true });
expect(fixture.writes[0].allow_lower_level_limits).toHaveProperty("allow_campaign_profiles", true);
await page.goto("/?mail-credential-policy&scope=campaign&language=en");
({ smtp, imap } = credentialRows(page));
await expect(smtp.getByRole("checkbox")).toHaveCount(0);
await expect(imap.getByRole("checkbox")).toHaveCount(0);
await smtp.locator("select").selectOption("profile");
await page.getByRole("button", { name: "Save policy", exact: true }).click();
await expect.poll(() => fixture.writes.length).toBe(2);
expect(fixture.writes[1].allow_lower_level_limits).toEqual({});
expect(fixture.unexpected).toEqual([]);
});
test("read-only and workflow-locked policy scopes cannot change credential selection", async ({ page }) => {
const fixture = await installPolicy(page);
for (const blocker of ["read-only", "locked"]) {
await page.goto(`/?mail-credential-policy&language=en&${blocker}`);
const { smtp, imap } = credentialRows(page);
await expect(smtp.locator("select")).toBeDisabled();
await expect(imap.locator("select")).toBeDisabled();
await expect(smtp.getByRole("checkbox", { name: "Allow override" })).toBeDisabled();
await expect(page.getByRole("button", { name: "Save policy", exact: true })).toBeDisabled();
}
expect(fixture.writes).toEqual([]);
expect(fixture.unexpected).toEqual([]);
});
test("successful policy saves remain successful when a dependent refresh fails", async ({ page }) => {
const fixture = await installPolicy(page);
await page.goto("/?mail-credential-policy&language=en&refresh-failure");
await credentialRows(page).smtp.locator("select").selectOption("profile");
await page.getByRole("button", { name: "Save policy", exact: true }).click();
await expect(page.getByRole("alert")).toContainText("Mail policy was saved, but refreshing dependent data failed");
await expect(page.locator(".alert.danger")).toHaveCount(0);
await expect(page.locator(".alert.success")).toContainText("Mail profile policy saved");
await expect(page.getByRole("button", { name: "Save policy", exact: true })).toBeDisabled();
expect(fixture.writes).toHaveLength(1);
await page.getByRole("button", { name: "Reload", exact: true }).click();
await expect.poll(() => fixture.reads).toBeGreaterThan(1);
await expect(credentialRows(page).smtp.locator("select")).toHaveValue("profile");
expect(fixture.writes).toHaveLength(1);
expect(fixture.unexpected).toEqual([]);
});
test("failed policy writes retain the draft and require an explicit retry", async ({ page }) => {
let finish!: () => void;
const fixture = await installPolicy(page, { failFirst: true, holdSave: new Promise<void>((resolve) => { finish = resolve; }) });
await page.goto("/?mail-credential-policy&language=en");
const { smtp } = credentialRows(page);
await smtp.locator("select").selectOption("profile");
await page.getByRole("button", { name: "Save policy", exact: true }).click();
await expect.poll(() => fixture.writes.length).toBe(1);
await expect(smtp.locator("select")).toBeDisabled();
finish();
await expect(page.locator(".alert.danger")).toContainText("Synthetic policy write failed");
await expect(smtp.locator("select")).toHaveValue("profile");
await expect(page.getByRole("button", { name: "Save policy", exact: true })).toBeEnabled();
expect(fixture.writes).toHaveLength(1);
await page.getByRole("button", { name: "Save policy", exact: true }).click();
await expect(page.getByRole("button", { name: "Save policy", exact: true })).toBeDisabled();
expect(fixture.writes).toHaveLength(2);
expect(fixture.writes[1]).toEqual(fixture.writes[0]);
expect(fixture.unexpected).toEqual([]);
});
test("failed policy loads cannot enable editing an unknown ancestor policy", async ({ page }) => {
const fixture = await installPolicy(page, { failReadOnce: true });
await page.goto("/?mail-credential-policy&language=en");
await expect(page.locator(".alert.danger")).toContainText("Synthetic policy load failed");
await expect(credentialRows(page).smtp.locator("select")).toBeDisabled();
await expect(page.getByRole("button", { name: "Save policy", exact: true })).toBeDisabled();
await page.getByRole("button", { name: "Reload", exact: true }).click();
await expect(credentialRows(page).smtp.locator("select")).toBeEnabled();
expect(fixture.writes).toEqual([]);
expect(fixture.unexpected).toEqual([]);
});
test("credential selection choices and explanations are available in German", async ({ page }) => {
const fixture = await installPolicy(page);
await page.goto("/?mail-credential-policy&language=de");
const section = page.getByTestId("mail-credential-policy");
await expect(section.getByRole("heading", { name: "Auswahl der Zugangsdaten", exact: true })).toBeVisible();
const smtp = section.getByRole("combobox", { name: "SMTP-Zugangsdaten auswählen", exact: true });
await expect(smtp).toBeEnabled();
await expect(smtp.locator('option[value="inherit"]')).toHaveText("Richtlinie vom übergeordneten Bereich erben");
await expect(smtp.locator('option[value="profile"]')).toHaveText("Standard-Zugangsdaten des Profils zulassen");
await expect(smtp.locator('option[value="explicit"]')).toHaveText("Ausdrückliche Mail-Zugangsdaten verlangen");
await expect(section).toContainText("Beide Optionen belassen Geheimnisse in Mail");
expect(fixture.writes).toEqual([]);
expect(fixture.unexpected).toEqual([]);
});