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:
426
reactrebuild0825/tests/auth-bulletproof.spec.ts
Normal file
426
reactrebuild0825/tests/auth-bulletproof.spec.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user