test: comprehensive API test coverage with full input variations
- OpenAPI endpoint: 5 tests (spec structure, paths, headers, schemas) - Proxy hosts: POST/PUT/GET with all nested fields (authentik, load_balancer, dns_resolver, geoblock, waf, mtls, redirects, rewrite) + error cases - L4 proxy hosts: TCP+TLS, UDP, matcher/protocol variations + error cases - Certificates: managed with provider_options, imported with PEM fields - Client certificates: all required fields, revoke with revoked_at - Access lists: seed users, entry add with username/password + error cases - Settings: GET+PUT for all 11 groups (was 3), full data shapes - API auth: empty Bearer, CSRF session vs Bearer, apiErrorResponse variants - API tokens integration: cross-user isolation, admin visibility, inactive user - CA certificates: PUT/DELETE error cases 646 tests total (54 new) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,9 +11,10 @@ vi.mock('@/src/lib/auth', () => ({
|
||||
checkSameOrigin: vi.fn(() => null),
|
||||
}));
|
||||
|
||||
import { authenticateApiRequest, requireApiUser, requireApiAdmin, ApiAuthError } from '@/src/lib/api-auth';
|
||||
import { authenticateApiRequest, requireApiUser, requireApiAdmin, ApiAuthError, apiErrorResponse } from '@/src/lib/api-auth';
|
||||
import { validateToken } from '@/src/lib/models/api-tokens';
|
||||
import { auth } from '@/src/lib/auth';
|
||||
import { auth, checkSameOrigin } from '@/src/lib/auth';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const mockValidateToken = vi.mocked(validateToken);
|
||||
const mockAuth = vi.mocked(auth);
|
||||
@@ -125,4 +126,81 @@ describe('requireApiUser', () => {
|
||||
expect(result.userId).toBe(5);
|
||||
expect(result.role).toBe('viewer');
|
||||
});
|
||||
|
||||
it('CSRF check blocks session-authenticated POST without same origin', async () => {
|
||||
mockAuth.mockResolvedValue({
|
||||
user: { id: '5', role: 'user', name: 'U', email: 'u@test.com' },
|
||||
expires: '',
|
||||
} as any);
|
||||
|
||||
const mockCheckSameOrigin = vi.mocked(checkSameOrigin);
|
||||
mockCheckSameOrigin.mockReturnValueOnce(NextResponse.json({ error: 'Forbidden' }, { status: 403 }) as any);
|
||||
|
||||
await expect(
|
||||
requireApiUser(createMockRequest({ method: 'POST' }))
|
||||
).rejects.toThrow(ApiAuthError);
|
||||
|
||||
try {
|
||||
mockCheckSameOrigin.mockReturnValueOnce(NextResponse.json({ error: 'Forbidden' }, { status: 403 }) as any);
|
||||
mockAuth.mockResolvedValue({
|
||||
user: { id: '5', role: 'user', name: 'U', email: 'u@test.com' },
|
||||
expires: '',
|
||||
} as any);
|
||||
await requireApiUser(createMockRequest({ method: 'POST' }));
|
||||
} catch (e) {
|
||||
expect((e as ApiAuthError).status).toBe(403);
|
||||
}
|
||||
});
|
||||
|
||||
it('CSRF check skips for Bearer-authenticated POST', async () => {
|
||||
mockValidateToken.mockResolvedValue({
|
||||
token: { id: 1, name: 'test', created_by: 42, created_at: '', last_used_at: null, expires_at: null },
|
||||
user: { id: 42, role: 'admin' },
|
||||
});
|
||||
|
||||
const mockCheckSameOrigin = vi.mocked(checkSameOrigin);
|
||||
mockCheckSameOrigin.mockReturnValueOnce(NextResponse.json({ error: 'Forbidden' }, { status: 403 }) as any);
|
||||
|
||||
const result = await requireApiUser(createMockRequest({ authorization: 'Bearer test-token', method: 'POST' }));
|
||||
expect(result.userId).toBe(42);
|
||||
expect(result.authMethod).toBe('bearer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('authenticateApiRequest - empty bearer', () => {
|
||||
it('rejects empty Bearer token', async () => {
|
||||
await expect(
|
||||
authenticateApiRequest(createMockRequest({ authorization: 'Bearer ' }))
|
||||
).rejects.toThrow(ApiAuthError);
|
||||
|
||||
try {
|
||||
await authenticateApiRequest(createMockRequest({ authorization: 'Bearer ' }));
|
||||
} catch (e) {
|
||||
expect((e as ApiAuthError).status).toBe(401);
|
||||
expect((e as ApiAuthError).message).toBe('Invalid Bearer token');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('apiErrorResponse', () => {
|
||||
it('handles ApiAuthError', async () => {
|
||||
const response = apiErrorResponse(new ApiAuthError('Forbidden', 403));
|
||||
expect(response.status).toBe(403);
|
||||
const data = await response.json();
|
||||
expect(data.error).toBe('Forbidden');
|
||||
});
|
||||
|
||||
it('handles generic Error', async () => {
|
||||
const response = apiErrorResponse(new Error('Something broke'));
|
||||
expect(response.status).toBe(500);
|
||||
const data = await response.json();
|
||||
expect(data.error).toBe('Something broke');
|
||||
});
|
||||
|
||||
it('handles unknown error', async () => {
|
||||
const response = apiErrorResponse('some string error');
|
||||
expect(response.status).toBe(500);
|
||||
const data = await response.json();
|
||||
expect(data.error).toBe('Internal server error');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user