import { describe, expect, it, mock } from 'bun:test'; import { processBatchJob, processItems } from '../src/batch-processor'; import type { BatchJobData } from '../src/types'; describe('Batch Processor', () => { const mockLogger = { info: mock(() => {}), error: mock(() => {}), warn: mock(() => {}), debug: mock(() => {}), trace: mock(() => {}), }; describe('processBatchJob', () => { it('should process all items successfully', async () => { const batchData: BatchJobData = { items: ['item1', 'item2', 'item3'], options: { batchSize: 2, concurrency: 1, }, }; const processor = mock((item: string) => Promise.resolve({ processed: item })); const result = await processBatchJob(batchData, processor, mockLogger); expect(result.totalItems).toBe(3); expect(result.successful).toBe(3); expect(result.failed).toBe(0); expect(result.errors).toHaveLength(0); expect(processor).toHaveBeenCalledTimes(3); }); it('should handle partial failures', async () => { const batchData: BatchJobData = { items: ['item1', 'item2', 'item3'], options: {}, }; const processor = mock((item: string) => { if (item === 'item2') { return Promise.reject(new Error('Processing failed')); } return Promise.resolve({ processed: item }); }); const result = await processBatchJob(batchData, processor, mockLogger); expect(result.totalItems).toBe(3); expect(result.successful).toBe(2); expect(result.failed).toBe(1); expect(result.errors).toHaveLength(1); expect(result.errors[0].item).toBe('item2'); expect(result.errors[0].error).toBe('Processing failed'); }); it('should handle empty items', async () => { const batchData: BatchJobData = { items: [], options: {}, }; const processor = mock(() => Promise.resolve({})); const result = await processBatchJob(batchData, processor, mockLogger); expect(result.totalItems).toBe(0); expect(result.successful).toBe(0); expect(result.failed).toBe(0); expect(processor).not.toHaveBeenCalled(); }); it('should track duration', async () => { const batchData: BatchJobData = { items: ['item1'], options: {}, }; const processor = mock(() => new Promise(resolve => setTimeout(() => resolve({}), 10)) ); const result = await processBatchJob(batchData, processor, mockLogger); expect(result.duration).toBeGreaterThan(0); }); }); describe('processItems', () => { it('should process items with default options', async () => { const items = [1, 2, 3, 4, 5]; const processor = mock((item: number) => Promise.resolve(item * 2)); const results = await processItems(items, processor); expect(results).toEqual([2, 4, 6, 8, 10]); expect(processor).toHaveBeenCalledTimes(5); }); it('should process items in batches', async () => { const items = [1, 2, 3, 4, 5]; const processor = mock((item: number) => Promise.resolve(item * 2)); const results = await processItems(items, processor, { batchSize: 2, concurrency: 1, }); expect(results).toEqual([2, 4, 6, 8, 10]); expect(processor).toHaveBeenCalledTimes(5); }); it('should handle concurrent processing', async () => { const items = [1, 2, 3, 4]; let activeCount = 0; let maxActiveCount = 0; const processor = mock(async (item: number) => { activeCount++; maxActiveCount = Math.max(maxActiveCount, activeCount); await new Promise(resolve => setTimeout(resolve, 10)); activeCount--; return item * 2; }); await processItems(items, processor, { batchSize: 10, concurrency: 2, }); // With concurrency 2, at most 2 items should be processed at once expect(maxActiveCount).toBeLessThanOrEqual(2); expect(processor).toHaveBeenCalledTimes(4); }); it('should handle empty array', async () => { const processor = mock(() => Promise.resolve({})); const results = await processItems([], processor); expect(results).toEqual([]); expect(processor).not.toHaveBeenCalled(); }); it('should propagate errors', async () => { const items = [1, 2, 3]; const processor = mock((item: number) => { if (item === 2) { return Promise.reject(new Error('Process error')); } return Promise.resolve(item); }); await expect(processItems(items, processor)).rejects.toThrow('Process error'); }); it('should process large batches efficiently', async () => { const items = Array.from({ length: 100 }, (_, i) => i); const processor = mock((item: number) => Promise.resolve(item + 1)); const results = await processItems(items, processor, { batchSize: 20, concurrency: 5, }); expect(results).toHaveLength(100); expect(results[0]).toBe(1); expect(results[99]).toBe(100); }); }); });