refactor:项目架构重构和命名规范化
- 统一文件命名为snake_case格式(kebab-case snake_case) - 重构zulip模块为zulip_core,明确Core层职责 - 重构user-mgmt模块为user_mgmt,统一命名规范 - 调整模块依赖关系,优化架构分层 - 删除过时的文件和目录结构 - 更新相关文档和配置文件 本次重构涉及大量文件重命名和模块重组, 旨在建立更清晰的项目架构和统一的命名规范。
This commit is contained in:
453
src/business/user_mgmt/user_management.service.spec.ts
Normal file
453
src/business/user_mgmt/user_management.service.spec.ts
Normal file
@@ -0,0 +1,453 @@
|
||||
/**
|
||||
* 用户管理业务服务测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试用户状态管理业务逻辑
|
||||
* - 测试批量用户操作功能
|
||||
* - 测试用户状态统计功能
|
||||
* - 测试状态变更审计功能
|
||||
*
|
||||
* 职责分离:
|
||||
* - 单元测试覆盖所有公共方法
|
||||
* - 异常情况和边界情况测试
|
||||
* - 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 { UserManagementService } from './user_management.service';
|
||||
import { AdminService } from '../admin/admin.service';
|
||||
import { UserStatusDto, BatchUserStatusDto } from './user_status.dto';
|
||||
import { UserStatus } from './user_status.enum';
|
||||
import { BATCH_OPERATION, ERROR_CODES, MESSAGES } from './user_mgmt.constants';
|
||||
|
||||
describe('UserManagementService', () => {
|
||||
let service: UserManagementService;
|
||||
let mockAdminService: jest.Mocked<AdminService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockAdminServiceProvider = {
|
||||
updateUserStatus: jest.fn(),
|
||||
batchUpdateUserStatus: jest.fn(),
|
||||
getUserStatusStats: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
UserManagementService,
|
||||
{
|
||||
provide: AdminService,
|
||||
useValue: mockAdminServiceProvider,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UserManagementService>(UserManagementService);
|
||||
mockAdminService = module.get(AdminService);
|
||||
|
||||
// Mock Logger to avoid console output during tests
|
||||
jest.spyOn(Logger.prototype, 'log').mockImplementation();
|
||||
jest.spyOn(Logger.prototype, 'warn').mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('updateUserStatus', () => {
|
||||
it('should update user status successfully', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(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: '用户状态修改成功'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockAdminService.updateUserStatus).toHaveBeenCalledWith(userId, userStatusDto);
|
||||
expect(mockAdminService.updateUserStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle update failure', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(999);
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '违规操作'
|
||||
};
|
||||
const expectedResult = {
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
error_code: 'USER_NOT_FOUND'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockAdminService.updateUserStatus).toHaveBeenCalledWith(userId, userStatusDto);
|
||||
});
|
||||
|
||||
it('should log success when update succeeds', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(123);
|
||||
const userStatusDto: UserStatusDto = {
|
||||
status: UserStatus.ACTIVE,
|
||||
reason: '测试'
|
||||
};
|
||||
const successResult = {
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: '123',
|
||||
username: 'testuser',
|
||||
nickname: '测试用户',
|
||||
status: UserStatus.ACTIVE,
|
||||
status_description: '正常',
|
||||
updated_at: new Date()
|
||||
},
|
||||
reason: '测试'
|
||||
},
|
||||
message: '成功'
|
||||
};
|
||||
|
||||
mockAdminService.updateUserStatus.mockResolvedValue(successResult);
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await service.updateUserStatus(userId, userStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'用户管理:用户状态修改成功',
|
||||
expect.objectContaining({
|
||||
operation: 'user_mgmt_update_status_success',
|
||||
userId: '123',
|
||||
newStatus: UserStatus.ACTIVE
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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: [],
|
||||
failed_users: [],
|
||||
success_count: 3,
|
||||
failed_count: 0,
|
||||
total_count: 3
|
||||
},
|
||||
reason: '批量锁定违规用户'
|
||||
},
|
||||
message: '批量用户状态修改完成'
|
||||
};
|
||||
|
||||
mockAdminService.batchUpdateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockAdminService.batchUpdateUserStatus).toHaveBeenCalledWith(batchUserStatusDto);
|
||||
});
|
||||
|
||||
it('should reject batch operation when user count exceeds limit', async () => {
|
||||
// Arrange
|
||||
const userIds = Array.from({ length: BATCH_OPERATION.MAX_USER_COUNT + 1 }, (_, i) => i.toString());
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds,
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '超限测试'
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = await service.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
message: MESSAGES.BATCH_OPERATION_LIMIT_ERROR,
|
||||
error_code: ERROR_CODES.BATCH_OPERATION_LIMIT_EXCEEDED
|
||||
});
|
||||
expect(mockAdminService.batchUpdateUserStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
message: '批量操作完成'
|
||||
};
|
||||
|
||||
mockAdminService.batchUpdateUserStatus.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('should log warning when batch operation exceeds limit', async () => {
|
||||
// Arrange
|
||||
const userIds = Array.from({ length: BATCH_OPERATION.MAX_USER_COUNT + 1 }, (_, i) => i.toString());
|
||||
const batchUserStatusDto: BatchUserStatusDto = {
|
||||
userIds,
|
||||
status: UserStatus.LOCKED,
|
||||
reason: '超限测试'
|
||||
};
|
||||
const warnSpy = jest.spyOn(Logger.prototype, 'warn');
|
||||
|
||||
// Act
|
||||
await service.batchUpdateUserStatus(batchUserStatusDto);
|
||||
|
||||
// Assert
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'用户管理:批量操作数量超限',
|
||||
expect.objectContaining({
|
||||
operation: 'user_mgmt_batch_update_limit_exceeded',
|
||||
requestCount: BATCH_OPERATION.MAX_USER_COUNT + 1,
|
||||
maxAllowed: BATCH_OPERATION.MAX_USER_COUNT
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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: '用户状态统计获取成功'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(mockAdminService.getUserStatusStats).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle statistics retrieval failure', async () => {
|
||||
// Arrange
|
||||
const expectedResult = {
|
||||
success: false,
|
||||
message: '统计数据获取失败',
|
||||
error_code: 'STATS_RETRIEVAL_FAILED'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(expectedResult);
|
||||
|
||||
// Act
|
||||
const result = await service.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('should calculate business metrics when stats are available', async () => {
|
||||
// Arrange
|
||||
const statsResult = {
|
||||
success: true,
|
||||
data: {
|
||||
stats: {
|
||||
active: 80,
|
||||
inactive: 10,
|
||||
locked: 5,
|
||||
banned: 3,
|
||||
deleted: 2,
|
||||
pending: 0,
|
||||
total: 100
|
||||
},
|
||||
timestamp: '2026-01-07T10:00:00.000Z'
|
||||
},
|
||||
message: '成功'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(statsResult);
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await service.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'用户管理:用户状态统计分析',
|
||||
expect.objectContaining({
|
||||
operation: 'user_mgmt_status_analysis',
|
||||
totalUsers: 100,
|
||||
activeUsers: 80,
|
||||
activeRate: '80.00%',
|
||||
problemUsers: 10 // locked + banned + deleted
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle zero total users in statistics', async () => {
|
||||
// Arrange
|
||||
const statsResult = {
|
||||
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: '成功'
|
||||
};
|
||||
|
||||
mockAdminService.getUserStatusStats.mockResolvedValue(statsResult);
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await service.getUserStatusStats();
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'用户管理:用户状态统计分析',
|
||||
expect.objectContaining({
|
||||
activeRate: '0%'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserStatusHistory', () => {
|
||||
it('should return mock history data with default limit', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(123);
|
||||
|
||||
// Act
|
||||
const result = await service.getUserStatusHistory(userId);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: {
|
||||
user_id: '123',
|
||||
history: [],
|
||||
total_count: 0
|
||||
},
|
||||
message: '状态变更历史获取成功(当前返回空数据,待实现完整功能)'
|
||||
});
|
||||
});
|
||||
|
||||
it('should return mock history data with custom limit', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(456);
|
||||
const customLimit = 20;
|
||||
|
||||
// Act
|
||||
const result = await service.getUserStatusHistory(userId, customLimit);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: {
|
||||
user_id: '456',
|
||||
history: [],
|
||||
total_count: 0
|
||||
},
|
||||
message: '状态变更历史获取成功(当前返回空数据,待实现完整功能)'
|
||||
});
|
||||
});
|
||||
|
||||
it('should log history query operation', async () => {
|
||||
// Arrange
|
||||
const userId = BigInt(789);
|
||||
const limit = 15;
|
||||
const logSpy = jest.spyOn(Logger.prototype, 'log');
|
||||
|
||||
// Act
|
||||
await service.getUserStatusHistory(userId, limit);
|
||||
|
||||
// Assert
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'用户管理:获取用户状态变更历史',
|
||||
expect.objectContaining({
|
||||
operation: 'user_mgmt_get_status_history',
|
||||
userId: '789',
|
||||
limit: 15
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user