import { describe, test, expect } from 'bun:test'; import { runWithConcurrency } from '../../src/client/concurrency.js'; async function* range(n: number): AsyncIterable { for (let i = 0; i < n; i++) yield i; } function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } describe('runWithConcurrency', () => { test('runs all items', async () => { const seen: number[] = []; await runWithConcurrency(range(10), async (i) => { seen.push(i); }, { concurrency: 4 }); expect(seen.sort((a, b) => a - b)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); }); test('respects concurrency cap (never exceeds N inflight)', async () => { let inflight = 0; let peak = 0; await runWithConcurrency(range(50), async () => { inflight++; peak = Math.max(peak, inflight); await delay(5); inflight--; }, { concurrency: 4 }); expect(peak).toBeLessThanOrEqual(4); expect(peak).toBeGreaterThanOrEqual(2); }); test('throws on first error by default (fail-fast)', async () => { let processed = 0; await expect( runWithConcurrency(range(20), async (i) => { await delay(1); if (i === 3) throw new Error('boom'); processed++; }, { concurrency: 2 }), ).rejects.toThrow('boom'); // We don't process all 20; bounded by fail-fast. expect(processed).toBeLessThan(20); }); test('continueOnError reports each + drains', async () => { const errors: number[] = []; let processed = 0; await runWithConcurrency(range(10), async (i) => { await delay(1); if (i % 3 === 0) throw new Error(`bad-${i}`); processed++; }, { concurrency: 3, continueOnError: true, onError: (item) => errors.push(item), }); expect(processed).toBe(6); // i = 1,2,4,5,7,8 expect(errors.sort((a, b) => a - b)).toEqual([0, 3, 6, 9]); }); test('aborts via signal', async () => { const ctrl = new AbortController(); let processed = 0; setTimeout(() => ctrl.abort(), 20); await expect( runWithConcurrency(range(100), async () => { await delay(5); processed++; }, { concurrency: 4, signal: ctrl.signal }), ).rejects.toThrow(); expect(processed).toBeLessThan(100); }); test('concurrency=1 is sequential', async () => { const order: number[] = []; await runWithConcurrency(range(5), async (i) => { order.push(i); await delay(1); }, { concurrency: 1 }); expect(order).toEqual([0, 1, 2, 3, 4]); }); test('throws on concurrency < 1', () => { expect(() => runWithConcurrency(range(0), async () => undefined, { concurrency: 0 }), ).toThrow('concurrency must be ≥ 1'); }); });