mirror of
https://github.com/tenrok/axios.git
synced 2026-06-17 19:21:29 +03:00
fix: prevent undefined error codes in settle (#7276)
* 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 <https://github.com/VeerShah41> Co-authored-by: Jason Saayman <jasonsaayman@gmail.com>
This commit is contained in:
+7
-11
@@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user