import { expect, type Page, test } from '@playwright/test'; import { BACKEND_API_URL, deleteOrganizations, deleteTestUsers, type CreatedTestUser, } from './helpers/seeded-users'; /** * Scoped provisioning e2e (Workstream 8 / §3.4, §3.7). Proves the onboarding * contract: when a system admin creates an `owner` user with no organization, * the backend auto-creates the company and links the owner to it. * * Requires the backend running with the database migrated + seeded, and the * seed passwords in the environment. */ const ADMIN_PASSWORD = 'flatlogicAdmin123!'; const ADMIN_EMAIL = 'admin@flatlogic.com'; interface RoleRow { readonly id: string; readonly name: string | null; } interface UserRow { readonly id: string; readonly email: string | null; readonly organizationId: string | null; } interface CreateUserResponse { readonly id?: string; readonly organizationId?: string | null; readonly temporaryPassword?: string; } async function login(page: Page, email: string, password: string): Promise { await page.goto('/login'); await page.getByPlaceholder('you@school.edu').fill(email); await page.getByPlaceholder('Enter your password').fill(password); await page.getByRole('button', { name: 'Sign In', exact: true }).click(); await page.waitForURL((url) => !url.pathname.startsWith('/login'), { timeout: 10_000, }); } test.describe('Scoped provisioning', () => { test('creating an owner auto-creates and links the company', async ({ page, }) => { await login(page, ADMIN_EMAIL, ADMIN_PASSWORD); let createdUser: CreatedTestUser | null = null; try { // Resolve the seeded `owner` role id. const rolesRes = await page.request.get(`${BACKEND_API_URL}/roles`); expect(rolesRes.status()).toBe(200); const rolesBody = (await rolesRes.json()) as { rows?: RoleRow[] }; const ownerRole = (rolesBody.rows ?? []).find((r) => r.name === 'owner'); expect(ownerRole, 'seeded owner role must exist').toBeTruthy(); // Create a brand-new owner with no organization. const email = `provisioned-owner-${Date.now()}@example.com`; const createRes = await page.request.post(`${BACKEND_API_URL}/users`, { data: { data: { email, app_role: ownerRole!.id } }, }); expect(createRes.ok()).toBe(true); const createBody = (await createRes.json()) as CreateUserResponse; expect(createBody.id, 'created owner id').toBeTruthy(); createdUser = { id: createBody.id!, email, password: createBody.temporaryPassword ?? '', organizationId: createBody.organizationId ?? null, }; // The owner now belongs to an auto-created company. const lookupRes = await page.request.get( `${BACKEND_API_URL}/users?email=${encodeURIComponent(email)}`, ); expect(lookupRes.status()).toBe(200); const lookupBody = (await lookupRes.json()) as { rows?: UserRow[] }; const created = (lookupBody.rows ?? []).find((u) => u.email === email); expect(created, 'the provisioned owner must exist').toBeTruthy(); expect( created!.organizationId, 'owner-create must auto-create and link a company', ).toBeTruthy(); createdUser = { ...createdUser, organizationId: created!.organizationId, }; } finally { if (createdUser) { await deleteTestUsers(page.request, [createdUser]); await deleteOrganizations(page.request, [createdUser.organizationId]); } } }); });