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; [key: string]: unknown; }; async function installPolicy(page: Page, options: { parent?: Policy | null; local?: Policy; failFirst?: boolean; failReadOnce?: boolean; holdSave?: Promise } = {}) { 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((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([]); });