import { describe, it, expect, vi, beforeEach } from 'vitest'; import { Logger } from '@nestjs/common'; import type { QueueHandle } from '@mosaic/queue'; import type { LogService } from '@mosaic/log'; import { SessionGCService } from './session-gc.service.js'; type MockRedis = { keys: ReturnType; del: ReturnType; }; describe('SessionGCService', () => { let service: SessionGCService; let mockRedis: MockRedis; let mockLogService: { logs: { promoteToWarm: ReturnType } }; beforeEach(() => { mockRedis = { keys: vi.fn().mockResolvedValue([]), del: vi.fn().mockResolvedValue(0), }; mockLogService = { logs: { promoteToWarm: vi.fn().mockResolvedValue(0), }, }; // Suppress logger output in tests vi.spyOn(Logger.prototype, 'log').mockImplementation(() => {}); service = new SessionGCService( mockRedis as unknown as QueueHandle['redis'], mockLogService as unknown as LogService, ); }); it('collect() deletes Valkey keys for session', async () => { mockRedis.keys.mockResolvedValue(['mosaic:session:abc:system', 'mosaic:session:abc:foo']); const result = await service.collect('abc'); expect(mockRedis.del).toHaveBeenCalledWith( 'mosaic:session:abc:system', 'mosaic:session:abc:foo', ); expect(result.cleaned.valkeyKeys).toBe(2); }); it('collect() with no keys returns empty cleaned valkeyKeys', async () => { mockRedis.keys.mockResolvedValue([]); const result = await service.collect('abc'); expect(result.cleaned.valkeyKeys).toBeUndefined(); }); it('collect() returns sessionId in result', async () => { const result = await service.collect('test-session-id'); expect(result.sessionId).toBe('test-session-id'); }); it('fullCollect() deletes all session keys', async () => { mockRedis.keys.mockResolvedValue(['mosaic:session:abc:system', 'mosaic:session:xyz:foo']); const result = await service.fullCollect(); expect(mockRedis.del).toHaveBeenCalled(); expect(result.valkeyKeys).toBe(2); }); it('fullCollect() with no keys returns 0 valkeyKeys', async () => { mockRedis.keys.mockResolvedValue([]); const result = await service.fullCollect(); expect(result.valkeyKeys).toBe(0); expect(mockRedis.del).not.toHaveBeenCalled(); }); it('fullCollect() returns duration', async () => { const result = await service.fullCollect(); expect(result.duration).toBeGreaterThanOrEqual(0); }); it('sweepOrphans() extracts unique session IDs and collects them', async () => { mockRedis.keys.mockResolvedValue([ 'mosaic:session:abc:system', 'mosaic:session:abc:messages', 'mosaic:session:xyz:system', ]); mockRedis.del.mockResolvedValue(1); const result = await service.sweepOrphans(); expect(result.orphanedSessions).toBeGreaterThanOrEqual(0); expect(result.duration).toBeGreaterThanOrEqual(0); }); it('sweepOrphans() returns empty when no session keys', async () => { mockRedis.keys.mockResolvedValue([]); const result = await service.sweepOrphans(); expect(result.orphanedSessions).toBe(0); expect(result.totalCleaned).toHaveLength(0); }); });