361 lines
14 KiB
TypeScript
361 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('normal 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 login form interaction would go here
|
|
// For now, we'll test the API endpoint directly
|
|
const response = await page.request.post(`${getWebsiteBaseUrl()}/api/auth/login`, {
|
|
data: {
|
|
email: 'demo.driver@example.com',
|
|
password: 'Demo1234!'
|
|
}
|
|
});
|
|
|
|
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 login with seeded credentials', async ({ page }) => {
|
|
// Test normal login with seeded demo user credentials
|
|
const response = await page.request.post(`${getWebsiteBaseUrl()}/api/auth/login`, {
|
|
data: {
|
|
email: 'demo.driver@example.com',
|
|
password: 'Demo1234!'
|
|
}
|
|
});
|
|
|
|
expect(response.ok()).toBe(true);
|
|
|
|
const session = await response.json();
|
|
expect(session.user).toBeDefined();
|
|
expect(session.user.email).toBe('demo.driver@example.com');
|
|
});
|
|
});
|
|
|
|
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();
|
|
});
|
|
}); |