/** * 聊天会话清理服务测试 * * 测试范围: * - 定时清理任务启动和停止 * - 过期会话清理逻辑 * - 手动触发清理功能 * - 资源释放和错误处理 * * @author moyin * @version 1.0.0 * @since 2026-01-14 * @lastModified 2026-01-14 */ import { Test, TestingModule } from '@nestjs/testing'; import { Logger } from '@nestjs/common'; import { ChatCleanupService } from './chat_cleanup.service'; import { ChatSessionService } from './chat_session.service'; describe('ChatCleanupService', () => { let service: ChatCleanupService; let sessionService: jest.Mocked; beforeEach(async () => { const mockSessionService = { cleanupExpiredSessions: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ ChatCleanupService, { provide: ChatSessionService, useValue: mockSessionService, }, ], }).compile(); service = module.get(ChatCleanupService); sessionService = module.get(ChatSessionService); // 禁用日志输出 jest.spyOn(Logger.prototype, 'log').mockImplementation(); jest.spyOn(Logger.prototype, 'error').mockImplementation(); jest.spyOn(Logger.prototype, 'warn').mockImplementation(); jest.spyOn(Logger.prototype, 'debug').mockImplementation(); }); afterEach(() => { jest.clearAllMocks(); jest.clearAllTimers(); }); describe('初始化', () => { it('应该成功创建服务实例', () => { expect(service).toBeDefined(); }); it('应该在模块初始化时启动清理任务', async () => { jest.useFakeTimers(); const startCleanupTaskSpy = jest.spyOn(service as any, 'startCleanupTask'); await service.onModuleInit(); expect(startCleanupTaskSpy).toHaveBeenCalled(); jest.useRealTimers(); }); it('应该在模块销毁时停止清理任务', async () => { jest.useFakeTimers(); const stopCleanupTaskSpy = jest.spyOn(service as any, 'stopCleanupTask'); await service.onModuleDestroy(); expect(stopCleanupTaskSpy).toHaveBeenCalled(); jest.useRealTimers(); }); }); describe('定时清理任务', () => { it('应该定时执行清理操作', async () => { jest.useFakeTimers(); sessionService.cleanupExpiredSessions.mockResolvedValue({ cleanedCount: 5, zulipQueueIds: ['queue_1', 'queue_2'], }); await service.onModuleInit(); // 快进5分钟 jest.advanceTimersByTime(5 * 60 * 1000); await Promise.resolve(); expect(sessionService.cleanupExpiredSessions).toHaveBeenCalled(); jest.useRealTimers(); }); it('应该在停止任务后不再执行清理', async () => { jest.useFakeTimers(); sessionService.cleanupExpiredSessions.mockResolvedValue({ cleanedCount: 0, zulipQueueIds: [], }); await service.onModuleInit(); await service.onModuleDestroy(); sessionService.cleanupExpiredSessions.mockClear(); // 快进5分钟 jest.advanceTimersByTime(5 * 60 * 1000); await Promise.resolve(); expect(sessionService.cleanupExpiredSessions).not.toHaveBeenCalled(); jest.useRealTimers(); }); }); describe('triggerCleanup', () => { it('应该成功执行手动清理', async () => { sessionService.cleanupExpiredSessions.mockResolvedValue({ cleanedCount: 3, zulipQueueIds: ['queue_1', 'queue_2', 'queue_3'], }); const result = await service.triggerCleanup(); expect(result.cleanedCount).toBe(3); expect(sessionService.cleanupExpiredSessions).toHaveBeenCalledWith(30); }); it('应该处理清理失败', async () => { sessionService.cleanupExpiredSessions.mockRejectedValue(new Error('Redis error')); await expect(service.triggerCleanup()).rejects.toThrow('Redis error'); }); it('应该返回清理数量为0当没有过期会话', async () => { sessionService.cleanupExpiredSessions.mockResolvedValue({ cleanedCount: 0, zulipQueueIds: [], }); const result = await service.triggerCleanup(); expect(result.cleanedCount).toBe(0); }); }); describe('清理逻辑', () => { it('应该清理多个过期会话', async () => { jest.useFakeTimers(); sessionService.cleanupExpiredSessions.mockResolvedValue({ cleanedCount: 10, zulipQueueIds: Array.from({ length: 10 }, (_, i) => `queue_${i}`), }); await service.onModuleInit(); jest.advanceTimersByTime(5 * 60 * 1000); await Promise.resolve(); expect(sessionService.cleanupExpiredSessions).toHaveBeenCalledWith(30); jest.useRealTimers(); }); it('应该处理清理过程中的异常', async () => { jest.useFakeTimers(); sessionService.cleanupExpiredSessions.mockRejectedValue(new Error('Cleanup failed')); await service.onModuleInit(); jest.advanceTimersByTime(5 * 60 * 1000); await Promise.resolve(); // 应该记录错误但不抛出异常 expect(sessionService.cleanupExpiredSessions).toHaveBeenCalled(); jest.useRealTimers(); }); it('应该处理Zulip队列清理', async () => { jest.useFakeTimers(); const zulipQueueIds = ['queue_1', 'queue_2', 'queue_3']; sessionService.cleanupExpiredSessions.mockResolvedValue({ cleanedCount: 3, zulipQueueIds, }); await service.onModuleInit(); jest.advanceTimersByTime(5 * 60 * 1000); await Promise.resolve(); expect(sessionService.cleanupExpiredSessions).toHaveBeenCalled(); jest.useRealTimers(); }); }); describe('边界情况', () => { it('应该处理空的清理结果', async () => { sessionService.cleanupExpiredSessions.mockResolvedValue({ cleanedCount: 0, zulipQueueIds: [], }); const result = await service.triggerCleanup(); expect(result.cleanedCount).toBe(0); }); it('应该处理大量过期会话', async () => { const largeCount = 1000; sessionService.cleanupExpiredSessions.mockResolvedValue({ cleanedCount: largeCount, zulipQueueIds: Array.from({ length: largeCount }, (_, i) => `queue_${i}`), }); const result = await service.triggerCleanup(); expect(result.cleanedCount).toBe(largeCount); }); it('应该处理重复启动清理任务', async () => { jest.useFakeTimers(); await service.onModuleInit(); await service.onModuleInit(); // 应该只有一个定时器在运行 jest.advanceTimersByTime(5 * 60 * 1000); await Promise.resolve(); jest.useRealTimers(); }); it('应该处理重复停止清理任务', async () => { jest.useFakeTimers(); await service.onModuleInit(); await service.onModuleDestroy(); await service.onModuleDestroy(); // 不应该抛出异常 expect(service['cleanupInterval']).toBeNull(); jest.useRealTimers(); }); }); });