/** * 用户状态管理控制器测试 * * 功能描述: * - 测试用户状态管理API接口 * - 测试HTTP请求处理和参数验证 * - 测试权限控制和频率限制 * - 测试响应格式和错误处理 * * 职责分离: * - 单元测试覆盖所有API端点 * - Mock业务服务依赖 * - 验证请求参数和响应格式 * * 最近修改: * - 2026-01-07: 代码规范优化 - 创建完整的控制器测试覆盖 (修改者: moyin) * * @author moyin * @version 1.0.1 * @since 2026-01-07 * @lastModified 2026-01-07 */ import { Test, TestingModule } from '@nestjs/testing'; import { Logger } from '@nestjs/common'; import { UserStatusController } from './user_status.controller'; import { UserManagementService } from './user_management.service'; import { AdminGuard } from '../admin/admin.guard'; import { UserStatusDto, BatchUserStatusDto } from './user_status.dto'; import { UserStatus } from './user_status.enum'; import { BATCH_OPERATION } from './user_mgmt.constants'; describe('UserStatusController', () => { let controller: UserStatusController; let mockUserManagementService: jest.Mocked; beforeEach(async () => { const mockUserManagementServiceProvider = { updateUserStatus: jest.fn(), batchUpdateUserStatus: jest.fn(), getUserStatusStats: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ controllers: [UserStatusController], providers: [ { provide: UserManagementService, useValue: mockUserManagementServiceProvider, }, ], }) .overrideGuard(AdminGuard) .useValue({ canActivate: jest.fn(() => true) }) .compile(); controller = module.get(UserStatusController); mockUserManagementService = module.get(UserManagementService); // Mock Logger to avoid console output during tests jest.spyOn(Logger.prototype, 'log').mockImplementation(); }); afterEach(() => { jest.clearAllMocks(); }); describe('updateUserStatus', () => { it('should update user status successfully', async () => { // Arrange const userId = '123'; const userStatusDto: UserStatusDto = { status: UserStatus.ACTIVE, reason: '用户申诉通过' }; const expectedResult = { success: true, data: { user: { id: '123', username: 'testuser', nickname: '测试用户', status: UserStatus.ACTIVE, status_description: '正常', updated_at: new Date() }, reason: '用户申诉通过' }, message: '用户状态修改成功' }; mockUserManagementService.updateUserStatus.mockResolvedValue(expectedResult); // Act const result = await controller.updateUserStatus(userId, userStatusDto); // Assert expect(result).toEqual(expectedResult); expect(mockUserManagementService.updateUserStatus).toHaveBeenCalledWith(BigInt(123), userStatusDto); expect(mockUserManagementService.updateUserStatus).toHaveBeenCalledTimes(1); }); it('should handle user not found error', async () => { // Arrange const userId = '999'; const userStatusDto: UserStatusDto = { status: UserStatus.LOCKED, reason: '违规操作' }; const expectedResult = { success: false, message: '用户不存在', error_code: 'USER_NOT_FOUND' }; mockUserManagementService.updateUserStatus.mockResolvedValue(expectedResult); // Act const result = await controller.updateUserStatus(userId, userStatusDto); // Assert expect(result).toEqual(expectedResult); expect(mockUserManagementService.updateUserStatus).toHaveBeenCalledWith(BigInt(999), userStatusDto); }); it('should log operation details', async () => { // Arrange const userId = '456'; const userStatusDto: UserStatusDto = { status: UserStatus.BANNED, reason: '严重违规' }; const mockResult = { success: true, data: { user: { id: '456', username: 'testuser', nickname: '测试用户', status: UserStatus.BANNED, status_description: '已封禁', updated_at: new Date() }, reason: '严重违规' }, message: '成功' }; mockUserManagementService.updateUserStatus.mockResolvedValue(mockResult); const logSpy = jest.spyOn(Logger.prototype, 'log'); // Act await controller.updateUserStatus(userId, userStatusDto); // Assert expect(logSpy).toHaveBeenCalledWith( '管理员修改用户状态', expect.objectContaining({ operation: 'update_user_status', userId: '456', newStatus: UserStatus.BANNED, reason: '严重违规' }) ); }); it('should convert string id to BigInt correctly', async () => { // Arrange const userId = '9007199254740991'; // Large number as string const userStatusDto: UserStatusDto = { status: UserStatus.INACTIVE, reason: '长期未活跃' }; const mockResult = { success: true, data: { user: { id: '9007199254740991', username: 'large_id_user', nickname: '大ID用户', status: UserStatus.INACTIVE, status_description: '非活跃', updated_at: new Date() }, reason: '长期未活跃' }, message: '成功' }; mockUserManagementService.updateUserStatus.mockResolvedValue(mockResult); // Act await controller.updateUserStatus(userId, userStatusDto); // Assert expect(mockUserManagementService.updateUserStatus).toHaveBeenCalledWith( BigInt('9007199254740991'), userStatusDto ); }); }); describe('batchUpdateUserStatus', () => { it('should batch update user status successfully', async () => { // Arrange const batchUserStatusDto: BatchUserStatusDto = { userIds: ['1', '2', '3'], status: UserStatus.LOCKED, reason: '批量锁定违规用户' }; const expectedResult = { success: true, data: { result: { success_users: [ { id: '1', username: 'user1', nickname: '用户1', status: UserStatus.LOCKED, status_description: '已锁定', updated_at: new Date() }, { id: '2', username: 'user2', nickname: '用户2', status: UserStatus.LOCKED, status_description: '已锁定', updated_at: new Date() }, { id: '3', username: 'user3', nickname: '用户3', status: UserStatus.LOCKED, status_description: '已锁定', updated_at: new Date() } ], failed_users: [], success_count: 3, failed_count: 0, total_count: 3 }, reason: '批量锁定违规用户' }, message: '批量用户状态修改完成' }; mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(expectedResult); // Act const result = await controller.batchUpdateUserStatus(batchUserStatusDto); // Assert expect(result).toEqual(expectedResult); expect(mockUserManagementService.batchUpdateUserStatus).toHaveBeenCalledWith(batchUserStatusDto); expect(mockUserManagementService.batchUpdateUserStatus).toHaveBeenCalledTimes(1); }); it('should handle partial success in batch operation', async () => { // Arrange const batchUserStatusDto: BatchUserStatusDto = { userIds: ['1', '2', '999'], status: UserStatus.ACTIVE, reason: '批量激活用户' }; const expectedResult = { success: true, data: { result: { success_users: [ { id: '1', username: 'user1', nickname: '用户1', status: UserStatus.ACTIVE, status_description: '正常', updated_at: new Date() }, { id: '2', username: 'user2', nickname: '用户2', status: UserStatus.ACTIVE, status_description: '正常', updated_at: new Date() } ], failed_users: [ { user_id: '999', error: '用户不存在' } ], success_count: 2, failed_count: 1, total_count: 3 }, reason: '批量激活用户' }, message: '批量用户状态修改完成' }; mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(expectedResult); // Act const result = await controller.batchUpdateUserStatus(batchUserStatusDto); // Assert expect(result).toEqual(expectedResult); expect(result.data.result.success_count).toBe(2); expect(result.data.result.failed_count).toBe(1); }); it('should handle empty user list', async () => { // Arrange const batchUserStatusDto: BatchUserStatusDto = { userIds: [], status: UserStatus.ACTIVE, reason: '空列表测试' }; const expectedResult = { success: true, data: { result: { success_users: [], failed_users: [], success_count: 0, failed_count: 0, total_count: 0 }, reason: '空列表测试' }, message: '批量操作完成' }; mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(expectedResult); // Act const result = await controller.batchUpdateUserStatus(batchUserStatusDto); // Assert expect(result).toEqual(expectedResult); expect(result.data.result.total_count).toBe(0); }); it('should log batch operation details', async () => { // Arrange const batchUserStatusDto: BatchUserStatusDto = { userIds: ['1', '2', '3', '4', '5'], status: UserStatus.BANNED, reason: '批量封禁违规用户' }; const mockResult = { success: true, data: { result: { success_users: [], failed_users: [], success_count: 0, failed_count: 0, total_count: 0 } }, message: '成功' }; mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(mockResult); const logSpy = jest.spyOn(Logger.prototype, 'log'); // Act await controller.batchUpdateUserStatus(batchUserStatusDto); // Assert expect(logSpy).toHaveBeenCalledWith( '管理员批量修改用户状态', expect.objectContaining({ operation: 'batch_update_user_status', userCount: 5, newStatus: UserStatus.BANNED, reason: '批量封禁违规用户' }) ); }); it('should handle large user list within limits', async () => { // Arrange const userIds = Array.from({ length: BATCH_OPERATION.MAX_USER_COUNT }, (_, i) => i.toString()); const batchUserStatusDto: BatchUserStatusDto = { userIds, status: UserStatus.INACTIVE, reason: '批量设置非活跃' }; const mockResult = { success: true, data: { result: { success_users: userIds.map(id => ({ id, username: `user_${id}`, nickname: `用户_${id}`, status: UserStatus.INACTIVE, status_description: '非活跃', updated_at: new Date() })), failed_users: [], success_count: userIds.length, failed_count: 0, total_count: userIds.length } }, message: '批量操作完成' }; mockUserManagementService.batchUpdateUserStatus.mockResolvedValue(mockResult); // Act const result = await controller.batchUpdateUserStatus(batchUserStatusDto); // Assert expect(result.success).toBe(true); expect(result.data.result.total_count).toBe(BATCH_OPERATION.MAX_USER_COUNT); expect(mockUserManagementService.batchUpdateUserStatus).toHaveBeenCalledWith(batchUserStatusDto); }); }); describe('getUserStatusStats', () => { it('should get user status statistics successfully', async () => { // Arrange const expectedResult = { success: true, data: { stats: { active: 1250, inactive: 45, locked: 12, banned: 8, deleted: 3, pending: 15, total: 1333 }, timestamp: '2026-01-07T10:00:00.000Z' }, message: '用户状态统计获取成功' }; mockUserManagementService.getUserStatusStats.mockResolvedValue(expectedResult); // Act const result = await controller.getUserStatusStats(); // Assert expect(result).toEqual(expectedResult); expect(mockUserManagementService.getUserStatusStats).toHaveBeenCalledTimes(1); }); it('should handle statistics retrieval failure', async () => { // Arrange const expectedResult = { success: false, message: '统计数据获取失败', error_code: 'STATS_RETRIEVAL_FAILED' }; mockUserManagementService.getUserStatusStats.mockResolvedValue(expectedResult); // Act const result = await controller.getUserStatusStats(); // Assert expect(result).toEqual(expectedResult); expect(result.success).toBe(false); }); it('should log statistics query operation', async () => { // Arrange const mockResult = { success: true, data: { stats: { active: 800, inactive: 150, locked: 30, banned: 15, deleted: 5, pending: 20, total: 1020 }, timestamp: '2026-01-07T15:30:00.000Z' }, message: '成功' }; mockUserManagementService.getUserStatusStats.mockResolvedValue(mockResult); const logSpy = jest.spyOn(Logger.prototype, 'log'); // Act await controller.getUserStatusStats(); // Assert expect(logSpy).toHaveBeenCalledWith( '管理员获取用户状态统计', expect.objectContaining({ operation: 'get_user_status_stats' }) ); }); it('should return detailed statistics breakdown', async () => { // Arrange const expectedResult = { success: true, data: { stats: { active: 800, inactive: 150, locked: 30, banned: 15, deleted: 5, pending: 20, total: 1020 }, timestamp: '2026-01-07T15:30:00.000Z', metadata: { last_updated: '2026-01-07T15:30:00.000Z', cache_duration: 300 } }, message: '用户状态统计获取成功' }; mockUserManagementService.getUserStatusStats.mockResolvedValue(expectedResult); // Act const result = await controller.getUserStatusStats(); // Assert expect(result.data.stats.total).toBe(1020); expect(result.data.stats.active).toBe(800); expect(result.data.stats.locked).toBe(30); expect(result.data.stats.banned).toBe(15); }); it('should handle zero statistics gracefully', async () => { // Arrange const expectedResult = { success: true, data: { stats: { active: 0, inactive: 0, locked: 0, banned: 0, deleted: 0, pending: 0, total: 0 }, timestamp: '2026-01-07T10:00:00.000Z' }, message: '用户状态统计获取成功' }; mockUserManagementService.getUserStatusStats.mockResolvedValue(expectedResult); // Act const result = await controller.getUserStatusStats(); // Assert expect(result.data.stats.total).toBe(0); expect(result.success).toBe(true); }); }); describe('AdminGuard Integration', () => { it('should be protected by AdminGuard', () => { // Verify that AdminGuard is applied to the controller methods const updateUserStatusMethod = Reflect.getMetadata('__guards__', UserStatusController.prototype.updateUserStatus); const batchUpdateMethod = Reflect.getMetadata('__guards__', UserStatusController.prototype.batchUpdateUserStatus); const getStatsMethod = Reflect.getMetadata('__guards__', UserStatusController.prototype.getUserStatusStats); // At least one method should have guards (they are applied via @UseGuards decorator) expect(updateUserStatusMethod || batchUpdateMethod || getStatsMethod).toBeTruthy(); }); }); describe('Error Handling', () => { it('should handle service errors gracefully in updateUserStatus', async () => { // Arrange const userId = '123'; const userStatusDto: UserStatusDto = { status: UserStatus.ACTIVE, reason: '测试错误处理' }; mockUserManagementService.updateUserStatus.mockRejectedValue(new Error('Service error')); // Act & Assert await expect(controller.updateUserStatus(userId, userStatusDto)).rejects.toThrow('Service error'); }); it('should handle service errors gracefully in batchUpdateUserStatus', async () => { // Arrange const batchUserStatusDto: BatchUserStatusDto = { userIds: ['1', '2'], status: UserStatus.ACTIVE, reason: '测试错误处理' }; mockUserManagementService.batchUpdateUserStatus.mockRejectedValue(new Error('Batch service error')); // Act & Assert await expect(controller.batchUpdateUserStatus(batchUserStatusDto)).rejects.toThrow('Batch service error'); }); it('should handle service errors gracefully in getUserStatusStats', async () => { // Arrange mockUserManagementService.getUserStatusStats.mockRejectedValue(new Error('Stats service error')); // Act & Assert await expect(controller.getUserStatusStats()).rejects.toThrow('Stats service error'); }); }); });