feat(audit): 增加审计事件基础模块

This commit is contained in:
2026-07-08 16:17:36 +08:00
parent 64f49c512f
commit 73e8dfa8c8
9 changed files with 297 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
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,
});
});
});