343 lines
13 KiB
TypeScript
343 lines
13 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { setWebsiteAuthContext } from './websiteAuth';
|
|
|
|
/**
|
|
* Website AuthGuard Component Tests
|
|
*
|
|
* These tests verify the AuthGuard component behavior:
|
|
* - Loading states during session verification
|
|
* - Redirect behavior for unauthorized access
|
|
* - Role-based access control
|
|
* - Component rendering with different auth states
|
|
*/
|
|
|
|
function getWebsiteBaseUrl(): string {
|
|
const configured = process.env.WEBSITE_BASE_URL ?? process.env.PLAYWRIGHT_BASE_URL;
|
|
if (configured && configured.trim()) {
|
|
return configured.trim().replace(/\/$/, '');
|
|
}
|
|
return 'http://localhost:3100';
|
|
}
|
|
|
|
test.describe('AuthGuard Component - Loading States', () => {
|
|
test('shows loading state during session verification', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
// Monitor for loading indicators
|
|
page.on('request', async (req) => {
|
|
if (req.url().includes('/auth/session')) {
|
|
// Check if loading indicator is visible during session fetch
|
|
await page.locator('text=/Verifying authentication|Loading/').isVisible().catch(() => false);
|
|
}
|
|
});
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should eventually show dashboard content
|
|
await expect(page.locator('body')).toBeVisible();
|
|
expect(page.url()).toContain('/dashboard');
|
|
});
|
|
|
|
test('handles rapid auth state changes', async ({ page, context }) => {
|
|
// Start unauthenticated
|
|
await setWebsiteAuthContext(context, 'public');
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
expect(new URL(page.url()).pathname).toBe('/auth/login');
|
|
|
|
// Quickly switch to authenticated
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
expect(new URL(page.url()).pathname).toBe('/dashboard');
|
|
|
|
// Quickly switch back
|
|
await setWebsiteAuthContext(context, 'public');
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
expect(new URL(page.url()).pathname).toBe('/auth/login');
|
|
});
|
|
|
|
test('handles session fetch failures gracefully', async ({ page, context }) => {
|
|
// Clear cookies to simulate session fetch returning null
|
|
await context.clearCookies();
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should redirect to login
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
});
|
|
});
|
|
|
|
test.describe('AuthGuard Component - Redirect Behavior', () => {
|
|
test('redirects to login with returnTo parameter', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const protectedRoutes = [
|
|
'/dashboard',
|
|
'/profile',
|
|
'/leagues/league-1/settings',
|
|
'/sponsor/dashboard',
|
|
];
|
|
|
|
for (const route of protectedRoutes) {
|
|
await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
expect(currentUrl.searchParams.get('returnTo')).toBe(route);
|
|
}
|
|
});
|
|
|
|
test('redirects back to protected route after login', async ({ page, context }) => {
|
|
const targetRoute = '/leagues/league-1/settings';
|
|
|
|
// Start unauthenticated, try to access protected route
|
|
await setWebsiteAuthContext(context, 'public');
|
|
await page.goto(`${getWebsiteBaseUrl()}${targetRoute}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Verify redirect to login
|
|
let currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
expect(currentUrl.searchParams.get('returnTo')).toBe(targetRoute);
|
|
|
|
// Simulate login by switching auth context
|
|
await setWebsiteAuthContext(context, 'admin');
|
|
await page.goto(`${getWebsiteBaseUrl()}${targetRoute}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should be on target route
|
|
currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe(targetRoute);
|
|
});
|
|
|
|
test('handles auth routes when authenticated', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
// Try to access login page while authenticated
|
|
await page.goto(`${getWebsiteBaseUrl()}/auth/login`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should redirect to dashboard
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/dashboard');
|
|
});
|
|
|
|
test('sponsor auth routes redirect to sponsor dashboard', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'sponsor');
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/auth/login`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/sponsor/dashboard');
|
|
});
|
|
});
|
|
|
|
test.describe('AuthGuard Component - Role-Based Access', () => {
|
|
test('admin routes allow admin users', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'admin');
|
|
|
|
const adminRoutes = ['/admin', '/admin/users'];
|
|
|
|
for (const route of adminRoutes) {
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).toBe(200);
|
|
expect(page.url()).toContain(route);
|
|
}
|
|
});
|
|
|
|
test('admin routes block non-admin users', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
const adminRoutes = ['/admin', '/admin/users'];
|
|
|
|
for (const route of adminRoutes) {
|
|
await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
}
|
|
});
|
|
|
|
test('sponsor routes allow sponsor users', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'sponsor');
|
|
|
|
const sponsorRoutes = ['/sponsor', '/sponsor/dashboard', '/sponsor/settings'];
|
|
|
|
for (const route of sponsorRoutes) {
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).toBe(200);
|
|
expect(page.url()).toContain(route);
|
|
}
|
|
});
|
|
|
|
test('sponsor routes block non-sponsor users', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
const sponsorRoutes = ['/sponsor', '/sponsor/dashboard', '/sponsor/settings'];
|
|
|
|
for (const route of sponsorRoutes) {
|
|
await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
}
|
|
});
|
|
|
|
test('league admin routes require league admin role', async ({ page, context }) => {
|
|
// Test as regular driver
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
await page.goto(`${getWebsiteBaseUrl()}/leagues/league-1/settings`, { waitUntil: 'domcontentloaded' });
|
|
|
|
let currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
|
|
// Test as admin (has access to league admin routes)
|
|
await setWebsiteAuthContext(context, 'admin');
|
|
await page.goto(`${getWebsiteBaseUrl()}/leagues/league-1/settings`, { waitUntil: 'domcontentloaded' });
|
|
|
|
currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/leagues/league-1/settings');
|
|
});
|
|
|
|
test('authenticated users can access auth-required routes', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
const authRoutes = ['/dashboard', '/profile', '/onboarding'];
|
|
|
|
for (const route of authRoutes) {
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).toBe(200);
|
|
expect(page.url()).toContain(route);
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('AuthGuard Component - Component Rendering', () => {
|
|
test('renders protected content when access granted', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should render the dashboard content
|
|
await expect(page.locator('body')).toBeVisible();
|
|
|
|
// Should not show loading or redirect messages
|
|
const loadingText = await page.locator('text=/Verifying authentication|Redirecting/').count();
|
|
expect(loadingText).toBe(0);
|
|
});
|
|
|
|
test('shows redirect message briefly before redirect', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
// This is hard to catch, but we can verify the final state
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should end up at login
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
});
|
|
|
|
test('handles multiple AuthGuard instances on same page', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
// Visit a page that might have nested AuthGuards
|
|
await page.goto(`${getWebsiteBaseUrl()}/leagues/league-1`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should render correctly
|
|
await expect(page.locator('body')).toBeVisible();
|
|
expect(page.url()).toContain('/leagues/league-1');
|
|
});
|
|
|
|
test('preserves child component state during auth checks', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
// Visit dashboard
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should maintain component state (no full page reload)
|
|
// This is verified by the fact that the page loads without errors
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('AuthGuard Component - Error Handling', () => {
|
|
test('handles network errors during session check', async ({ page, context }) => {
|
|
// Clear cookies to simulate failed session check
|
|
await context.clearCookies();
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should redirect to login
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
});
|
|
|
|
test('handles invalid session data', async ({ page, context }) => {
|
|
// Set invalid session cookie
|
|
await setWebsiteAuthContext(context, 'sponsor', { sessionDrift: 'invalid-cookie' });
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/sponsor/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should redirect to login
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
});
|
|
|
|
test('handles expired session', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'sponsor', { sessionDrift: 'expired' });
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/sponsor/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should redirect to login
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
});
|
|
|
|
test('handles missing required role data', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'sponsor', { sessionDrift: 'missing-sponsor-id' });
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/sponsor/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should redirect to login
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
});
|
|
});
|
|
|
|
test.describe('AuthGuard Component - Performance', () => {
|
|
test('auth check completes within reasonable time', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
const startTime = Date.now();
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
const endTime = Date.now();
|
|
|
|
// Should complete within 5 seconds
|
|
expect(endTime - startTime).toBeLessThan(5000);
|
|
});
|
|
|
|
test('no excessive session checks', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
let sessionCheckCount = 0;
|
|
page.on('request', (req) => {
|
|
if (req.url().includes('/auth/session')) {
|
|
sessionCheckCount++;
|
|
}
|
|
});
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'networkidle' });
|
|
|
|
// Should check session once or twice (initial + maybe one refresh)
|
|
expect(sessionCheckCount).toBeLessThan(3);
|
|
});
|
|
|
|
test('handles concurrent route access', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
// Navigate to multiple routes rapidly
|
|
const routes = ['/dashboard', '/profile', '/leagues', '/dashboard'];
|
|
|
|
for (const route of routes) {
|
|
await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
expect(page.url()).toContain(route);
|
|
}
|
|
});
|
|
}); |