112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
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<string, unknown>) {
|
|
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: '保存失败',
|
|
},
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
});
|