import axios from "axios"; import { beforeEach, afterEach, describe, expect, it } from "vitest"; import { __configureRequestQueueForTests, __resetRequestQueueForTests, requestQueueState, } from "@/services/requestQueue.js"; import { __resetAxiosRequestQueueInstallerForTests, installAxiosRequestQueue, } from "@/services/installAxiosRequestQueue.js"; const flushMicrotasks = async () => { await Promise.resolve(); await Promise.resolve(); }; const flushManyMicrotasks = async (rounds = 10) => { for (let index = 0; index < rounds; index += 1) { await Promise.resolve(); } }; const createDeferred = () => { let resolve; let reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }; const createResponse = (data) => ({ status: 200, statusText: "OK", headers: {}, config: {}, data, }); describe("axios request queue interceptor", () => { beforeEach(() => { __resetAxiosRequestQueueInstallerForTests(); __resetRequestQueueForTests(); __configureRequestQueueForTests({ maxConcurrentGet: 1, maxConcurrentOther: 1, spacingMs: 0 }); installAxiosRequestQueue(); }); afterEach(() => { __resetAxiosRequestQueueInstallerForTests(); __resetRequestQueueForTests(); }); it("queues plain axios requests system-wide", async () => { const first = createDeferred(); const second = createDeferred(); const adapters = [first, second]; const requestOne = axios({ url: "/queue-test-1", method: "GET", adapter: () => adapters.shift()?.promise, }); const requestTwo = axios({ url: "/queue-test-2", method: "GET", adapter: () => adapters.shift()?.promise, }); await flushMicrotasks(); expect(requestQueueState.active).toBe(1); expect(requestQueueState.pending).toBe(1); expect(requestQueueState.batchTotal).toBe(2); first.resolve(createResponse({ id: 1 })); await flushManyMicrotasks(); expect(requestQueueState.active).toBe(1); expect(requestQueueState.pending).toBe(0); second.resolve(createResponse({ id: 2 })); const [responseOne, responseTwo] = await Promise.all([requestOne, requestTwo]); expect(responseOne.data).toEqual({ id: 1 }); expect(responseTwo.data).toEqual({ id: 2 }); await flushMicrotasks(); expect(requestQueueState.active).toBe(0); expect(requestQueueState.pending).toBe(0); expect(requestQueueState.batchCompleted).toBe(2); expect(requestQueueState.batchFailed).toBe(0); }); it("allows 5 concurrent GET requests", async () => { __configureRequestQueueForTests({ maxConcurrentGet: 5, maxConcurrentOther: 1, spacingMs: 0 }); const allDeferred = Array.from({ length: 6 }, () => createDeferred()); const pendingAdapters = [...allDeferred]; const adapter = () => pendingAdapters.shift()?.promise; const requests = Array.from({ length: 6 }, (_, index) => axios({ url: `/queue-get-${index + 1}`, method: "GET", adapter, }) ); await flushMicrotasks(); expect(requestQueueState.active).toBe(5); expect(requestQueueState.pending).toBe(1); for (let index = 0; index < 5; index += 1) { allDeferred[index].resolve(createResponse({ id: index + 1 })); } await flushManyMicrotasks(); expect(requestQueueState.active).toBe(1); expect(requestQueueState.pending).toBe(0); allDeferred[5].resolve(createResponse({ id: 6 })); const responses = await Promise.all(requests); expect(responses.map((item) => item.data.id)).toEqual([1, 2, 3, 4, 5, 6]); }); it("retries configured response status codes", async () => { __configureRequestQueueForTests({ maxConcurrentGet: 1, maxConcurrentOther: 1, spacingMs: 0, retryByStatusCode: { 429: 2 }, retryDelayBaseMs: 0, retryDelayMaxMs: 0, retryDelayJitterMs: 0, }); let attempts = 0; const request = axios({ url: "/queue-retry-429", method: "GET", adapter: async () => { attempts += 1; if (attempts < 3) { throw { response: { status: 429 } }; } return createResponse({ ok: true, attempts }); }, }); const response = await request; expect(response.data).toEqual({ ok: true, attempts: 3 }); expect(attempts).toBe(3); expect(requestQueueState.batchCompleted).toBe(1); expect(requestQueueState.batchFailed).toBe(0); }); });