163 lines
4.7 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
});
|