Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Added OnboardClaimPage and OnboardMigrationPage components for user onboarding. - Integrated OAuth login flow for Google and Microsoft accounts. - Implemented error handling and user feedback for claim and migration processes. - Created MigrationStepList and MigrationOnboardingAlerts components for progress tracking. - Added MailDomainsSection and MigrationProjectsPanel for admin settings. - Introduced e2e tests for onboarding migration scenarios.
192 lines
5.9 KiB
TypeScript
192 lines
5.9 KiB
TypeScript
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<string, number>
|
|
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}`))
|
|
}
|