import { test, expect } from '@playwright/test'; /** * P3.3: E2E Authentication Flow Tests * * Comprehensive tests for the complete authentication flow to prevent regressions. * Tests cover login, register, logout, session persistence, and token refresh. */ const BASE_URL = 'http://localhost:5173'; const API_URL = 'http://localhost:8080'; // Test credentials const TEST_USER = { email: 'test@veza.app', username: 'testuser', password: 'test123', }; const NEW_USER = { email: `test-${Date.now()}@veza.app`, username: `testuser${Date.now()}`, password: 'test123', }; test.describe('Authentication Flow', () => { test.beforeEach(async ({ page }) => { // Clear cookies and storage before each test await page.context().clearCookies(); await page.goto(BASE_URL); }); test('login with valid credentials should succeed', async ({ page }) => { // Navigate to login page await page.goto(`${BASE_URL}/login`); // Fill in credentials await page.fill('input[name="email"], input[type="email"]', TEST_USER.email); await page.fill('input[name="password"], input[type="password"]', TEST_USER.password); // Submit form await page.click('button[type="submit"]'); // Should redirect to dashboard or home await expect(page).toHaveURL(/\/(dashboard|home|library)/); // Verify user is authenticated (check for logout button or user menu) await expect(page.locator('text=/logout/i, [aria-label*="logout"], [data-testid="logout"]')).toBeVisible({ timeout: 5000 }); }); test('login with invalid credentials should fail', async ({ page }) => { await page.goto(`${BASE_URL}/login`); // Fill in invalid credentials await page.fill('input[name="email"], input[type="email"]', 'invalid@example.com'); await page.fill('input[name="password"], input[type="password"]', 'wrongpassword'); // Submit form await page.click('button[type="submit"]'); // Should show error message await expect(page.locator('text=/invalid|incorrect|failed/i')).toBeVisible({ timeout: 3000 }); // Should stay on login page await expect(page).toHaveURL(/\/login/); }); test('session should persist after page refresh', async ({ page }) => { // Login first await page.goto(`${BASE_URL}/login`); await page.fill('input[name="email"], input[type="email"]', TEST_USER.email); await page.fill('input[name="password"], input[type="password"]', TEST_USER.password); await page.click('button[type="submit"]'); // Wait for redirect await expect(page).toHaveURL(/\/(dashboard|home|library)/); // Refresh the page await page.reload(); // Should still be authenticated (not redirected to login) await expect(page).not.toHaveURL(/\/login/); await expect(page.locator('text=/logout/i, [aria-label*="logout"]')).toBeVisible({ timeout: 5000 }); }); test('logout should clear session and redirect to login', async ({ page }) => { // Login first await page.goto(`${BASE_URL}/login`); await page.fill('input[name="email"], input[type="email"]', TEST_USER.email); await page.fill('input[name="password"], input[type="password"]', TEST_USER.password); await page.click('button[type="submit"]'); // Wait for redirect await expect(page).toHaveURL(/\/(dashboard|home|library)/); // Click logout await page.click('text=/logout/i, [aria-label*="logout"], [data-testid="logout"]'); // Should redirect to login await expect(page).toHaveURL(/\/login/); // Try to access protected route - should redirect back to login await page.goto(`${BASE_URL}/dashboard`); await expect(page).toHaveURL(/\/login/); }); test('register new user should succeed', async ({ page }) => { await page.goto(`${BASE_URL}/register`); // Fill in registration form await page.fill('input[name="email"], input[type="email"]', NEW_USER.email); await page.fill('input[name="username"]', NEW_USER.username); await page.fill('input[name="password"], input[type="password"]', NEW_USER.password); // May have password confirmation field const confirmPasswordField = page.locator('input[name="confirmPassword"], input[name="password_confirmation"]'); if (await confirmPasswordField.isVisible()) { await confirmPasswordField.fill(NEW_USER.password); } // Submit form await page.click('button[type="submit"]'); // Should redirect to dashboard or show success message await expect(page).toHaveURL(/\/(dashboard|home|library|login)/, { timeout: 10000 }); }); test('protected routes should redirect to login when not authenticated', async ({ page }) => { // Try to access protected routes without authentication const protectedRoutes = ['/dashboard', '/library', '/playlists', '/profile']; for (const route of protectedRoutes) { await page.goto(`${BASE_URL}${route}`); // Should redirect to login await expect(page).toHaveURL(/\/login/, { timeout: 3000 }); } }); test('health endpoint should be accessible', async ({ page }) => { // Test the health endpoint created in P1.6 const response = await page.request.get(`${API_URL}/api/v1/health`); expect(response.ok()).toBeTruthy(); const data = await response.json(); expect(data.status).toBe('ok'); expect(data.timestamp).toBeDefined(); }); test('CORS headers should be present on API requests', async ({ page }) => { await page.goto(`${BASE_URL}/login`); // Make a request to the API const response = await page.request.get(`${API_URL}/api/v1/health`, { headers: { 'Origin': BASE_URL, }, }); // Check for CORS headers (P1.1 fix) const headers = response.headers(); expect(headers['access-control-allow-origin']).toBeDefined(); }); }); test.describe('Token Refresh Flow', () => { test('should handle token refresh gracefully', async ({ page }) => { // Login await page.goto(`${BASE_URL}/login`); await page.fill('input[name="email"], input[type="email"]', TEST_USER.email); await page.fill('input[name="password"], input[type="password"]', TEST_USER.password); await page.click('button[type="submit"]'); await expect(page).toHaveURL(/\/(dashboard|home|library)/); // Wait for potential token refresh (access token expires after 15min in prod) // In dev, we can't easily test this without mocking time // But we can verify the app doesn't crash on refresh await page.reload(); // Should still be authenticated await expect(page).not.toHaveURL(/\/login/); }); test('should logout after max refresh attempts (P1.4)', async ({ page }) => { // This test would require mocking the backend to return 401 repeatedly // For now, we just document the expected behavior: // - After 3 failed refresh attempts, user should be logged out // - User should see error message: "Session expired after multiple attempts" // Placeholder test - would need backend mocking to implement fully expect(true).toBe(true); }); }); test.describe('CSRF Protection', () => { test('mutations should include CSRF token (P1.3)', async ({ page }) => { // Login first await page.goto(`${BASE_URL}/login`); await page.fill('input[name="email"], input[type="email"]', TEST_USER.email); await page.fill('input[name="password"], input[type="password"]', TEST_USER.password); // Intercept POST requests to verify CSRF token let csrfTokenPresent = false; page.on('request', (request) => { if (request.method() === 'POST' && request.url().includes('/api/')) { const headers = request.headers(); if (headers['x-csrf-token']) { csrfTokenPresent = true; } } }); await page.click('button[type="submit"]'); // Wait for request to complete await page.waitForTimeout(1000); // CSRF token should be present on POST requests expect(csrfTokenPresent).toBe(true); }); });