Added admin panel.
This commit is contained in:
828
frontend/tests/admin-panel.spec.ts
Normal file
828
frontend/tests/admin-panel.spec.ts
Normal file
@ -0,0 +1,828 @@
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
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 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user