feat(audit): 收口领域写接口权限审计

This commit is contained in:
2026-07-08 16:29:22 +08:00
parent 73e8dfa8c8
commit 18380edda8
24 changed files with 638 additions and 10 deletions

View File

@@ -0,0 +1,59 @@
import { lastValueFrom, of } from 'rxjs';
import { AuditMutationInterceptor } from './audit-mutation.interceptor';
import { AuditMutation } from './audit-mutation.decorator';
describe('AuditMutationInterceptor', () => {
it('writes an audit event after a successful mutation response', async () => {
const record = jest.fn().mockResolvedValue({ id: 'audit-1' });
const resolveCurrentUser = jest.fn().mockResolvedValue({ id: 'm-8', name: '超级管理员', roleId: 'role-admin' });
const interceptor = new AuditMutationInterceptor(
new (jest.requireActual('@nestjs/core').Reflector)(),
{ record } as any,
{ resolveCurrentUser } as any,
);
const handler = decorate(() => undefined);
const result = await lastValueFrom(interceptor.intercept(contextFor(handler), {
handle: () => of({ item: { id: 'task-1', productId: 'product-1', projectId: 'project-1', versionId: 'version-1' } }),
} as any));
expect(result).toEqual({ item: { id: 'task-1', productId: 'product-1', projectId: 'project-1', versionId: 'version-1' } });
expect(record).toHaveBeenCalledWith({
actor: { id: 'm-8', name: '超级管理员', roleId: 'role-admin' },
action: 'dev_task.update',
entityType: 'dev_task',
entityId: 'task-1',
productId: 'product-1',
projectId: 'project-1',
versionId: 'version-1',
scope: { productId: 'product-1', projectId: 'project-1', versionId: 'version-1' },
after: { item: { id: 'task-1', productId: 'product-1', projectId: 'project-1', versionId: 'version-1' } },
metadata: { route: 'PATCH /versions/version-1/dev-tasks/task-1' },
});
});
});
function decorate(handler: Function) {
AuditMutation({
action: 'dev_task.update',
entityType: 'dev_task',
entityIdParam: 'id',
versionIdParam: 'versionId',
})(handler as any, undefined as any, undefined as any);
return handler;
}
function contextFor(handler: Function) {
const request = {
method: 'PATCH',
originalUrl: '/versions/version-1/dev-tasks/task-1',
params: { id: 'task-1', versionId: 'version-1' },
body: {},
headers: {},
};
return {
getHandler: () => handler,
getClass: () => class TestController {},
switchToHttp: () => ({ getRequest: () => request }),
} as any;
}