Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 52 additions & 18 deletions apps/dashboard/e2e/onboarding-monerium-eu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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<void>(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<boolean>(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 }) => {
Expand All @@ -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);
});

Expand Down Expand Up @@ -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([]);
});
18 changes: 11 additions & 7 deletions apps/dashboard/e2e/quote-explorer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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;
Expand All @@ -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([]);
Expand Down
Loading