Files
whale-town-end/src/business/location_broadcast/services/cleanup.service.spec.ts
moyin c31cbe559d feat:实现位置广播系统
- 添加位置广播核心控制器和服务
- 实现健康检查和位置同步功能
- 添加WebSocket实时位置更新支持
- 完善位置广播的测试覆盖
2026-01-08 23:05:52 +08:00

419 lines
13 KiB
TypeScript

/**
* 自动清理服务单元测试
*
* 功能描述:
* - 测试自动清理服务的所有功能
* - 验证定时清理和手动清理操作
* - 确保配置更新和统计信息正确
* - 提供完整的测试覆盖率
*
* 测试范围:
* - 清理调度器的启动和停止
* - 各种清理操作的执行
* - 配置更新和统计信息管理
* - 异常情况处理
*
* @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>(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);
});
});
});
});