/** * Authentication Helpers for E2E Tests * * Shared utilities for authentication testing * v0.5.0 - JWT and API Key Authentication Support */ import { Page, APIRequestContext, expect } from '@playwright/test'; // Base URLs const API_BASE_URL = process.env.VITE_API_URL || 'http://localhost:8000/api/v1'; const FRONTEND_URL = process.env.TEST_BASE_URL || 'http://localhost:5173'; // Test user storage for cleanup const testUsers: { email: string; password: string }[] = []; /** * Register a new user via API */ export async function registerUser( request: APIRequestContext, email: string, password: string, fullName: string ): Promise<{ user: { id: string; email: string }; access_token: string; refresh_token: string }> { const response = await request.post(`${API_BASE_URL}/auth/register`, { data: { email, password, full_name: fullName, }, }); expect(response.ok()).toBeTruthy(); const data = await response.json(); // Track for cleanup testUsers.push({ email, password }); return data; } /** * Login user via API */ export async function loginUser( request: APIRequestContext, email: string, password: string ): Promise<{ access_token: string; refresh_token: string; token_type: string }> { const response = await request.post(`${API_BASE_URL}/auth/login`, { data: { email, password, }, }); expect(response.ok()).toBeTruthy(); return await response.json(); } /** * Login user via UI */ export async function loginUserViaUI( page: Page, email: string, password: string ): Promise { await page.goto('/login'); await page.waitForLoadState('networkidle'); // Fill login form await page.getByLabel(/email/i).fill(email); await page.getByLabel(/password/i).fill(password); // Submit form await page.getByRole('button', { name: /login|sign in/i }).click(); // Wait for redirect to dashboard await page.waitForURL('/', { timeout: 10000 }); await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); } /** * Register user via UI */ export async function registerUserViaUI( page: Page, email: string, password: string, fullName: string ): Promise { await page.goto('/register'); await page.waitForLoadState('networkidle'); // Fill registration form await page.getByLabel(/full name|name/i).fill(fullName); await page.getByLabel(/email/i).fill(email); await page.getByLabel(/^password$/i).fill(password); await page.getByLabel(/confirm password|repeat password/i).fill(password); // Submit form await page.getByRole('button', { name: /register|sign up|create account/i }).click(); // Wait for redirect to dashboard await page.waitForURL('/', { timeout: 10000 }); await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); // Track for cleanup testUsers.push({ email, password }); } /** * Logout user via UI */ export async function logoutUser(page: Page): Promise { // Click on user dropdown const userDropdown = page.locator('[data-testid="user-dropdown"]').or( page.locator('header').getByText(/user|profile|account/i).first() ); if (await userDropdown.isVisible().catch(() => false)) { await userDropdown.click(); // Click logout const logoutButton = page.getByRole('menuitem', { name: /logout|sign out/i }).or( page.getByText(/logout|sign out/i).first() ); await logoutButton.click(); } // Wait for redirect to login await page.waitForURL('/login', { timeout: 10000 }); } /** * Create authentication header with JWT token */ export function createAuthHeader(accessToken: string): { Authorization: string } { return { Authorization: `Bearer ${accessToken}`, }; } /** * Create API Key header */ export function createApiKeyHeader(apiKey: string): { 'X-API-Key': string } { return { 'X-API-Key': apiKey, }; } /** * Get current user info via API */ export async function getCurrentUser( request: APIRequestContext, accessToken: string ): Promise<{ id: string; email: string; full_name: string }> { const response = await request.get(`${API_BASE_URL}/auth/me`, { headers: createAuthHeader(accessToken), }); expect(response.ok()).toBeTruthy(); return await response.json(); } /** * Refresh access token */ export async function refreshToken( request: APIRequestContext, refreshToken: string ): Promise<{ access_token: string; refresh_token: string }> { const response = await request.post(`${API_BASE_URL}/auth/refresh`, { data: { refresh_token: refreshToken }, }); expect(response.ok()).toBeTruthy(); return await response.json(); } /** * Create an API key via API */ export async function createApiKeyViaAPI( request: APIRequestContext, accessToken: string, name: string, scopes: string[] = ['read:scenarios'], expiresDays?: number ): Promise<{ id: string; name: string; key: string; prefix: string; scopes: string[] }> { const data: { name: string; scopes: string[]; expires_days?: number } = { name, scopes, }; if (expiresDays !== undefined) { data.expires_days = expiresDays; } const response = await request.post(`${API_BASE_URL}/api-keys`, { data, headers: createAuthHeader(accessToken), }); expect(response.ok()).toBeTruthy(); return await response.json(); } /** * List API keys via API */ export async function listApiKeys( request: APIRequestContext, accessToken: string ): Promise> { const response = await request.get(`${API_BASE_URL}/api-keys`, { headers: createAuthHeader(accessToken), }); expect(response.ok()).toBeTruthy(); return await response.json(); } /** * Revoke API key via API */ export async function revokeApiKey( request: APIRequestContext, accessToken: string, apiKeyId: string ): Promise { const response = await request.delete(`${API_BASE_URL}/api-keys/${apiKeyId}`, { headers: createAuthHeader(accessToken), }); expect(response.ok()).toBeTruthy(); } /** * Validate API key via API */ export async function validateApiKey( request: APIRequestContext, apiKey: string ): Promise { const response = await request.get(`${API_BASE_URL}/auth/me`, { headers: createApiKeyHeader(apiKey), }); return response.ok(); } /** * Generate unique test email */ export function generateTestEmail(prefix = 'test'): string { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 8); return `${prefix}.${timestamp}.${random}@test.mockupaws.com`; } /** * Generate unique test user data */ export function generateTestUser(prefix = 'Test'): { email: string; password: string; fullName: string; } { const timestamp = Date.now(); return { email: `user.${timestamp}@test.mockupaws.com`, password: 'TestPassword123!', fullName: `${prefix} User ${timestamp}`, }; } /** * Clear all test users (cleanup function) */ export async function cleanupTestUsers(request: APIRequestContext): Promise { for (const user of testUsers) { try { // Try to login and delete user (if API supports it) const loginResponse = await request.post(`${API_BASE_URL}/auth/login`, { data: { email: user.email, password: user.password }, }); if (loginResponse.ok()) { const { access_token } = await loginResponse.json(); // Delete user - endpoint may vary await request.delete(`${API_BASE_URL}/auth/me`, { headers: createAuthHeader(access_token), }); } } catch { // Ignore cleanup errors } } testUsers.length = 0; } /** * Check if user is authenticated on the page */ export async function isAuthenticated(page: Page): Promise { // Check for user dropdown or authenticated state indicators const userDropdown = page.locator('[data-testid="user-dropdown"]'); const logoutButton = page.getByRole('button', { name: /logout/i }); const hasUserDropdown = await userDropdown.isVisible().catch(() => false); const hasLogoutButton = await logoutButton.isVisible().catch(() => false); return hasUserDropdown || hasLogoutButton; } /** * Wait for auth redirect */ export async function waitForAuthRedirect(page: Page, expectedPath: string = '/login'): Promise { await page.waitForURL(expectedPath, { timeout: 5000 }); } /** * Set local storage token (for testing protected routes) */ export async function setAuthToken(page: Page, token: string): Promise { await page.evaluate((t) => { localStorage.setItem('access_token', t); }, token); } /** * Clear local storage token */ export async function clearAuthToken(page: Page): Promise { await page.evaluate(() => { localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); }); }