feat:实现位置广播系统
- 添加位置广播核心控制器和服务 - 实现健康检查和位置同步功能 - 添加WebSocket实时位置更新支持 - 完善位置广播的测试覆盖
This commit is contained in:
419
src/business/location_broadcast/services/cleanup.service.spec.ts
Normal file
419
src/business/location_broadcast/services/cleanup.service.spec.ts
Normal file
@@ -0,0 +1,419 @@
|
||||
/**
|
||||
* 自动清理服务单元测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试自动清理服务的所有功能
|
||||
* - 验证定时清理和手动清理操作
|
||||
* - 确保配置更新和统计信息正确
|
||||
* - 提供完整的测试覆盖率
|
||||
*
|
||||
* 测试范围:
|
||||
* - 清理调度器的启动和停止
|
||||
* - 各种清理操作的执行
|
||||
* - 配置更新和统计信息管理
|
||||
* - 异常情况处理
|
||||
*
|
||||
* @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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user