diff --git a/frontend/e2e/fixtures/auth.ts b/frontend/e2e/fixtures/auth.ts new file mode 100644 index 00000000..79fed5ad --- /dev/null +++ b/frontend/e2e/fixtures/auth.ts @@ -0,0 +1,242 @@ +import { Page, BrowserContext } from '@playwright/test'; + +/** + * Authentication fixture for E2E tests + * Handles login flows and session management + */ + +export interface LoginCredentials { + username: string; + password: string; +} + +export interface AuthSession { + token: string; + userId: string; + email: string; + isAdmin: boolean; +} + +/** + * Perform LDAP authentication + */ +export async function loginWithLdap( + page: Page, + credentials: LoginCredentials, + baseUrl: string = 'http://localhost' +): Promise { + await page.goto(`${baseUrl}`); + + // Wait for login form to appear + await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 }); + + // Fill in username + await page.fill('[data-testid="username-input"]', credentials.username); + + // Fill in password + await page.fill('[data-testid="password-input"]', credentials.password); + + // Click login button + await page.click('[data-testid="login-submit"]'); + + // Wait for navigation to inventory page + await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); +} + +/** + * Perform local user authentication + */ +export async function loginWithLocalUser( + page: Page, + credentials: LoginCredentials, + baseUrl: string = 'http://localhost' +): Promise { + await page.goto(`${baseUrl}`); + + // Wait for identity check overlay + await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 }); + + // Click "Local User" tab if it exists + const localUserTab = page.locator('[data-testid="local-user-tab"]'); + if (await localUserTab.isVisible()) { + await localUserTab.click(); + } + + // Fill in username + await page.fill('[data-testid="local-username-input"]', credentials.username); + + // Fill in password + await page.fill('[data-testid="local-password-input"]', credentials.password); + + // Click login button + await page.click('[data-testid="local-login-submit"]'); + + // Wait for navigation to inventory page + await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' }); +} + +/** + * Logout from the application + */ +export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise { + // Click logout button or menu + const logoutButton = page.locator('[data-testid="logout-button"]'); + if (await logoutButton.isVisible()) { + await logoutButton.click(); + } else { + // Try finding it in menu + const menu = page.locator('[data-testid="user-menu"]'); + if (await menu.isVisible()) { + await menu.click(); + await page.click('[data-testid="logout-menu-item"]'); + } + } + + // Wait for redirect to login + await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 }); +} + +/** + * Get authentication token from local storage + */ +export async function getAuthToken(page: Page): Promise { + const token = await page.evaluate(() => { + return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token'); + }); + return token; +} + +/** + * Set authentication token in local storage + */ +export async function setAuthToken(page: Page, token: string): Promise { + await page.evaluate((t) => { + localStorage.setItem('auth_token', t); + }, token); +} + +/** + * Clear authentication data + */ +export async function clearAuth(page: Page): Promise { + await page.evaluate(() => { + localStorage.removeItem('auth_token'); + sessionStorage.removeItem('auth_token'); + localStorage.removeItem('user_data'); + sessionStorage.removeItem('user_data'); + }); +} + +/** + * Check if user is authenticated + */ +export async function isAuthenticated(page: Page): Promise { + const token = await getAuthToken(page); + return !!token; +} + +/** + * Get current user info from page + */ +export async function getCurrentUser(page: Page): Promise<{ username: string; email: string } | null> { + try { + const userData = await page.evaluate(() => { + const stored = localStorage.getItem('user_data') || sessionStorage.getItem('user_data'); + return stored ? JSON.parse(stored) : null; + }); + return userData; + } catch { + return null; + } +} + +/** + * Wait for authentication to complete + */ +export async function waitForAuth(page: Page, timeout: number = 5000): Promise { + // Wait for either: + // 1. Token to be in storage + // 2. Navigation to happen + // 3. Auth overlay to disappear + await page.waitForFunction( + () => { + const token = + typeof window !== 'undefined' + ? localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token') + : null; + return !!token; + }, + { timeout } + ); +} + +/** + * Verify login was successful by checking inventory page loaded + */ +export async function verifyLoginSuccess(page: Page, baseUrl: string = 'http://localhost'): Promise { + try { + // Check if we're on the inventory page + await page.waitForURL(/inventory|dashboard/, { timeout: 5000 }); + return true; + } catch { + return false; + } +} + +/** + * Create test user via API (helper for setup) + */ +export async function createTestUserViaApi( + baseUrl: string, + token: string, + user: { username: string; password: string; email: string } +): Promise<{ id: string; username: string }> { + const response = await fetch(`${baseUrl}/api/admin/users`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify(user), + }); + + if (!response.ok) { + throw new Error(`Failed to create user: ${response.statusText}`); + } + + return response.json(); +} + +/** + * Delete test user via API (helper for cleanup) + */ +export async function deleteTestUserViaApi( + baseUrl: string, + token: string, + userId: string +): Promise { + const response = await fetch(`${baseUrl}/api/admin/users/${userId}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error(`Failed to delete user: ${response.statusText}`); + } +} + +/** + * Helper to setup admin context for tests + */ +export async function setupAdminContext( + context: BrowserContext, + baseUrl: string, + adminToken: string +): Promise { + // Add auth headers to all API requests + await context.addInitScript((token) => { + (window as any).__testAuthToken = token; + }, adminToken); +}