import { describe, test, expect, mock } from "bun:test"; import { attachCwConcurrencyLimiter } from "../../src/modules/cw-utils/cwConcurrencyLimiter"; /** * Build a minimal fake Axios instance with interceptor registration. * Collect registered interceptors so we can invoke them in tests. */ function createMockAxios() { const requestHandlers: Array<(config: any) => any> = []; const responseSuccessHandlers: Array<(res: any) => any> = []; const responseErrorHandlers: Array<(err: any) => any> = []; return { interceptors: { request: { use(fn: (config: any) => any) { requestHandlers.push(fn); }, }, response: { use(onSuccess: (res: any) => any, onError: (err: any) => any) { responseSuccessHandlers.push(onSuccess); responseErrorHandlers.push(onError); }, }, }, _requestHandlers: requestHandlers, _responseSuccessHandlers: responseSuccessHandlers, _responseErrorHandlers: responseErrorHandlers, }; } describe("attachCwConcurrencyLimiter", () => { test("attaches request and response interceptors", () => { const api = createMockAxios(); attachCwConcurrencyLimiter(api as any); expect(api._requestHandlers).toHaveLength(1); expect(api._responseSuccessHandlers).toHaveLength(1); expect(api._responseErrorHandlers).toHaveLength(1); }); test("request interceptor resolves immediately when under limit", async () => { const api = createMockAxios(); attachCwConcurrencyLimiter(api as any, 2); const config = { url: "/test" }; const result = await api._requestHandlers[0](config); expect(result).toEqual(config); }); test("response success interceptor passes through response", async () => { const api = createMockAxios(); attachCwConcurrencyLimiter(api as any, 2); // Acquire a slot first await api._requestHandlers[0]({}); const response = { data: "ok", status: 200 }; const result = api._responseSuccessHandlers[0](response); expect(result).toEqual(response); }); test("response error interceptor rejects with the error and releases slot", async () => { const api = createMockAxios(); attachCwConcurrencyLimiter(api as any, 2); // Acquire a slot await api._requestHandlers[0]({}); const error = new Error("fail"); try { await api._responseErrorHandlers[0](error); expect(true).toBe(false); // should not reach } catch (e) { expect(e).toBe(error); } }); test("queues requests when at max concurrency", async () => { const api = createMockAxios(); attachCwConcurrencyLimiter(api as any, 1); // First request acquires the single slot await api._requestHandlers[0]({ id: 1 }); // Second request should be queued (not resolved yet) let secondResolved = false; const secondPromise = api._requestHandlers[0]({ id: 2 }).then( (config: any) => { secondResolved = true; return config; }, ); // Give the event loop a tick — second should still be pending await new Promise((r) => setTimeout(r, 10)); expect(secondResolved).toBe(false); // Release the first slot via response handler api._responseSuccessHandlers[0]({ status: 200 }); // Now the second should resolve const result = await secondPromise; expect(secondResolved).toBe(true); expect(result).toEqual({ id: 2 }); }); test("multiple requests under limit all proceed immediately", async () => { const api = createMockAxios(); attachCwConcurrencyLimiter(api as any, 3); const results = await Promise.all([ api._requestHandlers[0]({ id: 1 }), api._requestHandlers[0]({ id: 2 }), api._requestHandlers[0]({ id: 3 }), ]); expect(results).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); }); test("FIFO ordering: queued requests resolve in order", async () => { const api = createMockAxios(); attachCwConcurrencyLimiter(api as any, 1); // Fill the single slot await api._requestHandlers[0]({ id: 1 }); const order: number[] = []; const p2 = api._requestHandlers[0]({ id: 2 }).then(() => order.push(2)); const p3 = api._requestHandlers[0]({ id: 3 }).then(() => order.push(3)); // Release slot → should wake request 2 api._responseSuccessHandlers[0]({}); await p2; // Release again → should wake request 3 api._responseSuccessHandlers[0]({}); await p3; expect(order).toEqual([2, 3]); }); test("error release also unblocks queued requests", async () => { const api = createMockAxios(); attachCwConcurrencyLimiter(api as any, 1); await api._requestHandlers[0]({ id: 1 }); let secondResolved = false; const secondPromise = api._requestHandlers[0]({ id: 2 }).then(() => { secondResolved = true; }); // Release via error path try { await api._responseErrorHandlers[0](new Error("fail")); } catch {} await secondPromise; expect(secondResolved).toBe(true); }); test("defaults to max 6 concurrency", async () => { const api = createMockAxios(); attachCwConcurrencyLimiter(api as any); // default max = 6 // 6 requests should all proceed immediately const promises = []; for (let i = 0; i < 6; i++) { promises.push(api._requestHandlers[0]({ id: i })); } const results = await Promise.all(promises); expect(results).toHaveLength(6); // 7th should queue let seventhResolved = false; const seventh = api._requestHandlers[0]({ id: 7 }).then(() => { seventhResolved = true; }); await new Promise((r) => setTimeout(r, 10)); expect(seventhResolved).toBe(false); // Release one to unblock api._responseSuccessHandlers[0]({}); await seventh; expect(seventhResolved).toBe(true); }); });