Files
ReadMeABook/tests/api/auth-local.routes.test.ts
T
kikootwo 94dbaf073b Add backend unit test framework and modularize settings UI
Introduced a Vitest-based backend unit testing framework with supporting scripts, helpers, and GitHub Actions integration. Refactored the admin settings page to a modular architecture, splitting monolithic logic into feature-specific tabs and hooks for improved maintainability and testability. Updated documentation to reflect the new testing setup and settings architecture, and added new dependencies for testing utilities.
2026-01-28 11:41:59 -05:00

124 lines
4.0 KiB
TypeScript

/**
* 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<string, string>) => ({
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/);
});
});