From 5107ee69aee527b19eabaf80000ca65752135435 Mon Sep 17 00:00:00 2001 From: Veer Shah <185037322+VeerShah41@users.noreply.github.com> Date: Fri, 1 May 2026 22:54:28 +0530 Subject: [PATCH] fix: prevent undefined error codes in settle (#7276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(core): prevent undefined error codes in settle for non-4xx/5xx status codes Replace array indexing logic with conditional check to ensure proper error code assignment for all HTTP status code ranges. Previously, the error code selection used: [ERR_BAD_REQUEST, ERR_BAD_RESPONSE][Math.floor(status / 100) - 4] This produced undefined for status codes outside 4xx/5xx ranges (1xx, 2xx, 3xx, 6xx+), which could break error handling when users customize validateStatus to reject non-standard status codes. Now uses: status >= 400 && status < 500 ? ERR_BAD_REQUEST : ERR_BAD_RESPONSE This ensures: - 4xx codes → ERR_BAD_REQUEST - All other error codes → ERR_BAD_RESPONSE * test(core): add unit tests for settle error code assignment Cover the boundary conditions that were previously untested: - 4xx status codes → ERR_BAD_REQUEST - 5xx status codes → ERR_BAD_RESPONSE - Non-4xx/5xx codes rejected via custom validateStatus → ERR_BAD_RESPONSE (defined, not undefined) These tests would have caught the original array-indexing bug where Math.floor(status/100) - 4 returned negative/out-of-range indices for 1xx, 2xx, 3xx, and 6xx+ status codes, resulting in undefined error.code. * test(core): describe behavior, not PR history, in settle tests --------- Co-authored-by: VeerShah41 Co-authored-by: Jason Saayman --- lib/core/settle.js | 18 ++--- tests/unit/core/settle.test.js | 130 +++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 tests/unit/core/settle.test.js diff --git a/lib/core/settle.js b/lib/core/settle.js index 8629ad05..782f5712 100644 --- a/lib/core/settle.js +++ b/lib/core/settle.js @@ -16,16 +16,12 @@ export default function settle(resolve, reject, response) { if (!response.status || !validateStatus || validateStatus(response.status)) { resolve(response); } else { - reject( - new AxiosError( - 'Request failed with status code ' + response.status, - [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][ - Math.floor(response.status / 100) - 4 - ], - response.config, - response.request, - response - ) - ); + reject(new AxiosError( + 'Request failed with status code ' + response.status, + response.status >= 400 && response.status < 500 ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE, + response.config, + response.request, + response + )); } } diff --git a/tests/unit/core/settle.test.js b/tests/unit/core/settle.test.js new file mode 100644 index 00000000..1403758d --- /dev/null +++ b/tests/unit/core/settle.test.js @@ -0,0 +1,130 @@ +import { describe, it, expect, vi } from 'vitest'; +import settle from '../../../lib/core/settle.js'; +import AxiosError from '../../../lib/core/AxiosError.js'; + +/** + * Builds a minimal response object that settle() expects. + * @param {number} status - HTTP status code + * @param {Function} [validateStatus] - optional custom validator + */ +function makeResponse(status, validateStatus = (s) => s >= 200 && s < 300) { + return { + status, + config: { validateStatus }, + request: {}, + data: null, + headers: {}, + }; +} + +describe('core::settle', () => { + describe('resolves the promise', () => { + it('resolves for a 200 status by default', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(200)); + expect(resolve).toHaveBeenCalledTimes(1); + expect(reject).not.toHaveBeenCalled(); + }); + + it('resolves when validateStatus is not provided', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + const response = makeResponse(500); + response.config.validateStatus = null; + settle(resolve, reject, response); + expect(resolve).toHaveBeenCalledTimes(1); + }); + + it('resolves when status is 0 (no response, e.g. network error)', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + const response = makeResponse(0); + settle(resolve, reject, response); + expect(resolve).toHaveBeenCalledTimes(1); + }); + }); + + describe('error code assignment', () => { + it('assigns ERR_BAD_REQUEST for a 400 status', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(400, () => false)); + expect(reject).toHaveBeenCalledTimes(1); + const error = reject.mock.calls[0][0]; + expect(error).toBeInstanceOf(AxiosError); + expect(error.code).toBe(AxiosError.ERR_BAD_REQUEST); + }); + + it('assigns ERR_BAD_REQUEST for a 404 status', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(404, () => false)); + const error = reject.mock.calls[0][0]; + expect(error.code).toBe(AxiosError.ERR_BAD_REQUEST); + }); + + it('assigns ERR_BAD_REQUEST for a 499 status (top of 4xx range)', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(499, () => false)); + const error = reject.mock.calls[0][0]; + expect(error.code).toBe(AxiosError.ERR_BAD_REQUEST); + }); + + it('assigns ERR_BAD_RESPONSE for a 500 status', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(500, () => false)); + const error = reject.mock.calls[0][0]; + expect(error.code).toBe(AxiosError.ERR_BAD_RESPONSE); + }); + + it('assigns ERR_BAD_RESPONSE for a 503 status', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(503, () => false)); + const error = reject.mock.calls[0][0]; + expect(error.code).toBe(AxiosError.ERR_BAD_RESPONSE); + }); + + // A custom validateStatus can reject any status — including 1xx, 2xx, 3xx, + // or ≥ 600 — and the resulting AxiosError must always have a defined code. + it('assigns ERR_BAD_RESPONSE for a 3xx status rejected via custom validateStatus', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(301, () => false)); + const error = reject.mock.calls[0][0]; + expect(error.code).toBeDefined(); + expect(error.code).toBe(AxiosError.ERR_BAD_RESPONSE); + }); + + it('assigns ERR_BAD_RESPONSE for a 2xx status rejected via custom validateStatus', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(200, () => false)); + const error = reject.mock.calls[0][0]; + expect(error.code).toBeDefined(); + expect(error.code).toBe(AxiosError.ERR_BAD_RESPONSE); + }); + + it('assigns ERR_BAD_RESPONSE for a 6xx status rejected via custom validateStatus', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(600, () => false)); + const error = reject.mock.calls[0][0]; + expect(error.code).toBeDefined(); + expect(error.code).toBe(AxiosError.ERR_BAD_RESPONSE); + }); + }); + + describe('error message', () => { + it('includes the status code in the error message', () => { + const resolve = vi.fn(); + const reject = vi.fn(); + settle(resolve, reject, makeResponse(404, () => false)); + const error = reject.mock.calls[0][0]; + expect(error.message).toBe('Request failed with status code 404'); + }); + }); +});