175 lines
5.0 KiB
JavaScript
175 lines
5.0 KiB
JavaScript
// @vitest-environment jsdom
|
|
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const { axiosMock } = vi.hoisted(() => ({
|
|
axiosMock: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("axios", () => ({
|
|
default: axiosMock,
|
|
}));
|
|
|
|
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
|
|
import { __resetReleaseTimelineForTests, configureReleaseRuntime } from "@/services/releaseTimeline.js";
|
|
import {
|
|
__configureRequestQueueForTests,
|
|
__resetRequestQueueForTests,
|
|
requestQueueState,
|
|
} from "@/services/requestQueue.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 };
|
|
};
|
|
|
|
describe("authenticatedRequest", () => {
|
|
beforeEach(() => {
|
|
axiosMock.mockReset();
|
|
__resetRequestQueueForTests();
|
|
__resetReleaseTimelineForTests();
|
|
__configureRequestQueueForTests({
|
|
maxConcurrentGet: 1,
|
|
maxConcurrentOther: 1,
|
|
spacingMs: 0,
|
|
retryByStatusCode: {},
|
|
});
|
|
localStorage.clear();
|
|
localStorage.setItem("token", "token");
|
|
});
|
|
|
|
it("resolves scanner lpr no-plate responses without recording a failed request", async () => {
|
|
const response = {
|
|
status: 200,
|
|
data: {
|
|
success: false,
|
|
data: {
|
|
message: "No license plate detected.",
|
|
reason: "no_license_plate_detected",
|
|
},
|
|
meta: [],
|
|
includes: [],
|
|
},
|
|
};
|
|
|
|
axiosMock.mockResolvedValueOnce(response);
|
|
const catchCallable = vi.fn();
|
|
|
|
const result = await authenticatedRequest("/modules/scanner/lpr", "POST", { base64_image: "x" }, catchCallable);
|
|
|
|
expect(result).toBe(response);
|
|
expect(catchCallable).not.toHaveBeenCalled();
|
|
expect(requestQueueState.batchCompleted).toBe(1);
|
|
expect(requestQueueState.batchFailed).toBe(0);
|
|
expect(requestQueueState.errorRequests).toHaveLength(0);
|
|
});
|
|
|
|
it("continues to reject scanner errors", async () => {
|
|
const error = {
|
|
response: {
|
|
status: 500,
|
|
data: {
|
|
success: false,
|
|
data: {
|
|
message: "Internal server error: Different scanner problem.",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
axiosMock.mockRejectedValueOnce(error);
|
|
const catchCallable = vi.fn();
|
|
|
|
await expect(
|
|
authenticatedRequest("/modules/scanner/lpr", "POST", { base64_image: "x" }, catchCallable)
|
|
).rejects.toBe(error);
|
|
|
|
expect(catchCallable).toHaveBeenCalledWith(error);
|
|
});
|
|
|
|
it("does not send bearer credentials to untrusted absolute request URLs", async () => {
|
|
const response = { status: 200, data: { ok: true } };
|
|
axiosMock.mockResolvedValueOnce(response);
|
|
localStorage.setItem("is_subuser", "true");
|
|
localStorage.setItem("selected_customer_number", "1234");
|
|
configureReleaseRuntime({ api_base_url: "https://attacker.example/api" });
|
|
|
|
await authenticatedRequest("https://attacker.example/api/orders", "GET");
|
|
|
|
expect(axiosMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
url: "https://attacker.example/api/orders",
|
|
headers: expect.not.objectContaining({
|
|
Authorization: "Bearer token",
|
|
"X-Customer-Number": 1234,
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
|
|
it("queues requests and executes them sequentially", async () => {
|
|
const first = createDeferred();
|
|
const second = createDeferred();
|
|
const third = createDeferred();
|
|
const deferredCalls = [first, second, third];
|
|
|
|
axiosMock.mockImplementation(() => deferredCalls.shift()?.promise);
|
|
|
|
const request1 = authenticatedRequest("/one", "GET");
|
|
const request2 = authenticatedRequest("/two", "GET");
|
|
const request3 = authenticatedRequest("/three", "GET");
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(axiosMock).toHaveBeenCalledTimes(1);
|
|
expect(requestQueueState.active).toBe(1);
|
|
expect(requestQueueState.pending).toBe(2);
|
|
expect(requestQueueState.batchTotal).toBe(3);
|
|
|
|
first.resolve({ status: 200, data: { id: 1 } });
|
|
await flushManyMicrotasks();
|
|
|
|
expect(axiosMock).toHaveBeenCalledTimes(2);
|
|
expect(requestQueueState.active).toBe(1);
|
|
expect(requestQueueState.pending).toBe(1);
|
|
|
|
second.resolve({ status: 200, data: { id: 2 } });
|
|
await flushManyMicrotasks();
|
|
|
|
expect(axiosMock).toHaveBeenCalledTimes(3);
|
|
expect(requestQueueState.active).toBe(1);
|
|
expect(requestQueueState.pending).toBe(0);
|
|
|
|
third.resolve({ status: 200, data: { id: 3 } });
|
|
await expect(Promise.all([request1, request2, request3])).resolves.toEqual([
|
|
{ status: 200, data: { id: 1 } },
|
|
{ status: 200, data: { id: 2 } },
|
|
{ status: 200, data: { id: 3 } },
|
|
]);
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(requestQueueState.active).toBe(0);
|
|
expect(requestQueueState.pending).toBe(0);
|
|
expect(requestQueueState.batchCompleted).toBe(3);
|
|
expect(requestQueueState.batchFailed).toBe(0);
|
|
});
|
|
});
|