import { expect, type Page } from "@playwright/test" export const E2E_INVITE_TOKEN = "e2e-onboard-invite-token" export const E2E_MIGRATEE_EMAIL = "alice@acme.com" export const E2E_PROJECT_ID = "11111111-1111-4111-8111-111111111111" export type MigrationMockState = { projectStatus?: "draft" | "active" | "cutover" authMode?: "oauth" | "google_dwd" | "microsoft_app" sourceProvider?: "google" | "microsoft" inviteStatus?: "invited" | "claimed" hasMigrationCredentials?: boolean waitingForAdmin?: boolean jobs?: Array<{ service: string status: string stats_json?: Record error?: string }> } const DEFAULT_JOBS = [ { service: "mail", status: "pending", stats_json: { imported: 0 } }, { service: "contacts", status: "pending", stats_json: { imported: 0 } }, { service: "calendar", status: "pending", stats_json: { imported: 0 } }, { service: "drive", status: "pending", stats_json: { imported: 0 } }, ] function buildProject(state: MigrationMockState) { return { id: E2E_PROJECT_ID, name: "Migration E2E ACME", source_provider: state.sourceProvider ?? "google", auth_mode: state.authMode ?? "oauth", status: state.projectStatus ?? "draft", delta_mode: state.projectStatus === "cutover", created_at: "2026-01-15T10:00:00Z", } } function buildInvite(state: MigrationMockState) { return { id: "22222222-2222-4222-8222-222222222222", project_id: E2E_PROJECT_ID, email: E2E_MIGRATEE_EMAIL, status: state.inviteStatus ?? "invited", } } export function buildOnboardingHints(state: MigrationMockState) { const projectStatus = state.projectStatus ?? "draft" const waiting = state.waitingForAdmin ?? (projectStatus !== "active" && projectStatus !== "cutover") const authMode = state.authMode ?? "oauth" const sourceProvider = state.sourceProvider ?? "google" return { needs_user_oauth: authMode !== "google_dwd" && authMode !== "microsoft_app", oauth_provider: sourceProvider, waiting_for_admin: waiting, waiting_reason: waiting ? "project_not_activated" : undefined, has_migration_credentials: state.hasMigrationCredentials ?? false, needs_microsoft_admin_consent: sourceProvider === "microsoft", } } export async function mockAuthSession( page: Page, opts: { authenticated: boolean; email?: string; name?: string } = { authenticated: true, } ) { await page.route("**/api/auth/session", async (route) => { if (!opts.authenticated) { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ authenticated: false }), }) return } const email = opts.email ?? E2E_MIGRATEE_EMAIL const name = opts.name ?? "Alice Test" await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ authenticated: true, accessToken: "e2e-access-token", refreshToken: "e2e-refresh-token", expiresAt: Date.now() + 3_600_000, user: { sub: "e2e-user-sub", email, name, firstName: name.split(/\s+/)[0] ?? name, }, }), }) }) } export async function installMigrationApiMocks( page: Page, state: MigrationMockState = {} ) { let inviteClaimed = (state.inviteStatus ?? "invited") === "claimed" const snapshot = () => { const inviteStatus = inviteClaimed ? "claimed" : "invited" const merged = { ...state, inviteStatus: inviteStatus as "invited" | "claimed" } return { project: buildProject(merged), invite: buildInvite(merged), onboarding: buildOnboardingHints(merged), jobs: state.jobs ?? DEFAULT_JOBS, } } await page.route("**/api/v1/migration/invite**", async (route) => { const body = snapshot() await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ invite: body.invite, project: body.project, onboarding: body.onboarding, }), }) }) await page.route("**/api/v1/migration/status**", async (route) => { const body = snapshot() await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(body), }) }) await page.route("**/api/v1/migration/claim**", async (route) => { if (route.request().method() !== "POST") { await route.continue() return } inviteClaimed = true const body = snapshot() await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(body), }) }) await page.route("**/api/v1/migration/oauth/start**", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ auth_url: `/onboard/migration?token=${E2E_INVITE_TOKEN}&oauth=success`, }), }) }) } export async function gotoClaimPage(page: Page, token = E2E_INVITE_TOKEN) { await page.goto(`/onboard/claim?token=${encodeURIComponent(token)}`) await page.waitForResponse((res) => res.url().includes("/migration/invite")) } export async function gotoMigrationPage(page: Page, token = E2E_INVITE_TOKEN) { await page.goto(`/onboard/migration?token=${encodeURIComponent(token)}`) } export async function expectClaimAuthenticatedStep(page: Page) { await expect(page.getByRole("heading", { name: "Revendiquer votre compte" })).toBeVisible() await expect(page.getByRole("button", { name: "Revendiquer mon compte" })).toBeVisible() await expect( page.getByText(/L'email de connexion doit correspondre exactement/) ).toBeVisible() } export async function submitClaim(page: Page) { const claimResponse = page.waitForResponse( (res) => res.url().includes("/migration/claim") && res.request().method() === "POST" ) await page.getByRole("button", { name: "Revendiquer mon compte" }).click() await claimResponse await page.waitForURL(new RegExp(`/onboard/migration\\?token=${E2E_INVITE_TOKEN}`)) }