From bf765769a18cf1a0e0d8ab692d26c99c4a33ec2d Mon Sep 17 00:00:00 2001 From: chrisbamonday Date: Wed, 20 May 2026 14:55:01 -0400 Subject: [PATCH] flattened the axios error approach, which avoiding any issues with stringified errors --- .../axios-call-endpoint-task.ts | 25 +++++++++-- test/callback-queue-processor.test.ts | 43 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/tasks/call-endpoint-task/axios-call-endpoint-task.ts b/src/tasks/call-endpoint-task/axios-call-endpoint-task.ts index a0e38c0..d6ab2e6 100644 --- a/src/tasks/call-endpoint-task/axios-call-endpoint-task.ts +++ b/src/tasks/call-endpoint-task/axios-call-endpoint-task.ts @@ -6,6 +6,22 @@ import { } from "../../types/task-types"; import { StatusCodes } from "http-status-codes"; +function flattenAxiosError(error: unknown): Error { + if (!axios.isAxiosError(error)) { + return error instanceof Error ? error : new Error(String(error)); + } + const flat = new Error(error.message); + flat.name = error.name; + Object.assign(flat, { + code: error.code, + status: error.response?.status ?? error.status, + method: error.config?.method, + url: error.config?.url, + data: error.response?.data, + }); + return flat; +} + export class AxiosCallEndpointTask extends BaseCallEndpointTask { private readonly axios: AxiosInstance; constructor(baseURL?: string) { @@ -33,11 +49,14 @@ export class AxiosCallEndpointTask extends BaseCallEndpointTask { status: response.status, }; } catch (error) { + const status = + (error as AxiosError).response?.status ?? + (error as AxiosError).status ?? + StatusCodes.INTERNAL_SERVER_ERROR; return { success: false, - error: error, - status: - (error as AxiosError).status || StatusCodes.INTERNAL_SERVER_ERROR, + error: flattenAxiosError(error), + status, }; } } diff --git a/test/callback-queue-processor.test.ts b/test/callback-queue-processor.test.ts index 2e1b958..b566433 100644 --- a/test/callback-queue-processor.test.ts +++ b/test/callback-queue-processor.test.ts @@ -6,6 +6,7 @@ import { BasePushToQueueTask, CallbackQueueJob, CallEndpointResult, + AxiosCallEndpointTask, } from "../src"; // Mock the dependencies @@ -325,6 +326,48 @@ describe("CallbackQueueProcessor", () => { { delay: expect.any(Number) }, ); }); + + test("requeues when AxiosCallEndpointTask throws an AxiosError with a circular req/res cycle", async () => { + const req: any = {}; + const res: any = { status: 500, data: { boom: true } }; + req.res = res; + res.req = req; + const circularError: any = new Error( + "Request failed with status code 500", + ); + circularError.name = "AxiosError"; + circularError.isAxiosError = true; + circularError.config = { url: "https://example.test/webhook", method: "post" }; + circularError.request = req; + circularError.response = res; + circularError.status = 500; + circularError.code = "ERR_BAD_RESPONSE"; + + const axiosTask = new AxiosCallEndpointTask(); + (axiosTask as any).axios = { + request: vi.fn().mockRejectedValue(circularError), + }; + + (mockRequeueTask.execute as Mock).mockResolvedValue({ + success: true, + data: { messageId: "retry-msg-circular", success: true }, + }); + + const realProcessor = new CallbackQueueProcessor({ + callEndpointTask: axiosTask, + requeueTask: mockRequeueTask, + maxRetries: 3, + shouldRetryOnGeneralError: true, + }); + + const outcome = await realProcessor.process(validJob); + + expect(outcome).toEqual({ status: "requeued", attempts: 1 }); + expect(mockRequeueTask.execute).toHaveBeenCalledWith( + { ...validJob, attempts: 1 }, + { delay: expect.any(Number) }, + ); + }); }); describe("process - max retries exceeded", () => {