import { CallHandler, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { lastValueFrom, of, throwError } from 'rxjs'; import { AuditService } from './audit.service'; import { OperationLogInterceptor } from './operation-log.interceptor'; function createContext(request: Record) { return { getHandler: () => function handler() {}, getClass: () => class Controller {}, switchToHttp: () => ({ getRequest: () => request, }), } as unknown as ExecutionContext; } describe('OperationLogInterceptor', () => { it('writes a success operation log with masked request body', async () => { const write = jest.fn(); const reflector = { getAllAndOverride: jest.fn().mockReturnValue({ action: 'admin.users.create', resourceType: 'user', resourceIdFromResult: 'id', metadataFromBody: ['name', 'password'], }), } as unknown as Reflector; const interceptor = new OperationLogInterceptor( { write } as unknown as AuditService, reflector, ); const request = { user: { id: 'actor-1', name: 'root' }, params: {}, query: {}, body: { name: 'demo', password: '123456' }, headers: { 'user-agent': 'vitest' }, ip: '127.0.0.1', }; const next = { handle: () => of({ id: 'user-1' }), } as CallHandler; await expect(lastValueFrom(interceptor.intercept(createContext(request), next))).resolves.toEqual({ id: 'user-1', }); expect(write).toHaveBeenCalledWith( expect.objectContaining({ actorId: 'actor-1', actorUsername: 'root', action: 'admin.users.create', resourceType: 'user', resourceId: 'user-1', ipAddress: '127.0.0.1', metadata: expect.objectContaining({ status: 'success', request: expect.objectContaining({ body: { name: 'demo', password: '******' }, }), }), }), ); }); it('writes a failed operation log and rethrows the error', async () => { const write = jest.fn(); const reflector = { getAllAndOverride: jest.fn().mockReturnValue({ action: 'admin.users.update', resourceType: 'user', resourceIdParam: 'id', }), } as unknown as Reflector; const interceptor = new OperationLogInterceptor( { write } as unknown as AuditService, reflector, ); const error = new Error('保存失败'); const request = { user: { id: 'actor-1', name: 'root' }, params: { id: 'user-1' }, query: {}, body: {}, headers: {}, ip: '127.0.0.1', }; const next = { handle: () => throwError(() => error), } as CallHandler; await expect( lastValueFrom(interceptor.intercept(createContext(request), next)), ).rejects.toThrow('保存失败'); expect(write).toHaveBeenCalledWith( expect.objectContaining({ action: 'admin.users.update', resourceId: 'user-1', metadata: expect.objectContaining({ status: 'failed', error: { name: 'Error', message: '保存失败', }, }), }), ); }); });