/** * 用户档案管理属性测试 * * Property 3: 用户档案管理操作完整性 * Property 4: 地图用户查询正确性 * * Validates: Requirements 2.1-2.6 * * 测试目标: * - 验证用户档案CRUD操作的完整性 * - 确保地图查询功能的正确性 * - 验证位置数据的处理逻辑 * * 最近修改: * - 2026-01-08: 注释规范优化 - 修正@author字段,更新版本号和修改记录 (修改者: moyin) * - 2026-01-08: 功能新增 - 创建用户档案管理属性测试 (修改者: assistant) * * @author moyin * @version 1.0.1 * @since 2026-01-08 * @lastModified 2026-01-08 */ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AdminDatabaseController } from './admin_database.controller'; import { DatabaseManagementService } from './database_management.service'; import { AdminOperationLogService } from './admin_operation_log.service'; import { AdminOperationLogInterceptor } from './admin_operation_log.interceptor'; import { AdminDatabaseExceptionFilter } from './admin_database_exception.filter'; import { AdminGuard } from './admin.guard'; import { PropertyTestRunner, PropertyTestGenerators, PropertyTestAssertions, DEFAULT_PROPERTY_CONFIG } from './admin_property_test.base'; describe('Property Test: 用户档案管理功能', () => { let app: INestApplication; let module: TestingModule; let controller: AdminDatabaseController; let mockUserProfilesService: any; beforeAll(async () => { mockUserProfilesService = { findAll: jest.fn().mockResolvedValue([]), findOne: jest.fn(), create: jest.fn(), update: jest.fn(), remove: jest.fn().mockResolvedValue(undefined), findByMap: jest.fn(), count: jest.fn().mockResolvedValue(0) }; module = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: ['.env.test', '.env'] }) ], controllers: [AdminDatabaseController], providers: [ DatabaseManagementService, { provide: AdminOperationLogService, useValue: { 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 }) } }, { provide: AdminOperationLogInterceptor, useValue: { intercept: jest.fn().mockImplementation((context, next) => next.handle()) } }, { provide: 'UsersService', useValue: { findAll: jest.fn().mockResolvedValue([]), findOne: jest.fn().mockResolvedValue({ id: BigInt(1) }), create: jest.fn().mockResolvedValue({ id: BigInt(1) }), update: jest.fn().mockResolvedValue({ id: BigInt(1) }), remove: jest.fn().mockResolvedValue(undefined), search: jest.fn().mockResolvedValue([]), count: jest.fn().mockResolvedValue(0) } }, { provide: 'IUserProfilesService', useValue: mockUserProfilesService }, { provide: 'ZulipAccountsService', useValue: { findMany: jest.fn().mockResolvedValue({ accounts: [] }), findById: jest.fn().mockResolvedValue({ id: '1' }), create: jest.fn().mockResolvedValue({ id: '1' }), update: jest.fn().mockResolvedValue({ id: '1' }), delete: jest.fn().mockResolvedValue(undefined), getStatusStatistics: jest.fn().mockResolvedValue({ active: 0, inactive: 0, suspended: 0, error: 0, total: 0 }) } } ] }) .overrideGuard(AdminGuard) .useValue({ canActivate: () => true }) .compile(); app = module.createNestApplication(); app.useGlobalFilters(new AdminDatabaseExceptionFilter()); await app.init(); controller = module.get(AdminDatabaseController); }); afterAll(async () => { await app.close(); }); describe('Property 3: 用户档案管理操作完整性', () => { it('创建用户档案后应该能够读取相同的数据', async () => { await PropertyTestRunner.runPropertyTest( '用户档案创建-读取一致性', () => PropertyTestGenerators.generateUserProfile(), async (profileData) => { const profileWithId = { ...profileData, id: BigInt(1) }; // Mock创建和读取操作 mockUserProfilesService.create.mockResolvedValueOnce(profileWithId); mockUserProfilesService.findOne.mockResolvedValueOnce(profileWithId); // 执行创建操作 const createResponse = await controller.createUserProfile(profileData); // 执行读取操作 const readResponse = await controller.getUserProfileById('1'); // 验证一致性 PropertyTestAssertions.assertCrudConsistency( createResponse, readResponse, createResponse ); expect(createResponse.data.user_id).toBe(profileData.user_id); expect(readResponse.data.user_id).toBe(profileData.user_id); }, { ...DEFAULT_PROPERTY_CONFIG, iterations: 30 } ); }); it('更新用户档案后数据应该反映变更', async () => { await PropertyTestRunner.runPropertyTest( '用户档案更新一致性', () => ({ original: PropertyTestGenerators.generateUserProfile(), updates: PropertyTestGenerators.generateUserProfile() }), async ({ original, updates }) => { const originalWithId = { ...original, id: BigInt(1) }; const updatedProfile = { ...originalWithId, ...updates }; // Mock操作 mockUserProfilesService.findOne.mockResolvedValueOnce(originalWithId); mockUserProfilesService.update.mockResolvedValueOnce(updatedProfile); // 执行更新操作 const updateResponse = await controller.updateUserProfile('1', updates); expect(updateResponse.success).toBe(true); expect(updateResponse.data.id).toBe('1'); // 验证更新的字段 if (updates.bio) { expect(updateResponse.data.bio).toBe(updates.bio); } if (updates.current_map) { expect(updateResponse.data.current_map).toBe(updates.current_map); } }, { ...DEFAULT_PROPERTY_CONFIG, iterations: 30 } ); }); it('位置数据应该被正确处理', async () => { await PropertyTestRunner.runPropertyTest( '位置数据处理正确性', () => { const profile = PropertyTestGenerators.generateUserProfile(); return { ...profile, pos_x: Math.random() * 2000 - 1000, // -1000 到 1000 pos_y: Math.random() * 2000 - 1000, // -1000 到 1000 }; }, async (profileData) => { const profileWithId = { ...profileData, id: BigInt(1) }; mockUserProfilesService.create.mockResolvedValueOnce(profileWithId); const createResponse = await controller.createUserProfile(profileData); expect(createResponse.success).toBe(true); expect(typeof createResponse.data.pos_x).toBe('number'); expect(typeof createResponse.data.pos_y).toBe('number'); // 验证位置数据的合理性 expect(createResponse.data.pos_x).toBe(profileData.pos_x); expect(createResponse.data.pos_y).toBe(profileData.pos_y); }, { ...DEFAULT_PROPERTY_CONFIG, iterations: 25 } ); }); it('JSON字段应该被正确序列化和反序列化', async () => { await PropertyTestRunner.runPropertyTest( 'JSON字段处理正确性', () => { const profile = PropertyTestGenerators.generateUserProfile(); return { ...profile, tags: JSON.stringify(['tag1', 'tag2', 'tag3']), social_links: JSON.stringify({ github: 'https://github.com/user', linkedin: 'https://linkedin.com/in/user', twitter: 'https://twitter.com/user' }) }; }, async (profileData) => { const profileWithId = { ...profileData, id: BigInt(1) }; mockUserProfilesService.create.mockResolvedValueOnce(profileWithId); const createResponse = await controller.createUserProfile(profileData); expect(createResponse.success).toBe(true); expect(createResponse.data.tags).toBe(profileData.tags); expect(createResponse.data.social_links).toBe(profileData.social_links); // 验证JSON格式有效性 expect(() => JSON.parse(profileData.tags)).not.toThrow(); expect(() => JSON.parse(profileData.social_links)).not.toThrow(); }, { ...DEFAULT_PROPERTY_CONFIG, iterations: 20 } ); }); }); describe('Property 4: 地图用户查询正确性', () => { it('按地图查询应该返回正确的用户档案', async () => { await PropertyTestRunner.runPropertyTest( '地图查询正确性', () => { const maps = ['plaza', 'forest', 'beach', 'mountain', 'city']; const selectedMap = maps[Math.floor(Math.random() * maps.length)]; const profiles = Array.from({ length: 5 }, () => { const profile = PropertyTestGenerators.generateUserProfile(); return { ...profile, id: BigInt(Math.floor(Math.random() * 1000) + 1), current_map: Math.random() > 0.5 ? selectedMap : maps[Math.floor(Math.random() * maps.length)] }; }); return { selectedMap, profiles }; }, async ({ selectedMap, profiles }) => { // 过滤出应该匹配的档案 const expectedProfiles = profiles.filter(p => p.current_map === selectedMap); mockUserProfilesService.findByMap.mockResolvedValueOnce(expectedProfiles); mockUserProfilesService.count.mockResolvedValueOnce(expectedProfiles.length); const response = await controller.getUserProfilesByMap(selectedMap, 20, 0); expect(response.success).toBe(true); PropertyTestAssertions.assertListResponseFormat(response); // 验证返回的档案都属于指定地图 response.data.items.forEach((profile: any) => { expect(profile.current_map).toBe(selectedMap); }); expect(response.data.items.length).toBe(expectedProfiles.length); }, { ...DEFAULT_PROPERTY_CONFIG, iterations: 25 } ); }); it('不存在的地图应该返回空结果', async () => { await PropertyTestRunner.runPropertyTest( '不存在地图查询处理', () => ({ nonExistentMap: `nonexistent_${Math.random().toString(36).substring(7)}` }), async ({ nonExistentMap }) => { mockUserProfilesService.findByMap.mockResolvedValueOnce([]); mockUserProfilesService.count.mockResolvedValueOnce(0); const response = await controller.getUserProfilesByMap(nonExistentMap, 20, 0); expect(response.success).toBe(true); expect(response.data.items).toEqual([]); expect(response.data.total).toBe(0); }, { ...DEFAULT_PROPERTY_CONFIG, iterations: 15 } ); }); it('地图查询应该支持分页', async () => { await PropertyTestRunner.runPropertyTest( '地图查询分页支持', () => { const map = 'plaza'; const pagination = PropertyTestGenerators.generatePaginationParams(); const totalProfiles = Math.floor(Math.random() * 100) + 50; // 50-149个档案 return { map, pagination, totalProfiles }; }, async ({ map, pagination, totalProfiles }) => { const { limit, offset } = pagination; const safeLimit = Math.min(Math.max(limit, 1), 100); const safeOffset = Math.max(offset, 0); // 模拟分页结果 const itemsToReturn = Math.min(safeLimit, Math.max(0, totalProfiles - safeOffset)); const mockProfiles = Array.from({ length: itemsToReturn }, (_, i) => ({ ...PropertyTestGenerators.generateUserProfile(), id: BigInt(safeOffset + i + 1), current_map: map })); mockUserProfilesService.findByMap.mockResolvedValueOnce(mockProfiles); mockUserProfilesService.count.mockResolvedValueOnce(totalProfiles); const response = await controller.getUserProfilesByMap(map, safeLimit, safeOffset); expect(response.success).toBe(true); PropertyTestAssertions.assertPaginationLogic(response, safeLimit, safeOffset); // 验证返回的档案数量 expect(response.data.items.length).toBe(itemsToReturn); expect(response.data.total).toBe(totalProfiles); }, { ...DEFAULT_PROPERTY_CONFIG, iterations: 20 } ); }); it('地图名称应该被正确处理', async () => { await PropertyTestRunner.runPropertyTest( '地图名称处理', () => { const mapNames = [ 'plaza', 'forest', 'beach', 'mountain', 'city', 'special-map', 'map_with_underscore', 'map123', '中文地图', 'café-map' ]; return { mapName: mapNames[Math.floor(Math.random() * mapNames.length)] }; }, async ({ mapName }) => { mockUserProfilesService.findByMap.mockResolvedValueOnce([]); mockUserProfilesService.count.mockResolvedValueOnce(0); const response = await controller.getUserProfilesByMap(mapName, 20, 0); expect(response.success).toBe(true); expect(response.data).toBeDefined(); // 验证地图名称被正确传递 expect(mockUserProfilesService.findByMap).toHaveBeenCalledWith( mapName, undefined, 20, 0 ); }, { ...DEFAULT_PROPERTY_CONFIG, iterations: 15 } ); }); }); });