Files
whale-town-end/src/business/user_mgmt/user_status.controller.spec.ts
moyin 5f662ef091 feat: 完善管理员系统和用户管理模块
- 更新管理员控制器和数据库管理功能
- 完善管理员操作日志系统
- 添加全面的属性测试覆盖
- 优化用户管理和用户档案服务
- 更新代码检查规范文档

功能改进:
- 增强管理员权限验证
- 完善操作日志记录
- 优化数据库管理接口
- 提升系统安全性和可维护性
2026-01-09 17:05:08 +08:00

586 lines
18 KiB
TypeScript

/**
* 用户状态管理控制器测试
*
* 功能描述:
* - 测试用户状态管理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<UserManagementService>;
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>(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');
});
});
});