- 更新管理员控制器和数据库管理功能 - 完善管理员操作日志系统 - 添加全面的属性测试覆盖 - 优化用户管理和用户档案服务 - 更新代码检查规范文档 功能改进: - 增强管理员权限验证 - 完善操作日志记录 - 优化数据库管理接口 - 提升系统安全性和可维护性
492 lines
15 KiB
TypeScript
492 lines
15 KiB
TypeScript
/**
|
||
* 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);
|
||
});
|
||
});
|
||
}); |