import { AuditService } from './audit.service'; describe('AuditService', () => { const create = jest.fn(); const findMany = jest.fn(); const prisma = { auditEvent: { create, findMany } } as any; const service = new AuditService(prisma); beforeEach(() => { jest.clearAllMocks(); }); it('writes append-only audit events with sensitive fields redacted', async () => { create.mockResolvedValue({ id: 'audit-1' }); await service.record({ actor: { id: 'm-8', name: '超级管理员', roleId: 'role-admin' }, action: 'product.update', entityType: 'product', entityId: 'product-1', productId: 'product-1', before: { name: 'Old', password: '123456' }, after: { name: 'New', nested: { apiKey: 'sk-test', keep: 'visible' } }, metadata: { authorization: 'Bearer token', reason: 'manual edit' }, }); expect(create).toHaveBeenCalledWith({ data: expect.objectContaining({ actorId: 'm-8', actorName: '超级管理员', action: 'product.update', entityType: 'product', entityId: 'product-1', productId: 'product-1', before: { name: 'Old', password: '[REDACTED]' }, after: { name: 'New', nested: { apiKey: '[REDACTED]', keep: 'visible' } }, metadata: { authorization: '[REDACTED]', reason: 'manual edit' }, }), }); }); it('queries by actor, entity, scope, and date range with bounded page size', async () => { findMany.mockResolvedValue([]); await service.query({ actorId: 'm-8', entityType: 'bug', entityId: 'bug-1', productId: 'product-1', projectId: 'project-1', versionId: 'version-1', dateFrom: '2026-07-01T00:00:00.000Z', dateTo: '2026-07-08T23:59:59.000Z', take: '500', }); expect(findMany).toHaveBeenCalledWith({ where: { actorId: 'm-8', entityType: 'bug', entityId: 'bug-1', productId: 'product-1', projectId: 'project-1', versionId: 'version-1', createdAt: { gte: new Date('2026-07-01T00:00:00.000Z'), lte: new Date('2026-07-08T23:59:59.000Z'), }, }, orderBy: { createdAt: 'desc' }, take: 100, }); }); });