/** * 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; 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); 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'); }); }); });