829 lines
30 KiB
TypeScript
829 lines
30 KiB
TypeScript
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 });
|
|
});
|
|
});
|