/** * DatabaseManagementService 单元测试 * * 测试目标: * - 验证服务类各个方法的具体实现 * - 测试边界条件和异常情况 * - 确保代码覆盖率达标 * * 最近修改: * - 2026-01-08: 注释规范优化 - 修正@author字段,更新版本号和修改记录 (修改者: moyin) * - 2026-01-08: 功能新增 - 创建DatabaseManagementService单元测试 (修改者: assistant) * * @author moyin * @version 1.0.1 * @since 2026-01-08 * @lastModified 2026-01-08 */ import { Test, TestingModule } from '@nestjs/testing'; import { ConfigModule } from '@nestjs/config'; import { DatabaseManagementService } from './database_management.service'; import { AdminOperationLogService } from './admin_operation_log.service'; import { UserStatus } from '../user_mgmt/user_status.enum'; describe('DatabaseManagementService Unit Tests', () => { let service: DatabaseManagementService; let mockUsersService: any; let mockUserProfilesService: any; let mockZulipAccountsService: any; let mockLogService: any; beforeEach(async () => { mockUsersService = { findAll: jest.fn(), findOne: jest.fn(), create: jest.fn(), update: jest.fn(), remove: jest.fn(), search: jest.fn(), count: jest.fn() }; mockUserProfilesService = { findAll: jest.fn(), findOne: jest.fn(), create: jest.fn(), update: jest.fn(), remove: jest.fn(), findByMap: jest.fn(), count: jest.fn() }; mockZulipAccountsService = { findMany: jest.fn(), findById: jest.fn(), create: jest.fn(), update: jest.fn(), delete: jest.fn(), batchUpdateStatus: jest.fn().mockResolvedValue({ success: true, updatedCount: 0 }), getStatusStatistics: jest.fn() }; mockLogService = { createLog: jest.fn().mockResolvedValue({}), queryLogs: jest.fn().mockResolvedValue({ logs: [], total: 0 }), getLogById: jest.fn().mockResolvedValue(null), getStatistics: jest.fn().mockResolvedValue({}), cleanupExpiredLogs: jest.fn().mockResolvedValue(0), getAdminOperationHistory: jest.fn().mockResolvedValue([]), getSensitiveOperations: jest.fn().mockResolvedValue({ logs: [], total: 0 }) }; const module: TestingModule = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: ['.env.test', '.env'] }) ], providers: [ DatabaseManagementService, { provide: AdminOperationLogService, useValue: mockLogService }, { provide: 'UsersService', useValue: mockUsersService }, { provide: 'IUserProfilesService', useValue: mockUserProfilesService }, { provide: 'ZulipAccountsService', useValue: mockZulipAccountsService } ] }).compile(); service = module.get(DatabaseManagementService); }); describe('User Management', () => { describe('getUserList', () => { it('should return paginated user list with correct format', async () => { const mockUsers = [ { id: BigInt(1), username: 'user1', email: 'user1@test.com' }, { id: BigInt(2), username: 'user2', email: 'user2@test.com' } ]; const totalCount = 10; mockUsersService.findAll.mockResolvedValue(mockUsers); mockUsersService.count.mockResolvedValue(totalCount); const result = await service.getUserList(5, 0); expect(result.success).toBe(true); expect(result.data.items).toEqual(mockUsers.map(u => ({ ...u, id: u.id.toString() }))); expect(result.data.total).toBe(totalCount); expect(result.data.limit).toBe(5); expect(result.data.offset).toBe(0); expect(result.data.has_more).toBe(true); }); it('should handle empty result set', async () => { mockUsersService.findAll.mockResolvedValue([]); mockUsersService.count.mockResolvedValue(0); const result = await service.getUserList(10, 0); expect(result.success).toBe(true); expect(result.data.items).toEqual([]); expect(result.data.total).toBe(0); expect(result.data.has_more).toBe(false); }); it('should apply limit and offset correctly', async () => { const mockUsers = [{ id: BigInt(1), username: 'user1' }]; mockUsersService.findAll.mockResolvedValue(mockUsers); mockUsersService.count.mockResolvedValue(1); await service.getUserList(20, 10); expect(mockUsersService.findAll).toHaveBeenCalledWith(undefined, 20, 10); }); it('should enforce maximum limit', async () => { mockUsersService.findAll.mockResolvedValue([]); mockUsersService.count.mockResolvedValue(0); await service.getUserList(200, 0); // 超过最大限制 expect(mockUsersService.findAll).toHaveBeenCalledWith(undefined, 100, 0); }); it('should handle negative offset', async () => { mockUsersService.findAll.mockResolvedValue([]); mockUsersService.count.mockResolvedValue(0); await service.getUserList(10, -5); expect(mockUsersService.findAll).toHaveBeenCalledWith(undefined, 10, 0); }); }); describe('getUserById', () => { it('should return user when found', async () => { const mockUser = { id: BigInt(1), username: 'testuser', email: 'test@example.com' }; mockUsersService.findOne.mockResolvedValue(mockUser); const result = await service.getUserById(BigInt(1)); expect(result.success).toBe(true); expect(result.data).toEqual({ ...mockUser, id: '1' }); expect(mockUsersService.findOne).toHaveBeenCalledWith(BigInt(1)); }); it('should return error when user not found', async () => { mockUsersService.findOne.mockResolvedValue(null); const result = await service.getUserById(BigInt(999)); expect(result.success).toBe(false); expect(result.error_code).toBe('USER_NOT_FOUND'); expect(result.message).toContain('User with ID 999 not found'); }); it('should handle invalid ID format', async () => { const result = await service.getUserById(BigInt(0)); // 使用有效的 bigint expect(result.success).toBe(false); expect(result.error_code).toBe('INVALID_USER_ID'); }); it('should handle service errors', async () => { mockUsersService.findOne.mockRejectedValue(new Error('Database error')); const result = await service.getUserById(BigInt(1)); expect(result.success).toBe(false); expect(result.error_code).toBe('DATABASE_ERROR'); }); }); describe('createUser', () => { it('should create user successfully', async () => { const userData = { username: 'newuser', email: 'new@example.com', nickname: 'New User', status: UserStatus.ACTIVE }; const createdUser = { ...userData, id: BigInt(1) }; mockUsersService.create.mockResolvedValue(createdUser); const result = await service.createUser(userData); expect(result.success).toBe(true); expect(result.data).toEqual({ ...createdUser, id: '1' }); expect(mockUsersService.create).toHaveBeenCalledWith(userData); }); it('should handle duplicate username error', async () => { const userData = { username: 'existing', email: 'test@example.com', nickname: 'Existing User', status: UserStatus.ACTIVE }; mockUsersService.create.mockRejectedValue(new Error('Duplicate key violation')); const result = await service.createUser(userData); expect(result.success).toBe(false); expect(result.error_code).toBe('DUPLICATE_USERNAME'); }); it('should validate required fields', async () => { const invalidData = { username: '', email: 'test@example.com', nickname: 'Test User', status: UserStatus.ACTIVE }; const result = await service.createUser(invalidData); expect(result.success).toBe(false); expect(result.error_code).toBe('VALIDATION_ERROR'); }); it('should validate email format', async () => { const invalidData = { username: 'test', email: 'invalid-email', nickname: 'Test User', status: UserStatus.ACTIVE }; const result = await service.createUser(invalidData); expect(result.success).toBe(false); expect(result.error_code).toBe('VALIDATION_ERROR'); }); }); describe('updateUser', () => { it('should update user successfully', async () => { const updateData = { nickname: 'Updated Name' }; const existingUser = { id: BigInt(1), username: 'test', email: 'test@example.com' }; const updatedUser = { ...existingUser, ...updateData }; mockUsersService.findOne.mockResolvedValue(existingUser); mockUsersService.update.mockResolvedValue(updatedUser); const result = await service.updateUser(BigInt(1), updateData); expect(result.success).toBe(true); expect(result.data).toEqual({ ...updatedUser, id: '1' }); expect(mockUsersService.update).toHaveBeenCalledWith(BigInt(1), updateData); }); it('should return error when user not found', async () => { mockUsersService.findOne.mockResolvedValue(null); const result = await service.updateUser(BigInt(999), { nickname: 'New Name' }); expect(result.success).toBe(false); expect(result.error_code).toBe('USER_NOT_FOUND'); }); it('should handle empty update data', async () => { const result = await service.updateUser(BigInt(1), {}); expect(result.success).toBe(false); expect(result.error_code).toBe('VALIDATION_ERROR'); expect(result.message).toContain('No valid fields to update'); }); }); describe('deleteUser', () => { it('should delete user successfully', async () => { const existingUser = { id: BigInt(1), username: 'test' }; mockUsersService.findOne.mockResolvedValue(existingUser); mockUsersService.remove.mockResolvedValue(undefined); const result = await service.deleteUser(BigInt(1)); expect(result.success).toBe(true); expect(result.data.deleted).toBe(true); expect(result.data.id).toBe('1'); expect(mockUsersService.remove).toHaveBeenCalledWith(BigInt(1)); }); it('should return error when user not found', async () => { mockUsersService.findOne.mockResolvedValue(null); const result = await service.deleteUser(BigInt(999)); expect(result.success).toBe(false); expect(result.error_code).toBe('USER_NOT_FOUND'); }); }); describe('searchUsers', () => { it('should search users successfully', async () => { const mockUsers = [ { id: BigInt(1), username: 'testuser', email: 'test@example.com' } ]; mockUsersService.search.mockResolvedValue(mockUsers); const result = await service.searchUsers('test', 10); expect(result.success).toBe(true); expect(result.data.items).toEqual(mockUsers.map(u => ({ ...u, id: u.id.toString() }))); expect(mockUsersService.search).toHaveBeenCalledWith('test', 10); }); it('should handle empty search term', async () => { const result = await service.searchUsers('', 10); expect(result.success).toBe(false); expect(result.error_code).toBe('VALIDATION_ERROR'); expect(result.message).toContain('Search term cannot be empty'); }); it('should apply search limit', async () => { mockUsersService.search.mockResolvedValue([]); await service.searchUsers('test', 200); // 超过限制 expect(mockUsersService.search).toHaveBeenCalledWith('test', 100); }); }); }); describe('User Profile Management', () => { describe('getUserProfileList', () => { it('should return paginated profile list', async () => { const mockProfiles = [ { id: BigInt(1), user_id: '1', bio: 'Test bio' } ]; mockUserProfilesService.findAll.mockResolvedValue(mockProfiles); mockUserProfilesService.count.mockResolvedValue(1); const result = await service.getUserProfileList(10, 0); expect(result.success).toBe(true); expect(result.data.items).toEqual(mockProfiles.map(p => ({ ...p, id: p.id.toString() }))); }); }); describe('createUserProfile', () => { it('should create profile successfully', async () => { const profileData = { user_id: '1', bio: 'Test bio', current_map: 'plaza', pos_x: 100, pos_y: 200 }; const createdProfile = { ...profileData, id: BigInt(1) }; mockUserProfilesService.create.mockResolvedValue(createdProfile); const result = await service.createUserProfile(profileData); expect(result.success).toBe(true); expect(result.data).toEqual({ ...createdProfile, id: '1' }); }); it('should validate position coordinates', async () => { const invalidData = { user_id: '1', bio: 'Test', pos_x: 'invalid' as any, pos_y: 100 }; const result = await service.createUserProfile(invalidData); expect(result.success).toBe(false); expect(result.error_code).toBe('VALIDATION_ERROR'); }); }); describe('getUserProfilesByMap', () => { it('should return profiles by map', async () => { const mockProfiles = [ { id: BigInt(1), user_id: '1', current_map: 'plaza' } ]; mockUserProfilesService.findByMap.mockResolvedValue(mockProfiles); mockUserProfilesService.count.mockResolvedValue(1); const result = await service.getUserProfilesByMap('plaza', 10, 0); expect(result.success).toBe(true); expect(result.data.items).toEqual(mockProfiles.map(p => ({ ...p, id: p.id.toString() }))); expect(mockUserProfilesService.findByMap).toHaveBeenCalledWith('plaza', undefined, 10, 0); }); it('should validate map name', async () => { const result = await service.getUserProfilesByMap('', 10, 0); expect(result.success).toBe(false); expect(result.error_code).toBe('VALIDATION_ERROR'); expect(result.message).toContain('Map name cannot be empty'); }); }); }); describe('Zulip Account Management', () => { describe('getZulipAccountList', () => { it('should return paginated account list', async () => { const mockAccounts = [ { id: '1', gameUserId: '1', zulipEmail: 'test@zulip.com' } ]; mockZulipAccountsService.findMany.mockResolvedValue({ accounts: mockAccounts, total: 1 }); const result = await service.getZulipAccountList(10, 0); expect(result.success).toBe(true); expect(result.data.items).toEqual(mockAccounts); expect(result.data.total).toBe(1); }); }); describe('createZulipAccount', () => { it('should create account successfully', async () => { const accountData = { gameUserId: '1', zulipUserId: 123, zulipEmail: 'test@zulip.com', zulipFullName: 'Test User', zulipApiKeyEncrypted: 'encrypted_key', status: 'active' as const }; const createdAccount = { ...accountData, id: '1' }; mockZulipAccountsService.create.mockResolvedValue(createdAccount); const result = await service.createZulipAccount(accountData); expect(result.success).toBe(true); expect(result.data).toEqual(createdAccount); }); it('should validate required fields', async () => { const invalidData = { gameUserId: '', zulipUserId: 123, zulipEmail: 'test@zulip.com', zulipFullName: 'Test', zulipApiKeyEncrypted: 'key', status: 'active' as const }; const result = await service.createZulipAccount(invalidData); expect(result.success).toBe(false); expect(result.error_code).toBe('VALIDATION_ERROR'); }); }); describe('batchUpdateZulipAccountStatus', () => { it('should update multiple accounts successfully', async () => { const ids = ['1', '2']; const status = 'active'; const reason = 'Test update'; mockZulipAccountsService.update .mockResolvedValueOnce({ id: '1', status: 'active' }) .mockResolvedValueOnce({ id: '2', status: 'active' }); const result = await service.batchUpdateZulipAccountStatus(ids, status, reason); expect(result.success).toBe(true); expect(result.data.total).toBe(2); expect(result.data.success).toBe(2); expect(result.data.failed).toBe(0); expect(result.data.results).toHaveLength(2); }); it('should handle partial failures', async () => { const ids = ['1', '2']; const status = 'active'; const reason = 'Test update'; mockZulipAccountsService.update .mockResolvedValueOnce({ id: '1', status: 'active' }) .mockRejectedValueOnce(new Error('Update failed')); const result = await service.batchUpdateZulipAccountStatus(ids, status, reason); expect(result.success).toBe(true); expect(result.data.total).toBe(2); expect(result.data.success).toBe(1); expect(result.data.failed).toBe(1); expect(result.data.errors).toHaveLength(1); }); it('should validate batch data', async () => { const ids: string[] = []; const status = 'active'; const reason = 'Test'; const result = await service.batchUpdateZulipAccountStatus(ids, status, reason); expect(result.success).toBe(false); expect(result.error_code).toBe('VALIDATION_ERROR'); expect(result.message).toContain('No account IDs provided'); }); }); describe('getZulipAccountStatistics', () => { it('should return statistics successfully', async () => { const mockStats = { active: 10, inactive: 5, suspended: 2, error: 1, total: 18 }; mockZulipAccountsService.getStatusStatistics.mockResolvedValue(mockStats); const result = await service.getZulipAccountStatistics(); expect(result.success).toBe(true); expect(result.data).toEqual(mockStats); }); }); }); // describe('Health Check', () => { // describe('healthCheck', () => { // it('should return healthy status', async () => { // const result = await service.healthCheck(); // expect(result.success).toBe(true); // expect(result.data.status).toBe('healthy'); // expect(result.data.timestamp).toBeDefined(); // expect(result.data.services).toBeDefined(); // }); // }); // }); describe('Error Handling', () => { it('should handle service injection errors', () => { expect(service).toBeDefined(); expect(service['usersService']).toBeDefined(); expect(service['userProfilesService']).toBeDefined(); expect(service['zulipAccountsService']).toBeDefined(); }); it('should format BigInt IDs correctly', async () => { const mockUser = { id: BigInt(123456789012345), username: 'test' }; mockUsersService.findOne.mockResolvedValue(mockUser); const result = await service.getUserById(BigInt('123456789012345')); expect(result.success).toBe(true); expect(result.data.id).toBe('123456789012345'); }); it('should handle concurrent operations', async () => { const mockUser = { id: BigInt(1), username: 'test' }; mockUsersService.findOne.mockResolvedValue(mockUser); const promises = [ service.getUserById(BigInt(1)), service.getUserById(BigInt(1)), service.getUserById(BigInt(1)) ]; const results = await Promise.all(promises); results.forEach(result => { expect(result.success).toBe(true); expect(result.data.id).toBe('1'); }); }); }); });