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

362 lines
14 KiB
TypeScript

import { test, expect } from '@playwright/test';
import {
setWebsiteAuthContext,
} from './websiteAuth';
import {
getWebsiteRouteInventory,
resolvePathTemplate,
} from './websiteRouteInventory';
/**
* Website Authentication Flow Integration Tests
*
* These tests verify the complete authentication flow including:
* - Middleware route protection
* - AuthGuard component functionality
* - Session management and loading states
* - Role-based access control
* - Auth state transitions
* - API integration
*/
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('Website Auth Flow - Middleware Protection', () => {
const routes = getWebsiteRouteInventory();
// Test public routes are accessible without auth
test('public routes are accessible without authentication', async ({ page, context }) => {
const publicRoutes = routes.filter(r => r.access === 'public');
expect(publicRoutes.length).toBeGreaterThan(0);
for (const route of publicRoutes.slice(0, 5)) { // Test first 5 to keep test fast
const resolvedPath = resolvePathTemplate(route.pathTemplate, route.params);
await setWebsiteAuthContext(context, 'public');
const response = await page.goto(`${getWebsiteBaseUrl()}${resolvedPath}`, { waitUntil: 'domcontentloaded' });
expect(response?.status()).toBe(200);
await expect(page.locator('body')).toBeVisible();
}
});
// Test protected routes redirect unauthenticated users
test('protected routes redirect unauthenticated users to login', async ({ page, context }) => {
const protectedRoutes = routes.filter(r => r.access !== 'public');
expect(protectedRoutes.length).toBeGreaterThan(0);
for (const route of protectedRoutes.slice(0, 3)) { // Test first 3
const resolvedPath = resolvePathTemplate(route.pathTemplate, route.params);
await setWebsiteAuthContext(context, 'public');
await page.goto(`${getWebsiteBaseUrl()}${resolvedPath}`, { waitUntil: 'domcontentloaded' });
const currentUrl = new URL(page.url());
expect(currentUrl.pathname).toBe('/auth/login');
expect(currentUrl.searchParams.get('returnTo')).toBe(resolvedPath);
}
});
// Test authenticated users can access protected routes
test('authenticated users can access protected routes', async ({ page, context }) => {
const authRoutes = routes.filter(r => r.access === 'auth');
expect(authRoutes.length).toBeGreaterThan(0);
for (const route of authRoutes.slice(0, 3)) {
const resolvedPath = resolvePathTemplate(route.pathTemplate, route.params);
await setWebsiteAuthContext(context, 'auth');
const response = await page.goto(`${getWebsiteBaseUrl()}${resolvedPath}`, { waitUntil: 'domcontentloaded' });
expect(response?.status()).toBe(200);
await expect(page.locator('body')).toBeVisible();
}
});
});
test.describe('Website Auth Flow - AuthGuard Component', () => {
test('dashboard route shows loading state then content', async ({ page, context }) => {
await setWebsiteAuthContext(context, 'auth');
const navigationPromise = page.waitForNavigation({ waitUntil: 'domcontentloaded' });
await page.goto(`${getWebsiteBaseUrl()}/dashboard`);
await navigationPromise;
// Should show loading briefly then render dashboard
await expect(page.locator('body')).toBeVisible();
expect(page.url()).toContain('/dashboard');
});
test('dashboard redirects unauthenticated users', async ({ page, context }) => {
await setWebsiteAuthContext(context, 'public');
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
// Should redirect to login with returnTo parameter
const currentUrl = new URL(page.url());
expect(currentUrl.pathname).toBe('/auth/login');
expect(currentUrl.searchParams.get('returnTo')).toBe('/dashboard');
});
test('admin routes require admin role', async ({ page, context }) => {
// Test as regular driver (should be denied)
await setWebsiteAuthContext(context, 'auth');
await page.goto(`${getWebsiteBaseUrl()}/admin`, { waitUntil: 'domcontentloaded' });
// Should redirect to login (no admin role)
const currentUrl = new URL(page.url());
expect(currentUrl.pathname).toBe('/auth/login');
// Test as admin (should be allowed)
await setWebsiteAuthContext(context, 'admin');
await page.goto(`${getWebsiteBaseUrl()}/admin`, { waitUntil: 'domcontentloaded' });
expect(page.url()).toContain('/admin');
await expect(page.locator('body')).toBeVisible();
});
test('sponsor routes require sponsor role', async ({ page, context }) => {
// Test as driver (should be denied)
await setWebsiteAuthContext(context, 'auth');
await page.goto(`${getWebsiteBaseUrl()}/sponsor/dashboard`, { waitUntil: 'domcontentloaded' });
const currentUrl = new URL(page.url());
expect(currentUrl.pathname).toBe('/auth/login');
// Test as sponsor (should be allowed)
await setWebsiteAuthContext(context, 'sponsor');
await page.goto(`${getWebsiteBaseUrl()}/sponsor/dashboard`, { waitUntil: 'domcontentloaded' });
expect(page.url()).toContain('/sponsor/dashboard');
await expect(page.locator('body')).toBeVisible();
});
});
test.describe('Website Auth Flow - Session Management', () => {
test('session is properly loaded on page visit', async ({ page, context }) => {
await setWebsiteAuthContext(context, 'auth');
// Visit dashboard
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
// Verify session is available by checking for user-specific content
// (This would depend on your actual UI, but we can verify no errors)
await expect(page.locator('body')).toBeVisible();
});
test('logout clears session and redirects', async ({ page, context }) => {
await setWebsiteAuthContext(context, 'auth');
// Go to dashboard
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
await expect(page.locator('body')).toBeVisible();
// Find and click logout (assuming it exists)
// This test would need to be adapted based on actual logout implementation
// For now, we'll test that clearing cookies works
await context.clearCookies();
await page.reload({ waitUntil: 'domcontentloaded' });
// Should redirect to login
const currentUrl = new URL(page.url());
expect(currentUrl.pathname).toBe('/auth/login');
});
test('auth state transitions work correctly', 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');
// Simulate login by setting auth context
await setWebsiteAuthContext(context, 'auth');
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
expect(new URL(page.url()).pathname).toBe('/dashboard');
// Simulate logout
await setWebsiteAuthContext(context, 'public');
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
expect(new URL(page.url()).pathname).toBe('/auth/login');
});
});
test.describe('Website Auth Flow - API Integration', () => {
test('session endpoint returns correct data', async ({ page, context }) => {
await setWebsiteAuthContext(context, 'auth');
// Direct API call to verify session endpoint
const response = await page.request.get(`${getWebsiteBaseUrl()}/api/auth/session`);
expect(response.ok()).toBe(true);
const session = await response.json();
expect(session).toBeDefined();
});
test('demo login flow works', async ({ page, context }) => {
// Clear any existing cookies
await context.clearCookies();
// Navigate to login page
await page.goto(`${getWebsiteBaseUrl()}/auth/login`, { waitUntil: 'domcontentloaded' });
// Verify login page loads
await expect(page.locator('body')).toBeVisible();
// Note: Actual demo login form interaction would go here
// For now, we'll test the API endpoint directly
const response = await page.request.post(`${getWebsiteBaseUrl()}/api/auth/demo-login`, {
data: { role: 'driver' }
});
expect(response.ok()).toBe(true);
// Verify cookies were set
const cookies = await context.cookies();
const gpSession = cookies.find(c => c.name === 'gp_session');
expect(gpSession).toBeDefined();
});
test('auth API handles different roles correctly', async ({ page }) => {
const roles = ['driver', 'sponsor', 'admin'] as const;
for (const role of roles) {
const response = await page.request.post(`${getWebsiteBaseUrl()}/api/auth/demo-login`, {
data: { role }
});
expect(response.ok()).toBe(true);
const session = await response.json();
expect(session.user).toBeDefined();
// Verify role-specific data
if (role === 'sponsor') {
expect(session.user.sponsorId).toBeDefined();
}
}
});
});
test.describe('Website Auth Flow - Edge Cases', () => {
test('handles auth state drift gracefully', async ({ page, context }) => {
// Set sponsor context but with missing sponsor ID
await setWebsiteAuthContext(context, 'sponsor', { sessionDrift: 'missing-sponsor-id' });
await page.goto(`${getWebsiteBaseUrl()}/sponsor/dashboard`, { waitUntil: 'domcontentloaded' });
// Should redirect to login due to invalid session
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 invalid session cookie', async ({ page, context }) => {
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('public routes accessible even with invalid auth cookies', async ({ page, context }) => {
await setWebsiteAuthContext(context, 'public', { sessionDrift: 'invalid-cookie' });
await page.goto(`${getWebsiteBaseUrl()}/leagues`, { waitUntil: 'domcontentloaded' });
// Should still work
expect(page.url()).toContain('/leagues');
await expect(page.locator('body')).toBeVisible();
});
});
test.describe('Website Auth Flow - Redirect Scenarios', () => {
test('auth routes redirect authenticated users away', 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' });
// Should redirect to sponsor dashboard
const currentUrl = new URL(page.url());
expect(currentUrl.pathname).toBe('/sponsor/dashboard');
});
test('returnTo parameter works correctly', async ({ page, context }) => {
await setWebsiteAuthContext(context, 'public');
const targetRoute = '/leagues/league-1/settings';
await page.goto(`${getWebsiteBaseUrl()}${targetRoute}`, { waitUntil: 'domcontentloaded' });
// Should redirect to login with returnTo
const currentUrl = new URL(page.url());
expect(currentUrl.pathname).toBe('/auth/login');
expect(currentUrl.searchParams.get('returnTo')).toBe(targetRoute);
// After login, should return to target
await setWebsiteAuthContext(context, 'admin');
await page.goto(`${getWebsiteBaseUrl()}${targetRoute}`, { waitUntil: 'domcontentloaded' });
expect(page.url()).toContain(targetRoute);
});
});
test.describe('Website Auth Flow - Performance', () => {
test('auth verification completes quickly', 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 reasonable time (under 5 seconds)
expect(endTime - startTime).toBeLessThan(5000);
// Should show content
await expect(page.locator('body')).toBeVisible();
});
test('no infinite loading states', async ({ page, context }) => {
await setWebsiteAuthContext(context, 'auth');
// Monitor for loading indicators
let loadingCount = 0;
page.on('request', (req) => {
if (req.url().includes('/auth/session')) loadingCount++;
});
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'networkidle' });
// Should not make excessive session calls
expect(loadingCount).toBeLessThan(3);
// Should eventually show content
await expect(page.locator('body')).toBeVisible();
});
});