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