diff --git a/.claude/CLAUDE-KNOWLEDGE.md b/.claude/CLAUDE-KNOWLEDGE.md index c958c395c..289125865 100644 --- a/.claude/CLAUDE-KNOWLEDGE.md +++ b/.claude/CLAUDE-KNOWLEDGE.md @@ -547,3 +547,6 @@ A: Keep the real outbound SMTP policy in `apps/backend/src/private/implementatio ## Q: What project-level `sourceOfTruth` config is supported? A: Project config overrides only support the hosted `sourceOfTruth` shape. Legacy external source-of-truth overrides such as Postgres or Neon are removed by `migrateConfigOverride("project", ...)`, while raw schema validation should reject them. + +## Q: How should managed email onboarding e2e tests wait for mock verification? +A: Do not rely on a fixed `wait(1500)` after setup. The mock onboarding path flips the domain to `verified` asynchronously through `runAsynchronously`, so tests should poll the managed-onboarding check endpoint until the expected status appears. diff --git a/apps/e2e/tests/backend/endpoints/api/v1/internal/managed-email-onboarding.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/internal/managed-email-onboarding.test.ts index 54e20fc69..92921f05e 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/internal/managed-email-onboarding.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/internal/managed-email-onboarding.test.ts @@ -3,6 +3,34 @@ import { describe } from "vitest"; import { it } from "../../../../../helpers"; import { Project, niceBackendFetch } from "../../../../backend-helpers"; +async function waitForManagedDomainStatus(options: { + domainId: string, + subdomain: string, + senderLocalPart: string, + status: string, +}) { + const deadline = performance.now() + 10_000; + let lastBody: unknown = undefined; + while (performance.now() < deadline) { + const response = await niceBackendFetch("/api/v1/internal/emails/managed-onboarding/check", { + method: "POST", + accessType: "admin", + body: { + domain_id: options.domainId, + subdomain: options.subdomain, + sender_local_part: options.senderLocalPart, + }, + }); + lastBody = response.body; + if (response.status === 200 && response.body.status === options.status) { + return; + } + await wait(250); + } + + throw new Error(`Timed out waiting for managed email domain ${options.domainId} to become ${options.status}; last response body: ${JSON.stringify(lastBody)}`); +} + describe("managed email onboarding internal endpoints", () => { it("rejects client access for setup endpoint", async ({ expect }) => { await Project.createAndSwitch(); @@ -51,10 +79,14 @@ describe("managed email onboarding internal endpoints", () => { expect(setupResponse.body.domain_id).toBeDefined(); expect(setupResponse.body.status).toBe("pending_verification"); - // Mock onboarding asynchronously flips status to "verified" ~1s after setup - // (mirroring the real Resend webhook flow). Wait for the transition before - // asserting verified state. - await wait(1500); + // Mock onboarding asynchronously flips status to "verified" after setup, + // mirroring the real Resend webhook flow. + await waitForManagedDomainStatus({ + domainId: setupResponse.body.domain_id, + subdomain: "mail.example.com", + senderLocalPart: "noreply", + status: "verified", + }); const listResponse = await niceBackendFetch("/api/v1/internal/emails/managed-onboarding/list", { method: "GET", @@ -202,7 +234,12 @@ describe("managed email onboarding internal endpoints", () => { sender_local_part: "noreply", }, }); - await wait(1500); + await waitForManagedDomainStatus({ + domainId: setupResponse.body.domain_id, + subdomain: "mail.example.com", + senderLocalPart: "noreply", + status: "verified", + }); const applyResponse = await niceBackendFetch("/api/v1/internal/emails/managed-onboarding/apply", { method: "POST", @@ -240,7 +277,12 @@ describe("managed email onboarding internal endpoints", () => { sender_local_part: "noreply", }, }); - await wait(1500); + await waitForManagedDomainStatus({ + domainId: setupResponse.body.domain_id, + subdomain: "mail.example.com", + senderLocalPart: "noreply", + status: "verified", + }); const applyResponse = await niceBackendFetch("/api/v1/internal/emails/managed-onboarding/apply", { method: "POST",