/** * Auth Feature Flow Tests * * These tests verify routing, guards, navigation, cross-screen state, and user flows * for the auth module. They run with real frontend and mocked contracts. * * Contracts are defined in apps/website/lib/types/generated * * @file apps/website/tests/flows/auth.test.ts */ import { test, expect } from '@playwright/test'; import { WebsiteAuthManager } from '../../../tests/shared/website/WebsiteAuthManager'; import { WebsiteRouteManager } from '../../../tests/shared/website/WebsiteRouteManager'; import { ConsoleErrorCapture } from '../../../tests/shared/website/ConsoleErrorCapture'; import { HttpDiagnostics } from '../../../tests/shared/website/HttpDiagnostics'; import { RouteContractSpec } from '../../../tests/shared/website/RouteContractSpec'; test.describe('Auth Feature Flow', () => { describe('Login Flow', () => { test('should navigate to login page', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Verify login form is displayed await expect(page.locator('form')).toBeVisible(); // Check for email and password inputs await expect(page.locator('[data-testid="email-input"]')).toBeVisible(); await expect(page.locator('[data-testid="password-input"]')).toBeVisible(); }); test('should display validation errors for empty fields', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Click submit without entering credentials await page.locator('[data-testid="submit-button"]').click(); // Verify validation errors are shown await expect(page.locator('[data-testid="email-error"]')).toBeVisible(); await expect(page.locator('[data-testid="password-error"]')).toBeVisible(); }); test('should display validation errors for invalid email format', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter invalid email format await page.locator('[data-testid="email-input"]').fill('invalid-email'); // Verify validation error is shown await expect(page.locator('[data-testid="email-error"]')).toBeVisible(); await expect(page.locator('[data-testid="email-error"]')).toContainText(/invalid email/i); }); test('should successfully login with valid credentials', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock LoginParamsDTO and AuthSessionDTO response const mockAuthSession = { token: 'test-token-123', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', role: 'user', }, }; await routeContractSpec.mockApiCall('Login', mockAuthSession); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter valid email and password await page.locator('[data-testid="email-input"]').fill('test@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify authentication is successful await expect(page).toHaveURL(/.*\/dashboard/); // Verify redirect to dashboard await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); }); test('should handle login with remember me option', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock AuthSessionDTO response const mockAuthSession = { token: 'test-token-123', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', role: 'user', }, }; await routeContractSpec.mockApiCall('Login', mockAuthSession); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Check remember me checkbox await page.locator('[data-testid="remember-me-checkbox"]').check(); // Enter valid credentials await page.locator('[data-testid="email-input"]').fill('test@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify authentication is successful await expect(page).toHaveURL(/.*\/dashboard/); // Verify AuthSessionDTO is stored with longer expiration // This would be verified by checking the cookie expiration const cookies = await page.context().cookies(); const sessionCookie = cookies.find(c => c.name === 'gp_session'); expect(sessionCookie).toBeDefined(); // Remember me should set a longer expiration (e.g., 30 days) // The exact expiration depends on the implementation }); test('should handle login errors (invalid credentials)', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock API to return authentication error await routeContractSpec.mockApiCall('Login', { error: 'Invalid credentials', status: 401, }); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter credentials await page.locator('[data-testid="email-input"]').fill('wrong@example.com'); await page.locator('[data-testid="password-input"]').fill('WrongPass123!'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify error message is displayed await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/invalid credentials/i); // Verify form remains in error state await expect(page.locator('[data-testid="email-input"]')).toHaveValue('wrong@example.com'); }); test('should handle login errors (server/network error)', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); const consoleErrorCapture = new ConsoleErrorCapture(page); // Mock API to return 500 error await routeContractSpec.mockApiCall('Login', { error: 'Internal Server Error', status: 500, }); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter credentials await page.locator('[data-testid="email-input"]').fill('test@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify generic error message is shown await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/error/i); // Verify console error was captured const errors = consoleErrorCapture.getErrors(); expect(errors.length).toBeGreaterThan(0); }); test('should redirect to dashboard if already authenticated', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); // Mock existing AuthSessionDTO by logging in await authManager.loginAsUser(); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Verify redirect to dashboard await expect(page).toHaveURL(/.*\/dashboard/); }); test('should navigate to forgot password from login', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Click forgot password link await page.locator('[data-testid="forgot-password-link"]').click(); // Verify navigation to /auth/forgot-password await expect(page).toHaveURL(/.*\/auth\/forgot-password/); }); test('should navigate to signup from login', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Click signup link await page.locator('[data-testid="signup-link"]').click(); // Verify navigation to /auth/signup await expect(page).toHaveURL(/.*\/auth\/signup/); }); }); describe('Signup Flow', () => { test('should navigate to signup page', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/signup await page.goto(routeManager.getRoute('/auth/signup')); // Verify signup form is displayed await expect(page.locator('form')).toBeVisible(); // Check for required fields (email, password, displayName) await expect(page.locator('[data-testid="email-input"]')).toBeVisible(); await expect(page.locator('[data-testid="password-input"]')).toBeVisible(); await expect(page.locator('[data-testid="display-name-input"]')).toBeVisible(); }); test('should display validation errors for empty required fields', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/signup await page.goto(routeManager.getRoute('/auth/signup')); // Click submit without entering any data await page.locator('[data-testid="submit-button"]').click(); // Verify validation errors for all required fields await expect(page.locator('[data-testid="email-error"]')).toBeVisible(); await expect(page.locator('[data-testid="password-error"]')).toBeVisible(); await expect(page.locator('[data-testid="display-name-error"]')).toBeVisible(); }); test('should display validation errors for weak password', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/signup await page.goto(routeManager.getRoute('/auth/signup')); // Enter password that doesn't meet requirements await page.locator('[data-testid="password-input"]').fill('weak'); // Verify password strength validation error await expect(page.locator('[data-testid="password-error"]')).toBeVisible(); await expect(page.locator('[data-testid="password-error"]')).toContainText(/password must be/i); }); test('should successfully signup with valid data', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock SignupParamsDTO and AuthSessionDTO response const mockAuthSession = { token: 'test-token-456', user: { userId: 'user-456', email: 'newuser@example.com', displayName: 'New User', role: 'user', }, }; await routeContractSpec.mockApiCall('Signup', mockAuthSession); // Navigate to /auth/signup await page.goto(routeManager.getRoute('/auth/signup')); // Enter valid email, password, and display name await page.locator('[data-testid="email-input"]').fill('newuser@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); await page.locator('[data-testid="display-name-input"]').fill('New User'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify authentication is successful await expect(page).toHaveURL(/.*\/dashboard/); // Verify redirect to dashboard await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); }); test('should handle signup with optional iRacing customer ID', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock SignupParamsDTO and AuthSessionDTO response const mockAuthSession = { token: 'test-token-789', user: { userId: 'user-789', email: 'iracing@example.com', displayName: 'iRacing User', role: 'user', }, }; await routeContractSpec.mockApiCall('Signup', mockAuthSession); // Navigate to /auth/signup await page.goto(routeManager.getRoute('/auth/signup')); // Enter valid credentials await page.locator('[data-testid="email-input"]').fill('iracing@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); await page.locator('[data-testid="display-name-input"]').fill('iRacing User'); // Enter optional iRacing customer ID await page.locator('[data-testid="iracing-customer-id-input"]').fill('123456'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify authentication is successful await expect(page).toHaveURL(/.*\/dashboard/); }); test('should handle signup errors (email already exists)', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock API to return email conflict error await routeContractSpec.mockApiCall('Signup', { error: 'Email already exists', status: 409, }); // Navigate to /auth/signup await page.goto(routeManager.getRoute('/auth/signup')); // Enter credentials await page.locator('[data-testid="email-input"]').fill('existing@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); await page.locator('[data-testid="display-name-input"]').fill('Existing User'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify error message about existing account await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/already exists/i); }); test('should handle signup errors (server error)', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); const consoleErrorCapture = new ConsoleErrorCapture(page); // Mock API to return 500 error await routeContractSpec.mockApiCall('Signup', { error: 'Internal Server Error', status: 500, }); // Navigate to /auth/signup await page.goto(routeManager.getRoute('/auth/signup')); // Enter valid credentials await page.locator('[data-testid="email-input"]').fill('test@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); await page.locator('[data-testid="display-name-input"]').fill('Test User'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify generic error message is shown await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/error/i); // Verify console error was captured const errors = consoleErrorCapture.getErrors(); expect(errors.length).toBeGreaterThan(0); }); test('should navigate to login from signup', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/signup await page.goto(routeManager.getRoute('/auth/signup')); // Click login link await page.locator('[data-testid="login-link"]').click(); // Verify navigation to /auth/login await expect(page).toHaveURL(/.*\/auth\/login/); }); test('should handle password visibility toggle', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/signup await page.goto(routeManager.getRoute('/auth/signup')); // Enter password await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); // Click show/hide password toggle await page.locator('[data-testid="password-toggle"]').click(); // Verify password visibility changes // Check that the input type changes from password to text const passwordInput = page.locator('[data-testid="password-input"]'); await expect(passwordInput).toHaveAttribute('type', 'text'); // Click toggle again to hide await page.locator('[data-testid="password-toggle"]').click(); await expect(passwordInput).toHaveAttribute('type', 'password'); }); }); describe('Forgot Password Flow', () => { test('should navigate to forgot password page', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/forgot-password await page.goto(routeManager.getRoute('/auth/forgot-password')); // Verify forgot password form is displayed await expect(page.locator('form')).toBeVisible(); // Check for email input field await expect(page.locator('[data-testid="email-input"]')).toBeVisible(); }); test('should display validation error for empty email', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/forgot-password await page.goto(routeManager.getRoute('/auth/forgot-password')); // Click submit without entering email await page.locator('[data-testid="submit-button"]').click(); // Verify validation error is shown await expect(page.locator('[data-testid="email-error"]')).toBeVisible(); }); test('should display validation error for invalid email format', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/forgot-password await page.goto(routeManager.getRoute('/auth/forgot-password')); // Enter invalid email format await page.locator('[data-testid="email-input"]').fill('invalid-email'); // Verify validation error is shown await expect(page.locator('[data-testid="email-error"]')).toBeVisible(); await expect(page.locator('[data-testid="email-error"]')).toContainText(/invalid email/i); }); test('should successfully submit forgot password request', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock ForgotPasswordDTO response const mockForgotPassword = { success: true, message: 'Password reset email sent', }; await routeContractSpec.mockApiCall('ForgotPassword', mockForgotPassword); // Navigate to /auth/forgot-password await page.goto(routeManager.getRoute('/auth/forgot-password')); // Enter valid email await page.locator('[data-testid="email-input"]').fill('test@example.com'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify success message is displayed await expect(page.locator('[data-testid="success-message"]')).toBeVisible(); await expect(page.locator('[data-testid="success-message"]')).toContainText(/password reset email sent/i); // Verify form is in success state await expect(page.locator('[data-testid="submit-button"]')).toBeDisabled(); }); test('should handle forgot password errors (email not found)', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock API to return email not found error await routeContractSpec.mockApiCall('ForgotPassword', { error: 'Email not found', status: 404, }); // Navigate to /auth/forgot-password await page.goto(routeManager.getRoute('/auth/forgot-password')); // Enter email await page.locator('[data-testid="email-input"]').fill('nonexistent@example.com'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify error message is displayed await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/email not found/i); }); test('should handle forgot password errors (rate limit)', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock API to return rate limit error await routeContractSpec.mockApiCall('ForgotPassword', { error: 'Rate limit exceeded', status: 429, }); // Navigate to /auth/forgot-password await page.goto(routeManager.getRoute('/auth/forgot-password')); // Enter email await page.locator('[data-testid="email-input"]').fill('test@example.com'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify rate limit message is shown await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/rate limit/i); }); test('should navigate back to login from forgot password', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/forgot-password await page.goto(routeManager.getRoute('/auth/forgot-password')); // Click back/login link await page.locator('[data-testid="login-link"]').click(); // Verify navigation to /auth/login await expect(page).toHaveURL(/.*\/auth\/login/); }); }); describe('Reset Password Flow', () => { test('should navigate to reset password page with token', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/reset-password?token=abc123 await page.goto(routeManager.getRoute('/auth/reset-password') + '?token=abc123'); // Verify reset password form is displayed await expect(page.locator('form')).toBeVisible(); // Check for new password and confirm password inputs await expect(page.locator('[data-testid="new-password-input"]')).toBeVisible(); await expect(page.locator('[data-testid="confirm-password-input"]')).toBeVisible(); }); test('should display validation errors for empty password fields', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/reset-password?token=abc123 await page.goto(routeManager.getRoute('/auth/reset-password') + '?token=abc123'); // Click submit without entering passwords await page.locator('[data-testid="submit-button"]').click(); // Verify validation errors are shown await expect(page.locator('[data-testid="new-password-error"]')).toBeVisible(); await expect(page.locator('[data-testid="confirm-password-error"]')).toBeVisible(); }); test('should display validation error for non-matching passwords', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/reset-password?token=abc123 await page.goto(routeManager.getRoute('/auth/reset-password') + '?token=abc123'); // Enter different passwords in new and confirm fields await page.locator('[data-testid="new-password-input"]').fill('ValidPass123!'); await page.locator('[data-testid="confirm-password-input"]').fill('DifferentPass456!'); // Verify validation error is shown await expect(page.locator('[data-testid="confirm-password-error"]')).toBeVisible(); await expect(page.locator('[data-testid="confirm-password-error"]')).toContainText(/passwords do not match/i); }); test('should display validation error for weak new password', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/reset-password?token=abc123 await page.goto(routeManager.getRoute('/auth/reset-password') + '?token=abc123'); // Enter weak password await page.locator('[data-testid="new-password-input"]').fill('weak'); // Verify password strength validation error await expect(page.locator('[data-testid="new-password-error"]')).toBeVisible(); await expect(page.locator('[data-testid="new-password-error"]')).toContainText(/password must be/i); }); test('should successfully reset password', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock successful password reset response const mockResetPassword = { success: true, message: 'Password reset successfully', }; await routeContractSpec.mockApiCall('ResetPassword', mockResetPassword); // Navigate to /auth/reset-password?token=abc123 await page.goto(routeManager.getRoute('/auth/reset-password') + '?token=abc123'); // Enter matching valid passwords await page.locator('[data-testid="new-password-input"]').fill('NewPass123!'); await page.locator('[data-testid="confirm-password-input"]').fill('NewPass123!'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify success message is displayed await expect(page.locator('[data-testid="success-message"]')).toBeVisible(); await expect(page.locator('[data-testid="success-message"]')).toContainText(/password reset successfully/i); // Verify redirect to login page await expect(page).toHaveURL(/.*\/auth\/login/); }); test('should handle reset password with invalid token', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock API to return invalid token error await routeContractSpec.mockApiCall('ResetPassword', { error: 'Invalid token', status: 400, }); // Navigate to /auth/reset-password?token=invalid await page.goto(routeManager.getRoute('/auth/reset-password') + '?token=invalid'); // Verify error message is displayed await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/invalid token/i); // Verify form is disabled await expect(page.locator('[data-testid="submit-button"]')).toBeDisabled(); }); test('should handle reset password with expired token', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock API to return expired token error await routeContractSpec.mockApiCall('ResetPassword', { error: 'Token expired', status: 400, }); // Navigate to /auth/reset-password?token=expired await page.goto(routeManager.getRoute('/auth/reset-password') + '?token=expired'); // Verify error message is displayed await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/token expired/i); // Verify link to request new reset email await expect(page.locator('[data-testid="request-new-link"]')).toBeVisible(); }); test('should handle reset password errors (server error)', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); const consoleErrorCapture = new ConsoleErrorCapture(page); // Mock API to return 500 error await routeContractSpec.mockApiCall('ResetPassword', { error: 'Internal Server Error', status: 500, }); // Navigate to /auth/reset-password?token=abc123 await page.goto(routeManager.getRoute('/auth/reset-password') + '?token=abc123'); // Enter valid passwords await page.locator('[data-testid="new-password-input"]').fill('NewPass123!'); await page.locator('[data-testid="confirm-password-input"]').fill('NewPass123!'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify generic error message is shown await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/error/i); // Verify console error was captured const errors = consoleErrorCapture.getErrors(); expect(errors.length).toBeGreaterThan(0); }); test('should navigate to login from reset password', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/reset-password?token=abc123 await page.goto(routeManager.getRoute('/auth/reset-password') + '?token=abc123'); // Click login link await page.locator('[data-testid="login-link"]').click(); // Verify navigation to /auth/login await expect(page).toHaveURL(/.*\/auth\/login/); }); }); describe('Logout Flow', () => { test('should successfully logout from authenticated session', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock existing AuthSessionDTO by logging in await authManager.loginAsUser(); // Mock logout API call await routeContractSpec.mockApiCall('Logout', { success: true }); // Navigate to dashboard await page.goto(routeManager.getRoute('/dashboard')); // Click logout button await page.locator('[data-testid="logout-button"]').click(); // Verify AuthSessionDTO is cleared const cookies = await page.context().cookies(); const sessionCookie = cookies.find(c => c.name === 'gp_session'); expect(sessionCookie).toBeUndefined(); // Verify redirect to login page await expect(page).toHaveURL(/.*\/auth\/login/); }); test('should handle logout errors gracefully', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock existing AuthSessionDTO by logging in await authManager.loginAsUser(); // Mock logout API to return error await routeContractSpec.mockApiCall('Logout', { error: 'Logout failed', status: 500, }); // Navigate to dashboard await page.goto(routeManager.getRoute('/dashboard')); // Click logout button await page.locator('[data-testid="logout-button"]').click(); // Verify session is still cleared locally const cookies = await page.context().cookies(); const sessionCookie = cookies.find(c => c.name === 'gp_session'); expect(sessionCookie).toBeUndefined(); // Verify redirect to login page await expect(page).toHaveURL(/.*\/auth\/login/); }); test('should clear all auth-related state on logout', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock existing AuthSessionDTO by logging in await authManager.loginAsUser(); // Mock logout API call await routeContractSpec.mockApiCall('Logout', { success: true }); // Navigate to various pages await page.goto(routeManager.getRoute('/dashboard')); await page.goto(routeManager.getRoute('/profile')); // Click logout button await page.locator('[data-testid="logout-button"]').click(); // Verify all auth state is cleared const cookies = await page.context().cookies(); const sessionCookie = cookies.find(c => c.name === 'gp_session'); expect(sessionCookie).toBeUndefined(); // Verify no auth data persists await expect(page).toHaveURL(/.*\/auth\/login/); // Try to access protected route again await page.goto(routeManager.getRoute('/dashboard')); // Should redirect to login await expect(page).toHaveURL(/.*\/auth\/login/); }); }); describe('Auth Route Guards', () => { test('should redirect unauthenticated users to login', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to protected route (e.g., /dashboard) await page.goto(routeManager.getRoute('/dashboard')); // Verify redirect to /auth/login await expect(page).toHaveURL(/.*\/auth\/login/); // Check return URL parameter const url = new URL(page.url()); expect(url.searchParams.get('returnUrl')).toBe('/dashboard'); }); test('should allow access to authenticated users', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); // Mock existing AuthSessionDTO await authManager.loginAsUser(); // Navigate to protected route await page.goto(routeManager.getRoute('/dashboard')); // Verify page loads successfully await expect(page).toHaveURL(/.*\/dashboard/); await expect(page.locator('[data-testid="dashboard"]')).toBeVisible(); }); test('should handle session expiration during navigation', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock existing AuthSessionDTO await authManager.loginAsUser(); // Navigate to protected route await page.goto(routeManager.getRoute('/dashboard')); // Mock session expiration await routeContractSpec.mockApiCall('GetDashboardData', { error: 'Unauthorized', status: 401, message: 'Session expired', }); // Attempt navigation to another protected route await page.goto(routeManager.getRoute('/profile')); // Verify redirect to login await expect(page).toHaveURL(/.*\/auth\/login/); }); test('should maintain return URL after authentication', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); const routeContractSpec = new RouteContractSpec(page); // Attempt to access protected route without auth await page.goto(routeManager.getRoute('/dashboard')); // Verify redirect to login with return URL await expect(page).toHaveURL(/.*\/auth\/login/); const url = new URL(page.url()); expect(url.searchParams.get('returnUrl')).toBe('/dashboard'); // Mock dashboard data for after login const mockDashboardData = { overview: { totalRaces: 10, totalLeagues: 5, }, }; await routeContractSpec.mockApiCall('GetDashboardData', mockDashboardData); // Login successfully await authManager.loginAsUser(); // Verify redirect back to original protected route await expect(page).toHaveURL(/.*\/dashboard/); }); test('should redirect authenticated users away from auth pages', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const authManager = new WebsiteAuthManager(page); // Mock existing AuthSessionDTO await authManager.loginAsUser(); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Verify redirect to dashboard await expect(page).toHaveURL(/.*\/dashboard/); }); }); describe('Auth Cross-Screen State Management', () => { test('should preserve form data when navigating between auth pages', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter email await page.locator('[data-testid="email-input"]').fill('test@example.com'); // Navigate to /auth/forgot-password await page.goto(routeManager.getRoute('/auth/forgot-password')); // Navigate back to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Verify email is preserved await expect(page.locator('[data-testid="email-input"]')).toHaveValue('test@example.com'); }); test('should clear form data after successful authentication', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock AuthSessionDTO response const mockAuthSession = { token: 'test-token-123', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', role: 'user', }, }; await routeContractSpec.mockApiCall('Login', mockAuthSession); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter credentials await page.locator('[data-testid="email-input"]').fill('test@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); // Login successfully await page.locator('[data-testid="submit-button"]').click(); // Navigate back to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Verify form is cleared await expect(page.locator('[data-testid="email-input"]')).toHaveValue(''); await expect(page.locator('[data-testid="password-input"]')).toHaveValue(''); }); test('should handle concurrent auth operations', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock AuthSessionDTO response const mockAuthSession = { token: 'test-token-123', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', role: 'user', }, }; await routeContractSpec.mockApiCall('Login', mockAuthSession); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter credentials await page.locator('[data-testid="email-input"]').fill('test@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); // Click submit multiple times quickly await Promise.all([ page.locator('[data-testid="submit-button"]').click(), page.locator('[data-testid="submit-button"]').click(), page.locator('[data-testid="submit-button"]').click(), ]); // Verify only one request is sent // This would be verified by checking the mock call count // For now, verify loading state is managed await expect(page).toHaveURL(/.*\/dashboard/); // Verify loading state is cleared await expect(page.locator('[data-testid="loading-spinner"]')).not.toBeVisible(); }); }); describe('Auth UI State Management', () => { test('should show loading states during auth operations', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); // Mock delayed auth response const mockAuthSession = { token: 'test-token-123', user: { userId: 'user-123', email: 'test@example.com', displayName: 'Test User', role: 'user', }, }; await routeContractSpec.mockApiCall('Login', mockAuthSession, { delay: 500 }); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter credentials await page.locator('[data-testid="email-input"]').fill('test@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); // Submit login form await page.locator('[data-testid="submit-button"]').click(); // Verify loading spinner is shown await expect(page.locator('[data-testid="loading-spinner"]')).toBeVisible(); // Wait for loading to complete await expect(page.locator('[data-testid="loading-spinner"]')).not.toBeVisible(); // Verify authentication is successful await expect(page).toHaveURL(/.*\/dashboard/); }); test('should handle error states gracefully', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); const consoleErrorCapture = new ConsoleErrorCapture(page); // Mock various auth error scenarios await routeContractSpec.mockApiCall('Login', { error: 'Invalid credentials', status: 401, }); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter credentials await page.locator('[data-testid="email-input"]').fill('wrong@example.com'); await page.locator('[data-testid="password-input"]').fill('WrongPass123!'); // Click submit await page.locator('[data-testid="submit-button"]').click(); // Verify error banner/message is displayed await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); // Verify UI remains usable after errors await expect(page.locator('[data-testid="email-input"]')).toBeEnabled(); await expect(page.locator('[data-testid="password-input"]')).toBeEnabled(); await expect(page.locator('[data-testid="submit-button"]')).toBeEnabled(); // Verify error can be dismissed await page.locator('[data-testid="error-dismiss"]').click(); await expect(page.locator('[data-testid="error-message"]')).not.toBeVisible(); // Verify console error was captured const errors = consoleErrorCapture.getErrors(); expect(errors.length).toBeGreaterThan(0); }); test('should handle network connectivity issues', async ({ page }) => { const routeManager = new WebsiteRouteManager(page); const routeContractSpec = new RouteContractSpec(page); const consoleErrorCapture = new ConsoleErrorCapture(page); // Mock network failure await routeContractSpec.mockApiCall('Login', { error: 'Network Error', status: 0, }); // Navigate to /auth/login await page.goto(routeManager.getRoute('/auth/login')); // Enter credentials await page.locator('[data-testid="email-input"]').fill('test@example.com'); await page.locator('[data-testid="password-input"]').fill('ValidPass123!'); // Attempt auth operation await page.locator('[data-testid="submit-button"]').click(); // Verify network error message is shown await expect(page.locator('[data-testid="error-message"]')).toBeVisible(); await expect(page.locator('[data-testid="error-message"]')).toContainText(/network/i); // Verify retry option is available await expect(page.locator('[data-testid="retry-button"]')).toBeVisible(); // Verify console error was captured const errors = consoleErrorCapture.getErrors(); expect(errors.length).toBeGreaterThan(0); }); }); });