import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import Fastify from 'fastify'; import type { FastifyInstance } from 'fastify'; import { registerAuditLogRoutes } from '../src/routes/audit-logs.js'; import { AuditLogService } from '../src/services/audit-log.service.js'; import type { IAuditLogRepository } from '../src/repositories/interfaces.js'; import { errorHandler } from '../src/middleware/error-handler.js'; function makeLog(id: string) { return { id, userId: 'user-1', action: 'CREATE', resource: 'servers', resourceId: null, details: {}, createdAt: new Date('2025-01-15T00:00:00Z'), }; } function mockRepo(): IAuditLogRepository { return { findAll: vi.fn(async () => [makeLog('log-1'), makeLog('log-2')]), findById: vi.fn(async () => null), create: vi.fn(async () => makeLog('new-log')), count: vi.fn(async () => 2), deleteOlderThan: vi.fn(async () => 3), }; } describe('Audit Log Routes', () => { let app: FastifyInstance; let repo: ReturnType; let service: AuditLogService; beforeEach(async () => { app = Fastify(); app.setErrorHandler(errorHandler); repo = mockRepo(); service = new AuditLogService(repo, 90); registerAuditLogRoutes(app, service); await app.ready(); }); afterEach(async () => { await app.close(); }); describe('GET /api/v1/audit-logs', () => { it('returns paginated audit logs', async () => { const res = await app.inject({ method: 'GET', url: '/api/v1/audit-logs' }); expect(res.statusCode).toBe(200); const body = res.json(); expect(body.logs).toHaveLength(2); expect(body.total).toBe(2); }); it('passes query filters to service', async () => { await app.inject({ method: 'GET', url: '/api/v1/audit-logs?userId=user-1&action=CREATE&resource=servers&limit=10&offset=5', }); expect(repo.findAll).toHaveBeenCalledWith( expect.objectContaining({ userId: 'user-1', action: 'CREATE', resource: 'servers', limit: 10, offset: 5, }), ); }); it('passes date range filters', async () => { await app.inject({ method: 'GET', url: '/api/v1/audit-logs?since=2025-01-01T00:00:00Z&until=2025-12-31T23:59:59Z', }); expect(repo.findAll).toHaveBeenCalledWith( expect.objectContaining({ since: expect.any(Date), until: expect.any(Date), }), ); }); }); describe('GET /api/v1/audit-logs/:id', () => { it('returns a specific log entry', async () => { vi.mocked(repo.findById).mockResolvedValue(makeLog('log-1')); const res = await app.inject({ method: 'GET', url: '/api/v1/audit-logs/log-1' }); expect(res.statusCode).toBe(200); expect(res.json().id).toBe('log-1'); }); it('returns 404 for missing log', async () => { const res = await app.inject({ method: 'GET', url: '/api/v1/audit-logs/nonexistent' }); expect(res.statusCode).toBe(404); }); }); describe('POST /api/v1/audit-logs/purge', () => { it('purges expired logs and returns count', async () => { const res = await app.inject({ method: 'POST', url: '/api/v1/audit-logs/purge' }); expect(res.statusCode).toBe(200); expect(res.json().deleted).toBe(3); }); }); });