import { describe, it, expect, vi, afterEach } from 'vitest'; import Fastify from 'fastify'; import type { FastifyInstance } from 'fastify'; import { registerAuditHook } from '../src/middleware/audit.js'; let app: FastifyInstance; afterEach(async () => { if (app) await app.close(); }); describe('audit middleware', () => { it('logs mutating requests from authenticated users', async () => { const createAuditLog = vi.fn(async () => {}); app = Fastify({ logger: false }); // Simulate authenticated request app.addHook('preHandler', async (request) => { request.userId = 'user-1'; }); registerAuditHook(app, { createAuditLog }); app.post('/api/v1/servers', async () => ({ ok: true })); await app.ready(); await app.inject({ method: 'POST', url: '/api/v1/servers', payload: {} }); expect(createAuditLog).toHaveBeenCalledOnce(); expect(createAuditLog).toHaveBeenCalledWith(expect.objectContaining({ userId: 'user-1', action: 'CREATE', resource: 'servers', })); }); it('does not log GET requests', async () => { const createAuditLog = vi.fn(async () => {}); app = Fastify({ logger: false }); app.addHook('preHandler', async (request) => { request.userId = 'user-1'; }); registerAuditHook(app, { createAuditLog }); app.get('/api/v1/servers', async () => []); await app.ready(); await app.inject({ method: 'GET', url: '/api/v1/servers' }); expect(createAuditLog).not.toHaveBeenCalled(); }); it('does not log unauthenticated requests', async () => { const createAuditLog = vi.fn(async () => {}); app = Fastify({ logger: false }); registerAuditHook(app, { createAuditLog }); app.post('/api/v1/servers', async () => ({ ok: true })); await app.ready(); await app.inject({ method: 'POST', url: '/api/v1/servers', payload: {} }); expect(createAuditLog).not.toHaveBeenCalled(); }); it('maps DELETE method to DELETE action', async () => { const createAuditLog = vi.fn(async () => {}); app = Fastify({ logger: false }); app.addHook('preHandler', async (request) => { request.userId = 'user-1'; }); registerAuditHook(app, { createAuditLog }); app.delete('/api/v1/servers/:id', async () => ({ ok: true })); await app.ready(); await app.inject({ method: 'DELETE', url: '/api/v1/servers/srv-123' }); expect(createAuditLog).toHaveBeenCalledWith(expect.objectContaining({ action: 'DELETE', resource: 'servers', resourceId: 'srv-123', })); }); it('maps PUT/PATCH to UPDATE action', async () => { const createAuditLog = vi.fn(async () => {}); app = Fastify({ logger: false }); app.addHook('preHandler', async (request) => { request.userId = 'user-1'; }); registerAuditHook(app, { createAuditLog }); app.put('/api/v1/servers/:id', async () => ({ ok: true })); await app.ready(); await app.inject({ method: 'PUT', url: '/api/v1/servers/srv-1', payload: {} }); expect(createAuditLog).toHaveBeenCalledWith(expect.objectContaining({ action: 'UPDATE', })); }); });