import { test, expect, Page } from '@playwright/test'; /** * Admin Panel E2E Tests * * Tests for the comprehensive Admin Panel functionality including: * - Dashboard with stats and settings * - User Management with search, filter, and actions * - Events tab with CRUD operations * - Data Tools for wipe and seed * - Simulation controls * - Audit Log viewing and filtering * * NOTE: These tests require an admin user to be authenticated. * If no admin user exists, tests will verify the access control behavior. */ // Test configuration const BASE_URL = 'http://localhost:5173'; const API_URL = 'http://localhost:8000'; // Test users - try admin first, then regular users const TEST_USERS = [ { email: 'admin@example.com', password: 'admin123', isAdmin: true }, { email: 'testadmin@example.com', password: 'admin123', isAdmin: true }, { email: 'alice@example.com', password: 'password123', isAdmin: false }, ]; // Helper function to perform login and return success status async function loginAsUser(page: Page, email: string, password: string): Promise { try { await page.goto(`${BASE_URL}/login`, { timeout: 10000 }); await page.waitForLoadState('domcontentloaded', { timeout: 10000 }); // Fill in login form using correct selectors from LoginForm.tsx const emailInput = page.locator('#email'); const passwordInput = page.locator('#password'); await emailInput.fill(email); await passwordInput.fill(password); // Submit the form const submitButton = page.locator('button[type="submit"]'); await submitButton.click(); // Wait for navigation or error try { await page.waitForURL(/^(?!.*\/login).*/, { timeout: 8000 }); return true; } catch { // Check for error message const error = page.locator('.bg-error\\/10'); if (await error.isVisible()) { return false; } return false; } } catch (error) { console.log('Login failed:', error); return false; } } // Helper to set up authentication via localStorage (mock auth) // NOTE: Must be called BEFORE navigating to the page async function setupMockAuth(page: Page): Promise { // Create mock auth tokens - must use access_token key (not token) const mockToken = 'mock_admin_token_for_testing'; // Set localStorage before page loads await page.addInitScript((token) => { localStorage.setItem('access_token', token); localStorage.setItem('refresh_token', 'mock_refresh_token'); }, mockToken); } // Helper to mock the auth/me endpoint async function mockAuthMe(page: Page): Promise { await page.route(`${API_URL}/api/v1/auth/me`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 1, email: 'admin@example.com', username: 'admin', display_name: 'Admin User', is_admin: true, }), }); }); } // Helper to navigate to admin page async function navigateToAdmin(page: Page): Promise { await page.goto(`${BASE_URL}/admin`, { timeout: 15000 }); await page.waitForLoadState('domcontentloaded'); } // ============================================================================ // Test Suite: Access Control (No Auth Required) // ============================================================================ test.describe('Admin Panel - Access Control', () => { test('should redirect to login when not authenticated', async ({ page }) => { await page.goto(`${BASE_URL}/admin`); await page.waitForLoadState('networkidle', { timeout: 10000 }); // Should be on login page await expect(page).toHaveURL(/.*login/); await expect(page.locator('h2:has-text("Login to your account")')).toBeVisible(); }); test('should show login form with correct fields', async ({ page }) => { await page.goto(`${BASE_URL}/login`); await page.waitForLoadState('domcontentloaded'); // Verify login form elements await expect(page.locator('#email')).toBeVisible(); await expect(page.locator('#password')).toBeVisible(); await expect(page.locator('button[type="submit"]:has-text("Login")')).toBeVisible(); }); test('should show error for invalid credentials', async ({ page }) => { await page.goto(`${BASE_URL}/login`); await page.locator('#email').fill('invalid@example.com'); await page.locator('#password').fill('wrongpassword'); await page.locator('button[type="submit"]').click(); // Wait for error message await expect(page.locator('.bg-error\\/10')).toBeVisible({ timeout: 5000 }); }); }); // ============================================================================ // Test Suite: Admin Panel UI Components (With Mocked Auth) // ============================================================================ test.describe('Admin Panel - UI Structure (Mocked Auth)', () => { test.beforeEach(async ({ page }) => { // Setup mock authentication - localStorage await setupMockAuth(page); // Mock auth/me endpoint await mockAuthMe(page); // Mock the admin API responses await page.route(`${API_URL}/api/v1/admin/dashboard`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ total_users: 25, active_users: 20, suspended_users: 2, admin_users: 3, total_events: 10, upcoming_events: 5, live_events: 2, total_bets: 50, open_bets: 15, matched_bets: 20, total_volume: 5000, escrow_locked: 1000, simulation_running: false, }), }); }); await page.route(`${API_URL}/api/v1/admin/settings`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 1, default_house_commission_percent: 2.5, default_min_bet_amount: 10, default_max_bet_amount: 1000, default_min_spread: -20, default_max_spread: 20, spread_increment: 0.5, platform_name: 'H2H', maintenance_mode: false, }), }); }); }); test('should display Admin Panel header', async ({ page }) => { await navigateToAdmin(page); await expect(page.locator('h1:has-text("Admin Panel")')).toBeVisible({ timeout: 10000 }); await expect(page.locator('text=Manage the H2H platform')).toBeVisible(); }); test('should display all navigation tabs', async ({ page }) => { await navigateToAdmin(page); const expectedTabs = ['Dashboard', 'Users', 'Events', 'Data Tools', 'Simulation', 'Audit Log']; for (const tabName of expectedTabs) { await expect(page.locator(`button:has-text("${tabName}")`)).toBeVisible({ timeout: 5000 }); } }); test('should display dashboard stats', async ({ page }) => { await navigateToAdmin(page); await page.waitForLoadState('networkidle', { timeout: 10000 }); // Check stat cards await expect(page.locator('text=Total Users')).toBeVisible({ timeout: 5000 }); await expect(page.locator('text=Total Events')).toBeVisible(); await expect(page.locator('text=Total Bets')).toBeVisible(); await expect(page.locator('text=Total Volume')).toBeVisible(); }); test('should display Platform Settings when loaded', async ({ page }) => { await navigateToAdmin(page); await page.waitForLoadState('networkidle', { timeout: 10000 }); await expect(page.locator('h2:has-text("Platform Settings")')).toBeVisible({ timeout: 5000 }); await expect(page.locator('text=House Commission')).toBeVisible(); }); }); // ============================================================================ // Test Suite: Users Tab (With Mocked Auth) // ============================================================================ test.describe('Admin Panel - Users Tab (Mocked Auth)', () => { test.beforeEach(async ({ page }) => { // Setup mock authentication - localStorage await setupMockAuth(page); // Mock auth/me endpoint await mockAuthMe(page); // Mock admin API responses await page.route(`${API_URL}/api/v1/admin/dashboard`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ total_users: 10, simulation_running: false }), }); }); await page.route(`${API_URL}/api/v1/admin/settings`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 1, default_house_commission_percent: 2.5 }), }); }); await page.route(`${API_URL}/api/v1/admin/users**`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ users: [ { id: 1, email: 'alice@example.com', username: 'alice', status: 'active', is_admin: false, balance: 1000, escrow: 0, wins: 5, losses: 3, win_rate: 0.625, created_at: '2024-01-01T00:00:00Z', }, { id: 2, email: 'bob@example.com', username: 'bob', status: 'active', is_admin: false, balance: 500, escrow: 100, wins: 2, losses: 4, win_rate: 0.333, created_at: '2024-01-02T00:00:00Z', }, ], total: 2, page: 1, page_size: 20, }), }); }); await navigateToAdmin(page); await page.locator('button:has-text("Users")').click(); await page.waitForLoadState('networkidle', { timeout: 10000 }); }); test('should display search input', async ({ page }) => { await expect(page.locator('input[placeholder*="Search"]')).toBeVisible({ timeout: 5000 }); }); test('should display status filter dropdown', async ({ page }) => { const statusFilter = page.locator('select').filter({ hasText: /All Status/ }); await expect(statusFilter).toBeVisible(); }); test('should display users table headers', async ({ page }) => { const headers = ['User', 'Status', 'Balance', 'Stats', 'Joined', 'Actions']; for (const header of headers) { await expect(page.locator(`th:has-text("${header}")`)).toBeVisible({ timeout: 5000 }); } }); test('should display user data in table', async ({ page }) => { await expect(page.locator('text=alice')).toBeVisible({ timeout: 5000 }); await expect(page.locator('text=bob')).toBeVisible(); }); test('should allow typing in search input', async ({ page }) => { const searchInput = page.locator('input[placeholder*="Search"]'); await searchInput.fill('test@example.com'); await expect(searchInput).toHaveValue('test@example.com'); }); test('should change status filter', async ({ page }) => { const statusFilter = page.locator('select').filter({ hasText: /All Status/ }); await statusFilter.selectOption('active'); await expect(statusFilter).toHaveValue('active'); }); }); // ============================================================================ // Test Suite: Events Tab (With Mocked Auth) // ============================================================================ test.describe('Admin Panel - Events Tab (Mocked Auth)', () => { test.beforeEach(async ({ page }) => { // Setup mock authentication - localStorage await setupMockAuth(page); // Mock auth/me endpoint await mockAuthMe(page); await page.route(`${API_URL}/api/v1/admin/dashboard`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ total_users: 10, simulation_running: false }), }); }); await page.route(`${API_URL}/api/v1/admin/settings`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 1, default_house_commission_percent: 2.5 }), }); }); await page.route(`${API_URL}/api/v1/admin/events**`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: 1, sport: 'football', home_team: 'Team A', away_team: 'Team B', official_spread: -3.5, game_time: '2024-02-01T19:00:00Z', venue: 'Stadium', league: 'NFL', status: 'upcoming', }, ]), }); }); await navigateToAdmin(page); await page.locator('button:has-text("Events")').click(); await page.waitForLoadState('networkidle', { timeout: 10000 }); }); test('should display Create Event button', async ({ page }) => { await expect(page.locator('button:has-text("Create Event")')).toBeVisible({ timeout: 5000 }); }); test('should display All Events section', async ({ page }) => { await expect(page.locator('h2:has-text("All Events")')).toBeVisible({ timeout: 5000 }); }); test('should open create event form', async ({ page }) => { await page.locator('button:has-text("Create Event")').click(); await expect(page.locator('h2:has-text("Create New Event")')).toBeVisible({ timeout: 5000 }); }); test('should display form fields when creating event', async ({ page }) => { await page.locator('button:has-text("Create Event")').click(); await expect(page.locator('label:has-text("Home Team")')).toBeVisible(); await expect(page.locator('label:has-text("Away Team")')).toBeVisible(); await expect(page.locator('label:has-text("Official Spread")')).toBeVisible(); await expect(page.locator('label:has-text("Game Time")')).toBeVisible(); }); test('should close form when clicking Cancel', async ({ page }) => { await page.locator('button:has-text("Create Event")').click(); await expect(page.locator('h2:has-text("Create New Event")')).toBeVisible(); await page.locator('button:has-text("Cancel")').first().click(); await expect(page.locator('h2:has-text("Create New Event")')).not.toBeVisible(); }); test('should display event in list', async ({ page }) => { await expect(page.locator('text=Team A vs Team B')).toBeVisible({ timeout: 5000 }); }); }); // ============================================================================ // Test Suite: Data Tools Tab (With Mocked Auth) // ============================================================================ test.describe('Admin Panel - Data Tools Tab (Mocked Auth)', () => { test.beforeEach(async ({ page }) => { // Setup mock authentication - localStorage await setupMockAuth(page); // Mock auth/me endpoint await mockAuthMe(page); await page.route(`${API_URL}/api/v1/admin/dashboard`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ total_users: 10, simulation_running: false }), }); }); await page.route(`${API_URL}/api/v1/admin/settings`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 1, default_house_commission_percent: 2.5 }), }); }); await page.route(`${API_URL}/api/v1/admin/data/wipe/preview`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ users_count: 10, spread_bets_count: 25, events_count: 5, transactions_count: 50, can_wipe: true, cooldown_remaining_seconds: 0, }), }); }); await navigateToAdmin(page); await page.locator('button:has-text("Data Tools")').click(); await page.waitForLoadState('networkidle', { timeout: 10000 }); }); test('should display Data Wiper section', async ({ page }) => { await expect(page.locator('h2:has-text("Data Wiper")')).toBeVisible({ timeout: 5000 }); }); test('should display Danger Zone warning', async ({ page }) => { await expect(page.locator('text=Danger Zone')).toBeVisible(); }); test('should display wipe preview counts', async ({ page }) => { await expect(page.locator('text=Users')).toBeVisible(); await expect(page.locator('text=Spread Bets')).toBeVisible(); await expect(page.locator('text=Events')).toBeVisible(); await expect(page.locator('text=Transactions')).toBeVisible(); }); test('should display Data Seeder section', async ({ page }) => { await expect(page.locator('h2:has-text("Data Seeder")')).toBeVisible({ timeout: 5000 }); }); test('should display seed configuration inputs', async ({ page }) => { await expect(page.locator('label:has-text("Number of Users")')).toBeVisible(); await expect(page.locator('label:has-text("Number of Events")')).toBeVisible(); await expect(page.locator('label:has-text("Bets per Event")')).toBeVisible(); await expect(page.locator('label:has-text("Starting Balance")')).toBeVisible(); }); test('should have Wipe Database button', async ({ page }) => { await expect(page.locator('button:has-text("Wipe Database")')).toBeVisible(); }); test('should have Seed Database button', async ({ page }) => { await expect(page.locator('button:has-text("Seed Database")')).toBeVisible(); }); test('should open wipe confirmation modal', async ({ page }) => { await page.locator('button:has-text("Wipe Database")').click(); await expect(page.locator('h3:has-text("Confirm Database Wipe")')).toBeVisible({ timeout: 5000 }); }); test('should require confirmation phrase in wipe modal', async ({ page }) => { await page.locator('button:has-text("Wipe Database")').click(); await expect(page.locator('input[placeholder="CONFIRM WIPE"]')).toBeVisible(); // Wipe Data button should be disabled const wipeDataButton = page.locator('button:has-text("Wipe Data")'); await expect(wipeDataButton).toBeDisabled(); }); test('should close wipe modal when clicking Cancel', async ({ page }) => { await page.locator('button:has-text("Wipe Database")').click(); await expect(page.locator('h3:has-text("Confirm Database Wipe")')).toBeVisible(); await page.locator('.fixed button:has-text("Cancel")').click(); await expect(page.locator('h3:has-text("Confirm Database Wipe")')).not.toBeVisible(); }); }); // ============================================================================ // Test Suite: Simulation Tab (With Mocked Auth) // ============================================================================ test.describe('Admin Panel - Simulation Tab (Mocked Auth)', () => { test.beforeEach(async ({ page }) => { // Setup mock authentication - localStorage await setupMockAuth(page); // Mock auth/me endpoint await mockAuthMe(page); await page.route(`${API_URL}/api/v1/admin/dashboard`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ total_users: 10, simulation_running: false }), }); }); await page.route(`${API_URL}/api/v1/admin/settings`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 1, default_house_commission_percent: 2.5 }), }); }); await page.route(`${API_URL}/api/v1/admin/simulation/status`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ is_running: false, started_by: null, started_at: null, iterations_completed: 0, last_activity: null, config: { delay_seconds: 2.0, actions_per_iteration: 3, create_users: true, create_bets: true, take_bets: true, add_comments: true, cancel_bets: true, }, }), }); }); await navigateToAdmin(page); await page.locator('button:has-text("Simulation")').click(); await page.waitForLoadState('networkidle', { timeout: 10000 }); }); test('should display Activity Simulation header', async ({ page }) => { await expect(page.locator('h2:has-text("Activity Simulation")')).toBeVisible({ timeout: 5000 }); }); test('should display simulation status badge (Stopped)', async ({ page }) => { await expect(page.locator('span:has-text("Stopped")')).toBeVisible(); }); test('should display Start Simulation button when stopped', async ({ page }) => { await expect(page.locator('button:has-text("Start Simulation")')).toBeVisible(); }); test('should display Configure button when stopped', async ({ page }) => { await expect(page.locator('button:has-text("Configure")')).toBeVisible(); }); test('should show configuration panel when clicking Configure', async ({ page }) => { await page.locator('button:has-text("Configure")').click(); await expect(page.locator('h3:has-text("Simulation Configuration")')).toBeVisible({ timeout: 5000 }); }); test('should display configuration options', async ({ page }) => { await page.locator('button:has-text("Configure")').click(); await expect(page.locator('label:has-text("Delay")')).toBeVisible(); await expect(page.locator('label:has-text("Actions per Iteration")')).toBeVisible(); await expect(page.locator('text=Create Users')).toBeVisible(); await expect(page.locator('text=Create Bets')).toBeVisible(); }); }); // ============================================================================ // Test Suite: Audit Log Tab (With Mocked Auth) // ============================================================================ test.describe('Admin Panel - Audit Log Tab (Mocked Auth)', () => { test.beforeEach(async ({ page }) => { // Setup mock authentication - localStorage await setupMockAuth(page); // Mock auth/me endpoint await mockAuthMe(page); await page.route(`${API_URL}/api/v1/admin/dashboard`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ total_users: 10, simulation_running: false }), }); }); await page.route(`${API_URL}/api/v1/admin/settings`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 1, default_house_commission_percent: 2.5 }), }); }); await page.route(`${API_URL}/api/v1/admin/audit-logs**`, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ logs: [ { id: 1, action: 'DATA_SEED', description: 'Seeded database with test data', admin_username: 'admin', target_type: null, target_id: null, details: '{"num_users": 10}', ip_address: '127.0.0.1', created_at: '2024-01-15T10:30:00Z', }, { id: 2, action: 'USER_STATUS_CHANGE', description: 'Changed user status', admin_username: 'admin', target_type: 'user', target_id: 5, details: '{"old_status": "active", "new_status": "suspended"}', ip_address: '127.0.0.1', created_at: '2024-01-14T15:00:00Z', }, ], total: 2, page: 1, page_size: 25, }), }); }); await navigateToAdmin(page); await page.locator('button:has-text("Audit Log")').click(); await page.waitForLoadState('networkidle', { timeout: 10000 }); }); test('should display Audit Log header', async ({ page }) => { await expect(page.locator('h2:has-text("Audit Log")')).toBeVisible({ timeout: 5000 }); }); test('should display action filter dropdown', async ({ page }) => { const filterDropdown = page.locator('select').filter({ hasText: /All Actions/ }); await expect(filterDropdown).toBeVisible(); }); test('should display log entries', async ({ page }) => { await expect(page.locator('text=Seeded database with test data')).toBeVisible({ timeout: 5000 }); }); test('should display log action badges', async ({ page }) => { await expect(page.locator('text=Data Seed')).toBeVisible(); }); test('should change filter selection', async ({ page }) => { const filterDropdown = page.locator('select').filter({ hasText: /All Actions/ }); await filterDropdown.selectOption('DATA_WIPE'); await expect(filterDropdown).toHaveValue('DATA_WIPE'); }); }); // ============================================================================ // Test Suite: Tab Navigation (With Mocked Auth) // ============================================================================ test.describe('Admin Panel - Tab Navigation (Mocked Auth)', () => { test.beforeEach(async ({ page }) => { // Setup mock authentication - localStorage await setupMockAuth(page); // Mock auth/me endpoint await mockAuthMe(page); // Mock all admin API endpoints await page.route(`${API_URL}/api/v1/admin/**`, async (route) => { const url = route.request().url(); if (url.includes('dashboard')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ total_users: 10, simulation_running: false }), }); } else if (url.includes('settings')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 1, default_house_commission_percent: 2.5 }), }); } else if (url.includes('users')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ users: [], total: 0, page: 1, page_size: 20 }), }); } else if (url.includes('events')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([]), }); } else if (url.includes('wipe/preview')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ users_count: 0, spread_bets_count: 0, events_count: 0, transactions_count: 0, can_wipe: true }), }); } else if (url.includes('simulation/status')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ is_running: false, config: {} }), }); } else if (url.includes('audit-logs')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ logs: [], total: 0, page: 1, page_size: 25 }), }); } else { await route.continue(); } }); await navigateToAdmin(page); }); test('should switch between all tabs', async ({ page }) => { const tabs = [ { name: 'Users', indicator: 'Search' }, { name: 'Events', indicator: 'Create Event' }, { name: 'Data Tools', indicator: 'Data Wiper' }, { name: 'Simulation', indicator: 'Activity Simulation' }, { name: 'Audit Log', indicator: 'Audit Log' }, { name: 'Dashboard', indicator: 'Total Users' }, ]; for (const tab of tabs) { await page.locator(`button:has-text("${tab.name}")`).click(); await page.waitForLoadState('networkidle', { timeout: 5000 }); const indicator = page.locator(`text=${tab.indicator}`).first(); await expect(indicator).toBeVisible({ timeout: 5000 }); } }); test('should highlight active tab', async ({ page }) => { // Click Users tab and verify it's highlighted await page.locator('button:has-text("Users")').click(); const usersTab = page.locator('button:has-text("Users")'); await expect(usersTab).toHaveClass(/text-blue-600|border-blue-600|bg-blue-50/); }); }); // ============================================================================ // Test Suite: Responsive Behavior // ============================================================================ test.describe('Admin Panel - Responsive Behavior', () => { test.beforeEach(async ({ page }) => { // Setup mock authentication - localStorage await setupMockAuth(page); // Mock auth/me endpoint await mockAuthMe(page); await page.route(`${API_URL}/api/v1/admin/**`, async (route) => { const url = route.request().url(); if (url.includes('dashboard')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ total_users: 10, simulation_running: false }), }); } else if (url.includes('settings')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 1, default_house_commission_percent: 2.5 }), }); } else { await route.continue(); } }); }); test('should render on tablet viewport', async ({ page }) => { await page.setViewportSize({ width: 768, height: 1024 }); await navigateToAdmin(page); await expect(page.locator('h1:has-text("Admin Panel")')).toBeVisible({ timeout: 10000 }); }); test('should render on mobile viewport', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await navigateToAdmin(page); await expect(page.locator('h1:has-text("Admin Panel")')).toBeVisible({ timeout: 10000 }); }); });