Files
pleno-vue/tests/unit/axios-request-queue.spec.js
T
Jeppe Bundgaard 861e263dfa Add configurable retry logic to request queue:
- Introduce retry settings for specific HTTP status codes with exponential backoff and jitter.
- Refactor `requestQueue` to support method-based concurrency limits and retry configuration.
- Update `RequestQueueProgress` to respect minimum batch size for progress visibility.
- Add unit tests for retry logic, progress visibility, and updated queue behavior.
- Centralize request queue configuration in `REQUEST_QUEUE_CONFIG`.
2026-03-19 14:15:12 +01:00

166 lines
4.4 KiB
JavaScript

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);
});
});