/** * 自动清理服务单元测试 * * 功能描述: * - 测试自动清理服务的所有功能 * - 验证定时清理和手动清理操作 * - 确保配置更新和统计信息正确 * - 提供完整的测试覆盖率 * * 测试范围: * - 清理调度器的启动和停止 * - 各种清理操作的执行 * - 配置更新和统计信息管理 * - 异常情况处理 * * @author moyin * @version 1.0.0 * @since 2026-01-08 * @lastModified 2026-01-08 */ import { Test, TestingModule } from '@nestjs/testing'; import { CleanupService } from './cleanup.service'; describe('CleanupService', () => { let service: CleanupService; let mockLocationBroadcastCore: any; beforeEach(async () => { // 创建位置广播核心服务的Mock mockLocationBroadcastCore = { cleanupExpiredData: jest.fn(), cleanupUserData: jest.fn(), cleanupEmptySession: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ CleanupService, { provide: 'ILocationBroadcastCore', useValue: mockLocationBroadcastCore, }, ], }).compile(); service = module.get(CleanupService); }); afterEach(() => { jest.clearAllMocks(); // 确保清理定时器被停止 service.stopCleanupScheduler(); }); describe('模块生命周期', () => { it('应该在模块初始化时启动清理调度器', () => { const startSpy = jest.spyOn(service, 'startCleanupScheduler'); service.onModuleInit(); expect(startSpy).toHaveBeenCalled(); }); it('应该在模块销毁时停止清理调度器', () => { const stopSpy = jest.spyOn(service, 'stopCleanupScheduler'); service.onModuleDestroy(); expect(stopSpy).toHaveBeenCalled(); }); it('应该在禁用时不启动清理调度器', () => { service.updateConfig({ enabled: false }); const startSpy = jest.spyOn(service, 'startCleanupScheduler'); service.onModuleInit(); expect(startSpy).not.toHaveBeenCalled(); }); }); describe('清理调度器管理', () => { it('应该成功启动清理调度器', () => { service.startCleanupScheduler(); // 验证调度器已启动(通过检查内部状态) expect(service['cleanupTimer']).not.toBeNull(); }); it('应该成功停止清理调度器', () => { service.startCleanupScheduler(); service.stopCleanupScheduler(); expect(service['cleanupTimer']).toBeNull(); }); it('应该防止重复启动调度器', () => { service.startCleanupScheduler(); const firstTimer = service['cleanupTimer']; service.startCleanupScheduler(); expect(service['cleanupTimer']).toBe(firstTimer); }); it('应该安全处理停止未启动的调度器', () => { expect(() => service.stopCleanupScheduler()).not.toThrow(); }); }); describe('手动清理操作', () => { it('应该成功执行手动清理', async () => { const results = await service.manualCleanup(); expect(results).toBeInstanceOf(Array); expect(results.length).toBeGreaterThan(0); expect(results.every(r => typeof r.operation === 'string')).toBe(true); expect(results.every(r => typeof r.count === 'number')).toBe(true); expect(results.every(r => typeof r.duration === 'number')).toBe(true); expect(results.every(r => typeof r.success === 'boolean')).toBe(true); }); it('应该更新统计信息', async () => { const statsBefore = service.getStats(); await service.manualCleanup(); const statsAfter = service.getStats(); expect(statsAfter.totalCleanups).toBe(statsBefore.totalCleanups + 1); expect(statsAfter.lastCleanupTime).toBeGreaterThan(statsBefore.lastCleanupTime); }); it('应该处理清理过程中的异常', async () => { // 模拟清理过程中的异常 const originalCleanupExpiredSessions = service['cleanupExpiredSessions']; service['cleanupExpiredSessions'] = jest.fn().mockRejectedValue(new Error('清理失败')); const results = await service.manualCleanup(); expect(results).toBeInstanceOf(Array); expect(results.some(r => !r.success)).toBe(true); // 恢复原方法 service['cleanupExpiredSessions'] = originalCleanupExpiredSessions; }); }); describe('配置管理', () => { it('应该返回当前配置', () => { const config = service.getConfig(); expect(config).toHaveProperty('sessionExpiry'); expect(config).toHaveProperty('positionExpiry'); expect(config).toHaveProperty('userOfflineTimeout'); expect(config).toHaveProperty('cleanupInterval'); expect(config).toHaveProperty('batchSize'); expect(config).toHaveProperty('enabled'); }); it('应该成功更新配置', () => { const newConfig = { cleanupInterval: 10000, batchSize: 50, enabled: false, }; service.updateConfig(newConfig); const config = service.getConfig(); expect(config.cleanupInterval).toBe(newConfig.cleanupInterval); expect(config.batchSize).toBe(newConfig.batchSize); expect(config.enabled).toBe(newConfig.enabled); }); it('应该在间隔时间改变时重启调度器', () => { const stopSpy = jest.spyOn(service, 'stopCleanupScheduler'); const startSpy = jest.spyOn(service, 'startCleanupScheduler'); service.startCleanupScheduler(); service.updateConfig({ cleanupInterval: 20000 }); expect(stopSpy).toHaveBeenCalled(); expect(startSpy).toHaveBeenCalledTimes(2); // 初始启动 + 重启 }); it('应该在启用状态改变时控制调度器', () => { service.startCleanupScheduler(); const stopSpy = jest.spyOn(service, 'stopCleanupScheduler'); service.updateConfig({ enabled: false }); expect(stopSpy).toHaveBeenCalled(); }); }); describe('统计信息管理', () => { it('应该返回初始统计信息', () => { const stats = service.getStats(); expect(stats.totalCleanups).toBe(0); expect(stats.cleanedSessions).toBe(0); expect(stats.cleanedPositions).toBe(0); expect(stats.cleanedUsers).toBe(0); expect(stats.errorCount).toBe(0); }); it('应该成功重置统计信息', () => { // 先执行一次清理以产生统计数据 service['stats'].totalCleanups = 5; service['stats'].cleanedSessions = 10; service.resetStats(); const stats = service.getStats(); expect(stats.totalCleanups).toBe(0); expect(stats.cleanedSessions).toBe(0); }); it('应该正确计算平均清理时间', async () => { // 重置清理时间数组 service['cleanupTimes'] = [100, 200, 300]; // 手动触发统计更新 service['updateStats']([], 0); const stats = service.getStats(); expect(stats.avgCleanupTime).toBeGreaterThan(0); }); }); describe('清理时间管理', () => { it('应该返回下次清理时间', () => { service.updateConfig({ enabled: true }); service.startCleanupScheduler(); service['stats'].lastCleanupTime = Date.now(); const nextTime = service.getNextCleanupTime(); expect(nextTime).toBeGreaterThan(Date.now()); service.stopCleanupScheduler(); }); it('应该在禁用时返回0', () => { service.updateConfig({ enabled: false }); const nextTime = service.getNextCleanupTime(); expect(nextTime).toBe(0); }); it('应该正确判断是否需要立即清理', () => { service.updateConfig({ enabled: true, cleanupInterval: 1000 }); // 设置上次清理时间为很久以前 service['stats'].lastCleanupTime = Date.now() - 2000; expect(service.shouldCleanupNow()).toBe(true); }); it('应该在最近清理过时返回false', () => { service.updateConfig({ enabled: true, cleanupInterval: 10000 }); // 设置上次清理时间为刚刚 service['stats'].lastCleanupTime = Date.now(); expect(service.shouldCleanupNow()).toBe(false); }); }); describe('健康状态检查', () => { it('应该返回健康状态', () => { service.updateConfig({ enabled: true }); service['stats'].lastCleanupTime = Date.now(); const health = service.getHealthStatus(); expect(health).toHaveProperty('status'); expect(health).toHaveProperty('details'); expect(['healthy', 'degraded', 'unhealthy']).toContain(health.status); }); it('应该在禁用时返回降级状态', () => { service.updateConfig({ enabled: false }); const health = service.getHealthStatus(); expect(health.status).toBe('degraded'); }); it('应该在长时间未清理时返回不健康状态', () => { service.updateConfig({ enabled: true, cleanupInterval: 1000 }); service['stats'].lastCleanupTime = Date.now() - 10000; // 10秒前 const health = service.getHealthStatus(); expect(health.status).toBe('unhealthy'); }); it('应该在错误率过高时返回降级状态', () => { service.updateConfig({ enabled: true }); service['stats'].lastCleanupTime = Date.now(); service['stats'].totalCleanups = 10; service['stats'].errorCount = 2; // 20%错误率 const health = service.getHealthStatus(); expect(health.status).toBe('degraded'); }); }); describe('私有方法测试', () => { it('应该成功执行清理过期会话', async () => { const result = await service['cleanupExpiredSessions'](); expect(result).toHaveProperty('operation', 'cleanup_expired_sessions'); expect(result).toHaveProperty('count'); expect(result).toHaveProperty('duration'); expect(result).toHaveProperty('success'); expect(typeof result.count).toBe('number'); expect(typeof result.duration).toBe('number'); expect(typeof result.success).toBe('boolean'); }); it('应该成功执行清理过期位置数据', async () => { const result = await service['cleanupExpiredPositions'](); expect(result).toHaveProperty('operation', 'cleanup_expired_positions'); expect(result.success).toBe(true); }); it('应该成功执行清理离线用户', async () => { const result = await service['cleanupOfflineUsers'](); expect(result).toHaveProperty('operation', 'cleanup_offline_users'); expect(result.success).toBe(true); }); it('应该成功执行清理缓存数据', async () => { const result = await service['cleanupCacheData'](); expect(result).toHaveProperty('operation', 'cleanup_cache_data'); expect(result.success).toBe(true); }); it('应该正确更新统计信息', () => { const results = [ { operation: 'cleanup_expired_sessions', count: 5, duration: 100, success: true }, { operation: 'cleanup_expired_positions', count: 10, duration: 200, success: true }, { operation: 'cleanup_offline_users', count: 3, duration: 50, success: false, error: '测试错误' }, ]; const statsBefore = service.getStats(); service['updateStats'](results, 350); const statsAfter = service.getStats(); expect(statsAfter.totalCleanups).toBe(statsBefore.totalCleanups + 1); expect(statsAfter.cleanedSessions).toBe(statsBefore.cleanedSessions + 5); expect(statsAfter.cleanedPositions).toBe(statsBefore.cleanedPositions + 10); expect(statsAfter.cleanedUsers).toBe(statsBefore.cleanedUsers + 3); expect(statsAfter.errorCount).toBe(statsBefore.errorCount + 1); expect(statsAfter.lastError).toBe('测试错误'); }); }); describe('边界条件测试', () => { it('应该处理空的清理结果', () => { expect(() => service['updateStats']([], 0)).not.toThrow(); }); it('应该处理极大的清理时间记录', () => { // 添加大量清理时间记录 for (let i = 0; i < 150; i++) { service['cleanupTimes'].push(100 + i); } service['updateStats']([ { operation: 'test', count: 1, duration: 200, success: true } ], 200); // 应该只保留最近的记录 expect(service['cleanupTimes'].length).toBeLessThanOrEqual(100); }); it('应该处理配置中的无效值', () => { expect(() => service.updateConfig({ cleanupInterval: -1000, batchSize: 0, })).not.toThrow(); }); }); describe('性能测试', () => { it('应该在合理时间内完成清理操作', async () => { const startTime = Date.now(); await service.manualCleanup(); const duration = Date.now() - startTime; expect(duration).toBeLessThan(1000); // 应该在1秒内完成 }); it('应该正确处理并发清理请求', async () => { const promises = [ service.manualCleanup(), service.manualCleanup(), service.manualCleanup(), ]; const results = await Promise.all(promises); expect(results).toHaveLength(3); results.forEach(result => { expect(result).toBeInstanceOf(Array); }); }); }); });