forked from datawhale/whale-town-end
- 更新管理员控制器和数据库管理功能 - 完善管理员操作日志系统 - 添加全面的属性测试覆盖 - 优化用户管理和用户档案服务 - 更新代码检查规范文档 功能改进: - 增强管理员权限验证 - 完善操作日志记录 - 优化数据库管理接口 - 提升系统安全性和可维护性
284 lines
8.9 KiB
TypeScript
284 lines
8.9 KiB
TypeScript
/**
|
||
* AdminOperationLogController 单元测试
|
||
*
|
||
* 功能描述:
|
||
* - 测试管理员操作日志控制器的所有HTTP端点
|
||
* - 验证请求参数处理和响应格式
|
||
* - 测试权限验证和异常处理
|
||
*
|
||
* 职责分离:
|
||
* - HTTP层测试,不涉及业务逻辑实现
|
||
* - Mock业务服务,专注控制器逻辑
|
||
* - 验证请求响应的正确性
|
||
*
|
||
* 最近修改:
|
||
* - 2026-01-09: 功能新增 - 创建AdminOperationLogController单元测试 (修改者: moyin)
|
||
*
|
||
* @author moyin
|
||
* @version 1.0.0
|
||
* @since 2026-01-09
|
||
* @lastModified 2026-01-09
|
||
*/
|
||
|
||
import { Test, TestingModule } from '@nestjs/testing';
|
||
import { AdminOperationLogController } from './admin_operation_log.controller';
|
||
import { AdminOperationLogService } from './admin_operation_log.service';
|
||
import { AdminGuard } from './admin.guard';
|
||
import { AdminOperationLog } from './admin_operation_log.entity';
|
||
|
||
describe('AdminOperationLogController', () => {
|
||
let controller: AdminOperationLogController;
|
||
let logService: jest.Mocked<AdminOperationLogService>;
|
||
|
||
const mockLogService = {
|
||
queryLogs: jest.fn(),
|
||
getLogById: jest.fn(),
|
||
getStatistics: jest.fn(),
|
||
getSensitiveOperations: jest.fn(),
|
||
getAdminOperationHistory: jest.fn(),
|
||
cleanupExpiredLogs: jest.fn(),
|
||
};
|
||
|
||
beforeEach(async () => {
|
||
const module: TestingModule = await Test.createTestingModule({
|
||
controllers: [AdminOperationLogController],
|
||
providers: [
|
||
{
|
||
provide: AdminOperationLogService,
|
||
useValue: mockLogService,
|
||
},
|
||
],
|
||
})
|
||
.overrideGuard(AdminGuard)
|
||
.useValue({ canActivate: () => true })
|
||
.compile();
|
||
|
||
controller = module.get<AdminOperationLogController>(AdminOperationLogController);
|
||
logService = module.get(AdminOperationLogService);
|
||
});
|
||
|
||
afterEach(() => {
|
||
jest.clearAllMocks();
|
||
});
|
||
|
||
describe('getOperationLogs', () => {
|
||
it('should query logs with default parameters', async () => {
|
||
const mockLogs = [
|
||
{ id: 'log1', operation_type: 'CREATE' },
|
||
{ id: 'log2', operation_type: 'UPDATE' },
|
||
] as AdminOperationLog[];
|
||
|
||
logService.queryLogs.mockResolvedValue({ logs: mockLogs, total: 2 });
|
||
|
||
const result = await controller.getOperationLogs(50, 0);
|
||
|
||
expect(logService.queryLogs).toHaveBeenCalledWith({
|
||
limit: 50,
|
||
offset: 0
|
||
});
|
||
expect(result.success).toBe(true);
|
||
expect(result.data.items).toEqual(mockLogs);
|
||
expect(result.data.total).toBe(2);
|
||
});
|
||
|
||
it('should query logs with custom parameters', async () => {
|
||
const mockLogs = [] as AdminOperationLog[];
|
||
|
||
logService.queryLogs.mockResolvedValue({ logs: mockLogs, total: 0 });
|
||
|
||
const result = await controller.getOperationLogs(
|
||
20,
|
||
10,
|
||
'admin1',
|
||
'CREATE',
|
||
'users',
|
||
'SUCCESS',
|
||
'2026-01-01',
|
||
'2026-01-31',
|
||
'true'
|
||
);
|
||
|
||
expect(logService.queryLogs).toHaveBeenCalledWith({
|
||
adminUserId: 'admin1',
|
||
operationType: 'CREATE',
|
||
targetType: 'users',
|
||
operationResult: 'SUCCESS',
|
||
startDate: new Date('2026-01-01'),
|
||
endDate: new Date('2026-01-31'),
|
||
isSensitive: true,
|
||
limit: 20,
|
||
offset: 10
|
||
});
|
||
expect(result.success).toBe(true);
|
||
});
|
||
|
||
it('should handle invalid date parameters', async () => {
|
||
await expect(controller.getOperationLogs(
|
||
50,
|
||
0,
|
||
undefined,
|
||
undefined,
|
||
undefined,
|
||
undefined,
|
||
'invalid',
|
||
'invalid'
|
||
)).rejects.toThrow('日期格式无效,请使用ISO格式');
|
||
});
|
||
|
||
it('should handle service error', async () => {
|
||
logService.queryLogs.mockRejectedValue(new Error('Database error'));
|
||
|
||
await expect(controller.getOperationLogs(50, 0)).rejects.toThrow('Database error');
|
||
});
|
||
});
|
||
|
||
describe('getOperationLogById', () => {
|
||
it('should get log by id successfully', async () => {
|
||
const mockLog = {
|
||
id: 'log1',
|
||
operation_type: 'CREATE',
|
||
target_type: 'users'
|
||
} as AdminOperationLog;
|
||
|
||
logService.getLogById.mockResolvedValue(mockLog);
|
||
|
||
const result = await controller.getOperationLogById('log1');
|
||
|
||
expect(logService.getLogById).toHaveBeenCalledWith('log1');
|
||
expect(result.success).toBe(true);
|
||
expect(result.data).toEqual(mockLog);
|
||
});
|
||
|
||
it('should handle log not found', async () => {
|
||
logService.getLogById.mockResolvedValue(null);
|
||
|
||
await expect(controller.getOperationLogById('nonexistent')).rejects.toThrow('操作日志不存在');
|
||
});
|
||
|
||
it('should handle service error', async () => {
|
||
logService.getLogById.mockRejectedValue(new Error('Database error'));
|
||
|
||
await expect(controller.getOperationLogById('log1')).rejects.toThrow('Database error');
|
||
});
|
||
});
|
||
|
||
describe('getOperationStatistics', () => {
|
||
it('should get statistics successfully', async () => {
|
||
const mockStats = {
|
||
totalOperations: 100,
|
||
successfulOperations: 80,
|
||
failedOperations: 20,
|
||
operationsByType: { CREATE: 50, UPDATE: 30, DELETE: 20 },
|
||
operationsByTarget: { users: 60, profiles: 40 },
|
||
operationsByAdmin: { admin1: 60, admin2: 40 },
|
||
averageDuration: 150.5,
|
||
sensitiveOperations: 10,
|
||
uniqueAdmins: 5
|
||
};
|
||
|
||
logService.getStatistics.mockResolvedValue(mockStats);
|
||
|
||
const result = await controller.getOperationStatistics();
|
||
|
||
expect(logService.getStatistics).toHaveBeenCalledWith(undefined, undefined);
|
||
expect(result.success).toBe(true);
|
||
expect(result.data).toEqual(mockStats);
|
||
});
|
||
|
||
it('should get statistics with date range', async () => {
|
||
const mockStats = {
|
||
totalOperations: 50,
|
||
successfulOperations: 40,
|
||
failedOperations: 10,
|
||
operationsByType: {},
|
||
operationsByTarget: {},
|
||
operationsByAdmin: {},
|
||
averageDuration: 100,
|
||
sensitiveOperations: 5,
|
||
uniqueAdmins: 3
|
||
};
|
||
|
||
logService.getStatistics.mockResolvedValue(mockStats);
|
||
|
||
const result = await controller.getOperationStatistics('2026-01-01', '2026-01-31');
|
||
|
||
expect(logService.getStatistics).toHaveBeenCalledWith(
|
||
new Date('2026-01-01'),
|
||
new Date('2026-01-31')
|
||
);
|
||
expect(result.success).toBe(true);
|
||
});
|
||
|
||
it('should handle invalid dates', async () => {
|
||
await expect(controller.getOperationStatistics('invalid', 'invalid')).rejects.toThrow('日期格式无效,请使用ISO格式');
|
||
});
|
||
|
||
it('should handle service error', async () => {
|
||
logService.getStatistics.mockRejectedValue(new Error('Statistics error'));
|
||
|
||
await expect(controller.getOperationStatistics()).rejects.toThrow('Statistics error');
|
||
});
|
||
});
|
||
|
||
describe('getSensitiveOperations', () => {
|
||
it('should get sensitive operations successfully', async () => {
|
||
const mockLogs = [
|
||
{ id: 'log1', is_sensitive: true }
|
||
] as AdminOperationLog[];
|
||
|
||
logService.getSensitiveOperations.mockResolvedValue({ logs: mockLogs, total: 1 });
|
||
|
||
const result = await controller.getSensitiveOperations(50, 0);
|
||
|
||
expect(logService.getSensitiveOperations).toHaveBeenCalledWith(50, 0);
|
||
expect(result.success).toBe(true);
|
||
expect(result.data.items).toEqual(mockLogs);
|
||
expect(result.data.total).toBe(1);
|
||
});
|
||
|
||
it('should get sensitive operations with pagination', async () => {
|
||
logService.getSensitiveOperations.mockResolvedValue({ logs: [], total: 0 });
|
||
|
||
const result = await controller.getSensitiveOperations(20, 10);
|
||
|
||
expect(logService.getSensitiveOperations).toHaveBeenCalledWith(20, 10);
|
||
});
|
||
|
||
it('should handle service error', async () => {
|
||
logService.getSensitiveOperations.mockRejectedValue(new Error('Query error'));
|
||
|
||
await expect(controller.getSensitiveOperations(50, 0)).rejects.toThrow('Query error');
|
||
});
|
||
});
|
||
|
||
describe('cleanupExpiredLogs', () => {
|
||
it('should cleanup logs successfully', async () => {
|
||
logService.cleanupExpiredLogs.mockResolvedValue(25);
|
||
|
||
const result = await controller.cleanupExpiredLogs(90);
|
||
|
||
expect(logService.cleanupExpiredLogs).toHaveBeenCalledWith(90);
|
||
expect(result.success).toBe(true);
|
||
expect(result.data.deleted_count).toBe(25);
|
||
});
|
||
|
||
it('should cleanup logs with custom retention days', async () => {
|
||
logService.cleanupExpiredLogs.mockResolvedValue(10);
|
||
|
||
const result = await controller.cleanupExpiredLogs(30);
|
||
|
||
expect(logService.cleanupExpiredLogs).toHaveBeenCalledWith(30);
|
||
expect(result.data.deleted_count).toBe(10);
|
||
});
|
||
|
||
it('should handle invalid retention days', async () => {
|
||
await expect(controller.cleanupExpiredLogs(5)).rejects.toThrow('保留天数必须在7-365天之间');
|
||
});
|
||
|
||
it('should handle service error', async () => {
|
||
logService.cleanupExpiredLogs.mockRejectedValue(new Error('Cleanup error'));
|
||
|
||
await expect(controller.cleanupExpiredLogs(90)).rejects.toThrow('Cleanup error');
|
||
});
|
||
});
|
||
}); |