import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { BatchQueue } from './batch-queue'; import type { BatchedEvent } from './types'; describe('BatchQueue', () => { let flushCallback: ReturnType; beforeEach(() => { flushCallback = vi.fn().mockResolvedValue(undefined); vi.useFakeTimers(); }); afterEach(() => { vi.restoreAllMocks(); vi.useRealTimers(); }); it('should batch events up to batchSize', async () => { const queue = new BatchQueue(3, 10000, flushCallback); const event1: BatchedEvent = { type: 'view', data: { contentId: '1', contentType: 'post', sessionId: 'session1', app: 'test' }, timestamp: Date.now(), }; const event2: BatchedEvent = { type: 'view', data: { contentId: '2', contentType: 'post', sessionId: 'session1', app: 'test' }, timestamp: Date.now(), }; const event3: BatchedEvent = { type: 'view', data: { contentId: '3', contentType: 'post', sessionId: 'session1', app: 'test' }, timestamp: Date.now(), }; queue.add(event1); queue.add(event2); expect(flushCallback).not.toHaveBeenCalled(); queue.add(event3); await Promise.resolve(); expect(flushCallback).toHaveBeenCalledTimes(1); expect(flushCallback).toHaveBeenCalledWith([event1, event2, event3]); queue.destroy(); }); it('should flush events on interval', async () => { const queue = new BatchQueue(10, 5000, flushCallback); const event: BatchedEvent = { type: 'view', data: { contentId: '1', contentType: 'post', sessionId: 'session1', app: 'test' }, timestamp: Date.now(), }; queue.add(event); expect(flushCallback).not.toHaveBeenCalled(); await vi.advanceTimersByTimeAsync(5000); expect(flushCallback).toHaveBeenCalledTimes(1); expect(flushCallback).toHaveBeenCalledWith([event]); queue.destroy(); }); it('should re-queue events on flush failure', async () => { const failingCallback = vi.fn().mockRejectedValueOnce(new Error('Network error')); const queue = new BatchQueue(2, 10000, failingCallback); const event: BatchedEvent = { type: 'view', data: { contentId: '1', contentType: 'post', sessionId: 'session1', app: 'test' }, timestamp: Date.now(), }; queue.add(event); queue.add(event); await Promise.resolve(); expect(failingCallback).toHaveBeenCalledTimes(1); queue.destroy(); }); it('should flush remaining events on destroy', async () => { const queue = new BatchQueue(10, 10000, flushCallback); const event: BatchedEvent = { type: 'view', data: { contentId: '1', contentType: 'post', sessionId: 'session1', app: 'test' }, timestamp: Date.now(), }; queue.add(event); queue.destroy(); await Promise.resolve(); expect(flushCallback).toHaveBeenCalledWith([event]); }); });