import { randomUUID } from "crypto"; import { graphql } from "../../src/graphql"; import { resolveTailorAuth } from "./auth-machine-user"; const CREATE_ROLE_MUTATION = graphql(` mutation E2eCreateRole($name: String!, $permissions: [String!]!) { createRole(name: $name, permissions: $permissions) { id name } } `); const CREATE_USER_MUTATION = graphql(` mutation E2eCreateUser($name: String!, $email: String!, $roleIds: [ID]) { createUser(name: $name, email: $email, roleIds: $roleIds) { id email name status initialPassword } } `); interface UserFactoryInput { name?: string; email?: string; permissions?: string[]; } /** A user created for E2E tests, with login credentials. */ export type TestUser = Awaited>; function buildUserInput(overrides: UserFactoryInput = {}) { const id = randomUUID(); return { name: overrides.name ?? "E2E Test User", email: overrides.email ?? `playwright-${id}@example.com`, permissions: overrides.permissions, }; } async function mutationWithRetry(fn: () => T | Promise, retries = 4): Promise> { let lastError: unknown; let backoff = 500; for (let attempt = 0; attempt < retries; attempt += 1) { try { return await fn(); } catch (error) { lastError = error; const message = error instanceof Error ? error.message : String(error); if (!/backend read error|network/i.test(message) || attempt === retries - 1) { throw error; } await new Promise((resolve) => setTimeout(resolve, backoff)); backoff = Math.min(backoff * 2, 4_000); } } throw lastError; } export async function createUser(inputOverrides: UserFactoryInput = {}) { const { graphqlClient } = resolveTailorAuth(); const userInput = buildUserInput(inputOverrides); // 1. Create role with permissions if specified let roleId: string | undefined; if (userInput.permissions?.length) { const roleName = `e2e-role-${randomUUID().slice(0, 8)}`; const roleResult = await mutationWithRetry(() => graphqlClient.mutation(CREATE_ROLE_MUTATION, { name: roleName, permissions: userInput.permissions ?? [], }), ); if (roleResult.error) { throw new Error(`Failed to create role: ${roleResult.error.message}`); } roleId = roleResult.data!.createRole.id; } // 2. Create user (also creates IDP user and returns initialPassword) const createResult = await mutationWithRetry(() => graphqlClient.mutation(CREATE_USER_MUTATION, { name: userInput.name, email: userInput.email, roleIds: roleId ? [roleId] : undefined, }), ); if (createResult.error) { throw new Error(`Failed to create user: ${createResult.error.message}`); } const created = createResult.data!.createUser; return { name: created.name, email: created.email, password: created.initialPassword, userId: created.id, roleId, }; }