diff --git a/apps/dashboard/e2e/onboarding-monerium-eu.spec.ts b/apps/dashboard/e2e/onboarding-monerium-eu.spec.ts index 40402d7fb..93f6ad2f5 100644 --- a/apps/dashboard/e2e/onboarding-monerium-eu.spec.ts +++ b/apps/dashboard/e2e/onboarding-monerium-eu.spec.ts @@ -2,13 +2,7 @@ import { expect, test } from "@playwright/test"; import { E2E_MANAGED_PROFILE_ID, mockBackend } from "./support/mockBackend"; import { E2E_USER_EMAIL, E2E_USER_ID, SESSION_KEYS, seedSession } from "./support/session"; -// EU onboarding (KYC and KYB) is temporarily disabled: the corridor card must not offer any -// actionable button and the wizard must refuse the corridor even via the ?onboarding=EU deep -// link, so no user can reach the Monerium flow. When EU is re-enabled, restore the OAuth -// round-trip coverage this file carried before (git history: "Monerium EU OAuth returns -// securely and refreshes approval"). - -test("EU onboarding is disabled: card action and wizard deep link are both blocked", async ({ page }) => { +test("Monerium EU OAuth returns securely and refreshes approval", async ({ page }) => { const backend = await mockBackend(page, { moneriumKyc: true }); await seedSession(page); await page.goto("/overview"); @@ -20,19 +14,54 @@ test("EU onboarding is disabled: card action and wizard deep link are both block await page.getByRole("option", { name: /Europe/ }).click(); await addDialog.getByRole("button", { name: "Add card" }).click(); - await expect(page.getByRole("button", { name: "KYC is temporarily unavailable" })).toBeDisabled(); - - await page.goto("/overview?onboarding=EU"); + await page.getByRole("button", { name: "Start KYC" }).click(); const wizard = page.getByRole("dialog"); - await expect(wizard.getByText("KYC is currently disabled in Europe.")).toBeVisible({ timeout: 20_000 }); - await wizard.getByRole("button", { exact: true, name: "Close" }).first().click(); + await expect(wizard.getByText("Verify with Monerium")).toBeVisible({ timeout: 20_000 }); + let continueNavigation: () => void; + const navigationGate = new Promise(resolve => { + continueNavigation = resolve; + }); + await page.route(`${new URL(page.url()).origin}/monerium/callback?**`, async route => { + await navigationGate; + await route.continue(); + }); + const connectingShown = page.evaluate( + () => + new Promise(resolve => { + const observer = new MutationObserver(() => { + if (document.body.textContent?.includes("Connecting to Monerium")) { + observer.disconnect(); + resolve(true); + } + }); + observer.observe(document.body, { childList: true, subtree: true }); + }) + ); + const navigation = wizard.getByRole("button", { name: "Continue to Monerium" }).click(); + try { + await expect(connectingShown).resolves.toBe(true); + } finally { + continueNavigation(); + } + await navigation; - expect(backend.monerium.startRequests).toEqual([]); + await expect(page).toHaveURL(/\/overview\?onboarding=EU$/, { timeout: 20_000 }); + await expect(page.getByRole("dialog").getByText("Verification in review")).toBeVisible(); + await expect(page.getByRole("button", { name: "Continue in background" })).toBeVisible(); + expect(backend.monerium.startRequests).toEqual([{ customerType: "individual" }]); + + await page.getByRole("button", { name: "Continue in background" }).click(); + await expect.poll(() => new URL(page.url()).search).toBe(""); + await expect(page.getByRole("button", { name: "Awaiting provider review" })).toBeVisible({ timeout: 20_000 }); + + // Approval alone is not the end of EU setup: the pay-in wallet still has to be linked. + backend.monerium.approved = true; + await expect(page.getByRole("button", { name: "Link wallet for EUR pay-ins" })).toBeVisible({ timeout: 25_000 }); expect(backend.unmatchedRequests).toEqual([]); expect(backend.unexpectedExternalRequests).toEqual([]); }); -test("Monerium callback refreshes an expired dashboard session, then lands on the disabled notice", async ({ page }) => { +test("Monerium callback refreshes an expired dashboard session before exchange", async ({ page }) => { const backend = await mockBackend(page, { moneriumKyc: true, moneriumRequireRefresh: true }); await page.addInitScript( ({ email, keys, userId }) => { @@ -47,7 +76,7 @@ test("Monerium callback refreshes an expired dashboard session, then lands on th await page.goto("/monerium/callback?code=e2e-code&state=e2e-state"); await expect(page).toHaveURL(/\/overview\?onboarding=EU$/, { timeout: 20_000 }); - await expect(page.getByRole("dialog").getByText("KYC is currently disabled in Europe.")).toBeVisible(); + await expect(page.getByRole("dialog").getByText("Verification in review")).toBeVisible(); expect(backend.auth.refreshes).toBe(1); }); @@ -98,13 +127,18 @@ test("Monerium callback does not complete OAuth while acting for a managed child expect(backend.apiRequests.filter(request => request.path === "/v1/monerium/oauth/complete")).toEqual([]); }); -test("in-review Monerium onboarding requiring reauthentication is disabled instead of actionable", async ({ page }) => { +test("in-review Monerium onboarding offers reauthentication when the status response requires it", async ({ page }) => { const backend = await mockBackend(page, { moneriumKyc: true }); backend.monerium.completed = true; await seedSession(page); await page.goto("/overview"); - const disabledButton = page.getByRole("button", { name: "KYC is temporarily unavailable" }); - await expect(disabledButton).toBeDisabled({ timeout: 20_000 }); + const reauthenticateButton = page.getByRole("button", { name: "Re-authenticate with Monerium" }); + await expect(reauthenticateButton).toBeVisible({ timeout: 20_000 }); + await reauthenticateButton.click(); + + const wizard = page.getByRole("dialog"); + await expect(wizard.getByText("Verify with Monerium")).toBeVisible(); + await expect(wizard.getByRole("button", { name: "Continue to Monerium" })).toBeVisible(); expect(backend.unmatchedRequests).toEqual([]); }); diff --git a/apps/dashboard/e2e/quote-explorer.spec.ts b/apps/dashboard/e2e/quote-explorer.spec.ts index 369334c9d..178b331d2 100644 --- a/apps/dashboard/e2e/quote-explorer.spec.ts +++ b/apps/dashboard/e2e/quote-explorer.spec.ts @@ -23,7 +23,7 @@ test("Base defaults to USDC when requesting a BUY quote", async ({ page }) => { }); test("EUR is quotable on BUY and an unapproved corridor routes to onboarding", async ({ page }) => { - const backend = await mockBackend(page); + const backend = await mockBackend(page, { moneriumKyc: true }); await seedSession(page); await page.goto("/quote"); @@ -40,13 +40,13 @@ test("EUR is quotable on BUY and an unapproved corridor routes to onboarding", a await onboardingCta.click(); await expect(page).toHaveURL(/\/overview\?onboarding=EU$/); - await expect(page.getByRole("dialog").getByText("KYC is currently disabled in Europe.")).toBeVisible(); + await expect(page.getByRole("dialog").getByText("Verify with Monerium")).toBeVisible(); expect(backend.unmatchedRequests).toEqual([]); expect(backend.unexpectedExternalRequests).toEqual([]); }); -test("An approved EUR BUY explains the missing transfer flow instead of linking the form", async ({ page }) => { +test("An approved EUR BUY links the onramp form for the EU corridor", async ({ page }) => { const backend = await mockBackend(page, { moneriumKyc: true }); // Onboarding status reports an approved Monerium (EU) account from the first poll. backend.monerium.completed = true; @@ -59,10 +59,14 @@ test("An approved EUR BUY explains the missing transfer flow instead of linking await page.getByRole("option", { name: /EURC/ }).click(); await page.getByLabel("You pay").fill("100"); - await expect(page.getByText("Buying crypto with EURC isn’t available in transfers yet.")).toBeVisible({ - timeout: 20_000 - }); - await expect(page.getByRole("link", { name: "Continue to transfer" })).toBeHidden(); + const continueLink = page.getByRole("link", { name: "Continue to transfer" }); + await expect(continueLink).toBeVisible({ timeout: 20_000 }); + await continueLink.click(); + + await expect(page).toHaveURL(/\/transfer/); + const search = new URL(page.url()).searchParams; + expect(search.get("corridorId")).toBe("EU"); + expect(search.get("mode")).toBe("onramp"); expect(backend.unmatchedRequests).toEqual([]); expect(backend.unexpectedExternalRequests).toEqual([]);