feat: add advanced analytics and territory management system

- Add comprehensive analytics components with export functionality
- Implement territory management with manager performance tracking
- Add seatmap components for venue layout management
- Create customer management features with modal interface
- Add advanced hooks for dashboard flags and territory data
- Implement seat selection and venue management utilities
- Add type definitions for ticketing and seatmap systems

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-26 09:25:10 -06:00
parent d5c3953888
commit aa81eb5adb
438 changed files with 90509 additions and 2787 deletions

View File

@@ -0,0 +1,426 @@
import { test, expect, Page } from '@playwright/test';
// Test configuration
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:5175';
const PERFORMANCE_THRESHOLDS = {
LOGIN_MAX_TIME: 3000, // 3 seconds max for login
LOGOUT_MAX_TIME: 1000, // 1 second max for logout
NAVIGATION_MAX_TIME: 2000, // 2 seconds max for route navigation
AUTH_CHECK_MAX_TIME: 1000, // 1 second max for auth check
};
// Test users with different roles
const TEST_USERS = {
admin: { email: 'admin@example.com', role: 'orgAdmin' },
superadmin: { email: 'superadmin@example.com', role: 'superadmin' },
staff: { email: 'staff@example.com', role: 'staff' },
territory: { email: 'territory@example.com', role: 'territoryManager' },
};
// Helper function to login with performance tracking
async function performLogin(page: Page, email: string, password: string = 'password123') {
const startTime = Date.now();
await page.goto(`${BASE_URL}/login`);
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', password);
const loginPromise = page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME });
await page.click('[data-testid="loginBtn"]');
await loginPromise;
const loginTime = Date.now() - startTime;
console.log(`Login completed in ${loginTime}ms`);
expect(loginTime).toBeLessThan(PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME);
return loginTime;
}
// Helper function to logout with performance tracking
async function performLogout(page: Page) {
const startTime = Date.now();
// Look for logout button in header or sidebar
await page.click('button:has-text("Sign out"), button:has-text("Logout")');
await page.waitForURL('**/login', { timeout: PERFORMANCE_THRESHOLDS.LOGOUT_MAX_TIME });
const logoutTime = Date.now() - startTime;
console.log(`Logout completed in ${logoutTime}ms`);
expect(logoutTime).toBeLessThan(PERFORMANCE_THRESHOLDS.LOGOUT_MAX_TIME);
return logoutTime;
}
test.describe('Bulletproof Authentication System', () => {
test.beforeEach(async ({ page }) => {
// Clear any existing auth state
await page.goto(BASE_URL);
await page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
});
test.describe('Core Authentication Flow', () => {
test('should login successfully without infinite loops', async ({ page }) => {
await page.goto(`${BASE_URL}/login`);
// Verify login page loads
await expect(page.locator('h1')).toContainText('Sign in to Black Canyon Tickets');
await expect(page.locator('[data-testid="loginBtn"]')).toBeVisible();
// Test admin login
const loginTime = await performLogin(page, TEST_USERS.admin.email);
// Verify successful login
await expect(page.locator('body')).not.toContainText('Loading...');
expect(page.url()).toMatch(/\/(dashboard|events)/);
// Take screenshot for documentation
await page.screenshot({ path: 'tests/screenshots/auth-login-success.png' });
});
test('should handle login errors gracefully', async ({ page }) => {
await page.goto(`${BASE_URL}/login`);
// Test invalid email
await page.fill('input[type="email"]', 'invalid@example.com');
await page.fill('input[type="password"]', 'password123');
await page.click('[data-testid="loginBtn"]');
// Should show error message
await expect(page.locator('.bg-red-500')).toContainText('Invalid email address');
// Test short password
await page.fill('input[type="email"]', TEST_USERS.admin.email);
await page.fill('input[type="password"]', 'ab');
await page.click('[data-testid="loginBtn"]');
await expect(page.locator('.bg-red-500')).toContainText('Password must be at least 3 characters');
await page.screenshot({ path: 'tests/screenshots/auth-login-errors.png' });
});
test('should complete full login/logout cycle', async ({ page }) => {
// Login
await performLogin(page, TEST_USERS.admin.email);
// Verify authenticated state
expect(page.url()).not.toContain('/login');
// Logout
await performLogout(page);
// Verify logged out state
await expect(page.locator('h1')).toContainText('Sign in to Black Canyon Tickets');
expect(page.url()).toContain('/login');
});
test('should redirect with returnTo parameter', async ({ page }) => {
// Try to access protected route
await page.goto(`${BASE_URL}/settings`);
// Should redirect to login with returnTo
await page.waitForURL('**/login*returnTo*', { timeout: 5000 });
expect(page.url()).toContain('returnTo=%2Fsettings');
// Login and verify redirect back to settings
await performLogin(page, TEST_USERS.admin.email);
expect(page.url()).toContain('/settings');
});
});
test.describe('Quick Login Functionality', () => {
test('should provide quick login buttons for all roles', async ({ page }) => {
await page.goto(`${BASE_URL}/login`);
// Verify all quick login buttons are present
await expect(page.locator('button:has-text("Admin")')).toBeVisible();
await expect(page.locator('button:has-text("Super Admin")')).toBeVisible();
await expect(page.locator('button:has-text("Staff")')).toBeVisible();
await expect(page.locator('button:has-text("Territory")')).toBeVisible();
// Test quick login for admin
await page.click('button:has-text("Admin")');
// Verify form is filled
await expect(page.locator('input[type="email"]')).toHaveValue('admin@example.com');
await expect(page.locator('input[type="password"]')).toHaveValue('password123');
// Complete login
await page.click('[data-testid="loginBtn"]');
await page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME });
await page.screenshot({ path: 'tests/screenshots/auth-quick-login.png' });
});
test('should test all user roles via quick login', async ({ page }) => {
for (const [roleKey, userData] of Object.entries(TEST_USERS)) {
await page.goto(`${BASE_URL}/login`);
// Click appropriate quick login button
const buttonText = roleKey === 'territory' ? 'Territory' :
roleKey === 'superadmin' ? 'Super Admin' :
roleKey.charAt(0).toUpperCase() + roleKey.slice(1);
await page.click(`button:has-text("${buttonText}")`);
await page.click('[data-testid="loginBtn"]');
// Verify login success
await page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME });
await page.screenshot({ path: `tests/screenshots/auth-role-${roleKey}.png` });
// Logout for next test
await performLogout(page);
}
});
});
test.describe('Session Persistence', () => {
test('should persist session across page reloads', async ({ page }) => {
// Login
await performLogin(page, TEST_USERS.admin.email);
// Reload page
const startTime = Date.now();
await page.reload();
await page.waitForLoadState('networkidle');
const reloadTime = Date.now() - startTime;
// Should still be authenticated
expect(page.url()).not.toContain('/login');
expect(reloadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.AUTH_CHECK_MAX_TIME);
console.log(`Session restore completed in ${reloadTime}ms`);
});
test('should persist session across browser navigation', async ({ page }) => {
// Login
await performLogin(page, TEST_USERS.admin.email);
// Navigate away and back
await page.goto('https://example.com');
await page.goto(`${BASE_URL}/dashboard`);
// Should still be authenticated
expect(page.url()).not.toContain('/login');
await expect(page.locator('body')).not.toContainText('Loading...');
});
test('should handle corrupted localStorage gracefully', async ({ page }) => {
await page.goto(BASE_URL);
// Inject corrupted auth data
await page.evaluate(() => {
localStorage.setItem('bct_auth_user', 'invalid-json');
});
// Refresh and should redirect to login
await page.reload();
await page.waitForURL('**/login', { timeout: 5000 });
// Should not show any errors
await expect(page.locator('.bg-red-500')).toHaveCount(0);
});
});
test.describe('Role-Based Access Control', () => {
const roleRouteTests = [
{ role: 'staff', allowedRoutes: ['/dashboard', '/events', '/tickets'], deniedRoutes: ['/admin'] },
{ role: 'territory', allowedRoutes: ['/dashboard', '/events', '/scan'], deniedRoutes: ['/admin'] },
{ role: 'admin', allowedRoutes: ['/dashboard', '/events', '/tickets', '/analytics'], deniedRoutes: ['/admin'] },
{ role: 'superadmin', allowedRoutes: ['/dashboard', '/events', '/admin'], deniedRoutes: [] }
];
roleRouteTests.forEach(({ role, allowedRoutes, deniedRoutes }) => {
test(`should enforce ${role} role permissions correctly`, async ({ page }) => {
const userData = TEST_USERS[role as keyof typeof TEST_USERS];
await performLogin(page, userData.email);
// Test allowed routes
for (const route of allowedRoutes) {
await page.goto(`${BASE_URL}${route}`);
await page.waitForLoadState('networkidle');
// Should not redirect to login or show access denied
expect(page.url()).not.toContain('/login');
await expect(page.locator('body')).not.toContainText('Access Denied');
}
// Test denied routes
for (const route of deniedRoutes) {
await page.goto(`${BASE_URL}${route}`);
await page.waitForLoadState('networkidle');
// Should show access denied or redirect
const hasAccessDenied = await page.locator('body').textContent();
expect(hasAccessDenied).toMatch(/(Access Denied|Sign in to)/);
}
await page.screenshot({ path: `tests/screenshots/auth-rbac-${role}.png` });
});
});
});
test.describe('Performance Benchmarks', () => {
test('should meet all performance thresholds', async ({ page }) => {
const metrics = {
loginTime: 0,
logoutTime: 0,
navigationTime: 0,
authCheckTime: 0
};
// Measure login performance
metrics.loginTime = await performLogin(page, TEST_USERS.admin.email);
// Measure navigation performance
const navStart = Date.now();
await page.goto(`${BASE_URL}/settings`);
await page.waitForLoadState('networkidle');
metrics.navigationTime = Date.now() - navStart;
// Measure auth check performance
const authStart = Date.now();
await page.reload();
await page.waitForLoadState('networkidle');
metrics.authCheckTime = Date.now() - authStart;
// Measure logout performance
metrics.logoutTime = await performLogout(page);
// Log all metrics
console.log('Performance Metrics:', metrics);
// Validate thresholds
expect(metrics.loginTime).toBeLessThan(PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME);
expect(metrics.logoutTime).toBeLessThan(PERFORMANCE_THRESHOLDS.LOGOUT_MAX_TIME);
expect(metrics.navigationTime).toBeLessThan(PERFORMANCE_THRESHOLDS.NAVIGATION_MAX_TIME);
expect(metrics.authCheckTime).toBeLessThan(PERFORMANCE_THRESHOLDS.AUTH_CHECK_MAX_TIME);
});
test('should handle rapid login attempts', async ({ page }) => {
await page.goto(`${BASE_URL}/login`);
// Rapid clicks should not cause issues
await page.fill('input[type="email"]', TEST_USERS.admin.email);
await page.fill('input[type="password"]', 'password123');
// Click login button multiple times quickly
await Promise.all([
page.click('[data-testid="loginBtn"]'),
page.click('[data-testid="loginBtn"]'),
page.click('[data-testid="loginBtn"]')
]);
// Should still login successfully once
await page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME });
expect(page.url()).not.toContain('/login');
});
});
test.describe('Timeout and Failsafe Testing', () => {
test('should prevent infinite loading with timeout failsafe', async ({ page }) => {
await page.goto(BASE_URL);
// Simulate slow auth check by delaying localStorage
await page.addInitScript(() => {
const originalGetItem = localStorage.getItem;
localStorage.getItem = (key) => {
if (key === 'bct_auth_user') {
// Delay to test timeout
return new Promise(resolve => setTimeout(() => resolve(originalGetItem.call(localStorage, key)), 3000));
}
return originalGetItem.call(localStorage, key);
};
});
// Should not hang indefinitely - timeout should kick in
await page.waitForTimeout(3000);
// Should redirect to login after timeout
expect(page.url()).toContain('/login');
await expect(page.locator('h1')).toContainText('Sign in to Black Canyon Tickets');
});
test('should handle network errors gracefully', async ({ page }) => {
// Simulate offline mode
await page.context().setOffline(true);
await page.goto(`${BASE_URL}/login`);
// Fill form and attempt login
await page.fill('input[type="email"]', TEST_USERS.admin.email);
await page.fill('input[type="password"]', 'password123');
await page.click('[data-testid="loginBtn"]');
// Since we're using mock auth, this should still work
await page.context().setOffline(false);
await page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME });
});
});
test.describe('Edge Cases and Security', () => {
test('should prevent authentication bypass attempts', async ({ page }) => {
await page.goto(BASE_URL);
// Try to inject fake user data
await page.evaluate(() => {
localStorage.setItem('bct_auth_user', JSON.stringify({
uid: 'hacker',
email: 'hacker@evil.com',
role: 'superadmin',
orgId: 'fake_org'
}));
});
// Navigate to protected route
await page.goto(`${BASE_URL}/admin`);
// Should redirect to login (fake user not in MOCK_USERS)
await page.waitForURL('**/login', { timeout: 5000 });
});
test('should handle malformed session data', async ({ page }) => {
await page.goto(BASE_URL);
// Set various malformed data
const malformedData = ['invalid', '{}', '{"incomplete":true}', 'null', 'undefined'];
for (const data of malformedData) {
await page.evaluate((data) => {
localStorage.setItem('bct_auth_user', data);
}, data);
await page.reload();
// Should gracefully handle and redirect to login
await page.waitForURL('**/login', { timeout: 5000 });
await expect(page.locator('h1')).toContainText('Sign in to Black Canyon Tickets');
}
});
test('should maintain security during concurrent operations', async ({ page }) => {
// Login normally
await performLogin(page, TEST_USERS.admin.email);
// Try to manipulate session during navigation
await Promise.all([
page.goto(`${BASE_URL}/settings`),
page.evaluate(() => {
localStorage.setItem('bct_auth_user', JSON.stringify({
uid: 'different-user',
email: 'different@example.com',
role: 'staff',
orgId: 'different_org'
}));
})
]);
// Should handle gracefully
await page.waitForLoadState('networkidle');
expect(page.url()).not.toContain('different');
});
});
});