Files
gridpilot.gg/apps/website/middleware.test.ts
2026-01-17 18:28:10 +01:00

163 lines
4.7 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { NextRequest } from 'next/server';
const mockGetSessionFromRequest = vi.fn();
// Mock Next.js server components
vi.mock('next/server', () => ({
NextResponse: {
next: vi.fn(() => ({
headers: {
set: vi.fn(),
},
})),
redirect: vi.fn((url: URL) => ({
headers: {
set: vi.fn(),
},
url: url.toString(),
})),
},
}));
// Mock SessionGateway
vi.mock('@/lib/gateways/SessionGateway', () => ({
SessionGateway: class {
getSessionFromRequest = mockGetSessionFromRequest;
},
}));
// Mock RouteConfig to have deterministic behavior in tests
vi.mock('@/lib/routing/RouteConfig', () => ({
routes: {
auth: { login: '/auth/login' },
public: { home: '/' },
protected: { dashboard: '/dashboard' },
sponsor: { root: '/sponsor', dashboard: '/sponsor/dashboard' },
admin: { root: '/admin' },
},
routeMatchers: {
isPublic: (path: string) => {
return ['/', '/auth/login'].includes(path);
},
isInGroup: (path: string, group: string) => {
if (group === 'admin') return path.startsWith('/admin');
if (group === 'sponsor') return path.startsWith('/sponsor');
return false;
},
requiresAuth: (path: string) => {
return !['/', '/auth/login'].includes(path);
},
requiresRole: (path: string) => {
if (path.startsWith('/admin')) return ['admin'];
if (path.startsWith('/sponsor')) return ['sponsor'];
return null;
},
},
}));
// Import middleware after mocks
import { middleware } from './middleware';
describe('Middleware - Route Protection', () => {
let mockRequest: NextRequest;
beforeEach(() => {
vi.clearAllMocks();
mockGetSessionFromRequest.mockReset();
mockRequest = {
url: 'http://localhost:3000',
nextUrl: { pathname: '/' },
method: 'GET',
headers: {
set: vi.fn(),
get: vi.fn().mockReturnValue(''),
},
} as any;
});
describe('Redirect logic and returnTo', () => {
it('should redirect unauthenticated users to login with returnTo', async () => {
mockRequest.nextUrl.pathname = '/dashboard';
mockGetSessionFromRequest.mockResolvedValue(null);
const response = await middleware(mockRequest);
expect(response.url).toContain('/auth/login');
expect(response.url).toContain('returnTo=%2Fdashboard');
});
it('should allow authenticated users to access protected routes', async () => {
mockRequest.nextUrl.pathname = '/dashboard';
mockGetSessionFromRequest.mockResolvedValue({
user: { userId: '123', role: 'driver' },
});
const response = await middleware(mockRequest);
// Should not be a redirect (no url property on NextResponse.next() mock)
expect(response.url).toBeUndefined();
});
});
describe('Role-based redirects', () => {
it('should redirect user with wrong role to their home page', async () => {
// Driver trying to access admin
mockRequest.nextUrl.pathname = '/admin';
mockGetSessionFromRequest.mockResolvedValue({
user: { userId: '123', role: 'driver' },
});
const response = await middleware(mockRequest);
// Should redirect to dashboard (driver's home)
expect(response.url).toBe('http://localhost:3000/dashboard');
});
it('should redirect sponsor with wrong role to sponsor dashboard', async () => {
// Sponsor trying to access admin
mockRequest.nextUrl.pathname = '/admin';
mockGetSessionFromRequest.mockResolvedValue({
user: { userId: '123', role: 'sponsor' },
});
const response = await middleware(mockRequest);
expect(response.url).toBe('http://localhost:3000/sponsor/dashboard');
});
it('should allow user with correct role to pass through', async () => {
mockRequest.nextUrl.pathname = '/admin';
mockGetSessionFromRequest.mockResolvedValue({
user: { userId: '123', role: 'admin' },
});
const response = await middleware(mockRequest);
expect(response.url).toBeUndefined();
});
});
describe('Public routes', () => {
it('should allow access to public routes without session', async () => {
mockRequest.nextUrl.pathname = '/';
mockGetSessionFromRequest.mockResolvedValue(null);
const response = await middleware(mockRequest);
expect(response.url).toBeUndefined();
});
});
describe('Special redirects', () => {
it('should handle /sponsor root redirect', async () => {
mockRequest.nextUrl.pathname = '/sponsor';
const response = await middleware(mockRequest);
expect(response.url).toBe('http://localhost:3000/sponsor/dashboard');
});
});
});