/* eslint-disable react-hooks/rules-of-hooks */ import 'dotenv/config'; import { test as base, expect, Page, APIRequestContext, APIResponse } from '@playwright/test'; export type TenantCredentials = { email: string; password: string; }; export type TenantAdminFixtures = { tenantAdminCredentials: TenantCredentials | null; signInTenantAdmin: () => Promise; }; export type MailboxEntry = { id: string; subject: string | null; to: Array<{ email: string; name: string | null }>; from: Array<{ email: string; name: string | null }>; html: string | null; text: string | null; sent_at: string; }; export type CouponSeedDefinition = { code: string; type: 'percentage' | 'flat' | 'flat_per_seat'; amount: number; currency?: string | null; description?: string; enabled_for_checkout?: boolean; usage_limit?: number | null; per_customer_limit?: number | null; starts_at?: string | null; ends_at?: string | null; packages?: number[]; }; export type TestingApiFixtures = { clearTestMailbox: () => Promise; getTestMailbox: () => Promise; seedTestCoupons: (definitions?: CouponSeedDefinition[]) => Promise>; getLatestCheckoutSession: (filters?: { email?: string; tenantId?: number; status?: string }) => Promise; simulatePaddleCompletion: (sessionId: string, overrides?: Partial) => Promise; fetchJoinToken: (params: { eventId?: number; slug?: string; ensureActive?: boolean }) => Promise; }; export type CheckoutSessionSummary = { id: string; status: string; provider: string | null; tenant_id: number | null; package_id: number | null; user_email: string | null; coupon_id: number | null; amount_subtotal: string | null; amount_total: string | null; created_at: string | null; }; export type PaddleSimulationOverrides = { event_type: string; transaction_id?: string; status?: string; checkout_id?: string; metadata?: Record; }; export type JoinTokenPayload = { event_id: number; token_id: number; token: string; join_url: string; qr_svg: string; expires_at: string | null; usage_count: number; usage_limit: number | null; }; const tenantAdminEmail = process.env.E2E_TENANT_EMAIL ?? 'hello@lumen-moments.demo'; const tenantAdminPassword = process.env.E2E_TENANT_PASSWORD ?? 'Demo1234!'; export const test = base.extend({ tenantAdminCredentials: async ({}, use) => { if (!tenantAdminEmail || !tenantAdminPassword) { await use(null); return; } await use({ email: tenantAdminEmail, password: tenantAdminPassword, }); }, signInTenantAdmin: async ({ page, tenantAdminCredentials }, use) => { if (!tenantAdminCredentials) { await use(async () => { throw new Error('Tenant admin credentials missing. Provide E2E_TENANT_EMAIL and E2E_TENANT_PASSWORD.'); }); return; } await use(async () => { await performTenantSignIn(page, tenantAdminCredentials); }); }, clearTestMailbox: async ({ request }, use) => { await use(async () => { await expectApiSuccess(request.delete('/api/_testing/mailbox')); }); }, getTestMailbox: async ({ request }, use) => { await use(async () => { const response = await expectApiSuccess(request.get('/api/_testing/mailbox')); const json = await response.json(); return Array.isArray(json.data) ? (json.data as MailboxEntry[]) : []; }); }, seedTestCoupons: async ({ request }, use) => { await use(async (definitions?: CouponSeedDefinition[]) => { const response = await expectApiSuccess( request.post('/api/_testing/coupons/seed', { data: definitions && definitions.length > 0 ? { coupons: definitions } : undefined, }) ); const json = await response.json(); return Array.isArray(json.data) ? json.data : []; }); }, getLatestCheckoutSession: async ({ request }, use) => { await use(async (filters?: { email?: string; tenantId?: number; status?: string }) => { const response = await request.get('/api/_testing/checkout/sessions/latest', { params: { email: filters?.email, tenant_id: filters?.tenantId, status: filters?.status, }, }); if (response.status() === 404) { return null; } await expectApiSuccess(Promise.resolve(response)); const json = await response.json(); return json.data as CheckoutSessionSummary; }); }, simulatePaddleCompletion: async ({ request }, use) => { await use(async (sessionId: string, overrides?: Partial) => { await expectApiSuccess( request.post(`/api/_testing/checkout/sessions/${sessionId}/simulate-paddle`, { data: overrides, }) ); }); }, fetchJoinToken: async ({ request }, use) => { await use(async ({ eventId, slug, ensureActive = true }: { eventId?: number; slug?: string; ensureActive?: boolean }) => { const response = await expectApiSuccess( request.get('/api/_testing/events/join-token', { params: { event_id: eventId, slug, ensure_active: ensureActive, }, }) ); const json = await response.json(); return json.data as JoinTokenPayload; }); }, }); export const expectFixture = expect; async function performTenantSignIn(page: Page, credentials: TenantCredentials) { const token = await exchangeToken(page.request, credentials); await page.addInitScript(({ stored }) => { localStorage.setItem('tenant_admin.token.v1', JSON.stringify(stored)); sessionStorage.setItem('tenant_admin.token.session.v1', JSON.stringify(stored)); }, { stored: token }); await page.goto('/event-admin'); await page.waitForLoadState('domcontentloaded'); } type StoredTokenPayload = { accessToken: string; abilities: string[]; issuedAt: number; }; async function exchangeToken(request: APIRequestContext, credentials: TenantCredentials): Promise { const response = await request.post('/api/v1/tenant-auth/login', { data: { login: credentials.email, password: credentials.password, }, }); if (!response.ok()) { throw new Error(`Tenant PAT login failed: ${response.status()} ${await response.text()}`); } const body = await response.json(); return { accessToken: body.token, abilities: Array.isArray(body.abilities) ? body.abilities : [], issuedAt: Date.now(), }; } async function expectApiSuccess(responsePromise: Promise): Promise { const response = await responsePromise; if (!response.ok()) { throw new Error(`Test API request failed: ${response.status()} ${await response.text()}`); } return response; }