438 lines
16 KiB
TypeScript
438 lines
16 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { setWebsiteAuthContext } from './websiteAuth';
|
|
|
|
/**
|
|
* Website Middleware Route Protection Tests
|
|
*
|
|
* These tests specifically verify the Next.js middleware behavior:
|
|
* - Public routes are always accessible
|
|
* - Protected routes require authentication
|
|
* - Auth routes redirect authenticated users
|
|
* - Role-based access control
|
|
*/
|
|
|
|
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 Middleware - Public Route Protection', () => {
|
|
test('root route is publicly accessible', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}/`, { waitUntil: 'domcontentloaded' });
|
|
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
|
|
test('league routes are publicly accessible', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const routes = ['/leagues', '/leagues/league-1', '/leagues/league-1/standings'];
|
|
|
|
for (const route of routes) {
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('body')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('driver routes are publicly accessible', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}/drivers/driver-1`, { waitUntil: 'domcontentloaded' });
|
|
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
|
|
test('team routes are publicly accessible', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}/teams/team-1`, { waitUntil: 'domcontentloaded' });
|
|
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
|
|
test('race routes are publicly accessible', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}/races/race-1`, { waitUntil: 'domcontentloaded' });
|
|
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
|
|
test('leaderboard routes are publicly accessible', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}/leaderboards`, { waitUntil: 'domcontentloaded' });
|
|
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
|
|
test('sponsor signup is publicly accessible', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}/sponsor/signup`, { waitUntil: 'domcontentloaded' });
|
|
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
|
|
test('auth routes are publicly accessible', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const authRoutes = [
|
|
'/auth/login',
|
|
'/auth/signup',
|
|
'/auth/forgot-password',
|
|
'/auth/reset-password',
|
|
'/auth/iracing',
|
|
];
|
|
|
|
for (const route of authRoutes) {
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('body')).toBeVisible();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Website Middleware - Protected Route Protection', () => {
|
|
test('dashboard redirects unauthenticated users to login', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
expect(currentUrl.searchParams.get('returnTo')).toBe('/dashboard');
|
|
});
|
|
|
|
test('profile routes redirect unauthenticated users to login', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const profileRoutes = ['/profile', '/profile/settings', '/profile/leagues'];
|
|
|
|
for (const route of profileRoutes) {
|
|
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('admin routes redirect unauthenticated users to login', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
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');
|
|
expect(currentUrl.searchParams.get('returnTo')).toBe(route);
|
|
}
|
|
});
|
|
|
|
test('sponsor routes redirect unauthenticated users to login', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
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');
|
|
expect(currentUrl.searchParams.get('returnTo')).toBe(route);
|
|
}
|
|
});
|
|
|
|
test('league admin routes redirect unauthenticated users to login', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const leagueAdminRoutes = [
|
|
'/leagues/league-1/roster/admin',
|
|
'/leagues/league-1/schedule/admin',
|
|
'/leagues/league-1/settings',
|
|
'/leagues/league-1/stewarding',
|
|
'/leagues/league-1/wallet',
|
|
];
|
|
|
|
for (const route of leagueAdminRoutes) {
|
|
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('onboarding redirects unauthenticated users to login', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
await page.goto(`${getWebsiteBaseUrl()}/onboarding`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
expect(currentUrl.searchParams.get('returnTo')).toBe('/onboarding');
|
|
});
|
|
});
|
|
|
|
test.describe('Website Middleware - Authenticated Access', () => {
|
|
test('dashboard is accessible when authenticated', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
|
|
expect(response?.status()).toBe(200);
|
|
expect(page.url()).toContain('/dashboard');
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
|
|
test('profile routes are accessible when authenticated', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
const profileRoutes = ['/profile', '/profile/settings', '/profile/leagues'];
|
|
|
|
for (const route of profileRoutes) {
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).toBe(200);
|
|
expect(page.url()).toContain(route);
|
|
}
|
|
});
|
|
|
|
test('onboarding is accessible when authenticated', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}/onboarding`, { waitUntil: 'domcontentloaded' });
|
|
|
|
expect(response?.status()).toBe(200);
|
|
expect(page.url()).toContain('/onboarding');
|
|
});
|
|
});
|
|
|
|
test.describe('Website Middleware - Role-Based Access', () => {
|
|
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' });
|
|
|
|
let 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' });
|
|
|
|
currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/admin');
|
|
});
|
|
|
|
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' });
|
|
|
|
let 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' });
|
|
|
|
currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/sponsor/dashboard');
|
|
});
|
|
|
|
test('league admin routes require admin role', async ({ page, context }) => {
|
|
// Test as regular driver (should be denied)
|
|
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 (should be allowed)
|
|
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('race stewarding routes require admin role', async ({ page, context }) => {
|
|
// Test as regular driver (should be denied)
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
await page.goto(`${getWebsiteBaseUrl()}/races/race-1/stewarding`, { waitUntil: 'domcontentloaded' });
|
|
|
|
let 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()}/races/race-1/stewarding`, { waitUntil: 'domcontentloaded' });
|
|
|
|
currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/races/race-1/stewarding');
|
|
});
|
|
});
|
|
|
|
test.describe('Website Middleware - Auth Route Behavior', () => {
|
|
test('auth routes redirect authenticated users away', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
const authRoutes = [
|
|
'/auth/login',
|
|
'/auth/signup',
|
|
'/auth/iracing',
|
|
];
|
|
|
|
for (const route of authRoutes) {
|
|
await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
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');
|
|
|
|
const authRoutes = [
|
|
'/auth/login',
|
|
'/auth/signup',
|
|
'/auth/iracing',
|
|
];
|
|
|
|
for (const route of authRoutes) {
|
|
await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/sponsor/dashboard');
|
|
}
|
|
});
|
|
|
|
test('admin auth routes redirect to admin dashboard', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'admin');
|
|
|
|
const authRoutes = [
|
|
'/auth/login',
|
|
'/auth/signup',
|
|
'/auth/iracing',
|
|
];
|
|
|
|
for (const route of authRoutes) {
|
|
await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/admin');
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Website Middleware - Edge Cases', () => {
|
|
test('handles trailing slashes correctly', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const routes = [
|
|
{ path: '/leagues', expected: '/leagues' },
|
|
{ path: '/leagues/', expected: '/leagues' },
|
|
{ path: '/drivers/driver-1', expected: '/drivers/driver-1' },
|
|
{ path: '/drivers/driver-1/', expected: '/drivers/driver-1' },
|
|
];
|
|
|
|
for (const { path, expected } of routes) {
|
|
await page.goto(`${getWebsiteBaseUrl()}${path}`, { waitUntil: 'domcontentloaded' });
|
|
expect(page.url()).toContain(expected);
|
|
}
|
|
});
|
|
|
|
test('handles invalid routes gracefully', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const invalidRoutes = [
|
|
'/invalid-route',
|
|
'/leagues/invalid-id',
|
|
'/drivers/invalid-id',
|
|
];
|
|
|
|
for (const route of invalidRoutes) {
|
|
const response = await page.goto(`${getWebsiteBaseUrl()}${route}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Should either show 404 or redirect to a valid page
|
|
const status = response?.status();
|
|
const url = page.url();
|
|
|
|
expect([200, 404].includes(status ?? 0) || url.includes('/auth/login')).toBe(true);
|
|
}
|
|
});
|
|
|
|
test('preserves query parameters during redirects', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const targetRoute = '/dashboard?tab=settings&filter=active';
|
|
await page.goto(`${getWebsiteBaseUrl()}${targetRoute}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
expect(currentUrl.searchParams.get('returnTo')).toBe('/dashboard?tab=settings&filter=active');
|
|
});
|
|
|
|
test('handles deeply nested routes', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const deepRoute = '/leagues/league-1/stewarding/protests/protest-1';
|
|
await page.goto(`${getWebsiteBaseUrl()}${deepRoute}`, { waitUntil: 'domcontentloaded' });
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
expect(currentUrl.searchParams.get('returnTo')).toBe(deepRoute);
|
|
});
|
|
});
|
|
|
|
test.describe('Website Middleware - Performance', () => {
|
|
test('middleware adds minimal overhead', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
const startTime = Date.now();
|
|
await page.goto(`${getWebsiteBaseUrl()}/leagues`, { waitUntil: 'domcontentloaded' });
|
|
const endTime = Date.now();
|
|
|
|
// Should complete quickly (under 3 seconds)
|
|
expect(endTime - startTime).toBeLessThan(3000);
|
|
});
|
|
|
|
test('no redirect loops', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'public');
|
|
|
|
// Try to access a protected route multiple times
|
|
for (let i = 0; i < 3; i++) {
|
|
await page.goto(`${getWebsiteBaseUrl()}/dashboard`, { waitUntil: 'domcontentloaded' });
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
}
|
|
});
|
|
|
|
test('handles rapid navigation', async ({ page, context }) => {
|
|
await setWebsiteAuthContext(context, 'auth');
|
|
|
|
// Navigate between multiple protected 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);
|
|
}
|
|
});
|
|
}); |