feat: 完善管理员系统和用户管理模块

- 更新管理员控制器和数据库管理功能
- 完善管理员操作日志系统
- 添加全面的属性测试覆盖
- 优化用户管理和用户档案服务
- 更新代码检查规范文档

功能改进:
- 增强管理员权限验证
- 完善操作日志记录
- 优化数据库管理接口
- 提升系统安全性和可维护性
This commit is contained in:
moyin
2026-01-09 17:05:08 +08:00
parent 8816b29b0a
commit 5f662ef091
30 changed files with 3881 additions and 599 deletions

View File

@@ -0,0 +1,492 @@
/**
* DatabaseManagementService 单元测试
*
* 功能描述:
* - 测试数据库管理服务的所有方法
* - 验证CRUD操作的正确性
* - 测试异常处理和边界情况
*
* 职责分离:
* - 业务逻辑测试不涉及HTTP层
* - Mock数据库服务专注业务服务逻辑
* - 验证数据处理和格式化的正确性
*
* 最近修改:
* - 2026-01-09: 功能新增 - 创建DatabaseManagementService单元测试 (修改者: moyin)
*
* @author moyin
* @version 1.0.0
* @since 2026-01-09
* @lastModified 2026-01-09
*/
import { Test, TestingModule } from '@nestjs/testing';
import { NotFoundException, ConflictException } from '@nestjs/common';
import { DatabaseManagementService } from './database_management.service';
import { UsersService } from '../../core/db/users/users.service';
import { UserProfilesService } from '../../core/db/user_profiles/user_profiles.service';
import { ZulipAccountsService } from '../../core/db/zulip_accounts/zulip_accounts.service';
import { Users } from '../../core/db/users/users.entity';
import { UserProfiles } from '../../core/db/user_profiles/user_profiles.entity';
describe('DatabaseManagementService', () => {
let service: DatabaseManagementService;
let usersService: jest.Mocked<UsersService>;
let userProfilesService: jest.Mocked<UserProfilesService>;
let zulipAccountsService: jest.Mocked<ZulipAccountsService>;
const mockUsersService = {
findAll: jest.fn(),
findOne: jest.fn(),
search: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
count: jest.fn(),
};
const mockUserProfilesService = {
findAll: jest.fn(),
findOne: jest.fn(),
findByMap: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
count: jest.fn(),
};
const mockZulipAccountsService = {
findMany: jest.fn(),
findById: jest.fn(),
getStatusStatistics: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
batchUpdateStatus: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
DatabaseManagementService,
{
provide: 'UsersService',
useValue: mockUsersService,
},
{
provide: 'IUserProfilesService',
useValue: mockUserProfilesService,
},
{
provide: 'ZulipAccountsService',
useValue: mockZulipAccountsService,
},
],
}).compile();
service = module.get<DatabaseManagementService>(DatabaseManagementService);
usersService = module.get('UsersService');
userProfilesService = module.get('IUserProfilesService');
zulipAccountsService = module.get('ZulipAccountsService');
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getUserList', () => {
it('should return user list successfully', async () => {
const mockUsers = [
{ id: BigInt(1), username: 'user1', email: 'user1@test.com' },
{ id: BigInt(2), username: 'user2', email: 'user2@test.com' }
] as Users[];
usersService.findAll.mockResolvedValue(mockUsers);
usersService.count.mockResolvedValue(2);
const result = await service.getUserList(20, 0);
expect(result.success).toBe(true);
expect(result.data.items).toHaveLength(2);
expect(result.data.total).toBe(2);
expect(result.message).toBe('用户列表获取成功');
});
it('should handle database error', async () => {
usersService.findAll.mockRejectedValue(new Error('Database error'));
const result = await service.getUserList(20, 0);
expect(result.success).toBe(true);
expect(result.data.items).toEqual([]);
expect(result.message).toContain('失败,返回空列表');
});
});
describe('getUserById', () => {
it('should return user by id successfully', async () => {
const mockUser = { id: BigInt(1), username: 'user1', email: 'user1@test.com' } as Users;
usersService.findOne.mockResolvedValue(mockUser);
const result = await service.getUserById(BigInt(1));
expect(result.success).toBe(true);
expect(result.data.id).toBe('1');
expect(result.message).toBe('用户详情获取成功');
});
it('should handle user not found', async () => {
usersService.findOne.mockRejectedValue(new NotFoundException('User not found'));
const result = await service.getUserById(BigInt(999));
expect(result.success).toBe(false);
expect(result.error_code).toBe('RESOURCE_NOT_FOUND');
});
});
describe('searchUsers', () => {
it('should search users successfully', async () => {
const mockUsers = [
{ id: BigInt(1), username: 'admin', email: 'admin@test.com' }
] as Users[];
usersService.search.mockResolvedValue(mockUsers);
const result = await service.searchUsers('admin', 20);
expect(result.success).toBe(true);
expect(result.data.items).toHaveLength(1);
expect(result.message).toBe('用户搜索成功');
});
});
describe('createUser', () => {
it('should create user successfully', async () => {
const userData = { username: 'newuser', email: 'new@test.com', nickname: 'New User' };
const mockUser = { id: BigInt(1), ...userData } as Users;
usersService.create.mockResolvedValue(mockUser);
const result = await service.createUser(userData);
expect(result.success).toBe(true);
expect(result.data.username).toBe('newuser');
expect(result.message).toBe('用户创建成功');
});
it('should handle creation conflict', async () => {
const userData = { username: 'existing', email: 'existing@test.com', nickname: 'Existing' };
usersService.create.mockRejectedValue(new ConflictException('Username already exists'));
const result = await service.createUser(userData);
expect(result.success).toBe(false);
expect(result.error_code).toBe('RESOURCE_CONFLICT');
});
});
describe('updateUser', () => {
it('should update user successfully', async () => {
const updateData = { nickname: 'Updated User' };
const mockUser = { id: BigInt(1), username: 'user1', nickname: 'Updated User' } as Users;
usersService.update.mockResolvedValue(mockUser);
const result = await service.updateUser(BigInt(1), updateData);
expect(result.success).toBe(true);
expect(result.data.nickname).toBe('Updated User');
expect(result.message).toBe('用户更新成功');
});
});
describe('deleteUser', () => {
it('should delete user successfully', async () => {
usersService.remove.mockResolvedValue(undefined);
const result = await service.deleteUser(BigInt(1));
expect(result.success).toBe(true);
expect(result.data.deleted).toBe(true);
expect(result.message).toBe('用户删除成功');
});
});
describe('getUserProfileList', () => {
it('should return user profile list successfully', async () => {
const mockProfiles = [
{ id: BigInt(1), user_id: BigInt(1), bio: 'Test bio' }
] as UserProfiles[];
userProfilesService.findAll.mockResolvedValue(mockProfiles);
userProfilesService.count.mockResolvedValue(1);
const result = await service.getUserProfileList(20, 0);
expect(result.success).toBe(true);
expect(result.data.items).toHaveLength(1);
expect(result.message).toBe('用户档案列表获取成功');
});
});
describe('getUserProfileById', () => {
it('should return user profile by id successfully', async () => {
const mockProfile = { id: BigInt(1), user_id: BigInt(1), bio: 'Test bio' } as UserProfiles;
userProfilesService.findOne.mockResolvedValue(mockProfile);
const result = await service.getUserProfileById(BigInt(1));
expect(result.success).toBe(true);
expect(result.data.id).toBe('1');
expect(result.message).toBe('用户档案详情获取成功');
});
});
describe('getUserProfilesByMap', () => {
it('should return user profiles by map successfully', async () => {
const mockProfiles = [
{ id: BigInt(1), user_id: BigInt(1), current_map: 'plaza' }
] as UserProfiles[];
userProfilesService.findByMap.mockResolvedValue(mockProfiles);
const result = await service.getUserProfilesByMap('plaza', 20, 0);
expect(result.success).toBe(true);
expect(result.data.items).toHaveLength(1);
expect(result.message).toContain('plaza');
});
});
describe('createUserProfile', () => {
it('should create user profile successfully', async () => {
const profileData = {
user_id: '1',
bio: 'Test bio',
resume_content: 'Test resume',
tags: '["tag1"]',
social_links: '{"github":"test"}',
skin_id: '1',
current_map: 'plaza',
pos_x: 100,
pos_y: 200,
status: 1
};
const mockProfile = { id: BigInt(1), user_id: BigInt(1), bio: 'Test bio' } as UserProfiles;
userProfilesService.create.mockResolvedValue(mockProfile);
const result = await service.createUserProfile(profileData);
expect(result.success).toBe(true);
expect(result.message).toBe('用户档案创建成功');
});
});
describe('updateUserProfile', () => {
it('should update user profile successfully', async () => {
const updateData = { bio: 'Updated bio' };
const mockProfile = { id: BigInt(1), user_id: BigInt(1), bio: 'Updated bio' } as UserProfiles;
userProfilesService.update.mockResolvedValue(mockProfile);
const result = await service.updateUserProfile(BigInt(1), updateData);
expect(result.success).toBe(true);
expect(result.message).toBe('用户档案更新成功');
});
});
describe('deleteUserProfile', () => {
it('should delete user profile successfully', async () => {
userProfilesService.remove.mockResolvedValue({ affected: 1, message: 'Deleted successfully' });
const result = await service.deleteUserProfile(BigInt(1));
expect(result.success).toBe(true);
expect(result.data.deleted).toBe(true);
expect(result.message).toBe('用户档案删除成功');
});
});
describe('getZulipAccountList', () => {
it('should return zulip account list successfully', async () => {
const mockAccounts = {
accounts: [{
id: '1',
gameUserId: '1',
zulipUserId: 123,
zulipEmail: 'test@zulip.com',
zulipFullName: 'Test User',
status: 'active' as const,
retryCount: 0,
createdAt: '2026-01-09T00:00:00.000Z',
updatedAt: '2026-01-09T00:00:00.000Z'
}],
total: 1,
count: 1
};
zulipAccountsService.findMany.mockResolvedValue(mockAccounts);
const result = await service.getZulipAccountList(20, 0);
expect(result.success).toBe(true);
expect(result.data.items).toHaveLength(1);
expect(result.message).toBe('Zulip账号关联列表获取成功');
});
});
describe('getZulipAccountById', () => {
it('should return zulip account by id successfully', async () => {
const mockAccount = {
id: '1',
gameUserId: '1',
zulipUserId: 123,
zulipEmail: 'test@zulip.com',
zulipFullName: 'Test User',
status: 'active' as const,
retryCount: 0,
createdAt: '2026-01-09T00:00:00.000Z',
updatedAt: '2026-01-09T00:00:00.000Z'
};
zulipAccountsService.findById.mockResolvedValue(mockAccount);
const result = await service.getZulipAccountById('1');
expect(result.success).toBe(true);
expect(result.data.id).toBe('1');
expect(result.message).toBe('Zulip账号关联详情获取成功');
});
});
describe('getZulipAccountStatistics', () => {
it('should return zulip account statistics successfully', async () => {
const mockStats = {
active: 10,
inactive: 5,
suspended: 2,
error: 1,
total: 18
};
zulipAccountsService.getStatusStatistics.mockResolvedValue(mockStats);
const result = await service.getZulipAccountStatistics();
expect(result.success).toBe(true);
expect(result.data).toEqual(mockStats);
expect(result.message).toBe('Zulip账号关联统计获取成功');
});
});
describe('createZulipAccount', () => {
it('should create zulip account successfully', async () => {
const accountData = {
gameUserId: '1',
zulipUserId: 123,
zulipEmail: 'test@zulip.com',
zulipFullName: 'Test User',
zulipApiKeyEncrypted: 'encrypted_key'
};
const mockAccount = {
id: '1',
gameUserId: '1',
zulipUserId: 123,
zulipEmail: 'test@zulip.com',
zulipFullName: 'Test User',
zulipApiKeyEncrypted: 'encrypted_key',
status: 'active' as const,
retryCount: 0,
createdAt: '2026-01-09T00:00:00.000Z',
updatedAt: '2026-01-09T00:00:00.000Z'
};
zulipAccountsService.create.mockResolvedValue(mockAccount);
const result = await service.createZulipAccount(accountData);
expect(result.success).toBe(true);
expect(result.message).toBe('Zulip账号关联创建成功');
});
});
describe('updateZulipAccount', () => {
it('should update zulip account successfully', async () => {
const updateData = { zulipFullName: 'Updated Name' };
const mockAccount = {
id: '1',
gameUserId: '1',
zulipUserId: 123,
zulipEmail: 'test@zulip.com',
zulipFullName: 'Updated Name',
status: 'active' as const,
retryCount: 0,
createdAt: '2026-01-09T00:00:00.000Z',
updatedAt: '2026-01-09T00:00:00.000Z'
};
zulipAccountsService.update.mockResolvedValue(mockAccount);
const result = await service.updateZulipAccount('1', updateData);
expect(result.success).toBe(true);
expect(result.message).toBe('Zulip账号关联更新成功');
});
});
describe('deleteZulipAccount', () => {
it('should delete zulip account successfully', async () => {
zulipAccountsService.delete.mockResolvedValue(true);
const result = await service.deleteZulipAccount('1');
expect(result.success).toBe(true);
expect(result.data.deleted).toBe(true);
expect(result.message).toBe('Zulip账号关联删除成功');
});
});
describe('batchUpdateZulipAccountStatus', () => {
it('should batch update zulip account status successfully', async () => {
const ids = ['1', '2', '3'];
const status = 'active';
const reason = 'Batch activation';
zulipAccountsService.batchUpdateStatus.mockResolvedValue({
success: true,
updatedCount: 3
});
const result = await service.batchUpdateZulipAccountStatus(ids, status, reason);
expect(result.success).toBe(true);
expect(result.data.success_count).toBe(3);
expect(result.data.failed_count).toBe(0);
expect(result.message).toContain('成功3失败0');
});
it('should handle partial batch update failure', async () => {
const ids = ['1', '2', '3'];
const status = 'active';
zulipAccountsService.batchUpdateStatus.mockResolvedValue({
success: true,
updatedCount: 2
});
const result = await service.batchUpdateZulipAccountStatus(ids, status);
expect(result.success).toBe(true);
expect(result.data.success_count).toBe(2);
expect(result.data.failed_count).toBe(1);
});
});
});