Files
gridpilot.gg/tests/integration/website/auth-guard.test.ts
2026-01-03 02:42:47 +01:00

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);
}
});
});