mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
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.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user