/** * Component: Local Auth API Route Tests * Documentation: documentation/testing.md */ import { beforeEach, describe, expect, it, vi } from 'vitest'; const localAuthProviderMock = { handleCallback: vi.fn(), register: vi.fn(), }; vi.mock('@/lib/services/auth/LocalAuthProvider', () => ({ LocalAuthProvider: class { handleCallback = localAuthProviderMock.handleCallback; register = localAuthProviderMock.register; }, })); const makeRequest = (body: any, headers?: Record) => ({ json: vi.fn().mockResolvedValue(body), headers: { get: (key: string) => headers?.[key.toLowerCase()] || null, }, }); describe('Local auth routes', () => { beforeEach(() => { vi.clearAllMocks(); delete process.env.DISABLE_LOCAL_LOGIN; }); it('rejects login when local auth is disabled', async () => { process.env.DISABLE_LOCAL_LOGIN = 'true'; const { POST } = await import('@/app/api/auth/local/login/route'); const response = await POST(makeRequest({ username: 'user', password: 'pass' }) as any); const payload = await response.json(); expect(response.status).toBe(403); expect(payload.error).toBe('Local login is disabled'); }); it('rejects login when username or password missing', async () => { const { POST } = await import('@/app/api/auth/local/login/route'); const response = await POST(makeRequest({ username: 'user' }) as any); const payload = await response.json(); expect(response.status).toBe(400); expect(payload.error).toBe('Username and password are required'); }); it('logs in successfully with local credentials', async () => { localAuthProviderMock.handleCallback.mockResolvedValue({ success: true, user: { id: 'u1' }, tokens: { accessToken: 'access', refreshToken: 'refresh' }, }); const { POST } = await import('@/app/api/auth/local/login/route'); const response = await POST(makeRequest({ username: 'user', password: 'pass' }) as any); const payload = await response.json(); expect(response.status).toBe(200); expect(payload.success).toBe(true); expect(payload.accessToken).toBe('access'); }); it('returns pending approval for local login', async () => { localAuthProviderMock.handleCallback.mockResolvedValue({ success: false, requiresApproval: true, }); const { POST } = await import('@/app/api/auth/local/login/route'); const response = await POST(makeRequest({ username: 'user', password: 'pass' }) as any); const payload = await response.json(); expect(response.status).toBe(200); expect(payload.pendingApproval).toBe(true); }); it('registers a local user and returns tokens', async () => { localAuthProviderMock.register.mockResolvedValue({ success: true, user: { id: 'u2' }, tokens: { accessToken: 'access', refreshToken: 'refresh' }, }); const { POST } = await import('@/app/api/auth/register/route'); const request = makeRequest({ username: 'user', password: 'pass' }, { 'x-forwarded-for': 'ip-1' }); const response = await POST(request as any); const payload = await response.json(); expect(response.status).toBe(200); expect(payload.success).toBe(true); expect(payload.refreshToken).toBe('refresh'); }); it('rate limits repeated registration attempts by IP', async () => { localAuthProviderMock.register.mockResolvedValue({ success: true, user: { id: 'u3' }, tokens: { accessToken: 'access', refreshToken: 'refresh' }, }); const { POST } = await import('@/app/api/auth/register/route'); const request = makeRequest({ username: 'user', password: 'pass' }, { 'x-forwarded-for': 'ip-2' }); for (let i = 0; i < 5; i += 1) { const response = await POST(request as any); expect(response.status).toBe(200); } const blocked = await POST(request as any); const payload = await blocked.json(); expect(blocked.status).toBe(429); expect(payload.error).toMatch(/Too many registration attempts/); }); });