import { expect, test, type Page } from "@playwright/test"; const zip = { enabled: true, archives: [{ id: "legacy-zip", name: "Existing recipient archive", method: "zip_standard", password_enabled: true, password_delivery_channel: "separate_mail" }] }; const defaultCredential = { id: "credential-default", name: "Default account", is_active: true, is_default: true, public_data: { username: "sender@example.test" } }; const alternateCredential = { id: "credential-alternate", name: "Explicit alternate account", is_active: true, is_default: false, public_data: { username: "alternate@example.test" } }; const inactiveCredential = { id: "credential-inactive", name: "Inactive account", is_active: false, is_default: false, public_data: { username: "inactive@example.test" } }; const profile = { id: "profile-1", name: "Campaign delivery", scope_type: "tenant", scope_id: "tenant-1", is_active: true, servers: [{ id: "smtp-1", name: "Active SMTP server", protocol: "smtp", is_active: true, is_default: true, config: { host: "smtp.example.test", port: 587, security: "starttls" }, credentials: [defaultCredential, alternateCredential, inactiveCredential] }, { id: "smtp-inactive", name: "Inactive SMTP server", protocol: "smtp", is_active: false, is_default: false, config: { host: "inactive.example.test", port: 587, security: "starttls" }, credentials: [defaultCredential] }] }; async function install(page: Page, options: { language?: "en" | "de"; selectedServer?: Record; excludeProfile?: boolean; requireCredential?: boolean; } = {}) { const errors: string[] = []; page.on("pageerror", (error) => { errors.push(error.message); }); let revision = 1; let migrationRequired = true; let raw: Record = { version: "1.0", campaign: { id: "campaign-mail", name: "Mail repair fixture", mode: "send" }, server: options.selectedServer ?? { mail_profile_id: "profile-1", smtp_server_id: "smtp-1" }, template: { subject: "Fixture subject", text: "Fixture body" }, recipients: { from: [{ email: "sender@example.test" }] }, entries: { inline: [] }, attachments: { zip }, delivery: { imap_append_sent: { enabled: false, folder: "auto" } } }; const writes: Record[] = []; const reads: URL[] = []; const unexpectedWrites: string[] = []; const version = () => ({ id: "version-mail", campaign_id: "campaign-mail", version_number: 2, schema_version: "1.0", edit_revision: revision, strong_etag: `"version-mail:${revision}"`, current_flow: "manual", current_step: "mail-settings", workflow_state: "editing", is_complete: false, editor_state: { created_from: "minimal_campaign" }, user_lock_state: null, locked_at: null, published_at: null, created_at: "2026-09-07T10:00:00Z", updated_at: "2026-09-07T10:00:00Z", raw_json: raw, mail_profile_migration_required: migrationRequired }); await page.route((url) => url.pathname.startsWith("/api/"), async (route) => { const request = route.request(); const url = new URL(request.url()); if (request.method() === "GET") { reads.push(url); if (url.pathname.endsWith("/workspace/delta")) return route.fulfill({ json: { campaign: { id: "campaign-mail", name: "Mail repair fixture", current_version_id: "version-mail", status: "draft" }, versions: [version()], current_version: version(), summary: null, deleted: [], watermark: `mail-watermark-${revision}`, has_more: false, full: true } }); if (url.pathname === "/api/v1/mail/profiles") return route.fulfill({ json: { profiles: options.excludeProfile ? [] : [profile] } }); if (url.pathname.endsWith("/versions/version-mail")) return route.fulfill({ json: version() }); return route.fulfill({ json: {} }); } if (request.method() === "POST" && url.pathname.endsWith("/versions/version-mail/autosave")) { const body = request.postDataJSON(); writes.push(body); if (options.requireCredential && !body.campaign_json.server.smtp_credential_id) { return route.fulfill({ status: 422, json: { detail: "Campaign delivery cannot use the selected profile because the effective SMTP credential policy requires an explicit credential selection for this campaign." } }); } if (body.base_revision !== revision) return route.fulfill({ status: 409, json: { detail: { code: "revision_conflict", resource: { type: "campaign_version", id: "version-mail" }, current_revision: revision, submitted_base_revision: body.base_revision } } }); raw = body.campaign_json; revision += 1; if (body.migrate_legacy_mail_settings) migrationRequired = false; return route.fulfill({ json: version() }); } unexpectedWrites.push(`${request.method()} ${url.pathname}`); return route.abort(); }); await page.goto(`/?campaign-mail-settings&language=${options.language ?? "en"}`); await expect(page.getByRole("combobox", { name: "SMTP credential", exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: /Reload profiles|Profile neu laden/ })).toBeEnabled(); return { writes, reads, errors, unexpectedWrites, raw: () => raw, revision: () => revision }; } for (const language of ["en", "de"] as const) { test(`real Mail settings never display inherited defaults as explicit saved credentials in ${language}`, async ({ page }) => { const fixture = await install(page, { language, requireCredential: true }); const credential = page.getByRole("combobox", { name: "SMTP credential", exact: true }); await expect(credential).toHaveValue(""); await expect(credential.locator("option:checked")).toContainText(language === "en" ? "Use profile credentials" : "Profil-Zugangsdaten verwenden"); await expect(credential.locator('option[value="credential-default"]')).toHaveCount(1); expect(fixture.writes).toHaveLength(0); await credential.selectOption("credential-default"); await page.getByRole("button", { name: language === "en" ? "Migrate to selected Mail profile" : "Auf ausgewähltes Mail-Profil umstellen", exact: true }).click(); await expect.poll(() => fixture.revision()).toBe(2); await expect(page.getByRole("button", { name: language === "en" ? "Migrate to selected Mail profile" : "Auf ausgewähltes Mail-Profil umstellen", exact: true })).toHaveCount(0); expect(fixture.writes).toHaveLength(1); expect(fixture.writes[0].migrate_legacy_mail_settings).toBe(true); expect(fixture.writes[0].campaign_json.server).toEqual({ mail_profile_id: "profile-1", smtp_server_id: "smtp-1", smtp_credential_id: "credential-default" }); expect(fixture.writes[0].campaign_json.attachments.zip).toEqual(zip); expect(fixture.writes[0].base_revision).toBe(1); expect(fixture.unexpectedWrites).toEqual([]); expect(fixture.errors).toEqual([]); await page.reload(); await expect(credential).toHaveValue("credential-default"); expect(fixture.writes).toHaveLength(1); }); } test("explicit-credential 422 preserves the real Mail draft and can be corrected then saved", async ({ page }) => { const fixture = await install(page, { requireCredential: true }); await page.getByRole("button", { name: "Migrate to selected Mail profile", exact: true }).click(); await expect(page.getByText(/effective SMTP credential policy requires an explicit credential selection/)).toBeVisible(); expect(fixture.revision()).toBe(1); expect(fixture.writes).toHaveLength(1); await expect(page.getByRole("combobox", { name: "SMTP server", exact: true })).toHaveValue("smtp-1"); await expect(page.getByRole("combobox", { name: "SMTP credential", exact: true })).toHaveValue(""); await expect(page.getByRole("button", { name: "Migrate to selected Mail profile", exact: true })).toBeEnabled(); await page.getByRole("combobox", { name: "SMTP credential", exact: true }).selectOption("credential-alternate"); await page.getByRole("button", { name: "Save", exact: true }).click(); await expect.poll(() => fixture.revision()).toBe(2); await expect(page.getByRole("button", { name: "Migrate to selected Mail profile", exact: true })).toHaveCount(0); expect(fixture.writes).toHaveLength(2); expect(fixture.writes[0].campaign_json.server.smtp_credential_id).toBeUndefined(); expect(fixture.writes[1].campaign_json.server.smtp_credential_id).toBe("credential-alternate"); expect(fixture.writes[1].campaign_json.server.smtp_server_id).toBe("smtp-1"); expect(fixture.writes[1].campaign_json.attachments.zip).toEqual(zip); expect(fixture.errors).toEqual([]); expect(fixture.unexpectedWrites).toEqual([]); }); for (const credentialId of ["credential-missing", "credential-inactive"]) { test(`a stored ${credentialId} stays visibly unavailable until explicitly replaced`, async ({ page }) => { const fixture = await install(page, { selectedServer: { mail_profile_id: "profile-1", smtp_server_id: "smtp-1", smtp_credential_id: credentialId } }); const credential = page.getByRole("combobox", { name: "SMTP credential", exact: true }); await expect(credential).toHaveValue(credentialId); await expect(credential.locator("option:checked")).toHaveText("Selected credential is unavailable"); await expect(credential.locator('option[value="credential-inactive"]')).toHaveCount(credentialId === "credential-inactive" ? 1 : 0); expect(fixture.writes).toHaveLength(0); await credential.selectOption("credential-default"); await page.getByRole("button", { name: "Save", exact: true }).click(); await expect.poll(() => fixture.revision()).toBe(2); expect(fixture.writes[0].campaign_json.server.smtp_credential_id).toBe("credential-default"); expect(fixture.errors).toEqual([]); }); } for (const serverId of ["smtp-missing", "smtp-inactive"]) { test(`a stored ${serverId} cannot silently fall back to the default SMTP endpoint`, async ({ page }) => { const fixture = await install(page, { selectedServer: { mail_profile_id: "profile-1", smtp_server_id: serverId, smtp_credential_id: "credential-default" } }); const server = page.getByRole("combobox", { name: "SMTP server", exact: true }); await expect(server).toHaveValue(serverId); await expect(server.locator("option:checked")).toContainText("unavailable"); await expect(page.getByRole("combobox", { name: "SMTP credential", exact: true })).toBeDisabled(); expect(fixture.writes).toHaveLength(0); await server.selectOption("smtp-1"); await page.getByRole("button", { name: "Save", exact: true }).click(); await expect.poll(() => fixture.revision()).toBe(2); expect(fixture.writes[0].campaign_json.server).toEqual({ mail_profile_id: "profile-1", smtp_server_id: "smtp-1", smtp_credential_id: "credential-default" }); expect(fixture.errors).toEqual([]); }); } test("an unavailable profile remains identified and cannot be silently migrated", async ({ page }) => { const fixture = await install(page, { excludeProfile: true }); await expect(page.getByRole("combobox", { name: "Profile", exact: true })).toHaveValue("profile-1"); await expect(page.getByRole("button", { name: "Migrate to selected Mail profile", exact: true })).toBeDisabled(); await expect(page.getByRole("combobox", { name: "SMTP server", exact: true })).toBeDisabled(); expect(fixture.writes).toHaveLength(0); expect(fixture.reads.filter((url) => url.pathname === "/api/v1/mail/profiles").every((url) => url.searchParams.get("campaign_id") === "campaign-mail")).toBe(true); expect(fixture.unexpectedWrites).toEqual([]); });