mirror of
https://github.com/axios/axios.git
synced 2026-04-11 02:11:50 +08:00
refactor: refresh test suite to be modernised (#7489)
* chore: port karma tests * chore: port karma tests * chore: port karma tests * chore: tests * chore: tests * chore: tests * chore: fix issues with port collisions * refactor: utils tests * refactor: utils tests * refactor: utils tests * refactor: tests to vitests * refactor: tests to vitests * refactor: tests to vitests * refactor: tests to vitests * refactor: tests to vitests * refactor: tests to vitests * refactor: tests to vitests * refactor: ci * chore: install pw deps * chore: fixx ai feedback * chore: wip compatability tests * chore: wip compatability tests * chore: wip compatability tests * refactor: wip smoke * chore: smoke test run * chore: update unzip * chore: update testing * chore: update testing * chore: update testing * chore: update testing * chore: update testing * chore: skip tests that cannot run on node 16 and lower * chore: fix 16x under tests * chore: rest of tests * fix: functions and runs * feat: added tests for esm smoke * feat: added smoke * chore: ignore ai gen plans * chore: ci fixes * chore: fix small p2s
This commit is contained in:
parent
f7a4ee21d5
commit
d905b7598d
99
.github/workflows/run-ci.yml
vendored
99
.github/workflows/run-ci.yml
vendored
@ -15,12 +15,43 @@ concurrency:
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: true
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24.x
|
||||
cache: npm
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build project
|
||||
run: npm run build
|
||||
- name: Install Playwright with deps
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run unit tests
|
||||
run: npm run test:vitest:unit
|
||||
- name: Run browser tests
|
||||
run: npm run test:vitest:browser:headless
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v4
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: axios
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
||||
cjs-smoke-tests:
|
||||
name: CJS smoke tests (Node ${{ matrix.node-version }})
|
||||
needs: ci
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x, 18.x, 20.x, 22.x, 24.x]
|
||||
fail-fast: false
|
||||
|
||||
matrix:
|
||||
node-version: [12, 14, 16, 18]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
@ -31,21 +62,51 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
- name: Install dependencies Node 14
|
||||
if: matrix.node-version == '14.x'
|
||||
run: npm i
|
||||
cache-dependency-path: tests/smoke/cjs/package-lock.json
|
||||
- name: Install dependencies
|
||||
if: matrix.node-version != '14.x'
|
||||
if: matrix.node-version == 16 || matrix.node-version == 18
|
||||
run: npm ci
|
||||
- name: Build project
|
||||
run: npm run build
|
||||
- name: Run unit tests
|
||||
run: npm run test:node
|
||||
- name: Run package tests
|
||||
run: npm run test:package
|
||||
- name: Run browser tests
|
||||
run: npm run test:browser
|
||||
if: matrix.node-version == '24.x'
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v4
|
||||
if: matrix.node-version == '24.x'
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: axios
|
||||
path: dist
|
||||
- name: Install CJS smoke test dependencies
|
||||
working-directory: tests/smoke/cjs
|
||||
run: npm install
|
||||
- name: Run CJS smoke tests
|
||||
working-directory: tests/smoke/cjs
|
||||
run: npm run test:smoke:cjs:mocha
|
||||
|
||||
esm-smoke-tests:
|
||||
name: ESM smoke tests (Node ${{ matrix.node-version }})
|
||||
needs: ci
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20, 22, 24]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: true
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
cache-dependency-path: tests/smoke/esm/package-lock.json
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: axios
|
||||
path: dist
|
||||
- name: Install ESM smoke test dependencies
|
||||
working-directory: tests/smoke/esm
|
||||
run: npm install
|
||||
- name: Run ESM smoke tests
|
||||
working-directory: tests/smoke/esm
|
||||
run: npm run test:smoke:esm:vitest
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -13,4 +13,5 @@ backup/
|
||||
.npmrc
|
||||
.env
|
||||
dist/
|
||||
.vscode/
|
||||
.vscode/
|
||||
docs/
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
"test:vitest": "vitest run",
|
||||
"test:vitest:unit": "vitest run --project unit",
|
||||
"test:vitest:browser": "vitest run --project browser",
|
||||
"test:vitest:browser:headless": "vitest run --project browser-headless",
|
||||
"test:vitest:watch": "vitest",
|
||||
"test:package": "npm run test:eslint && npm run test:exports",
|
||||
"test:eslint": "node bin/ssl_hotfix.js eslint lib/**/*.js",
|
||||
|
||||
178
tests/browser/adapter.browser.test.js
Normal file
178
tests/browser/adapter.browser.test.js
Normal file
@ -0,0 +1,178 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader() {}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return '';
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abort() {}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const sleep = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const waitForRequest = async (timeoutMs = 1000) => {
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const request = requests.at(-1);
|
||||
if (request) {
|
||||
return request;
|
||||
}
|
||||
|
||||
await sleep(0);
|
||||
}
|
||||
|
||||
throw new Error('Expected an XHR request to be sent');
|
||||
};
|
||||
|
||||
describe('adapter (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
axios.interceptors.request.handlers = [];
|
||||
axios.interceptors.response.handlers = [];
|
||||
});
|
||||
|
||||
it('should support custom adapter', async () => {
|
||||
const responsePromise = axios('/foo', {
|
||||
adapter(config) {
|
||||
return new Promise((resolve) => {
|
||||
const request = new XMLHttpRequest();
|
||||
request.open('GET', '/bar');
|
||||
|
||||
request.onreadystatechange = function onReadyStateChange() {
|
||||
resolve({
|
||||
config,
|
||||
request,
|
||||
});
|
||||
};
|
||||
|
||||
request.send(null);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const request = await waitForRequest();
|
||||
expect(request.url).toBe('/bar');
|
||||
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should execute adapter code synchronously', async () => {
|
||||
let asyncFlag = false;
|
||||
|
||||
const responsePromise = axios('/foo', {
|
||||
adapter(config) {
|
||||
return new Promise((resolve) => {
|
||||
const request = new XMLHttpRequest();
|
||||
request.open('GET', '/bar');
|
||||
|
||||
request.onreadystatechange = function onReadyStateChange() {
|
||||
resolve({
|
||||
config,
|
||||
request,
|
||||
});
|
||||
};
|
||||
|
||||
expect(asyncFlag).toBe(false);
|
||||
request.send(null);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
asyncFlag = true;
|
||||
|
||||
const request = await waitForRequest();
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should execute adapter code asynchronously when interceptor is present', async () => {
|
||||
let asyncFlag = false;
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
config.headers.async = 'async it!';
|
||||
return config;
|
||||
});
|
||||
|
||||
const responsePromise = axios('/foo', {
|
||||
adapter(config) {
|
||||
return new Promise((resolve) => {
|
||||
const request = new XMLHttpRequest();
|
||||
request.open('GET', '/bar');
|
||||
|
||||
request.onreadystatechange = function onReadyStateChange() {
|
||||
resolve({
|
||||
config,
|
||||
request,
|
||||
});
|
||||
};
|
||||
|
||||
expect(asyncFlag).toBe(true);
|
||||
request.send(null);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
asyncFlag = true;
|
||||
|
||||
const request = await waitForRequest();
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
});
|
||||
136
tests/browser/basicAuth.browser.test.js
Normal file
136
tests/browser/basicAuth.browser.test.js
Normal file
@ -0,0 +1,136 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = '';
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '', responseHeaders = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abort() {}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const startRequest = (...args) => {
|
||||
const promise = axios(...args);
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return { request, promise };
|
||||
};
|
||||
|
||||
const flushSuccess = async (request, promise) => {
|
||||
request.respondWith({ status: 200 });
|
||||
await promise;
|
||||
};
|
||||
|
||||
describe('basicAuth (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
});
|
||||
|
||||
it('should accept HTTP Basic auth with username/password', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
auth: {
|
||||
username: 'Aladdin',
|
||||
password: 'open sesame',
|
||||
},
|
||||
});
|
||||
|
||||
expect(request.requestHeaders.Authorization).toBe('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should accept HTTP Basic auth credentials without the password parameter', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
auth: {
|
||||
username: 'Aladdin',
|
||||
},
|
||||
});
|
||||
|
||||
expect(request.requestHeaders.Authorization).toBe('Basic QWxhZGRpbjo=');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should accept HTTP Basic auth credentials with non-Latin1 characters in password', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
auth: {
|
||||
username: 'Aladdin',
|
||||
password: 'open ßç£☃sesame',
|
||||
},
|
||||
});
|
||||
|
||||
expect(request.requestHeaders.Authorization).toBe('Basic QWxhZGRpbjpvcGVuIMOfw6fCo+KYg3Nlc2FtZQ==');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should fail to encode HTTP Basic auth credentials with non-Latin1 characters in username', async () => {
|
||||
await expect(axios('/foo', {
|
||||
auth: {
|
||||
username: 'Aladßç£☃din',
|
||||
password: 'open sesame',
|
||||
},
|
||||
})).rejects.toThrow(/character/i);
|
||||
});
|
||||
});
|
||||
173
tests/browser/cancel.browser.test.js
Normal file
173
tests/browser/cancel.browser.test.js
Normal file
@ -0,0 +1,173 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = '';
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.onabort = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '', responseHeaders = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abort() {
|
||||
this.statusText = 'abort';
|
||||
if (this.onabort) {
|
||||
this.onabort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const waitForRequest = async (timeoutMs = 1000) => {
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const request = requests.at(-1);
|
||||
if (request) {
|
||||
return request;
|
||||
}
|
||||
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
throw new Error('Expected an XHR request to be sent');
|
||||
};
|
||||
|
||||
describe('cancel (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
});
|
||||
|
||||
describe('when called before sending request', () => {
|
||||
it('rejects Promise with a CanceledError object', async () => {
|
||||
const source = axios.CancelToken.source();
|
||||
source.cancel('Operation has been canceled.');
|
||||
|
||||
const error = await axios
|
||||
.get('/foo', {
|
||||
cancelToken: source.token,
|
||||
})
|
||||
.catch((thrown) => thrown);
|
||||
|
||||
expect(axios.isCancel(error)).toBe(true);
|
||||
expect(error.message).toBe('Operation has been canceled.');
|
||||
expect(requests).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when called after request has been sent', () => {
|
||||
it('rejects Promise with a CanceledError object', async () => {
|
||||
const source = axios.CancelToken.source();
|
||||
const promise = axios.get('/foo/bar', {
|
||||
cancelToken: source.token,
|
||||
});
|
||||
|
||||
const request = await waitForRequest();
|
||||
|
||||
// Call cancel() after the request has been sent, but before response is received.
|
||||
source.cancel('Operation has been canceled.');
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: 'OK',
|
||||
});
|
||||
|
||||
const error = await promise.catch((thrown) => thrown);
|
||||
|
||||
expect(axios.isCancel(error)).toBe(true);
|
||||
expect(error.message).toBe('Operation has been canceled.');
|
||||
});
|
||||
|
||||
it('calls abort on request object', async () => {
|
||||
const source = axios.CancelToken.source();
|
||||
const promise = axios.get('/foo/bar', {
|
||||
cancelToken: source.token,
|
||||
});
|
||||
|
||||
const request = await waitForRequest();
|
||||
|
||||
// Call cancel() after the request has been sent, but before response is received.
|
||||
source.cancel();
|
||||
|
||||
await promise.catch(() => undefined);
|
||||
|
||||
expect(request.statusText).toBe('abort');
|
||||
});
|
||||
});
|
||||
|
||||
it('supports cancellation using AbortController signal', async () => {
|
||||
const controller = new AbortController();
|
||||
const promise = axios.get('/foo/bar', {
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
const request = await waitForRequest();
|
||||
|
||||
// Call abort() after the request has been sent, but before response is received.
|
||||
controller.abort();
|
||||
setTimeout(() => {
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: 'OK',
|
||||
});
|
||||
}, 0);
|
||||
|
||||
const error = await promise.catch((thrown) => thrown);
|
||||
expect(axios.isCancel(error)).toBe(true);
|
||||
});
|
||||
});
|
||||
90
tests/browser/cancelToken.browser.test.js
Normal file
90
tests/browser/cancelToken.browser.test.js
Normal file
@ -0,0 +1,90 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import CancelToken from '../../lib/cancel/CancelToken.js';
|
||||
import CanceledError from '../../lib/cancel/CanceledError.js';
|
||||
|
||||
describe('CancelToken (vitest browser)', () => {
|
||||
describe('constructor', () => {
|
||||
it('throws when executor is not specified', () => {
|
||||
expect(() => new CancelToken()).toThrowError(
|
||||
new TypeError('executor must be a function.')
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when executor is not a function', () => {
|
||||
expect(() => new CancelToken(123)).toThrowError(
|
||||
new TypeError('executor must be a function.')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reason', () => {
|
||||
it('returns a CanceledError if cancellation has been requested', () => {
|
||||
let cancel;
|
||||
const token = new CancelToken((c) => {
|
||||
cancel = c;
|
||||
});
|
||||
|
||||
cancel('Operation has been canceled.');
|
||||
|
||||
expect(token.reason).toBeInstanceOf(CanceledError);
|
||||
expect(token.reason?.message).toBe('Operation has been canceled.');
|
||||
});
|
||||
|
||||
it('returns undefined if cancellation has not been requested', () => {
|
||||
const token = new CancelToken(() => {});
|
||||
|
||||
expect(token.reason).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('promise', () => {
|
||||
it('resolves when cancellation is requested', async () => {
|
||||
let cancel;
|
||||
const token = new CancelToken((c) => {
|
||||
cancel = c;
|
||||
});
|
||||
|
||||
cancel('Operation has been canceled.');
|
||||
const reason = await token.promise;
|
||||
|
||||
expect(reason).toBeInstanceOf(CanceledError);
|
||||
expect(reason.message).toBe('Operation has been canceled.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('throwIfRequested', () => {
|
||||
it('throws if cancellation has been requested', () => {
|
||||
let cancel;
|
||||
const token = new CancelToken((c) => {
|
||||
cancel = c;
|
||||
});
|
||||
|
||||
cancel('Operation has been canceled.');
|
||||
|
||||
expect(() => token.throwIfRequested()).toThrow(CanceledError);
|
||||
expect(() => token.throwIfRequested()).toThrow('Operation has been canceled.');
|
||||
});
|
||||
|
||||
it('does not throw if cancellation has not been requested', () => {
|
||||
const token = new CancelToken(() => {});
|
||||
|
||||
expect(() => token.throwIfRequested()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('source', () => {
|
||||
it('returns an object containing token and cancel function', () => {
|
||||
const source = CancelToken.source();
|
||||
|
||||
expect(source.token).toBeInstanceOf(CancelToken);
|
||||
expect(source.cancel).toBeTypeOf('function');
|
||||
expect(source.token.reason).toBeUndefined();
|
||||
|
||||
source.cancel('Operation has been canceled.');
|
||||
|
||||
expect(source.token.reason).toBeInstanceOf(CanceledError);
|
||||
expect(source.token.reason?.message).toBe('Operation has been canceled.');
|
||||
});
|
||||
});
|
||||
});
|
||||
56
tests/browser/cookies.browser.test.js
Normal file
56
tests/browser/cookies.browser.test.js
Normal file
@ -0,0 +1,56 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import cookies from '../../lib/helpers/cookies.js';
|
||||
|
||||
const clearAllCookies = () => {
|
||||
const expiry = new Date(Date.now() - 86400000).toUTCString();
|
||||
|
||||
for (const cookie of document.cookie.split(';')) {
|
||||
const name = cookie.split('=')[0].trim();
|
||||
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clear both default-path and root-path cookies for the same key.
|
||||
document.cookie = `${name}=; expires=${expiry}`;
|
||||
document.cookie = `${name}=; expires=${expiry}; path=/`;
|
||||
}
|
||||
};
|
||||
|
||||
describe('helpers::cookies (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
clearAllCookies();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearAllCookies();
|
||||
});
|
||||
|
||||
it('writes cookies', () => {
|
||||
cookies.write('foo', 'baz');
|
||||
|
||||
expect(document.cookie).toBe('foo=baz');
|
||||
});
|
||||
|
||||
it('reads cookies', () => {
|
||||
cookies.write('foo', 'abc');
|
||||
cookies.write('bar', 'def');
|
||||
|
||||
expect(cookies.read('foo')).toBe('abc');
|
||||
expect(cookies.read('bar')).toBe('def');
|
||||
});
|
||||
|
||||
it('removes cookies', () => {
|
||||
cookies.write('foo', 'bar');
|
||||
cookies.remove('foo');
|
||||
|
||||
expect(cookies.read('foo')).toBeNull();
|
||||
});
|
||||
|
||||
it('uri encodes values', () => {
|
||||
cookies.write('foo', 'bar baz%');
|
||||
|
||||
expect(document.cookie).toBe('foo=bar%20baz%25');
|
||||
});
|
||||
});
|
||||
282
tests/browser/defaults.browser.test.js
Normal file
282
tests/browser/defaults.browser.test.js
Normal file
@ -0,0 +1,282 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
import defaults from '../../lib/defaults/index.js';
|
||||
import AxiosHeaders from '../../lib/core/AxiosHeaders.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = '';
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '', responseHeaders = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abort() {}
|
||||
}
|
||||
|
||||
const XSRF_COOKIE_NAME = 'CUSTOM-XSRF-TOKEN';
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const getLastRequest = () => {
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
const finishRequest = async (request, promise) => {
|
||||
request.respondWith({ status: 200 });
|
||||
await promise;
|
||||
};
|
||||
|
||||
describe('defaults (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
delete axios.defaults.baseURL;
|
||||
delete axios.defaults.headers.get['X-CUSTOM-HEADER'];
|
||||
delete axios.defaults.headers.post['X-CUSTOM-HEADER'];
|
||||
document.cookie = `${XSRF_COOKIE_NAME}=;expires=${new Date(Date.now() - 86400000).toUTCString()}`;
|
||||
});
|
||||
|
||||
it('should transform request json', () => {
|
||||
expect(defaults.transformRequest[0]({ foo: 'bar' }, new AxiosHeaders())).toBe('{"foo":"bar"}');
|
||||
});
|
||||
|
||||
it("should also transform request json when 'Content-Type' is 'application/json'", () => {
|
||||
const headers = new AxiosHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
expect(defaults.transformRequest[0](JSON.stringify({ foo: 'bar' }), headers)).toBe('{"foo":"bar"}');
|
||||
expect(defaults.transformRequest[0]([42, 43], headers)).toBe('[42,43]');
|
||||
expect(defaults.transformRequest[0]('foo', headers)).toBe('"foo"');
|
||||
expect(defaults.transformRequest[0](42, headers)).toBe('42');
|
||||
expect(defaults.transformRequest[0](true, headers)).toBe('true');
|
||||
expect(defaults.transformRequest[0](false, headers)).toBe('false');
|
||||
expect(defaults.transformRequest[0](null, headers)).toBe('null');
|
||||
});
|
||||
|
||||
it("should transform the plain data object to a FormData instance when header is 'multipart/form-data'", () => {
|
||||
const headers = new AxiosHeaders({
|
||||
'Content-Type': 'multipart/form-data',
|
||||
});
|
||||
|
||||
const transformed = defaults.transformRequest[0]({ x: 1 }, headers);
|
||||
|
||||
expect(transformed).toBeInstanceOf(FormData);
|
||||
});
|
||||
|
||||
it('should do nothing to request string', () => {
|
||||
expect(defaults.transformRequest[0]('foo=bar', new AxiosHeaders())).toBe('foo=bar');
|
||||
});
|
||||
|
||||
it('should transform response json', () => {
|
||||
const data = defaults.transformResponse[0].call(defaults, '{"foo":"bar"}');
|
||||
|
||||
expect(typeof data).toBe('object');
|
||||
expect(data.foo).toBe('bar');
|
||||
});
|
||||
|
||||
it('should do nothing to response string', () => {
|
||||
expect(defaults.transformResponse[0]('foo=bar')).toBe('foo=bar');
|
||||
});
|
||||
|
||||
it('should use global defaults config', async () => {
|
||||
const promise = axios('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.url).toBe('/foo');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should use modified defaults config', async () => {
|
||||
axios.defaults.baseURL = 'http://example.com/';
|
||||
|
||||
const promise = axios('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.url).toBe('http://example.com/foo');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should use request config', async () => {
|
||||
const promise = axios('/foo', {
|
||||
baseURL: 'http://www.example.com',
|
||||
});
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.url).toBe('http://www.example.com/foo');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should use default config for custom instance', async () => {
|
||||
const instance = axios.create({
|
||||
xsrfCookieName: XSRF_COOKIE_NAME,
|
||||
xsrfHeaderName: 'X-CUSTOM-XSRF-TOKEN',
|
||||
});
|
||||
document.cookie = `${instance.defaults.xsrfCookieName}=foobarbaz`;
|
||||
|
||||
const promise = instance.get('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.requestHeaders[instance.defaults.xsrfHeaderName]).toBe('foobarbaz');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should use GET headers', async () => {
|
||||
axios.defaults.headers.get['X-CUSTOM-HEADER'] = 'foo';
|
||||
|
||||
const promise = axios.get('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.requestHeaders['X-CUSTOM-HEADER']).toBe('foo');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should use POST headers', async () => {
|
||||
axios.defaults.headers.post['X-CUSTOM-HEADER'] = 'foo';
|
||||
|
||||
const promise = axios.post('/foo', {});
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.requestHeaders['X-CUSTOM-HEADER']).toBe('foo');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should use header config', async () => {
|
||||
const instance = axios.create({
|
||||
headers: {
|
||||
common: {
|
||||
'X-COMMON-HEADER': 'commonHeaderValue',
|
||||
},
|
||||
get: {
|
||||
'X-GET-HEADER': 'getHeaderValue',
|
||||
},
|
||||
post: {
|
||||
'X-POST-HEADER': 'postHeaderValue',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const promise = instance.get('/foo', {
|
||||
headers: {
|
||||
'X-FOO-HEADER': 'fooHeaderValue',
|
||||
'X-BAR-HEADER': 'barHeaderValue',
|
||||
},
|
||||
});
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.requestHeaders).toEqual(
|
||||
AxiosHeaders.concat(defaults.headers.common, defaults.headers.get, {
|
||||
'X-COMMON-HEADER': 'commonHeaderValue',
|
||||
'X-GET-HEADER': 'getHeaderValue',
|
||||
'X-FOO-HEADER': 'fooHeaderValue',
|
||||
'X-BAR-HEADER': 'barHeaderValue',
|
||||
}).toJSON()
|
||||
);
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should be used by custom instance if set before instance created', async () => {
|
||||
axios.defaults.baseURL = 'http://example.org/';
|
||||
const instance = axios.create();
|
||||
|
||||
const promise = instance.get('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.url).toBe('http://example.org/foo');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should not be used by custom instance if set after instance created', async () => {
|
||||
const instance = axios.create();
|
||||
axios.defaults.baseURL = 'http://example.org/';
|
||||
|
||||
const promise = instance.get('/foo/users');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.url).toBe('/foo/users');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should resistant to ReDoS attack', async () => {
|
||||
const instance = axios.create();
|
||||
const start = performance.now();
|
||||
const slashes = '/'.repeat(100000);
|
||||
instance.defaults.baseURL = `/${slashes}bar/`;
|
||||
|
||||
const promise = instance.get('/foo');
|
||||
const request = getLastRequest();
|
||||
const elapsedTimeMs = performance.now() - start;
|
||||
|
||||
expect(elapsedTimeMs).toBeLessThan(20);
|
||||
expect(request.url).toBe(`/${slashes}bar/foo`);
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
});
|
||||
104
tests/browser/formdata.browser.test.js
Normal file
104
tests/browser/formdata.browser.test.js
Normal file
@ -0,0 +1,104 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = '';
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '', responseHeaders = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abort() {}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const getLastRequest = () => {
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
describe('formdata (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
});
|
||||
|
||||
it('should allow FormData posting', async () => {
|
||||
const responsePromise = axios.postForm('/foo', {
|
||||
a: 'foo',
|
||||
b: 'bar',
|
||||
});
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.params).toBeInstanceOf(FormData);
|
||||
expect(Object.fromEntries(request.params.entries())).toEqual({
|
||||
a: 'foo',
|
||||
b: 'bar',
|
||||
});
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{}',
|
||||
responseHeaders: 'Content-Type: application/json',
|
||||
});
|
||||
await responsePromise;
|
||||
});
|
||||
});
|
||||
228
tests/browser/headers.browser.test.js
Normal file
228
tests/browser/headers.browser.test.js
Normal file
@ -0,0 +1,228 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import axios, { AxiosHeaders } from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return '';
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abort() {}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const getLastRequest = () => {
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
const finishRequest = async (request, promise) => {
|
||||
request.respondWith({ status: 200 });
|
||||
await promise;
|
||||
};
|
||||
|
||||
function testHeaderValue(headers, key, val) {
|
||||
let found = false;
|
||||
|
||||
for (const k in headers) {
|
||||
if (k.toLowerCase() === key.toLowerCase()) {
|
||||
found = true;
|
||||
expect(headers[k]).toBe(val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
if (typeof val === 'undefined') {
|
||||
expect(Object.prototype.hasOwnProperty.call(headers, key)).toBe(false);
|
||||
} else {
|
||||
throw new Error(`${key} was not found in headers`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('headers (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
});
|
||||
|
||||
it('should default common headers', async () => {
|
||||
const headers = axios.defaults.headers.common;
|
||||
const promise = axios('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
for (const key in headers) {
|
||||
if (Object.prototype.hasOwnProperty.call(headers, key)) {
|
||||
expect(request.requestHeaders[key]).toBe(headers[key]);
|
||||
}
|
||||
}
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should respect common Content-Type header', async () => {
|
||||
const instance = axios.create();
|
||||
instance.defaults.headers.common['Content-Type'] = 'application/custom';
|
||||
|
||||
const promise = instance.patch('/foo', '');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.requestHeaders['Content-Type']).toBe('application/custom');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should add extra headers for post', async () => {
|
||||
const headers = AxiosHeaders.from(axios.defaults.headers.common).toJSON();
|
||||
const promise = axios.post('/foo', 'fizz=buzz');
|
||||
const request = getLastRequest();
|
||||
|
||||
for (const key in headers) {
|
||||
expect(request.requestHeaders[key]).toBe(headers[key]);
|
||||
}
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should reset headers by null or explicit undefined', async () => {
|
||||
const promise = axios.create({
|
||||
headers: {
|
||||
common: {
|
||||
'x-header-a': 'a',
|
||||
'x-header-b': 'b',
|
||||
'x-header-c': 'c',
|
||||
},
|
||||
},
|
||||
}).post(
|
||||
'/foo',
|
||||
{ fizz: 'buzz' },
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': null,
|
||||
'x-header-a': null,
|
||||
'x-header-b': undefined,
|
||||
},
|
||||
}
|
||||
);
|
||||
const request = getLastRequest();
|
||||
|
||||
testHeaderValue(request.requestHeaders, 'Content-Type', undefined);
|
||||
testHeaderValue(request.requestHeaders, 'x-header-a', undefined);
|
||||
testHeaderValue(request.requestHeaders, 'x-header-b', undefined);
|
||||
testHeaderValue(request.requestHeaders, 'x-header-c', 'c');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should use application/json when posting an object', async () => {
|
||||
const promise = axios.post('/foo/bar', {
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
});
|
||||
const request = getLastRequest();
|
||||
|
||||
testHeaderValue(request.requestHeaders, 'Content-Type', 'application/json');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should remove content-type if data is empty', async () => {
|
||||
const promise = axios.post('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
testHeaderValue(request.requestHeaders, 'Content-Type', undefined);
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should preserve content-type if data is false', async () => {
|
||||
const promise = axios.post('/foo', false);
|
||||
const request = getLastRequest();
|
||||
|
||||
testHeaderValue(request.requestHeaders, 'Content-Type', 'application/x-www-form-urlencoded');
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
|
||||
it('should allow an AxiosHeaders instance to be used as the value of the headers option', async () => {
|
||||
const instance = axios.create({
|
||||
headers: new AxiosHeaders({
|
||||
xFoo: 'foo',
|
||||
xBar: 'bar',
|
||||
}),
|
||||
});
|
||||
|
||||
const promise = instance.get('/foo', {
|
||||
headers: {
|
||||
XFOO: 'foo2',
|
||||
xBaz: 'baz',
|
||||
},
|
||||
});
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.requestHeaders.xFoo).toBe('foo2');
|
||||
expect(request.requestHeaders.xBar).toBe('bar');
|
||||
expect(request.requestHeaders.xBaz).toBe('baz');
|
||||
expect(request.requestHeaders.XFOO).toBeUndefined();
|
||||
|
||||
await finishRequest(request, promise);
|
||||
});
|
||||
});
|
||||
255
tests/browser/instance.browser.test.js
Normal file
255
tests/browser/instance.browser.test.js
Normal file
@ -0,0 +1,255 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = '';
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '', responseHeaders = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const getLastRequest = () => {
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
const flushSuccess = async (request, promise) => {
|
||||
request.respondWith({ status: 200 });
|
||||
await promise;
|
||||
};
|
||||
|
||||
const waitForRequest = async (timeoutMs = 1000) => {
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const request = requests.at(-1);
|
||||
if (request) {
|
||||
return request;
|
||||
}
|
||||
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
throw new Error('Expected an XHR request to be sent');
|
||||
};
|
||||
|
||||
describe('instance (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
});
|
||||
|
||||
it('should have the same methods as default instance', () => {
|
||||
const instance = axios.create();
|
||||
|
||||
for (const prop in axios) {
|
||||
if (
|
||||
[
|
||||
'Axios',
|
||||
'AxiosError',
|
||||
'create',
|
||||
'Cancel',
|
||||
'CanceledError',
|
||||
'CancelToken',
|
||||
'isCancel',
|
||||
'all',
|
||||
'spread',
|
||||
'getUri',
|
||||
'isAxiosError',
|
||||
'mergeConfig',
|
||||
'getAdapter',
|
||||
'VERSION',
|
||||
'default',
|
||||
'toFormData',
|
||||
'formToJSON',
|
||||
'AxiosHeaders',
|
||||
'HttpStatusCode',
|
||||
].includes(prop)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
expect(typeof instance[prop]).toBe(typeof axios[prop]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should make an http request without verb helper', async () => {
|
||||
const instance = axios.create();
|
||||
const promise = instance('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.url).toBe('/foo');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should make an http request with url instead of baseURL', async () => {
|
||||
const instance = axios.create({
|
||||
url: 'https://api.example.com',
|
||||
});
|
||||
const promise = instance('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.url).toBe('/foo');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should make an http request', async () => {
|
||||
const instance = axios.create();
|
||||
const promise = instance.get('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.url).toBe('/foo');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should use instance options', async () => {
|
||||
const instance = axios.create({ timeout: 1000 });
|
||||
const promise = instance.get('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.timeout).toBe(1000);
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should have defaults.headers', () => {
|
||||
const instance = axios.create({
|
||||
baseURL: 'https://api.example.com',
|
||||
});
|
||||
|
||||
expect(typeof instance.defaults.headers).toBe('object');
|
||||
expect(typeof instance.defaults.headers.common).toBe('object');
|
||||
});
|
||||
|
||||
it('should have interceptors on the instance', async () => {
|
||||
const requestInterceptorId = axios.interceptors.request.use((config) => {
|
||||
config.foo = true;
|
||||
return config;
|
||||
});
|
||||
|
||||
const instance = axios.create();
|
||||
const instanceInterceptorId = instance.interceptors.request.use((config) => {
|
||||
config.bar = true;
|
||||
return config;
|
||||
});
|
||||
|
||||
try {
|
||||
const responsePromise = instance.get('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
|
||||
expect(response.config.foo).toBeUndefined();
|
||||
expect(response.config.bar).toBe(true);
|
||||
} finally {
|
||||
axios.interceptors.request.eject(requestInterceptorId);
|
||||
instance.interceptors.request.eject(instanceInterceptorId);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have getUri on the instance', () => {
|
||||
const instance = axios.create({
|
||||
baseURL: 'https://api.example.com',
|
||||
});
|
||||
const options = {
|
||||
url: 'foo/bar',
|
||||
params: {
|
||||
name: 'axios',
|
||||
},
|
||||
};
|
||||
|
||||
expect(instance.getUri(options)).toBe('https://api.example.com/foo/bar?name=axios');
|
||||
});
|
||||
|
||||
it('should correctly build url without baseURL', () => {
|
||||
const instance = axios.create();
|
||||
const options = {
|
||||
url: 'foo/bar?foo=bar',
|
||||
params: {
|
||||
name: 'axios',
|
||||
},
|
||||
};
|
||||
|
||||
expect(instance.getUri(options)).toBe('foo/bar?foo=bar&name=axios');
|
||||
});
|
||||
|
||||
it('should correctly discard url hash mark', () => {
|
||||
const instance = axios.create();
|
||||
const options = {
|
||||
baseURL: 'https://api.example.com',
|
||||
url: 'foo/bar?foo=bar#hash',
|
||||
params: {
|
||||
name: 'axios',
|
||||
},
|
||||
};
|
||||
|
||||
expect(instance.getUri(options)).toBe('https://api.example.com/foo/bar?foo=bar&name=axios');
|
||||
});
|
||||
});
|
||||
745
tests/browser/interceptors.browser.test.js
Normal file
745
tests/browser/interceptors.browser.test.js
Normal file
@ -0,0 +1,745 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = {};
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.responseURL = '';
|
||||
this.timeout = 0;
|
||||
this.withCredentials = false;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.onabort = null;
|
||||
this.onerror = null;
|
||||
this.ontimeout = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
if (typeof this.responseHeaders === 'string') {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
|
||||
return Object.entries(this.responseHeaders)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
this.readyState = 1;
|
||||
}
|
||||
|
||||
respondWith({
|
||||
status = 200,
|
||||
statusText = 'OK',
|
||||
responseText = '',
|
||||
response = null,
|
||||
responseHeaders = {},
|
||||
headers = {},
|
||||
responseURL = '',
|
||||
} = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = response === null ? responseText : response;
|
||||
this.responseHeaders = Object.keys(headers).length ? headers : responseHeaders;
|
||||
this.responseURL = responseURL;
|
||||
this.readyState = 4;
|
||||
this.finish();
|
||||
}
|
||||
|
||||
responseTimeout() {
|
||||
if (this.ontimeout) {
|
||||
this.ontimeout();
|
||||
}
|
||||
}
|
||||
|
||||
failNetworkError(message = 'Network Error') {
|
||||
if (this.onerror) {
|
||||
this.onerror({ message });
|
||||
}
|
||||
}
|
||||
|
||||
abort() {
|
||||
if (this.onabort) {
|
||||
this.onabort();
|
||||
}
|
||||
}
|
||||
|
||||
finish() {
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const sleep = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const waitForRequest = async (timeoutMs = 1000) => {
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const request = requests.at(-1);
|
||||
if (request) {
|
||||
return request;
|
||||
}
|
||||
|
||||
await sleep(0);
|
||||
}
|
||||
|
||||
throw new Error('Expected an XHR request to be sent');
|
||||
};
|
||||
|
||||
describe('interceptors (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
axios.interceptors.request.handlers = [];
|
||||
axios.interceptors.response.handlers = [];
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should add a request interceptor (asynchronous by default)', async () => {
|
||||
let asyncFlag = false;
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
config.headers.test = 'added by interceptor';
|
||||
expect(asyncFlag).toBe(true);
|
||||
return config;
|
||||
});
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
asyncFlag = true;
|
||||
|
||||
const request = await waitForRequest();
|
||||
expect(request.requestHeaders.test).toBe('added by interceptor');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should add a request interceptor (explicitly flagged as asynchronous)', async () => {
|
||||
let asyncFlag = false;
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
config.headers.test = 'added by interceptor';
|
||||
expect(asyncFlag).toBe(true);
|
||||
return config;
|
||||
},
|
||||
null,
|
||||
{ synchronous: false }
|
||||
);
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
asyncFlag = true;
|
||||
|
||||
const request = await waitForRequest();
|
||||
expect(request.requestHeaders.test).toBe('added by interceptor');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should add a request interceptor that is executed synchronously when flag is provided', async () => {
|
||||
let asyncFlag = false;
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
config.headers.test = 'added by synchronous interceptor';
|
||||
expect(asyncFlag).toBe(false);
|
||||
return config;
|
||||
},
|
||||
null,
|
||||
{ synchronous: true }
|
||||
);
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
asyncFlag = true;
|
||||
|
||||
const request = await waitForRequest();
|
||||
expect(request.requestHeaders.test).toBe('added by synchronous interceptor');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should execute asynchronously when not all interceptors are explicitly flagged as synchronous', async () => {
|
||||
let asyncFlag = false;
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
config.headers.foo = 'uh oh, async';
|
||||
expect(asyncFlag).toBe(true);
|
||||
return config;
|
||||
});
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
config.headers.test = 'added by synchronous interceptor';
|
||||
expect(asyncFlag).toBe(true);
|
||||
return config;
|
||||
},
|
||||
null,
|
||||
{ synchronous: true }
|
||||
);
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
config.headers.test = 'added by the async interceptor';
|
||||
expect(asyncFlag).toBe(true);
|
||||
return config;
|
||||
});
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
asyncFlag = true;
|
||||
|
||||
const request = await waitForRequest();
|
||||
expect(request.requestHeaders.foo).toBe('uh oh, async');
|
||||
expect(request.requestHeaders.test).toBe('added by synchronous interceptor');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should execute request interceptor in legacy order', async () => {
|
||||
let sequence = '';
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
sequence += '1';
|
||||
return config;
|
||||
});
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
sequence += '2';
|
||||
return config;
|
||||
});
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
sequence += '3';
|
||||
return config;
|
||||
});
|
||||
|
||||
const responsePromise = axios({ url: '/foo' });
|
||||
const request = await waitForRequest();
|
||||
|
||||
expect(sequence).toBe('321');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should execute request interceptor in order', async () => {
|
||||
let sequence = '';
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
sequence += '1';
|
||||
return config;
|
||||
});
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
sequence += '2';
|
||||
return config;
|
||||
});
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
sequence += '3';
|
||||
return config;
|
||||
});
|
||||
|
||||
const responsePromise = axios({
|
||||
url: '/foo',
|
||||
transitional: {
|
||||
legacyInterceptorReqResOrdering: false,
|
||||
},
|
||||
});
|
||||
const request = await waitForRequest();
|
||||
|
||||
expect(sequence).toBe('123');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('runs the interceptor if runWhen function is provided and resolves to true', async () => {
|
||||
const onGetCall = (config) => config.method === 'get';
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
config.headers.test = 'special get headers';
|
||||
return config;
|
||||
},
|
||||
null,
|
||||
{ runWhen: onGetCall }
|
||||
);
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
expect(request.requestHeaders.test).toBe('special get headers');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('does not run the interceptor if runWhen function is provided and resolves to false', async () => {
|
||||
const onPostCall = (config) => config.method === 'post';
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
config.headers.test = 'special get headers';
|
||||
return config;
|
||||
},
|
||||
null,
|
||||
{ runWhen: onPostCall }
|
||||
);
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
expect(request.requestHeaders.test).toBeUndefined();
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('does not run async interceptor if runWhen resolves to false (and runs synchronously)', async () => {
|
||||
let asyncFlag = false;
|
||||
const onPostCall = (config) => config.method === 'post';
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
config.headers.test = 'special get headers';
|
||||
return config;
|
||||
},
|
||||
null,
|
||||
{ synchronous: false, runWhen: onPostCall }
|
||||
);
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
config.headers.sync = 'hello world';
|
||||
expect(asyncFlag).toBe(false);
|
||||
return config;
|
||||
},
|
||||
null,
|
||||
{ synchronous: true }
|
||||
);
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
asyncFlag = true;
|
||||
|
||||
const request = await waitForRequest();
|
||||
expect(request.requestHeaders.test).toBeUndefined();
|
||||
expect(request.requestHeaders.sync).toBe('hello world');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should call request onRejected when interceptor throws', async () => {
|
||||
const rejectedSpy = vi.fn();
|
||||
const error = new Error('deadly error');
|
||||
|
||||
axios.interceptors.request.use(
|
||||
() => {
|
||||
throw error;
|
||||
},
|
||||
rejectedSpy,
|
||||
{ synchronous: true }
|
||||
);
|
||||
|
||||
const responsePromise = axios('/foo').catch(() => {});
|
||||
const request = await waitForRequest();
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
|
||||
expect(rejectedSpy).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('should add a request interceptor that returns a new config object', async () => {
|
||||
axios.interceptors.request.use(() => ({
|
||||
url: '/bar',
|
||||
method: 'post',
|
||||
}));
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
expect(request.method).toBe('POST');
|
||||
expect(request.url).toBe('/bar');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should add a request interceptor that returns a promise', async () => {
|
||||
axios.interceptors.request.use((config) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
config.headers.async = 'promise';
|
||||
resolve(config);
|
||||
}, 100);
|
||||
})
|
||||
);
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest(1500);
|
||||
|
||||
expect(request.requestHeaders.async).toBe('promise');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should add multiple request interceptors', async () => {
|
||||
axios.interceptors.request.use((config) => {
|
||||
config.headers.test1 = '1';
|
||||
return config;
|
||||
});
|
||||
axios.interceptors.request.use((config) => {
|
||||
config.headers.test2 = '2';
|
||||
return config;
|
||||
});
|
||||
axios.interceptors.request.use((config) => {
|
||||
config.headers.test3 = '3';
|
||||
return config;
|
||||
});
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
expect(request.requestHeaders.test1).toBe('1');
|
||||
expect(request.requestHeaders.test2).toBe('2');
|
||||
expect(request.requestHeaders.test3).toBe('3');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should add a response interceptor', async () => {
|
||||
axios.interceptors.response.use((data) => {
|
||||
data.data = `${data.data} - modified by interceptor`;
|
||||
return data;
|
||||
});
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: 'OK',
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
expect(response.data).toBe('OK - modified by interceptor');
|
||||
});
|
||||
|
||||
it('should add a response interceptor when request interceptor is defined', async () => {
|
||||
axios.interceptors.request.use((data) => data);
|
||||
|
||||
axios.interceptors.response.use((data) => {
|
||||
data.data = `${data.data} - modified by interceptor`;
|
||||
return data;
|
||||
});
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: 'OK',
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
expect(response.data).toBe('OK - modified by interceptor');
|
||||
});
|
||||
|
||||
it('should add a response interceptor that returns a new data object', async () => {
|
||||
axios.interceptors.response.use(() => ({
|
||||
data: 'stuff',
|
||||
}));
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: 'OK',
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
expect(response.data).toBe('stuff');
|
||||
});
|
||||
|
||||
it('should add a response interceptor that returns a promise', async () => {
|
||||
axios.interceptors.response.use((data) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
data.data = 'you have been promised!';
|
||||
resolve(data);
|
||||
}, 10);
|
||||
})
|
||||
);
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: 'OK',
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
expect(response.data).toBe('you have been promised!');
|
||||
});
|
||||
|
||||
describe('given multiple response interceptors', () => {
|
||||
const fireRequest = async () => {
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: 'OK',
|
||||
});
|
||||
|
||||
return responsePromise;
|
||||
};
|
||||
|
||||
it('then each interceptor is executed', async () => {
|
||||
const interceptor1 = vi.fn((response) => response);
|
||||
const interceptor2 = vi.fn((response) => response);
|
||||
|
||||
axios.interceptors.response.use(interceptor1);
|
||||
axios.interceptors.response.use(interceptor2);
|
||||
|
||||
await fireRequest();
|
||||
|
||||
expect(interceptor1).toHaveBeenCalled();
|
||||
expect(interceptor2).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('then they are executed in the order they were added', async () => {
|
||||
const interceptor1 = vi.fn((response) => response);
|
||||
const interceptor2 = vi.fn((response) => response);
|
||||
|
||||
axios.interceptors.response.use(interceptor1);
|
||||
axios.interceptors.response.use(interceptor2);
|
||||
|
||||
await fireRequest();
|
||||
|
||||
expect(interceptor1.mock.invocationCallOrder[0]).toBeLessThan(interceptor2.mock.invocationCallOrder[0]);
|
||||
});
|
||||
|
||||
it("then only the last interceptor's result is returned", async () => {
|
||||
axios.interceptors.response.use(() => 'response 1');
|
||||
axios.interceptors.response.use(() => 'response 2');
|
||||
|
||||
const response = await fireRequest();
|
||||
expect(response).toBe('response 2');
|
||||
});
|
||||
|
||||
it("then every interceptor receives the result of its predecessor", async () => {
|
||||
axios.interceptors.response.use(() => 'response 1');
|
||||
axios.interceptors.response.use((response) => [response, 'response 2']);
|
||||
|
||||
const response = await fireRequest();
|
||||
expect(response).toEqual(['response 1', 'response 2']);
|
||||
});
|
||||
|
||||
describe('and when the fulfillment interceptor throws', () => {
|
||||
const fireRequestCatch = async () => {
|
||||
const responsePromise = axios('/foo').catch(() => {});
|
||||
const request = await waitForRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: 'OK',
|
||||
});
|
||||
|
||||
await responsePromise;
|
||||
};
|
||||
|
||||
it('then the following fulfillment interceptor is not called', async () => {
|
||||
axios.interceptors.response.use(() => {
|
||||
throw new Error('throwing interceptor');
|
||||
});
|
||||
|
||||
const interceptor2 = vi.fn((response) => response);
|
||||
axios.interceptors.response.use(interceptor2);
|
||||
|
||||
await fireRequestCatch();
|
||||
expect(interceptor2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('then the following rejection interceptor is called', async () => {
|
||||
axios.interceptors.response.use(() => {
|
||||
throw new Error('throwing interceptor');
|
||||
});
|
||||
|
||||
const rejectIntercept = vi.fn((error) => Promise.reject(error));
|
||||
axios.interceptors.response.use(() => {}, rejectIntercept);
|
||||
|
||||
await fireRequestCatch();
|
||||
expect(rejectIntercept).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('once caught, another following fulfillment interceptor is called again', async () => {
|
||||
axios.interceptors.response.use(() => {
|
||||
throw new Error('throwing interceptor');
|
||||
});
|
||||
|
||||
axios.interceptors.response.use(
|
||||
() => {},
|
||||
() => 'recovered'
|
||||
);
|
||||
|
||||
const interceptor3 = vi.fn((response) => response);
|
||||
axios.interceptors.response.use(interceptor3);
|
||||
|
||||
await fireRequestCatch();
|
||||
expect(interceptor3).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow removing interceptors', async () => {
|
||||
axios.interceptors.response.use((data) => {
|
||||
data.data = `${data.data}1`;
|
||||
return data;
|
||||
});
|
||||
const intercept = axios.interceptors.response.use((data) => {
|
||||
data.data = `${data.data}2`;
|
||||
return data;
|
||||
});
|
||||
axios.interceptors.response.use((data) => {
|
||||
data.data = `${data.data}3`;
|
||||
return data;
|
||||
});
|
||||
|
||||
axios.interceptors.response.eject(intercept);
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: 'OK',
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
expect(response.data).toBe('OK13');
|
||||
});
|
||||
|
||||
it('should remove async interceptor before making request and execute synchronously', async () => {
|
||||
let asyncFlag = false;
|
||||
|
||||
const asyncIntercept = axios.interceptors.request.use(
|
||||
(config) => {
|
||||
config.headers.async = 'async it!';
|
||||
return config;
|
||||
},
|
||||
null,
|
||||
{ synchronous: false }
|
||||
);
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
config.headers.sync = 'hello world';
|
||||
expect(asyncFlag).toBe(false);
|
||||
return config;
|
||||
},
|
||||
null,
|
||||
{ synchronous: true }
|
||||
);
|
||||
|
||||
axios.interceptors.request.eject(asyncIntercept);
|
||||
|
||||
const responsePromise = axios('/foo');
|
||||
asyncFlag = true;
|
||||
|
||||
const request = await waitForRequest();
|
||||
expect(request.requestHeaders.async).toBeUndefined();
|
||||
expect(request.requestHeaders.sync).toBe('hello world');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should execute interceptors before transformers', async () => {
|
||||
axios.interceptors.request.use((config) => {
|
||||
config.data.baz = 'qux';
|
||||
return config;
|
||||
});
|
||||
|
||||
const responsePromise = axios.post('/foo', {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
const request = await waitForRequest();
|
||||
expect(request.params).toEqual('{"foo":"bar","baz":"qux"}');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should modify base URL in request interceptor', async () => {
|
||||
const instance = axios.create({
|
||||
baseURL: 'http://test.com/',
|
||||
});
|
||||
|
||||
instance.interceptors.request.use((config) => {
|
||||
config.baseURL = 'http://rebase.com/';
|
||||
return config;
|
||||
});
|
||||
|
||||
const responsePromise = instance.get('/foo');
|
||||
const request = await waitForRequest();
|
||||
|
||||
expect(request.url).toBe('http://rebase.com/foo');
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should clear all request interceptors', () => {
|
||||
const instance = axios.create({
|
||||
baseURL: 'http://test.com/',
|
||||
});
|
||||
|
||||
instance.interceptors.request.use((config) => config);
|
||||
instance.interceptors.request.clear();
|
||||
|
||||
expect(instance.interceptors.request.handlers.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should clear all response interceptors', () => {
|
||||
const instance = axios.create({
|
||||
baseURL: 'http://test.com/',
|
||||
});
|
||||
|
||||
instance.interceptors.response.use((config) => config);
|
||||
instance.interceptors.response.clear();
|
||||
|
||||
expect(instance.interceptors.response.handlers.length).toBe(0);
|
||||
});
|
||||
});
|
||||
13
tests/browser/isURLSameOrigin.browser.test.js
Normal file
13
tests/browser/isURLSameOrigin.browser.test.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import isURLSameOrigin from '../../lib/helpers/isURLSameOrigin.js';
|
||||
|
||||
describe('helpers::isURLSameOrigin (vitest browser)', () => {
|
||||
it('detects same origin', () => {
|
||||
expect(isURLSameOrigin(window.location.href)).toBe(true);
|
||||
});
|
||||
|
||||
it('detects different origin', () => {
|
||||
expect(isURLSameOrigin('https://github.com/axios/axios')).toBe(false);
|
||||
});
|
||||
});
|
||||
220
tests/browser/options.browser.test.js
Normal file
220
tests/browser/options.browser.test.js
Normal file
@ -0,0 +1,220 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = '';
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '', responseHeaders = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const startRequest = (...args) => {
|
||||
const promise = axios(...args);
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return { request, promise };
|
||||
};
|
||||
|
||||
const flushSuccess = async (request, promise) => {
|
||||
request.respondWith({ status: 200 });
|
||||
await promise;
|
||||
};
|
||||
|
||||
describe('options (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should default method to get', async () => {
|
||||
const { request, promise } = startRequest('/foo');
|
||||
|
||||
expect(request.method).toBe('GET');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should accept headers', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
});
|
||||
|
||||
expect(request.requestHeaders['X-Requested-With']).toBe('XMLHttpRequest');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should accept params', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
params: {
|
||||
foo: 123,
|
||||
bar: 456,
|
||||
},
|
||||
});
|
||||
|
||||
expect(request.url).toBe('/foo?foo=123&bar=456');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should allow overriding default headers', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
headers: {
|
||||
Accept: 'foo/bar',
|
||||
},
|
||||
});
|
||||
|
||||
expect(request.requestHeaders.Accept).toBe('foo/bar');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should accept base URL', async () => {
|
||||
const instance = axios.create({
|
||||
baseURL: 'http://test.com/',
|
||||
});
|
||||
|
||||
const promise = instance.get('/foo');
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
expect(request.url).toBe('http://test.com/foo');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should warn about baseUrl', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const instance = axios.create({
|
||||
baseUrl: 'http://example.com/',
|
||||
});
|
||||
|
||||
const promise = instance.get('/foo');
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
expect(warnSpy).toHaveBeenCalledWith('baseUrl is likely a misspelling of baseURL');
|
||||
expect(request.url).toBe('/foo');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should ignore base URL if request URL is absolute', async () => {
|
||||
const instance = axios.create({
|
||||
baseURL: 'http://someurl.com/',
|
||||
});
|
||||
|
||||
const promise = instance.get('http://someotherurl.com/');
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
expect(request.url).toBe('http://someotherurl.com/');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should combine the URLs if base url and request url exist and allowAbsoluteUrls is false', async () => {
|
||||
const instance = axios.create({
|
||||
baseURL: 'http://someurl.com/',
|
||||
allowAbsoluteUrls: false,
|
||||
});
|
||||
|
||||
const promise = instance.get('http://someotherurl.com/');
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
expect(request.url).toBe('http://someurl.com/http://someotherurl.com/');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should change only the baseURL of the specified instance', () => {
|
||||
const instance1 = axios.create();
|
||||
const instance2 = axios.create();
|
||||
|
||||
instance1.defaults.baseURL = 'http://instance1.example.com/';
|
||||
|
||||
expect(instance2.defaults.baseURL).not.toBe('http://instance1.example.com/');
|
||||
});
|
||||
|
||||
it('should change only the headers of the specified instance', () => {
|
||||
const instance1 = axios.create();
|
||||
const instance2 = axios.create();
|
||||
|
||||
instance1.defaults.headers.common.Authorization = 'faketoken';
|
||||
instance2.defaults.headers.common.Authorization = 'differentfaketoken';
|
||||
|
||||
instance1.defaults.headers.common['Content-Type'] = 'application/xml';
|
||||
instance2.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
|
||||
expect(axios.defaults.headers.common.Authorization).toBeUndefined();
|
||||
expect(instance1.defaults.headers.common.Authorization).toBe('faketoken');
|
||||
expect(instance2.defaults.headers.common.Authorization).toBe('differentfaketoken');
|
||||
|
||||
expect(axios.defaults.headers.common['Content-Type']).toBeUndefined();
|
||||
expect(instance1.defaults.headers.common['Content-Type']).toBe('application/xml');
|
||||
expect(instance2.defaults.headers.common['Content-Type']).toBe('application/x-www-form-urlencoded');
|
||||
});
|
||||
});
|
||||
230
tests/browser/progress.browser.test.js
Normal file
230
tests/browser/progress.browser.test.js
Normal file
@ -0,0 +1,230 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = {};
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.timeout = 0;
|
||||
this.withCredentials = false;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.onabort = null;
|
||||
this.onerror = null;
|
||||
this.ontimeout = null;
|
||||
this._listeners = {};
|
||||
this._uploadListeners = {};
|
||||
this.upload = {
|
||||
addEventListener: (type, listener) => {
|
||||
this._uploadListeners[type] ||= [];
|
||||
this._uploadListeners[type].push(listener);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener(type, listener) {
|
||||
this._listeners[type] ||= [];
|
||||
this._listeners[type].push(listener);
|
||||
}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return Object.entries(this.responseHeaders)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
this.readyState = 1;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
getListenerCount(type, target = 'request') {
|
||||
const listeners = target === 'upload' ? this._uploadListeners : this._listeners;
|
||||
return listeners[type]?.length || 0;
|
||||
}
|
||||
|
||||
emit(type, target = 'request', event = {}) {
|
||||
const listeners = target === 'upload' ? this._uploadListeners : this._listeners;
|
||||
(listeners[type] || []).forEach((listener) => listener(event));
|
||||
}
|
||||
|
||||
respondWith({
|
||||
status = 200,
|
||||
statusText = 'OK',
|
||||
responseText = '',
|
||||
response = null,
|
||||
headers = {},
|
||||
} = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = response;
|
||||
this.responseHeaders = headers;
|
||||
this.readyState = 4;
|
||||
|
||||
this.emit('progress', 'request', {
|
||||
loaded: responseText.length,
|
||||
total: responseText.length,
|
||||
lengthComputable: true,
|
||||
});
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const getLastRequest = () => {
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
describe('progress (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should add a download progress handler', async () => {
|
||||
const progressSpy = vi.fn();
|
||||
const responsePromise = axios('/foo', { onDownloadProgress: progressSpy });
|
||||
const request = getLastRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{"foo": "bar"}',
|
||||
});
|
||||
await responsePromise;
|
||||
|
||||
expect(progressSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add an upload progress handler', async () => {
|
||||
const progressSpy = vi.fn();
|
||||
const responsePromise = axios('/foo', { onUploadProgress: progressSpy });
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.getListenerCount('progress', 'upload')).toBe(1);
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{"foo": "bar"}',
|
||||
});
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should add both upload and download progress handlers', async () => {
|
||||
const downloadProgressSpy = vi.fn();
|
||||
const uploadProgressSpy = vi.fn();
|
||||
const responsePromise = axios('/foo', {
|
||||
onDownloadProgress: downloadProgressSpy,
|
||||
onUploadProgress: uploadProgressSpy,
|
||||
});
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(downloadProgressSpy).not.toHaveBeenCalled();
|
||||
expect(request.getListenerCount('progress', 'request')).toBe(1);
|
||||
expect(request.getListenerCount('progress', 'upload')).toBe(1);
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{"foo": "bar"}',
|
||||
});
|
||||
await responsePromise;
|
||||
|
||||
expect(downloadProgressSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add a download progress handler from instance config', async () => {
|
||||
const progressSpy = vi.fn();
|
||||
const instance = axios.create({
|
||||
onDownloadProgress: progressSpy,
|
||||
});
|
||||
|
||||
const responsePromise = instance.get('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{"foo": "bar"}',
|
||||
});
|
||||
await responsePromise;
|
||||
|
||||
expect(progressSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add an upload progress handler from instance config', async () => {
|
||||
const progressSpy = vi.fn();
|
||||
const instance = axios.create({
|
||||
onUploadProgress: progressSpy,
|
||||
});
|
||||
|
||||
const responsePromise = instance.get('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.getListenerCount('progress', 'upload')).toBe(1);
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{"foo": "bar"}',
|
||||
});
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should add upload and download progress handlers from instance config', async () => {
|
||||
const downloadProgressSpy = vi.fn();
|
||||
const uploadProgressSpy = vi.fn();
|
||||
const instance = axios.create({
|
||||
onDownloadProgress: downloadProgressSpy,
|
||||
onUploadProgress: uploadProgressSpy,
|
||||
});
|
||||
|
||||
const responsePromise = instance.get('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(downloadProgressSpy).not.toHaveBeenCalled();
|
||||
expect(request.getListenerCount('progress', 'request')).toBe(1);
|
||||
expect(request.getListenerCount('progress', 'upload')).toBe(1);
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{"foo": "bar"}',
|
||||
});
|
||||
await responsePromise;
|
||||
|
||||
expect(downloadProgressSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
122
tests/browser/promise.browser.test.js
Normal file
122
tests/browser/promise.browser.test.js
Normal file
@ -0,0 +1,122 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = '';
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '', responseHeaders = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abort() {}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const getLastRequest = () => {
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
describe('promise (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
});
|
||||
|
||||
it('should provide succinct object to then', async () => {
|
||||
const responsePromise = axios('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{"hello":"world"}',
|
||||
responseHeaders: 'Content-Type: application/json',
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
|
||||
expect(typeof response).toBe('object');
|
||||
expect(response.data.hello).toBe('world');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers['content-type']).toBe('application/json');
|
||||
expect(response.config.url).toBe('/foo');
|
||||
});
|
||||
|
||||
it('should support all', async () => {
|
||||
const result = await axios.all([true, 123]);
|
||||
|
||||
expect(result).toEqual([true, 123]);
|
||||
});
|
||||
|
||||
it('should support spread', async () => {
|
||||
let fulfilled = false;
|
||||
const result = await axios.all([123, 456]).then(
|
||||
axios.spread((a, b) => {
|
||||
expect(a + b).toBe(123 + 456);
|
||||
fulfilled = true;
|
||||
return 'hello world';
|
||||
})
|
||||
);
|
||||
|
||||
expect(fulfilled).toBe(true);
|
||||
expect(result).toBe('hello world');
|
||||
});
|
||||
});
|
||||
497
tests/browser/requests.browser.test.js
Normal file
497
tests/browser/requests.browser.test.js
Normal file
@ -0,0 +1,497 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = {};
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.responseURL = '';
|
||||
this.timeout = 0;
|
||||
this.withCredentials = false;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.onabort = null;
|
||||
this.onerror = null;
|
||||
this.ontimeout = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return Object.entries(this.responseHeaders)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
this.readyState = 1;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({
|
||||
status = 200,
|
||||
statusText = 'OK',
|
||||
responseText = '',
|
||||
response = null,
|
||||
headers = {},
|
||||
responseURL = '',
|
||||
} = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = response;
|
||||
this.responseHeaders = headers;
|
||||
this.responseURL = responseURL;
|
||||
this.readyState = 4;
|
||||
this.finish();
|
||||
}
|
||||
|
||||
responseTimeout() {
|
||||
if (this.ontimeout) {
|
||||
this.ontimeout();
|
||||
}
|
||||
}
|
||||
|
||||
failNetworkError(message = 'Network Error') {
|
||||
if (this.onerror) {
|
||||
this.onerror({ message });
|
||||
}
|
||||
}
|
||||
|
||||
abort() {
|
||||
if (this.onabort) {
|
||||
this.onabort();
|
||||
}
|
||||
}
|
||||
|
||||
finish() {
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const startRequest = (...args) => {
|
||||
const promise = axios(...args);
|
||||
const request = requests.at(-1);
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return { request, promise };
|
||||
};
|
||||
|
||||
const flushSuccess = async (request, promise) => {
|
||||
request.respondWith({ status: 200 });
|
||||
await promise;
|
||||
};
|
||||
|
||||
describe('requests (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should treat single string arg as url', async () => {
|
||||
const { request, promise } = startRequest('/foo');
|
||||
|
||||
expect(request.url).toBe('/foo');
|
||||
expect(request.method).toBe('GET');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should treat method value as lowercase string', async () => {
|
||||
const { request, promise } = startRequest({
|
||||
url: '/foo',
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
request.respondWith({ status: 200 });
|
||||
const response = await promise;
|
||||
|
||||
expect(response.config.method).toBe('post');
|
||||
});
|
||||
|
||||
it('should allow string arg as url, and config arg', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
method: 'post',
|
||||
});
|
||||
|
||||
expect(request.url).toBe('/foo');
|
||||
expect(request.method).toBe('POST');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should allow data', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
method: 'delete',
|
||||
data: { foo: 'bar' },
|
||||
});
|
||||
|
||||
expect(request.params).toBe(JSON.stringify({ foo: 'bar' }));
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should make an http request', async () => {
|
||||
const { request, promise } = startRequest('/foo');
|
||||
|
||||
expect(request.url).toBe('/foo');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
describe('timeouts', () => {
|
||||
it('should handle timeouts', async () => {
|
||||
const { request, promise } = startRequest({
|
||||
url: '/foo',
|
||||
timeout: 100,
|
||||
});
|
||||
|
||||
request.responseTimeout();
|
||||
|
||||
const err = await promise.catch((error) => error);
|
||||
|
||||
expect(err).toBeInstanceOf(Error);
|
||||
expect(err.code).toBe('ECONNABORTED');
|
||||
});
|
||||
|
||||
describe('transitional.clarifyTimeoutError', () => {
|
||||
it('should throw ETIMEDOUT instead of ECONNABORTED on request timeouts', async () => {
|
||||
const { request, promise } = startRequest({
|
||||
url: '/foo',
|
||||
timeout: 100,
|
||||
transitional: {
|
||||
clarifyTimeoutError: true,
|
||||
},
|
||||
});
|
||||
|
||||
request.responseTimeout();
|
||||
|
||||
const err = await promise.catch((error) => error);
|
||||
|
||||
expect(err).toBeInstanceOf(Error);
|
||||
expect(err.code).toBe('ETIMEDOUT');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject on network errors', async () => {
|
||||
const { request, promise } = startRequest('http://thisisnotaserver/foo');
|
||||
|
||||
request.failNetworkError();
|
||||
|
||||
const reason = await promise.catch((error) => error);
|
||||
|
||||
expect(reason).toBeInstanceOf(Error);
|
||||
expect(reason.config.method).toBe('get');
|
||||
expect(reason.config.url).toBe('http://thisisnotaserver/foo');
|
||||
expect(reason.request).toBeInstanceOf(MockXMLHttpRequest);
|
||||
});
|
||||
|
||||
it('should reject on abort', async () => {
|
||||
const { request, promise } = startRequest('/foo');
|
||||
|
||||
request.abort();
|
||||
|
||||
const reason = await promise.catch((error) => error);
|
||||
|
||||
expect(reason).toBeInstanceOf(Error);
|
||||
expect(reason.config.method).toBe('get');
|
||||
expect(reason.config.url).toBe('/foo');
|
||||
expect(reason.request).toBeInstanceOf(MockXMLHttpRequest);
|
||||
});
|
||||
|
||||
it('should reject when validateStatus returns false', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
validateStatus(status) {
|
||||
return status !== 500;
|
||||
},
|
||||
});
|
||||
|
||||
request.respondWith({ status: 500 });
|
||||
const reason = await promise.catch((error) => error);
|
||||
|
||||
expect(reason).toBeInstanceOf(Error);
|
||||
expect(reason.message).toBe('Request failed with status code 500');
|
||||
expect(reason.config.method).toBe('get');
|
||||
expect(reason.config.url).toBe('/foo');
|
||||
expect(reason.response.status).toBe(500);
|
||||
});
|
||||
|
||||
it('should resolve when validateStatus returns true', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
validateStatus(status) {
|
||||
return status === 500;
|
||||
},
|
||||
});
|
||||
|
||||
request.respondWith({ status: 500 });
|
||||
await expect(promise).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it('should resolve when the response status is 0 (file protocol)', async () => {
|
||||
const { request, promise } = startRequest('file:///xxx');
|
||||
|
||||
request.respondWith({
|
||||
status: 0,
|
||||
responseURL: 'file:///xxx',
|
||||
});
|
||||
|
||||
await expect(promise).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it('should resolve when validateStatus is null', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
request.respondWith({ status: 500 });
|
||||
await expect(promise).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it('should resolve when validateStatus is undefined', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
validateStatus: undefined,
|
||||
});
|
||||
|
||||
request.respondWith({ status: 500 });
|
||||
await expect(promise).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
// https://github.com/axios/axios/issues/378
|
||||
it('should return JSON when rejecting', async () => {
|
||||
const { request, promise } = startRequest(
|
||||
'/api/account/signup',
|
||||
{
|
||||
username: null,
|
||||
password: null,
|
||||
},
|
||||
{
|
||||
method: 'post',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
request.respondWith({
|
||||
status: 400,
|
||||
statusText: 'Bad Request',
|
||||
responseText: '{"error": "BAD USERNAME", "code": 1}',
|
||||
});
|
||||
|
||||
const error = await promise.catch((err) => err);
|
||||
const response = error.response;
|
||||
|
||||
expect(typeof response.data).toBe('object');
|
||||
expect(response.data.error).toBe('BAD USERNAME');
|
||||
expect(response.data.code).toBe(1);
|
||||
});
|
||||
|
||||
it('should make cross domain http request', async () => {
|
||||
const { request, promise } = startRequest('www.someurl.com/foo', {
|
||||
method: 'post',
|
||||
});
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
responseText: '{"foo": "bar"}',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const response = await promise;
|
||||
|
||||
expect(response.data.foo).toBe('bar');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.statusText).toBe('OK');
|
||||
expect(response.headers['content-type']).toBe('application/json');
|
||||
});
|
||||
|
||||
it('should supply correct response', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
method: 'post',
|
||||
});
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
responseText: '{"foo": "bar"}',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const response = await promise;
|
||||
|
||||
expect(response.data.foo).toBe('bar');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.statusText).toBe('OK');
|
||||
expect(response.headers['content-type']).toBe('application/json');
|
||||
});
|
||||
|
||||
it('should not modify the config url with relative baseURL', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
baseURL: '/api',
|
||||
});
|
||||
|
||||
request.respondWith({
|
||||
status: 404,
|
||||
statusText: 'NOT FOUND',
|
||||
responseText: 'Resource not found',
|
||||
});
|
||||
|
||||
const error = await promise.catch((err) => err);
|
||||
const config = error.config;
|
||||
|
||||
expect(config.baseURL).toBe('/api');
|
||||
expect(config.url).toBe('/foo');
|
||||
});
|
||||
|
||||
it('should allow overriding Content-Type header case-insensitive', async () => {
|
||||
const contentType = 'application/vnd.myapp.type+json';
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
method: 'post',
|
||||
data: { prop: 'value' },
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
},
|
||||
});
|
||||
|
||||
expect(request.requestHeaders['Content-Type']).toBe(contentType);
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should support binary data as array buffer', async () => {
|
||||
const input = new Int8Array([1, 2]);
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
method: 'post',
|
||||
data: input.buffer,
|
||||
});
|
||||
|
||||
const output = new Int8Array(request.params);
|
||||
expect(output.length).toBe(2);
|
||||
expect(output[0]).toBe(1);
|
||||
expect(output[1]).toBe(2);
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should support binary data as array buffer view', async () => {
|
||||
const input = new Int8Array([1, 2]);
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
method: 'post',
|
||||
data: input,
|
||||
});
|
||||
|
||||
const output = new Int8Array(request.params);
|
||||
expect(output.length).toBe(2);
|
||||
expect(output[0]).toBe(1);
|
||||
expect(output[1]).toBe(2);
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should support array buffer response', async () => {
|
||||
const str2ab = (str) => {
|
||||
const buff = new ArrayBuffer(str.length * 2);
|
||||
const view = new Uint16Array(buff);
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
view[i] = str.charCodeAt(i);
|
||||
}
|
||||
|
||||
return buff;
|
||||
};
|
||||
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
response: str2ab('Hello world'),
|
||||
});
|
||||
|
||||
const response = await promise;
|
||||
expect(response.data.byteLength).toBe(22);
|
||||
});
|
||||
|
||||
it('should support URLSearchParams', async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('param1', 'value1');
|
||||
params.append('param2', 'value2');
|
||||
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
method: 'post',
|
||||
data: params,
|
||||
});
|
||||
|
||||
expect(request.requestHeaders['Content-Type']).toBe(
|
||||
'application/x-www-form-urlencoded;charset=utf-8'
|
||||
);
|
||||
expect(request.params).toBe('param1=value1¶m2=value2');
|
||||
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should support HTTP protocol', async () => {
|
||||
const { request, promise } = startRequest('/foo', {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
expect(request.method).toBe('GET');
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should support HTTPS protocol', async () => {
|
||||
const { request, promise } = startRequest('https://www.google.com', {
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
expect(request.method).toBe('GET');
|
||||
await flushSuccess(request, promise);
|
||||
});
|
||||
|
||||
it('should return unsupported protocol error message', async () => {
|
||||
await expect(axios.get('ftp:localhost')).rejects.toMatchObject({
|
||||
message: 'Unsupported protocol ftp:',
|
||||
});
|
||||
});
|
||||
});
|
||||
99
tests/browser/settle.browser.test.js
Normal file
99
tests/browser/settle.browser.test.js
Normal file
@ -0,0 +1,99 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import settle from '../../lib/core/settle.js';
|
||||
import AxiosError from '../../lib/core/AxiosError.js';
|
||||
|
||||
describe('core::settle (vitest browser)', () => {
|
||||
it('resolves when response status is missing', () => {
|
||||
const resolve = vi.fn();
|
||||
const reject = vi.fn();
|
||||
const response = {
|
||||
config: {
|
||||
validateStatus: () => true,
|
||||
},
|
||||
};
|
||||
|
||||
settle(resolve, reject, response);
|
||||
|
||||
expect(resolve).toHaveBeenCalledOnce();
|
||||
expect(resolve).toHaveBeenCalledWith(response);
|
||||
expect(reject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('resolves when validateStatus is not configured', () => {
|
||||
const resolve = vi.fn();
|
||||
const reject = vi.fn();
|
||||
const response = {
|
||||
status: 500,
|
||||
config: {},
|
||||
};
|
||||
|
||||
settle(resolve, reject, response);
|
||||
|
||||
expect(resolve).toHaveBeenCalledOnce();
|
||||
expect(resolve).toHaveBeenCalledWith(response);
|
||||
expect(reject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('resolves when validateStatus returns true', () => {
|
||||
const resolve = vi.fn();
|
||||
const reject = vi.fn();
|
||||
const response = {
|
||||
status: 500,
|
||||
config: {
|
||||
validateStatus: () => true,
|
||||
},
|
||||
};
|
||||
|
||||
settle(resolve, reject, response);
|
||||
|
||||
expect(resolve).toHaveBeenCalledOnce();
|
||||
expect(resolve).toHaveBeenCalledWith(response);
|
||||
expect(reject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects with an AxiosError when validateStatus returns false', () => {
|
||||
const resolve = vi.fn();
|
||||
const reject = vi.fn();
|
||||
const request = {
|
||||
path: '/foo',
|
||||
};
|
||||
const response = {
|
||||
status: 500,
|
||||
config: {
|
||||
validateStatus: () => false,
|
||||
},
|
||||
request,
|
||||
};
|
||||
|
||||
settle(resolve, reject, response);
|
||||
|
||||
expect(resolve).not.toHaveBeenCalled();
|
||||
expect(reject).toHaveBeenCalledOnce();
|
||||
|
||||
const reason = reject.mock.calls[0][0];
|
||||
expect(reason).toBeInstanceOf(AxiosError);
|
||||
expect(reason.message).toBe('Request failed with status code 500');
|
||||
expect(reason.code).toBe(AxiosError.ERR_BAD_RESPONSE);
|
||||
expect(reason.config).toBe(response.config);
|
||||
expect(reason.request).toBe(request);
|
||||
expect(reason.response).toBe(response);
|
||||
});
|
||||
|
||||
it('passes response status to validateStatus', () => {
|
||||
const resolve = vi.fn();
|
||||
const reject = vi.fn();
|
||||
const validateStatus = vi.fn();
|
||||
const response = {
|
||||
status: 500,
|
||||
config: {
|
||||
validateStatus,
|
||||
},
|
||||
};
|
||||
|
||||
settle(resolve, reject, response);
|
||||
|
||||
expect(validateStatus).toHaveBeenCalledOnce();
|
||||
expect(validateStatus).toHaveBeenCalledWith(500);
|
||||
});
|
||||
});
|
||||
@ -1,10 +0,0 @@
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
test('runs in browser environment', () => {
|
||||
document.body.innerHTML = '<div data-testid="smoke">vitest browser smoke</div>';
|
||||
|
||||
const el = document.querySelector('[data-testid="smoke"]');
|
||||
|
||||
expect(el?.textContent).toBe('vitest browser smoke');
|
||||
expect(globalThis.window).toBeDefined();
|
||||
});
|
||||
121
tests/browser/toFormData.browser.test.js
Normal file
121
tests/browser/toFormData.browser.test.js
Normal file
@ -0,0 +1,121 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import toFormData from '../../lib/helpers/toFormData.js';
|
||||
|
||||
describe('helpers::toFormData (vitest browser)', () => {
|
||||
it('converts nested data object to FormData with dots option enabled', () => {
|
||||
const data = {
|
||||
val: 123,
|
||||
nested: {
|
||||
arr: ['hello', 'world'],
|
||||
},
|
||||
};
|
||||
|
||||
const form = toFormData(data, null, { dots: true });
|
||||
|
||||
expect(form).toBeInstanceOf(FormData);
|
||||
expect(Array.from(form.keys())).toHaveLength(3);
|
||||
expect(form.get('val')).toBe('123');
|
||||
expect(form.get('nested.arr.0')).toBe('hello');
|
||||
});
|
||||
|
||||
it('respects metaTokens option', () => {
|
||||
const data = {
|
||||
'obj{}': { x: 1, y: 2 },
|
||||
};
|
||||
const serialized = JSON.stringify(data['obj{}']);
|
||||
|
||||
const form = toFormData(data, null, { metaTokens: false });
|
||||
|
||||
expect(Array.from(form.keys())).toHaveLength(1);
|
||||
expect(form.getAll('obj')).toEqual([serialized]);
|
||||
});
|
||||
|
||||
describe('flat arrays serialization', () => {
|
||||
it('includes full indexes when indexes option is true', () => {
|
||||
const data = {
|
||||
arr: [1, 2, 3],
|
||||
arr2: [1, [2], 3],
|
||||
};
|
||||
|
||||
const form = toFormData(data, null, { indexes: true });
|
||||
|
||||
expect(Array.from(form.keys())).toHaveLength(6);
|
||||
expect(form.get('arr[0]')).toBe('1');
|
||||
expect(form.get('arr[1]')).toBe('2');
|
||||
expect(form.get('arr[2]')).toBe('3');
|
||||
expect(form.get('arr2[0]')).toBe('1');
|
||||
expect(form.get('arr2[1][0]')).toBe('2');
|
||||
expect(form.get('arr2[2]')).toBe('3');
|
||||
});
|
||||
|
||||
it('includes brackets only when indexes option is false', () => {
|
||||
const data = {
|
||||
arr: [1, 2, 3],
|
||||
arr2: [1, [2], 3],
|
||||
};
|
||||
|
||||
const form = toFormData(data, null, { indexes: false });
|
||||
|
||||
expect(Array.from(form.keys())).toHaveLength(6);
|
||||
expect(form.getAll('arr[]')).toEqual(['1', '2', '3']);
|
||||
expect(form.get('arr2[0]')).toBe('1');
|
||||
expect(form.get('arr2[1][0]')).toBe('2');
|
||||
expect(form.get('arr2[2]')).toBe('3');
|
||||
});
|
||||
|
||||
it('omits brackets when indexes option is null', () => {
|
||||
const data = {
|
||||
arr: [1, 2, 3],
|
||||
arr2: [1, [2], 3],
|
||||
};
|
||||
|
||||
const form = toFormData(data, null, { indexes: null });
|
||||
|
||||
expect(Array.from(form.keys())).toHaveLength(6);
|
||||
expect(form.getAll('arr')).toEqual(['1', '2', '3']);
|
||||
expect(form.get('arr2[0]')).toBe('1');
|
||||
expect(form.get('arr2[1][0]')).toBe('2');
|
||||
expect(form.get('arr2[2]')).toBe('3');
|
||||
});
|
||||
});
|
||||
|
||||
it('converts nested data object to FormData', () => {
|
||||
const data = {
|
||||
val: 123,
|
||||
nested: {
|
||||
arr: ['hello', 'world'],
|
||||
},
|
||||
};
|
||||
|
||||
const form = toFormData(data);
|
||||
|
||||
expect(form).toBeInstanceOf(FormData);
|
||||
expect(Array.from(form.keys())).toHaveLength(3);
|
||||
expect(form.get('val')).toBe('123');
|
||||
expect(form.get('nested[arr][0]')).toBe('hello');
|
||||
});
|
||||
|
||||
it('appends value whose key ends with [] as separate values with the same key', () => {
|
||||
const data = {
|
||||
'arr[]': [1, 2, 3],
|
||||
};
|
||||
|
||||
const form = toFormData(data);
|
||||
|
||||
expect(Array.from(form.keys())).toHaveLength(3);
|
||||
expect(form.getAll('arr[]')).toEqual(['1', '2', '3']);
|
||||
});
|
||||
|
||||
it('appends value whose key ends with {} as a JSON string', () => {
|
||||
const data = {
|
||||
'obj{}': { x: 1, y: 2 },
|
||||
};
|
||||
const serialized = JSON.stringify(data['obj{}']);
|
||||
|
||||
const form = toFormData(data);
|
||||
|
||||
expect(Array.from(form.keys())).toHaveLength(1);
|
||||
expect(form.getAll('obj{}')).toEqual([serialized]);
|
||||
});
|
||||
});
|
||||
265
tests/browser/transform.browser.test.js
Normal file
265
tests/browser/transform.browser.test.js
Normal file
@ -0,0 +1,265 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
import AxiosError from '../../lib/core/AxiosError.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.responseHeaders = '';
|
||||
this.readyState = 0;
|
||||
this.status = 0;
|
||||
this.statusText = '';
|
||||
this.responseText = '';
|
||||
this.response = null;
|
||||
this.timeout = 0;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.onabort = null;
|
||||
this.onerror = null;
|
||||
this.ontimeout = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.params = data;
|
||||
requests.push(this);
|
||||
}
|
||||
|
||||
respondWith({ status = 200, statusText = 'OK', responseText = '', responseHeaders = '' } = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.responseText = responseText;
|
||||
this.response = responseText;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abort() {}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const getLastRequest = () => {
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
describe('transform (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
});
|
||||
|
||||
it('should transform JSON to string', async () => {
|
||||
const responsePromise = axios.post('/foo', { foo: 'bar' });
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.params).toBe('{"foo":"bar"}');
|
||||
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should transform string to JSON', async () => {
|
||||
const responsePromise = axios('/foo');
|
||||
const request = getLastRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{"foo": "bar"}',
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
|
||||
expect(typeof response.data).toBe('object');
|
||||
expect(response.data.foo).toBe('bar');
|
||||
});
|
||||
|
||||
it('should throw a SyntaxError if JSON parsing failed and responseType is "json" if silentJSONParsing is false', async () => {
|
||||
const responsePromise = axios({
|
||||
url: '/foo',
|
||||
responseType: 'json',
|
||||
transitional: { silentJSONParsing: false },
|
||||
});
|
||||
const request = getLastRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{foo": "bar"}',
|
||||
});
|
||||
|
||||
const thrown = await responsePromise.catch((error) => error);
|
||||
|
||||
expect(thrown).toBeTruthy();
|
||||
expect(thrown.name).toContain('SyntaxError');
|
||||
expect(thrown.code).toBe(AxiosError.ERR_BAD_RESPONSE);
|
||||
});
|
||||
|
||||
it('should send data as JSON if request content-type is application/json', async () => {
|
||||
const responsePromise = axios.post('/foo', 123, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
const request = getLastRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '',
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
|
||||
expect(response).toBeTruthy();
|
||||
expect(request.requestHeaders['Content-Type']).toBe('application/json');
|
||||
expect(JSON.parse(request.params)).toBe(123);
|
||||
});
|
||||
|
||||
it('should not assume JSON if responseType is not `json`', async () => {
|
||||
const responsePromise = axios.get('/foo', {
|
||||
responseType: 'text',
|
||||
transitional: {
|
||||
forcedJSONParsing: false,
|
||||
},
|
||||
});
|
||||
const request = getLastRequest();
|
||||
const rawData = '{"x":1}';
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: rawData,
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
|
||||
expect(response).toBeTruthy();
|
||||
expect(response.data).toBe(rawData);
|
||||
});
|
||||
|
||||
it('should override default transform', async () => {
|
||||
const responsePromise = axios.post(
|
||||
'/foo',
|
||||
{ foo: 'bar' },
|
||||
{
|
||||
transformRequest(data) {
|
||||
return data;
|
||||
},
|
||||
}
|
||||
);
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(typeof request.params).toBe('object');
|
||||
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should allow an Array of transformers', async () => {
|
||||
const responsePromise = axios.post(
|
||||
'/foo',
|
||||
{ foo: 'bar' },
|
||||
{
|
||||
transformRequest: axios.defaults.transformRequest.concat(function (data) {
|
||||
return data.replace('bar', 'baz');
|
||||
}),
|
||||
}
|
||||
);
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.params).toBe('{"foo":"baz"}');
|
||||
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should allowing mutating headers', async () => {
|
||||
const token = Math.floor(Math.random() * Math.pow(2, 64)).toString(36);
|
||||
const responsePromise = axios('/foo', {
|
||||
transformRequest(data, headers) {
|
||||
headers['X-Authorization'] = token;
|
||||
return data;
|
||||
},
|
||||
});
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.requestHeaders['X-Authorization']).toBe(token);
|
||||
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it("should normalize 'content-type' header when using a custom transformRequest", async () => {
|
||||
const responsePromise = axios.post(
|
||||
'/foo',
|
||||
{ foo: 'bar' },
|
||||
{
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||
transformRequest: [
|
||||
function () {
|
||||
return 'aa=44';
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
const request = getLastRequest();
|
||||
|
||||
expect(request.requestHeaders['Content-Type']).toBe('application/x-www-form-urlencoded');
|
||||
|
||||
request.respondWith();
|
||||
await responsePromise;
|
||||
});
|
||||
|
||||
it('should return response.data as parsed JSON object when responseType is json', async () => {
|
||||
const instance = axios.create({
|
||||
baseURL: '/api',
|
||||
responseType: 'json',
|
||||
});
|
||||
const responsePromise = instance.get('my/endpoint', { responseType: 'json' });
|
||||
const request = getLastRequest();
|
||||
|
||||
request.respondWith({
|
||||
status: 200,
|
||||
responseText: '{"key1": "value1"}',
|
||||
responseHeaders: 'content-type: application/json',
|
||||
});
|
||||
|
||||
const response = await responsePromise;
|
||||
|
||||
expect(response).toBeTruthy();
|
||||
expect(response.data).toEqual({ key1: 'value1' });
|
||||
});
|
||||
});
|
||||
182
tests/browser/xsrf.browser.test.js
Normal file
182
tests/browser/xsrf.browser.test.js
Normal file
@ -0,0 +1,182 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import axios from '../../index.js';
|
||||
import cookies from '../../lib/helpers/cookies.js';
|
||||
|
||||
class MockXMLHttpRequest {
|
||||
constructor() {
|
||||
this.requestHeaders = {};
|
||||
this.readyState = 0;
|
||||
this.status = 200;
|
||||
this.statusText = 'OK';
|
||||
this.responseText = '';
|
||||
this.timeout = 0;
|
||||
this.onreadystatechange = null;
|
||||
this.onloadend = null;
|
||||
this.onabort = null;
|
||||
this.onerror = null;
|
||||
this.ontimeout = null;
|
||||
this.upload = {
|
||||
addEventListener() {},
|
||||
};
|
||||
}
|
||||
|
||||
open(method, url, async = true) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
setRequestHeader(key, value) {
|
||||
this.requestHeaders[key] = value;
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
|
||||
getAllResponseHeaders() {
|
||||
return '';
|
||||
}
|
||||
|
||||
send() {
|
||||
requests.push(this);
|
||||
this.readyState = 4;
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (this.onloadend) {
|
||||
this.onloadend();
|
||||
} else if (this.onreadystatechange) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abort() {}
|
||||
}
|
||||
|
||||
let requests = [];
|
||||
let OriginalXMLHttpRequest;
|
||||
|
||||
const setXsrfCookie = (value) => {
|
||||
document.cookie = `${axios.defaults.xsrfCookieName}=${value}; path=/`;
|
||||
};
|
||||
|
||||
const clearXsrfCookie = () => {
|
||||
document.cookie = `${axios.defaults.xsrfCookieName}=; expires=${new Date(
|
||||
Date.now() - 86400000
|
||||
).toUTCString()}; path=/`;
|
||||
};
|
||||
|
||||
const sendRequest = async (url, config) => {
|
||||
const responsePromise = axios(url, config);
|
||||
const request = requests.at(-1);
|
||||
|
||||
expect(request).toBeDefined();
|
||||
await responsePromise;
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
describe('xsrf (vitest browser)', () => {
|
||||
beforeEach(() => {
|
||||
requests = [];
|
||||
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearXsrfCookie();
|
||||
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should not set xsrf header if cookie is null', async () => {
|
||||
const request = await sendRequest('/foo');
|
||||
|
||||
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should set xsrf header if cookie is set', async () => {
|
||||
setXsrfCookie('12345');
|
||||
|
||||
const request = await sendRequest('/foo');
|
||||
|
||||
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBe('12345');
|
||||
});
|
||||
|
||||
it('should not set xsrf header if xsrfCookieName is null', async () => {
|
||||
setXsrfCookie('12345');
|
||||
|
||||
const request = await sendRequest('/foo', {
|
||||
xsrfCookieName: null,
|
||||
});
|
||||
|
||||
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not read cookies at all if xsrfCookieName is null', async () => {
|
||||
const readSpy = vi.spyOn(cookies, 'read');
|
||||
|
||||
await sendRequest('/foo', {
|
||||
xsrfCookieName: null,
|
||||
});
|
||||
|
||||
expect(readSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not set xsrf header for cross origin', async () => {
|
||||
setXsrfCookie('12345');
|
||||
|
||||
const request = await sendRequest('http://example.com/');
|
||||
|
||||
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not set xsrf header for cross origin when using withCredentials', async () => {
|
||||
setXsrfCookie('12345');
|
||||
|
||||
const request = await sendRequest('http://example.com/', {
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('withXSRFToken option', () => {
|
||||
it('should set xsrf header for cross origin when withXSRFToken = true', async () => {
|
||||
const token = '12345';
|
||||
|
||||
setXsrfCookie(token);
|
||||
|
||||
const request = await sendRequest('http://example.com/', {
|
||||
withXSRFToken: true,
|
||||
});
|
||||
|
||||
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBe(token);
|
||||
});
|
||||
|
||||
it('should not set xsrf header for the same origin when withXSRFToken = false', async () => {
|
||||
const token = '12345';
|
||||
|
||||
setXsrfCookie(token);
|
||||
|
||||
const request = await sendRequest('/foo', {
|
||||
withXSRFToken: false,
|
||||
});
|
||||
|
||||
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should support function resolver', async () => {
|
||||
const token = '12345';
|
||||
|
||||
setXsrfCookie(token);
|
||||
|
||||
const request = await sendRequest('/foo', {
|
||||
withXSRFToken: (config) => config.userFlag === 'yes',
|
||||
userFlag: 'yes',
|
||||
});
|
||||
|
||||
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBe(token);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -6,8 +6,6 @@ import { Throttle } from 'stream-throttle';
|
||||
import formidable from 'formidable';
|
||||
import selfsigned from 'selfsigned';
|
||||
|
||||
export const LOCAL_SERVER_URL = 'http://localhost:4444';
|
||||
|
||||
export const SERVER_HANDLER_STREAM_ECHO = (req, res) => req.pipe(res);
|
||||
|
||||
export const setTimeoutAsync = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
1305
tests/smoke/cjs/package-lock.json
generated
Normal file
1305
tests/smoke/cjs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
tests/smoke/cjs/package.json
Normal file
19
tests/smoke/cjs/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@axios/cjs-smoke-tests",
|
||||
"version": "1.0.0",
|
||||
"description": "CJS smoke tests for axios",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test:smoke:cjs:mocha": "mocha \"tests/**/*.smoke.test.cjs\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "axios team",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "file:../../../"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "9.2.2",
|
||||
"chai": "4.5.0"
|
||||
}
|
||||
}
|
||||
137
tests/smoke/cjs/tests/auth.smoke.test.cjs
Normal file
137
tests/smoke/cjs/tests/auth.smoke.test.cjs
Normal file
@ -0,0 +1,137 @@
|
||||
const http = require('http');
|
||||
const axios = require('axios');
|
||||
const { describe, it, afterEach } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const startServer = (handler) => {
|
||||
return new Promise((resolve) => {
|
||||
const server = http.createServer(handler);
|
||||
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const stopServer = (server) => {
|
||||
if (!server || !server.listening) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.close((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('auth compat (dist export only)', () => {
|
||||
let server;
|
||||
|
||||
afterEach(async () => {
|
||||
await stopServer(server);
|
||||
server = undefined;
|
||||
});
|
||||
|
||||
const requestWithConfig = async (config) => {
|
||||
server = await startServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(req.headers.authorization || '');
|
||||
});
|
||||
|
||||
const { port } = server.address();
|
||||
|
||||
return axios.get(
|
||||
`http://127.0.0.1:${port}/`,
|
||||
Object.assign(
|
||||
{
|
||||
proxy: false,
|
||||
},
|
||||
config || {}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
it('sets Basic Authorization header from auth credentials', async () => {
|
||||
const response = await requestWithConfig({
|
||||
auth: {
|
||||
username: 'janedoe',
|
||||
password: 's00pers3cret',
|
||||
},
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('janedoe:s00pers3cret', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).to.equal(expected);
|
||||
});
|
||||
|
||||
it('supports auth without password', async () => {
|
||||
const response = await requestWithConfig({
|
||||
auth: {
|
||||
username: 'Aladdin',
|
||||
},
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('Aladdin:', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).to.equal(expected);
|
||||
});
|
||||
|
||||
it('overwrites an existing Authorization header when auth is provided', async () => {
|
||||
const response = await requestWithConfig({
|
||||
headers: {
|
||||
Authorization: 'Bearer token-123',
|
||||
},
|
||||
auth: {
|
||||
username: 'foo',
|
||||
password: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('foo:bar', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).to.equal(expected);
|
||||
});
|
||||
|
||||
it('uses URL credentials when auth config is not provided (node adapter behavior)', async () => {
|
||||
server = await startServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(req.headers.authorization || '');
|
||||
});
|
||||
|
||||
const { port } = server.address();
|
||||
|
||||
const response = await axios.get(`http://urluser:urlpass@127.0.0.1:${port}/`, {
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('urluser:urlpass', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).to.equal(expected);
|
||||
});
|
||||
|
||||
it('prefers auth config over URL credentials', async () => {
|
||||
server = await startServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(req.headers.authorization || '');
|
||||
});
|
||||
|
||||
const { port } = server.address();
|
||||
|
||||
const response = await axios.get(`http://urluser:urlpass@127.0.0.1:${port}/`, {
|
||||
proxy: false,
|
||||
auth: {
|
||||
username: 'configuser',
|
||||
password: 'configpass',
|
||||
},
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('configuser:configpass', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).to.equal(expected);
|
||||
});
|
||||
});
|
||||
145
tests/smoke/cjs/tests/basic.smoke.test.cjs
Normal file
145
tests/smoke/cjs/tests/basic.smoke.test.cjs
Normal file
@ -0,0 +1,145 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const createTransportCapture = () => {
|
||||
let capturedOptions;
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
capturedOptions = options;
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end('{"ok":true}');
|
||||
};
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
};
|
||||
};
|
||||
|
||||
const runRequest = async (run) => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
await run(transport);
|
||||
|
||||
return getCapturedOptions();
|
||||
};
|
||||
|
||||
describe('basic compat (dist export only)', () => {
|
||||
it('supports the simplest axios(url) request pattern', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios('http://example.com/users', {
|
||||
transport,
|
||||
proxy: false,
|
||||
})
|
||||
);
|
||||
|
||||
expect(options.method).to.equal('GET');
|
||||
expect(options.path).to.equal('/users');
|
||||
});
|
||||
|
||||
it('supports get()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.get('http://example.com/items?limit=10', { transport, proxy: false })
|
||||
);
|
||||
|
||||
expect(options.method).to.equal('GET');
|
||||
expect(options.path).to.equal('/items?limit=10');
|
||||
});
|
||||
|
||||
it('supports delete()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.delete('http://example.com/items/1', { transport, proxy: false })
|
||||
);
|
||||
|
||||
expect(options.method).to.equal('DELETE');
|
||||
expect(options.path).to.equal('/items/1');
|
||||
});
|
||||
|
||||
it('supports head()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.head('http://example.com/health', { transport, proxy: false })
|
||||
);
|
||||
|
||||
expect(options.method).to.equal('HEAD');
|
||||
expect(options.path).to.equal('/health');
|
||||
});
|
||||
|
||||
it('supports options()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.options('http://example.com/items', { transport, proxy: false })
|
||||
);
|
||||
|
||||
expect(options.method).to.equal('OPTIONS');
|
||||
expect(options.path).to.equal('/items');
|
||||
});
|
||||
|
||||
it('supports post()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.post(
|
||||
'http://example.com/items',
|
||||
{ name: 'widget' },
|
||||
{
|
||||
transport,
|
||||
proxy: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(options.method).to.equal('POST');
|
||||
expect(options.path).to.equal('/items');
|
||||
});
|
||||
|
||||
it('supports put()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.put(
|
||||
'http://example.com/items/1',
|
||||
{ name: 'updated-widget' },
|
||||
{
|
||||
transport,
|
||||
proxy: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(options.method).to.equal('PUT');
|
||||
expect(options.path).to.equal('/items/1');
|
||||
});
|
||||
|
||||
it('supports patch()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.patch(
|
||||
'http://example.com/items/1',
|
||||
{ status: 'active' },
|
||||
{
|
||||
transport,
|
||||
proxy: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(options.method).to.equal('PATCH');
|
||||
expect(options.path).to.equal('/items/1');
|
||||
});
|
||||
});
|
||||
117
tests/smoke/cjs/tests/cancel.smoke.test.cjs
Normal file
117
tests/smoke/cjs/tests/cancel.smoke.test.cjs
Normal file
@ -0,0 +1,117 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const NODE_VERSION = parseInt(process.versions.node.split('.')[0]);
|
||||
const itWithAbortController = NODE_VERSION < 16 ? it.skip : it;
|
||||
|
||||
const createPendingTransport = () => {
|
||||
let requestCount = 0;
|
||||
|
||||
const transport = {
|
||||
request() {
|
||||
requestCount += 1;
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.end = () => {};
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getRequestCount: () => requestCount,
|
||||
};
|
||||
};
|
||||
|
||||
describe('cancel compat (dist export only)', () => {
|
||||
itWithAbortController(
|
||||
'supports cancellation with AbortController (pre-aborted signal)',
|
||||
async () => {
|
||||
const { transport, getRequestCount } = createPendingTransport();
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
|
||||
try {
|
||||
const request = axios.get('http://example.com/resource', {
|
||||
signal: controller.signal,
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
await request;
|
||||
} catch (error) {
|
||||
expect(error).to.have.property('code', 'ERR_CANCELED');
|
||||
}
|
||||
|
||||
expect(getRequestCount()).to.equal(0);
|
||||
}
|
||||
);
|
||||
|
||||
itWithAbortController('supports cancellation with AbortController (in-flight)', async () => {
|
||||
const { transport, getRequestCount } = createPendingTransport();
|
||||
const controller = new AbortController();
|
||||
|
||||
try {
|
||||
const request = axios.get('http://example.com/resource', {
|
||||
signal: controller.signal,
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
await request;
|
||||
} catch (error) {
|
||||
expect(error).to.have.property('code', 'ERR_CANCELED');
|
||||
}
|
||||
|
||||
expect(getRequestCount()).to.equal(1);
|
||||
});
|
||||
|
||||
it('supports cancellation with CancelToken (pre-canceled token)', async () => {
|
||||
const { transport, getRequestCount } = createPendingTransport();
|
||||
const source = axios.CancelToken.source();
|
||||
source.cancel('Operation canceled by the user.');
|
||||
|
||||
const error = await axios
|
||||
.get('http://example.com/resource', {
|
||||
cancelToken: source.token,
|
||||
transport,
|
||||
proxy: false,
|
||||
})
|
||||
.catch((err) => err);
|
||||
|
||||
expect(axios.isCancel(error)).to.be.true;
|
||||
expect(error.code).to.equal('ERR_CANCELED');
|
||||
expect(getRequestCount()).to.equal(0);
|
||||
});
|
||||
|
||||
it('supports cancellation with CancelToken (in-flight)', async () => {
|
||||
const { transport, getRequestCount } = createPendingTransport();
|
||||
const source = axios.CancelToken.source();
|
||||
|
||||
const request = axios.get('http://example.com/resource', {
|
||||
cancelToken: source.token,
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
source.cancel('Operation canceled by the user.');
|
||||
|
||||
const error = await request.catch((err) => err);
|
||||
|
||||
expect(axios.isCancel(error)).to.be.true;
|
||||
expect(error.code).to.equal('ERR_CANCELED');
|
||||
expect(getRequestCount()).to.equal(1);
|
||||
});
|
||||
});
|
||||
137
tests/smoke/cjs/tests/error.smoke.test.cjs
Normal file
137
tests/smoke/cjs/tests/error.smoke.test.cjs
Normal file
@ -0,0 +1,137 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const createTransport = (config) => {
|
||||
const opts = config || {};
|
||||
|
||||
return {
|
||||
request(options, onResponse) {
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
|
||||
req.setTimeout = (_ms, cb) => {
|
||||
if (opts.timeout) {
|
||||
req._timeoutCallback = cb;
|
||||
}
|
||||
};
|
||||
|
||||
req.end = () => {
|
||||
if (opts.error) {
|
||||
req.emit('error', opts.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.timeout && req._timeoutCallback) {
|
||||
req._timeoutCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode =
|
||||
opts.response && opts.response.statusCode !== undefined ? opts.response.statusCode : 200;
|
||||
res.statusMessage =
|
||||
opts.response && opts.response.statusMessage ? opts.response.statusMessage : 'OK';
|
||||
res.headers =
|
||||
opts.response && opts.response.headers
|
||||
? opts.response.headers
|
||||
: { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
res.end(opts.response && opts.response.body ? opts.response.body : '{"ok":true}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('error compat (dist export only)', () => {
|
||||
it('rejects with AxiosError for non-2xx responses by default', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/fail', {
|
||||
proxy: false,
|
||||
transport: createTransport({
|
||||
response: {
|
||||
statusCode: 500,
|
||||
statusMessage: 'Internal Server Error',
|
||||
body: '{"error":"boom"}',
|
||||
},
|
||||
}),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).to.be.true;
|
||||
expect(err.response.status).to.equal(500);
|
||||
expect(err.message).to.include('500');
|
||||
});
|
||||
|
||||
it('resolves when validateStatus allows non-2xx responses', async () => {
|
||||
const response = await axios.get('http://example.com/allowed', {
|
||||
proxy: false,
|
||||
validateStatus: () => true,
|
||||
transport: createTransport({
|
||||
response: {
|
||||
statusCode: 500,
|
||||
statusMessage: 'Internal Server Error',
|
||||
body: '{"ok":false}',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.status).to.equal(500);
|
||||
expect(response.data).to.deep.equal({ ok: false });
|
||||
});
|
||||
|
||||
it('wraps transport errors as AxiosError', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/network', {
|
||||
proxy: false,
|
||||
transport: createTransport({
|
||||
error: new Error('socket hang up'),
|
||||
}),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).to.be.true;
|
||||
expect(err.message).to.include('socket hang up');
|
||||
expect(err.toJSON).to.be.a('function');
|
||||
});
|
||||
|
||||
it('rejects with ECONNABORTED on timeout', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: 10,
|
||||
transport: createTransport({ timeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).to.be.true;
|
||||
expect(err.code).to.equal('ECONNABORTED');
|
||||
expect(err.message).to.equal('timeout of 10ms exceeded');
|
||||
});
|
||||
|
||||
it('uses timeoutErrorMessage when provided', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: 25,
|
||||
timeoutErrorMessage: 'custom timeout message',
|
||||
transport: createTransport({ timeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).to.be.true;
|
||||
expect(err.code).to.equal('ECONNABORTED');
|
||||
expect(err.message).to.equal('custom timeout message');
|
||||
});
|
||||
});
|
||||
145
tests/smoke/cjs/tests/fetch.smoke.test.cjs
Normal file
145
tests/smoke/cjs/tests/fetch.smoke.test.cjs
Normal file
@ -0,0 +1,145 @@
|
||||
const axios = require('axios');
|
||||
const { it, describe } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const NODE_VERSION = parseInt(process.versions.node.split('.')[0]);
|
||||
const describeWithFetch = NODE_VERSION < 18 ? describe.skip : describe;
|
||||
|
||||
const createFetchMock = (responseFactory) => {
|
||||
const calls = [];
|
||||
|
||||
const mockFetch = async (input, init) => {
|
||||
calls.push({ input, init: init || {} });
|
||||
|
||||
if (responseFactory) {
|
||||
return responseFactory(input, init || {});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
mockFetch,
|
||||
getCalls: () => calls,
|
||||
};
|
||||
};
|
||||
|
||||
describeWithFetch('fetch compat (dist export only)', () => {
|
||||
it('uses fetch adapter and resolves JSON response', async () => {
|
||||
const { mockFetch, getCalls } = createFetchMock();
|
||||
|
||||
const response = await axios.get('https://example.com/users', {
|
||||
adapter: 'fetch',
|
||||
env: {
|
||||
fetch: mockFetch,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.data).to.deep.equal({ ok: true });
|
||||
expect(response.status).to.equal(200);
|
||||
expect(getCalls()).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it('sends method, headers and body for post requests', async () => {
|
||||
const { mockFetch, getCalls } = createFetchMock(async (input, init) => {
|
||||
const requestInit = init || {};
|
||||
const isRequest = input && typeof input !== 'string';
|
||||
const method = isRequest ? input.method : requestInit.method;
|
||||
|
||||
const body =
|
||||
isRequest && typeof input.clone === 'function'
|
||||
? await input.clone().text()
|
||||
: requestInit.body;
|
||||
|
||||
let contentType;
|
||||
if (isRequest && input.headers) {
|
||||
contentType = input.headers.get('content-type');
|
||||
} else if (requestInit.headers) {
|
||||
contentType = requestInit.headers['Content-Type'] || requestInit.headers['content-type'];
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
url: typeof input === 'string' ? input : input.url,
|
||||
method,
|
||||
contentType,
|
||||
body,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const response = await axios.post(
|
||||
'https://example.com/items',
|
||||
{ name: 'widget' },
|
||||
{
|
||||
adapter: 'fetch',
|
||||
env: {
|
||||
fetch: mockFetch,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(getCalls()).to.have.lengthOf(1);
|
||||
expect(response.data.url).to.equal('https://example.com/items');
|
||||
expect(response.data.method).to.equal('POST');
|
||||
expect(response.data.contentType).to.include('application/json');
|
||||
expect(response.data.body).to.equal(JSON.stringify({ name: 'widget' }));
|
||||
});
|
||||
|
||||
it('rejects non-2xx fetch responses by default', async () => {
|
||||
const { mockFetch } = createFetchMock(
|
||||
() =>
|
||||
new Response(JSON.stringify({ error: 'boom' }), {
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
);
|
||||
|
||||
const err = await axios
|
||||
.get('https://example.com/fail', {
|
||||
adapter: 'fetch',
|
||||
env: {
|
||||
fetch: mockFetch,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).to.be.true;
|
||||
expect(err.response.status).to.equal(500);
|
||||
});
|
||||
|
||||
it('supports cancellation with AbortController in fetch mode', async () => {
|
||||
const { mockFetch } = createFetchMock();
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
|
||||
const err = await axios
|
||||
.get('https://example.com/cancel', {
|
||||
adapter: 'fetch',
|
||||
signal: controller.signal,
|
||||
env: {
|
||||
fetch: mockFetch,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isCancel(err)).to.be.true;
|
||||
expect(err.code).to.equal('ERR_CANCELED');
|
||||
});
|
||||
});
|
||||
115
tests/smoke/cjs/tests/files.smoke.test.cjs
Normal file
115
tests/smoke/cjs/tests/files.smoke.test.cjs
Normal file
@ -0,0 +1,115 @@
|
||||
const { PassThrough, Readable, Writable } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const createCaptureTransport = (buildResponse) => {
|
||||
return {
|
||||
request(options, onResponse) {
|
||||
const chunks = [];
|
||||
|
||||
const req = new Writable({
|
||||
write(chunk, _encoding, callback) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.close = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
|
||||
const originalDestroy = req.destroy.bind(req);
|
||||
req.destroy = (...args) => {
|
||||
req.destroyed = true;
|
||||
return originalDestroy(...args);
|
||||
};
|
||||
|
||||
const originalEnd = req.end.bind(req);
|
||||
req.end = (...args) => {
|
||||
originalEnd(...args);
|
||||
|
||||
const body = Buffer.concat(chunks);
|
||||
const response = buildResponse ? buildResponse(body, options) : {};
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = response.statusCode !== undefined ? response.statusCode : 200;
|
||||
res.statusMessage = response.statusMessage || 'OK';
|
||||
res.headers = response.headers || { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
res.end(response.body || JSON.stringify({ size: body.length }));
|
||||
};
|
||||
|
||||
req.on('error', () => {});
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('files compat (dist export only)', () => {
|
||||
it('supports posting Buffer payloads', async () => {
|
||||
const source = Buffer.from('binary-\x00-data', 'utf8');
|
||||
|
||||
const response = await axios.post('http://example.com/upload', source, {
|
||||
proxy: false,
|
||||
transport: createCaptureTransport((body) => ({
|
||||
body: JSON.stringify({ echoed: body.toString('base64') }),
|
||||
})),
|
||||
});
|
||||
|
||||
expect(response.data.echoed).to.equal(source.toString('base64'));
|
||||
});
|
||||
|
||||
it('supports posting Uint8Array payloads', async () => {
|
||||
const source = Uint8Array.from([1, 2, 3, 4, 255]);
|
||||
|
||||
const response = await axios.post('http://example.com/upload', source, {
|
||||
proxy: false,
|
||||
transport: createCaptureTransport((body) => ({
|
||||
body: JSON.stringify({ echoed: Array.from(body.values()) }),
|
||||
})),
|
||||
});
|
||||
|
||||
expect(response.data.echoed).to.deep.equal([1, 2, 3, 4, 255]);
|
||||
});
|
||||
|
||||
it('supports posting Readable stream payloads', async () => {
|
||||
const streamData = ['hello ', 'stream ', 'world'];
|
||||
const source = Readable.from(streamData);
|
||||
|
||||
const response = await axios.post('http://example.com/upload', source, {
|
||||
proxy: false,
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
transport: createCaptureTransport((body, options) => ({
|
||||
body: JSON.stringify({
|
||||
text: body.toString('utf8'),
|
||||
contentType:
|
||||
options.headers && (options.headers['Content-Type'] || options.headers['content-type']),
|
||||
}),
|
||||
})),
|
||||
});
|
||||
|
||||
expect(response.data.text).to.equal('hello stream world');
|
||||
expect(response.data.contentType).to.contain('application/octet-stream');
|
||||
});
|
||||
|
||||
it('supports binary downloads with responseType=arraybuffer', async () => {
|
||||
const binary = Buffer.from([0xde, 0xad, 0xbe, 0xef]);
|
||||
|
||||
const response = await axios.get('http://example.com/file.bin', {
|
||||
proxy: false,
|
||||
responseType: 'arraybuffer',
|
||||
transport: createCaptureTransport(() => ({
|
||||
headers: { 'content-type': 'application/octet-stream' },
|
||||
body: binary,
|
||||
})),
|
||||
});
|
||||
|
||||
expect(Buffer.isBuffer(response.data)).to.equal(true);
|
||||
expect(response.data.equals(binary)).to.equal(true);
|
||||
});
|
||||
});
|
||||
110
tests/smoke/cjs/tests/formData.smoke.test.cjs
Normal file
110
tests/smoke/cjs/tests/formData.smoke.test.cjs
Normal file
@ -0,0 +1,110 @@
|
||||
const { Writable, PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const NODE_VERSION = parseInt(process.versions.node.split('.')[0]);
|
||||
const describeWithFormData = NODE_VERSION < 18 ? describe.skip : describe;
|
||||
|
||||
const createCaptureTransport = (buildResponse) => {
|
||||
return {
|
||||
request(options, onResponse) {
|
||||
const chunks = [];
|
||||
|
||||
const req = new Writable({
|
||||
write(chunk, _encoding, callback) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = req.write.bind(req);
|
||||
req.close = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
|
||||
const originalDestroy = req.destroy.bind(req);
|
||||
req.destroy = (...args) => {
|
||||
req.destroyed = true;
|
||||
return originalDestroy(...args);
|
||||
};
|
||||
|
||||
const originalEnd = req.end.bind(req);
|
||||
req.end = (...args) => {
|
||||
originalEnd(...args);
|
||||
|
||||
const body = Buffer.concat(chunks);
|
||||
const response = buildResponse ? buildResponse(body, options) : {};
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = response.statusCode !== undefined ? response.statusCode : 200;
|
||||
res.statusMessage = response.statusMessage || 'OK';
|
||||
res.headers = response.headers || { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
res.end(response.body || JSON.stringify({ ok: true }));
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const bodyAsUtf8 = (value) => {
|
||||
return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);
|
||||
};
|
||||
|
||||
describeWithFormData('formData compat (dist export only)', () => {
|
||||
it('supports posting FormData instances', async () => {
|
||||
const form = new FormData();
|
||||
form.append('username', 'janedoe');
|
||||
form.append('role', 'admin');
|
||||
|
||||
const response = await axios.post('http://example.com/form', form, {
|
||||
proxy: false,
|
||||
transport: createCaptureTransport((body, options) => ({
|
||||
body: JSON.stringify({
|
||||
contentType:
|
||||
options.headers && (options.headers['Content-Type'] || options.headers['content-type']),
|
||||
payload: bodyAsUtf8(body),
|
||||
}),
|
||||
})),
|
||||
});
|
||||
|
||||
expect(response.data.contentType).to.contain('multipart/form-data');
|
||||
expect(response.data.payload).to.contain('name="username"');
|
||||
expect(response.data.payload).to.contain('janedoe');
|
||||
expect(response.data.payload).to.contain('name="role"');
|
||||
expect(response.data.payload).to.contain('admin');
|
||||
});
|
||||
|
||||
it('supports axios.postForm helper', async () => {
|
||||
const response = await axios.postForm(
|
||||
'http://example.com/post-form',
|
||||
{
|
||||
project: 'axios',
|
||||
mode: 'compat',
|
||||
},
|
||||
{
|
||||
proxy: false,
|
||||
transport: createCaptureTransport((body, options) => ({
|
||||
body: JSON.stringify({
|
||||
contentType:
|
||||
options.headers &&
|
||||
(options.headers['Content-Type'] || options.headers['content-type']),
|
||||
payload: bodyAsUtf8(body),
|
||||
}),
|
||||
})),
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.data.contentType).to.contain('multipart/form-data');
|
||||
expect(response.data.payload).to.contain('name="project"');
|
||||
expect(response.data.payload).to.contain('axios');
|
||||
expect(response.data.payload).to.contain('name="mode"');
|
||||
expect(response.data.payload).to.contain('compat');
|
||||
});
|
||||
});
|
||||
125
tests/smoke/cjs/tests/headers.smoke.test.cjs
Normal file
125
tests/smoke/cjs/tests/headers.smoke.test.cjs
Normal file
@ -0,0 +1,125 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const normalizeHeaders = (headers) => {
|
||||
const result = {};
|
||||
|
||||
Object.entries(headers || {}).forEach(([key, value]) => {
|
||||
result[key.toLowerCase()] = value;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const createTransportCapture = () => {
|
||||
let capturedOptions;
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
capturedOptions = options;
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end('{"ok":true}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
};
|
||||
};
|
||||
|
||||
describe('headers compat (dist export only)', () => {
|
||||
it('sends default Accept header', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.get('http://example.com/default-headers', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers.accept).to.equal('application/json, text/plain, */*');
|
||||
});
|
||||
|
||||
it('supports custom headers', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.get('http://example.com/custom-headers', {
|
||||
transport,
|
||||
proxy: false,
|
||||
headers: {
|
||||
'X-Trace-Id': 'trace-123',
|
||||
Authorization: 'Bearer token-abc',
|
||||
},
|
||||
});
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers['x-trace-id']).to.equal('trace-123');
|
||||
expect(headers.authorization).to.equal('Bearer token-abc');
|
||||
});
|
||||
|
||||
it('treats header names as case-insensitive when overriding', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.get('http://example.com/case-insensitive', {
|
||||
transport,
|
||||
proxy: false,
|
||||
headers: {
|
||||
authorization: 'Bearer old-token',
|
||||
AuThOrIzAtIoN: 'Bearer new-token',
|
||||
},
|
||||
});
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers.authorization).to.equal('Bearer new-token');
|
||||
});
|
||||
|
||||
it('sets content-type for json post payloads', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.post(
|
||||
'http://example.com/post-json',
|
||||
{ name: 'widget' },
|
||||
{
|
||||
transport,
|
||||
proxy: false,
|
||||
}
|
||||
);
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers['content-type']).to.contain('application/json');
|
||||
});
|
||||
|
||||
it('does not force content-type for get requests without body', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.get('http://example.com/get-no-body', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers['content-type']).to.be.undefined;
|
||||
});
|
||||
});
|
||||
86
tests/smoke/cjs/tests/http2.smoke.test.cjs
Normal file
86
tests/smoke/cjs/tests/http2.smoke.test.cjs
Normal file
@ -0,0 +1,86 @@
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
describe('http2 compat (dist export only)', () => {
|
||||
it('keeps instance-level httpVersion and http2Options in request config', async () => {
|
||||
const client = axios.create({
|
||||
baseURL: 'https://example.com',
|
||||
httpVersion: 2,
|
||||
http2Options: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.get('/resource', {
|
||||
adapter: async (config) => ({
|
||||
data: {
|
||||
httpVersion: config.httpVersion,
|
||||
http2Options: config.http2Options,
|
||||
},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.httpVersion).to.equal(2);
|
||||
expect(response.data.http2Options).to.deep.equal({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('merges request http2Options with instance http2Options', async () => {
|
||||
const client = axios.create({
|
||||
baseURL: 'https://example.com',
|
||||
httpVersion: 2,
|
||||
http2Options: {
|
||||
rejectUnauthorized: false,
|
||||
sessionTimeout: 1000,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.get('/resource', {
|
||||
http2Options: {
|
||||
sessionTimeout: 5000,
|
||||
customFlag: true,
|
||||
},
|
||||
adapter: async (config) => ({
|
||||
data: config.http2Options,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data).to.deep.equal({
|
||||
rejectUnauthorized: false,
|
||||
sessionTimeout: 5000,
|
||||
customFlag: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('allows request-level httpVersion override', async () => {
|
||||
const client = axios.create({
|
||||
baseURL: 'https://example.com',
|
||||
httpVersion: 2,
|
||||
});
|
||||
|
||||
const response = await client.get('/resource', {
|
||||
httpVersion: 1,
|
||||
adapter: async (config) => ({
|
||||
data: {
|
||||
httpVersion: config.httpVersion,
|
||||
},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.httpVersion).to.equal(1);
|
||||
});
|
||||
});
|
||||
146
tests/smoke/cjs/tests/instance.smoke.test.cjs
Normal file
146
tests/smoke/cjs/tests/instance.smoke.test.cjs
Normal file
@ -0,0 +1,146 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const createTransportCapture = (responseBody) => {
|
||||
const calls = [];
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
calls.push(options);
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end(responseBody || '{"ok":true}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCalls: () => calls,
|
||||
};
|
||||
};
|
||||
|
||||
describe('instance compat (dist export only)', () => {
|
||||
it('creates isolated instances with separate defaults', async () => {
|
||||
const { transport, getCalls } = createTransportCapture();
|
||||
|
||||
const clientA = axios.create({
|
||||
baseURL: 'http://example.com/api-a',
|
||||
headers: {
|
||||
'X-App': 'A',
|
||||
},
|
||||
});
|
||||
const clientB = axios.create({
|
||||
baseURL: 'http://example.com/api-b',
|
||||
headers: {
|
||||
'X-App': 'B',
|
||||
},
|
||||
});
|
||||
|
||||
await clientA.get('/users', { transport, proxy: false });
|
||||
await clientB.get('/users', { transport, proxy: false });
|
||||
|
||||
const [callA, callB] = getCalls();
|
||||
expect(callA.path).to.equal('/api-a/users');
|
||||
expect(callB.path).to.equal('/api-b/users');
|
||||
expect(callA.headers['X-App']).to.equal('A');
|
||||
expect(callB.headers['X-App']).to.equal('B');
|
||||
});
|
||||
|
||||
it('supports callable instance form instance(config)', async () => {
|
||||
const { transport, getCalls } = createTransportCapture();
|
||||
const client = axios.create({
|
||||
baseURL: 'http://example.com',
|
||||
});
|
||||
|
||||
await client({
|
||||
url: '/status',
|
||||
method: 'get',
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(getCalls()).to.have.lengthOf(1);
|
||||
expect(getCalls()[0].method).to.equal('GET');
|
||||
expect(getCalls()[0].path).to.equal('/status');
|
||||
});
|
||||
|
||||
it('applies instance request interceptors', async () => {
|
||||
const { transport, getCalls } = createTransportCapture();
|
||||
const client = axios.create({
|
||||
baseURL: 'http://example.com',
|
||||
});
|
||||
|
||||
client.interceptors.request.use((config) => {
|
||||
config.headers = config.headers || {};
|
||||
config.headers['X-From-Interceptor'] = 'yes';
|
||||
return config;
|
||||
});
|
||||
|
||||
await client.get('/intercepted', { transport, proxy: false });
|
||||
|
||||
expect(getCalls()).to.have.lengthOf(1);
|
||||
expect(getCalls()[0].headers['X-From-Interceptor']).to.equal('yes');
|
||||
});
|
||||
|
||||
it('applies instance response interceptors', async () => {
|
||||
const { transport } = createTransportCapture('{"name":"axios"}');
|
||||
const client = axios.create({
|
||||
baseURL: 'http://example.com',
|
||||
});
|
||||
|
||||
client.interceptors.response.use((response) => {
|
||||
response.data = Object.assign({}, response.data, {
|
||||
intercepted: true,
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
||||
const response = await client.get('/response-interceptor', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(response.data).to.deep.equal({
|
||||
name: 'axios',
|
||||
intercepted: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('builds URLs with getUri from instance defaults and request params', () => {
|
||||
const client = axios.create({
|
||||
baseURL: 'http://example.com/api',
|
||||
params: {
|
||||
apiKey: 'abc',
|
||||
},
|
||||
});
|
||||
|
||||
const uri = client.getUri({
|
||||
url: '/users',
|
||||
params: {
|
||||
page: 2,
|
||||
},
|
||||
});
|
||||
|
||||
expect(uri).to.equal('http://example.com/api/users?apiKey=abc&page=2');
|
||||
});
|
||||
});
|
||||
149
tests/smoke/cjs/tests/interceptors.smoke.test.cjs
Normal file
149
tests/smoke/cjs/tests/interceptors.smoke.test.cjs
Normal file
@ -0,0 +1,149 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const createTransport = (responseBody) => {
|
||||
const calls = [];
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
calls.push(options);
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end(responseBody || '{"value":"ok"}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCalls: () => calls,
|
||||
};
|
||||
};
|
||||
|
||||
describe('interceptors compat (dist export only)', () => {
|
||||
it('applies request interceptors before dispatch', async () => {
|
||||
const { transport, getCalls } = createTransport();
|
||||
const client = axios.create();
|
||||
|
||||
client.interceptors.request.use((config) => {
|
||||
config.headers = config.headers || {};
|
||||
config.headers['X-One'] = '1';
|
||||
return config;
|
||||
});
|
||||
|
||||
client.interceptors.request.use((config) => {
|
||||
config.headers['X-Two'] = '2';
|
||||
return config;
|
||||
});
|
||||
|
||||
await client.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(getCalls()).to.have.lengthOf(1);
|
||||
expect(getCalls()[0].headers['X-One']).to.equal('1');
|
||||
expect(getCalls()[0].headers['X-Two']).to.equal('2');
|
||||
});
|
||||
|
||||
it('applies response interceptors in registration order', async () => {
|
||||
const { transport } = createTransport('{"n":1}');
|
||||
const client = axios.create();
|
||||
|
||||
client.interceptors.response.use((response) => {
|
||||
response.data.n += 1;
|
||||
return response;
|
||||
});
|
||||
|
||||
client.interceptors.response.use((response) => {
|
||||
response.data.n *= 10;
|
||||
return response;
|
||||
});
|
||||
|
||||
const response = await client.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(response.data.n).to.equal(20);
|
||||
});
|
||||
|
||||
it('supports ejecting request interceptors', async () => {
|
||||
const { transport, getCalls } = createTransport();
|
||||
const client = axios.create();
|
||||
|
||||
const id = client.interceptors.request.use((config) => {
|
||||
config.headers = config.headers || {};
|
||||
config.headers['X-Ejected'] = 'yes';
|
||||
return config;
|
||||
});
|
||||
|
||||
client.interceptors.request.eject(id);
|
||||
|
||||
await client.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(getCalls()).to.have.lengthOf(1);
|
||||
expect(getCalls()[0].headers['X-Ejected']).to.be.undefined;
|
||||
});
|
||||
|
||||
it('supports async request interceptors', async () => {
|
||||
const { transport, getCalls } = createTransport();
|
||||
const client = axios.create();
|
||||
|
||||
client.interceptors.request.use(async (config) => {
|
||||
await Promise.resolve();
|
||||
config.headers = config.headers || {};
|
||||
config.headers['X-Async'] = 'true';
|
||||
return config;
|
||||
});
|
||||
|
||||
await client.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(getCalls()[0].headers['X-Async']).to.equal('true');
|
||||
});
|
||||
|
||||
it('propagates errors thrown by request interceptors', async () => {
|
||||
const { transport, getCalls } = createTransport();
|
||||
const client = axios.create();
|
||||
|
||||
client.interceptors.request.use(() => {
|
||||
throw new Error('blocked-by-interceptor');
|
||||
});
|
||||
|
||||
const err = await client
|
||||
.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(err.message).to.contain('blocked-by-interceptor');
|
||||
expect(getCalls()).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
113
tests/smoke/cjs/tests/progress.smoke.test.cjs
Normal file
113
tests/smoke/cjs/tests/progress.smoke.test.cjs
Normal file
@ -0,0 +1,113 @@
|
||||
const { Readable, Writable, PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const createProgressTransport = (config) => {
|
||||
const opts = config || {};
|
||||
const responseChunks = opts.responseChunks || ['ok'];
|
||||
const responseHeaders = opts.responseHeaders || {};
|
||||
|
||||
return {
|
||||
request(_options, onResponse) {
|
||||
const req = new Writable({
|
||||
write(_chunk, _encoding, callback) {
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.close = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
|
||||
const originalDestroy = req.destroy.bind(req);
|
||||
req.destroy = (...args) => {
|
||||
req.destroyed = true;
|
||||
return originalDestroy(...args);
|
||||
};
|
||||
|
||||
const originalEnd = req.end.bind(req);
|
||||
req.end = (...args) => {
|
||||
originalEnd(...args);
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = Object.assign(
|
||||
{
|
||||
'content-type': 'text/plain',
|
||||
},
|
||||
responseHeaders
|
||||
);
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
|
||||
responseChunks.forEach((chunk) => {
|
||||
res.write(chunk);
|
||||
});
|
||||
res.end();
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('progress compat (dist export only)', () => {
|
||||
it('emits upload progress events for stream payloads', async () => {
|
||||
const samples = [];
|
||||
const payload = ['abc', 'def', 'ghi'];
|
||||
const total = payload.join('').length;
|
||||
|
||||
await axios.post('http://example.com/upload', Readable.from(payload), {
|
||||
proxy: false,
|
||||
headers: {
|
||||
'Content-Length': String(total),
|
||||
},
|
||||
onUploadProgress: ({ loaded, total: reportedTotal, upload }) => {
|
||||
samples.push({ loaded, total: reportedTotal, upload });
|
||||
},
|
||||
transport: createProgressTransport({
|
||||
responseChunks: ['uploaded'],
|
||||
}),
|
||||
});
|
||||
|
||||
expect(samples.length).to.be.greaterThan(0);
|
||||
expect(samples[samples.length - 1]).to.deep.include({
|
||||
loaded: total,
|
||||
total,
|
||||
upload: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('emits download progress events', async () => {
|
||||
const samples = [];
|
||||
const chunks = ['ab', 'cd', 'ef'];
|
||||
const total = chunks.join('').length;
|
||||
|
||||
const response = await axios.get('http://example.com/download', {
|
||||
proxy: false,
|
||||
responseType: 'text',
|
||||
onDownloadProgress: ({ loaded, total: reportedTotal, download }) => {
|
||||
samples.push({ loaded, total: reportedTotal, download });
|
||||
},
|
||||
transport: createProgressTransport({
|
||||
responseChunks: chunks,
|
||||
responseHeaders: {
|
||||
'content-length': String(total),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data).to.equal('abcdef');
|
||||
expect(samples.length).to.be.greaterThan(0);
|
||||
expect(samples[samples.length - 1]).to.deep.include({
|
||||
loaded: total,
|
||||
total,
|
||||
download: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
105
tests/smoke/cjs/tests/rateLimit.smoke.test.cjs
Normal file
105
tests/smoke/cjs/tests/rateLimit.smoke.test.cjs
Normal file
@ -0,0 +1,105 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const createTransportCapture = () => {
|
||||
let capturedOptions;
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
capturedOptions = options;
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end('{"ok":true}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
};
|
||||
};
|
||||
|
||||
describe('rateLimit compat (dist export only)', () => {
|
||||
it('accepts numeric maxRate config', async () => {
|
||||
const response = await axios.get('http://example.com/rate', {
|
||||
maxRate: 1024,
|
||||
adapter: async (config) => ({
|
||||
data: { maxRate: config.maxRate },
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.maxRate).to.equal(1024);
|
||||
});
|
||||
|
||||
it('accepts tuple maxRate config [upload, download]', async () => {
|
||||
const response = await axios.get('http://example.com/rate', {
|
||||
maxRate: [2048, 4096],
|
||||
adapter: async (config) => ({
|
||||
data: { maxRate: config.maxRate },
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.maxRate).to.deep.equal([2048, 4096]);
|
||||
});
|
||||
|
||||
it('merges instance and request maxRate values', async () => {
|
||||
const client = axios.create({
|
||||
maxRate: [1000, 2000],
|
||||
});
|
||||
|
||||
const response = await client.get('http://example.com/rate', {
|
||||
maxRate: [3000, 4000],
|
||||
adapter: async (config) => ({
|
||||
data: { maxRate: config.maxRate },
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.maxRate).to.deep.equal([3000, 4000]);
|
||||
});
|
||||
|
||||
it('supports maxRate in node transport flow without errors', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
const response = await axios.get('http://example.com/rate', {
|
||||
proxy: false,
|
||||
maxRate: [1500, 2500],
|
||||
transport,
|
||||
});
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(getCapturedOptions().method).to.equal('GET');
|
||||
expect(getCapturedOptions().path).to.equal('/rate');
|
||||
});
|
||||
});
|
||||
116
tests/smoke/cjs/tests/timeout.smoke.test.cjs
Normal file
116
tests/smoke/cjs/tests/timeout.smoke.test.cjs
Normal file
@ -0,0 +1,116 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const createTransport = (config) => {
|
||||
const opts = config || {};
|
||||
|
||||
return {
|
||||
request(_options, onResponse) {
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req._timeoutCallback = null;
|
||||
|
||||
req.setTimeout = (_ms, callback) => {
|
||||
req._timeoutCallback = callback;
|
||||
};
|
||||
|
||||
req.write = () => true;
|
||||
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
|
||||
req.end = () => {
|
||||
if (opts.triggerTimeout && req._timeoutCallback) {
|
||||
req._timeoutCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
res.end(opts.body || '{"ok":true}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('timeout compat (dist export only)', () => {
|
||||
it('rejects with ECONNABORTED on timeout', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: 25,
|
||||
transport: createTransport({ triggerTimeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).to.equal(true);
|
||||
expect(err.code).to.equal('ECONNABORTED');
|
||||
expect(err.message).to.equal('timeout of 25ms exceeded');
|
||||
});
|
||||
|
||||
it('uses timeoutErrorMessage when provided', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: 25,
|
||||
timeoutErrorMessage: 'custom timeout',
|
||||
transport: createTransport({ triggerTimeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).to.equal(true);
|
||||
expect(err.code).to.equal('ECONNABORTED');
|
||||
expect(err.message).to.equal('custom timeout');
|
||||
});
|
||||
|
||||
it('accepts timeout as a numeric string', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: '30',
|
||||
transport: createTransport({ triggerTimeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).to.equal(true);
|
||||
expect(err.code).to.equal('ECONNABORTED');
|
||||
expect(err.message).to.equal('timeout of 30ms exceeded');
|
||||
});
|
||||
|
||||
it('rejects with ERR_BAD_OPTION_VALUE when timeout is not parsable', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: { invalid: true },
|
||||
transport: createTransport(),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).to.equal(true);
|
||||
expect(err.code).to.equal('ERR_BAD_OPTION_VALUE');
|
||||
expect(err.message).to.equal('error trying to parse `config.timeout` to int');
|
||||
});
|
||||
|
||||
it('does not time out when timeout is 0', async () => {
|
||||
const response = await axios.get('http://example.com/no-timeout', {
|
||||
proxy: false,
|
||||
timeout: 0,
|
||||
transport: createTransport({ body: '{"ok":true}' }),
|
||||
});
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.data).to.deep.equal({ ok: true });
|
||||
});
|
||||
});
|
||||
146
tests/smoke/cjs/tests/urlencode.smoke.test.cjs
Normal file
146
tests/smoke/cjs/tests/urlencode.smoke.test.cjs
Normal file
@ -0,0 +1,146 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { PassThrough } = require('stream');
|
||||
const axios = require('axios');
|
||||
const { describe, it } = require('mocha');
|
||||
const { expect } = require('chai');
|
||||
|
||||
const createEchoTransport = () => {
|
||||
let capturedOptions;
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
capturedOptions = options;
|
||||
const chunks = [];
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.write = (chunk) => {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
return true;
|
||||
};
|
||||
req.end = (chunk) => {
|
||||
if (chunk) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
}
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
path: options.path,
|
||||
body: Buffer.concat(chunks).toString('utf8'),
|
||||
contentType:
|
||||
options.headers &&
|
||||
(options.headers['Content-Type'] || options.headers['content-type']),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
};
|
||||
};
|
||||
|
||||
describe('urlencode compat (dist export only)', () => {
|
||||
it('serializes params into request URL', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
|
||||
const response = await axios.get('http://example.com/search', {
|
||||
proxy: false,
|
||||
transport,
|
||||
params: {
|
||||
q: 'axios docs',
|
||||
page: 2,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.data.path).to.equal('/search?q=axios+docs&page=2');
|
||||
});
|
||||
|
||||
it('supports custom paramsSerializer function', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
|
||||
const response = await axios.get('http://example.com/search', {
|
||||
proxy: false,
|
||||
transport,
|
||||
params: { q: 'ignored' },
|
||||
paramsSerializer: () => 'fixed=1',
|
||||
});
|
||||
|
||||
expect(response.data.path).to.equal('/search?fixed=1');
|
||||
});
|
||||
|
||||
it('supports URLSearchParams payloads', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
const payload = new URLSearchParams();
|
||||
payload.append('name', 'axios');
|
||||
payload.append('mode', 'compat');
|
||||
|
||||
const response = await axios.post('http://example.com/form', payload, {
|
||||
proxy: false,
|
||||
transport,
|
||||
});
|
||||
|
||||
expect(response.data.body).to.equal('name=axios&mode=compat');
|
||||
expect(response.data.contentType).to.contain('application/x-www-form-urlencoded');
|
||||
});
|
||||
|
||||
it('serializes object payload when content-type is application/x-www-form-urlencoded', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
|
||||
const response = await axios.post(
|
||||
'http://example.com/form',
|
||||
{
|
||||
name: 'axios',
|
||||
mode: 'compat',
|
||||
},
|
||||
{
|
||||
proxy: false,
|
||||
transport,
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.data.body).to.equal('name=axios&mode=compat');
|
||||
expect(response.data.contentType).to.contain('application/x-www-form-urlencoded');
|
||||
});
|
||||
|
||||
it('respects formSerializer options for index formatting', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
|
||||
const response = await axios.post(
|
||||
'http://example.com/form',
|
||||
{
|
||||
arr: ['1', '2'],
|
||||
},
|
||||
{
|
||||
proxy: false,
|
||||
transport,
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
formSerializer: {
|
||||
indexes: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.data.body).to.equal('arr%5B0%5D=1&arr%5B1%5D=2');
|
||||
});
|
||||
});
|
||||
1549
tests/smoke/esm/package-lock.json
generated
Normal file
1549
tests/smoke/esm/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
tests/smoke/esm/package.json
Normal file
18
tests/smoke/esm/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@axios/esm-smoke-tests",
|
||||
"version": "1.0.0",
|
||||
"description": "ESM smoke tests for axios",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test:smoke:esm:vitest": "vitest run --config vitest.config.js --project smoke"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "axios team",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "file:../../../"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "4.0.18"
|
||||
}
|
||||
}
|
||||
137
tests/smoke/esm/tests/auth.smoke.test.js
Normal file
137
tests/smoke/esm/tests/auth.smoke.test.js
Normal file
@ -0,0 +1,137 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import http from 'http';
|
||||
import axios from 'axios';
|
||||
|
||||
const startServer = (handler) => {
|
||||
return new Promise((resolve) => {
|
||||
const server = http.createServer(handler);
|
||||
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const stopServer = (server) => {
|
||||
if (!server || !server.listening) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.close((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('auth compat (dist export only)', () => {
|
||||
let server;
|
||||
|
||||
afterEach(async () => {
|
||||
await stopServer(server);
|
||||
server = undefined;
|
||||
});
|
||||
|
||||
const requestWithConfig = async (config) => {
|
||||
server = await startServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(req.headers.authorization || '');
|
||||
});
|
||||
|
||||
const { port } = server.address();
|
||||
|
||||
return axios.get(
|
||||
`http://127.0.0.1:${port}/`,
|
||||
Object.assign(
|
||||
{
|
||||
proxy: false,
|
||||
},
|
||||
config || {}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
it('sets Basic Authorization header from auth credentials', async () => {
|
||||
const response = await requestWithConfig({
|
||||
auth: {
|
||||
username: 'janedoe',
|
||||
password: 's00pers3cret',
|
||||
},
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('janedoe:s00pers3cret', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).toBe(expected);
|
||||
});
|
||||
|
||||
it('supports auth without password', async () => {
|
||||
const response = await requestWithConfig({
|
||||
auth: {
|
||||
username: 'Aladdin',
|
||||
},
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('Aladdin:', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).toBe(expected);
|
||||
});
|
||||
|
||||
it('overwrites an existing Authorization header when auth is provided', async () => {
|
||||
const response = await requestWithConfig({
|
||||
headers: {
|
||||
Authorization: 'Bearer token-123',
|
||||
},
|
||||
auth: {
|
||||
username: 'foo',
|
||||
password: 'bar',
|
||||
},
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('foo:bar', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).toBe(expected);
|
||||
});
|
||||
|
||||
it('uses URL credentials when auth config is not provided (node adapter behavior)', async () => {
|
||||
server = await startServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(req.headers.authorization || '');
|
||||
});
|
||||
|
||||
const { port } = server.address();
|
||||
|
||||
const response = await axios.get(`http://urluser:urlpass@127.0.0.1:${port}/`, {
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('urluser:urlpass', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).toBe(expected);
|
||||
});
|
||||
|
||||
it('prefers auth config over URL credentials', async () => {
|
||||
server = await startServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(req.headers.authorization || '');
|
||||
});
|
||||
|
||||
const { port } = server.address();
|
||||
|
||||
const response = await axios.get(`http://urluser:urlpass@127.0.0.1:${port}/`, {
|
||||
proxy: false,
|
||||
auth: {
|
||||
username: 'configuser',
|
||||
password: 'configpass',
|
||||
},
|
||||
});
|
||||
|
||||
const expected = `Basic ${Buffer.from('configuser:configpass', 'utf8').toString('base64')}`;
|
||||
|
||||
expect(response.data).toBe(expected);
|
||||
});
|
||||
});
|
||||
145
tests/smoke/esm/tests/basic.smoke.test.js
Normal file
145
tests/smoke/esm/tests/basic.smoke.test.js
Normal file
@ -0,0 +1,145 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createTransportCapture = () => {
|
||||
let capturedOptions;
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
capturedOptions = options;
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end('{"ok":true}');
|
||||
};
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
};
|
||||
};
|
||||
|
||||
const runRequest = async (run) => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
await run(transport);
|
||||
|
||||
return getCapturedOptions();
|
||||
};
|
||||
|
||||
describe('basic compat (dist export only)', () => {
|
||||
it('supports the simplest axios(url) request pattern', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios('http://example.com/users', {
|
||||
transport,
|
||||
proxy: false,
|
||||
})
|
||||
);
|
||||
|
||||
expect(options.method).toBe('GET');
|
||||
expect(options.path).toBe('/users');
|
||||
});
|
||||
|
||||
it('supports get()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.get('http://example.com/items?limit=10', { transport, proxy: false })
|
||||
);
|
||||
|
||||
expect(options.method).toBe('GET');
|
||||
expect(options.path).toBe('/items?limit=10');
|
||||
});
|
||||
|
||||
it('supports delete()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.delete('http://example.com/items/1', { transport, proxy: false })
|
||||
);
|
||||
|
||||
expect(options.method).toBe('DELETE');
|
||||
expect(options.path).toBe('/items/1');
|
||||
});
|
||||
|
||||
it('supports head()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.head('http://example.com/health', { transport, proxy: false })
|
||||
);
|
||||
|
||||
expect(options.method).toBe('HEAD');
|
||||
expect(options.path).toBe('/health');
|
||||
});
|
||||
|
||||
it('supports options()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.options('http://example.com/items', { transport, proxy: false })
|
||||
);
|
||||
|
||||
expect(options.method).toBe('OPTIONS');
|
||||
expect(options.path).toBe('/items');
|
||||
});
|
||||
|
||||
it('supports post()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.post(
|
||||
'http://example.com/items',
|
||||
{ name: 'widget' },
|
||||
{
|
||||
transport,
|
||||
proxy: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(options.method).toBe('POST');
|
||||
expect(options.path).toBe('/items');
|
||||
});
|
||||
|
||||
it('supports put()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.put(
|
||||
'http://example.com/items/1',
|
||||
{ name: 'updated-widget' },
|
||||
{
|
||||
transport,
|
||||
proxy: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(options.method).toBe('PUT');
|
||||
expect(options.path).toBe('/items/1');
|
||||
});
|
||||
|
||||
it('supports patch()', async () => {
|
||||
const options = await runRequest((transport) =>
|
||||
axios.patch(
|
||||
'http://example.com/items/1',
|
||||
{ status: 'active' },
|
||||
{
|
||||
transport,
|
||||
proxy: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(options.method).toBe('PATCH');
|
||||
expect(options.path).toBe('/items/1');
|
||||
});
|
||||
});
|
||||
111
tests/smoke/esm/tests/cancel.smoke.test.js
Normal file
111
tests/smoke/esm/tests/cancel.smoke.test.js
Normal file
@ -0,0 +1,111 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import axios from 'axios';
|
||||
|
||||
const createPendingTransport = () => {
|
||||
let requestCount = 0;
|
||||
|
||||
const transport = {
|
||||
request() {
|
||||
requestCount += 1;
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.end = () => {};
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getRequestCount: () => requestCount,
|
||||
};
|
||||
};
|
||||
|
||||
describe('cancel compat (dist export only)', () => {
|
||||
it('supports cancellation with AbortController (pre-aborted signal)', async () => {
|
||||
const { transport, getRequestCount } = createPendingTransport();
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
|
||||
try {
|
||||
const request = axios.get('http://example.com/resource', {
|
||||
signal: controller.signal,
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
await request;
|
||||
} catch (error) {
|
||||
expect(error.code).toBe('ERR_CANCELED');
|
||||
}
|
||||
|
||||
expect(getRequestCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('supports cancellation with AbortController (in-flight)', async () => {
|
||||
const { transport, getRequestCount } = createPendingTransport();
|
||||
const controller = new AbortController();
|
||||
|
||||
try {
|
||||
const request = axios.get('http://example.com/resource', {
|
||||
signal: controller.signal,
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
await request;
|
||||
} catch (error) {
|
||||
expect(error.code).toBe('ERR_CANCELED');
|
||||
}
|
||||
|
||||
expect(getRequestCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('supports cancellation with CancelToken (pre-canceled token)', async () => {
|
||||
const { transport, getRequestCount } = createPendingTransport();
|
||||
const source = axios.CancelToken.source();
|
||||
source.cancel('Operation canceled by the user.');
|
||||
|
||||
const error = await axios
|
||||
.get('http://example.com/resource', {
|
||||
cancelToken: source.token,
|
||||
transport,
|
||||
proxy: false,
|
||||
})
|
||||
.catch((err) => err);
|
||||
|
||||
expect(axios.isCancel(error)).toBe(true);
|
||||
expect(error.code).toBe('ERR_CANCELED');
|
||||
expect(getRequestCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('supports cancellation with CancelToken (in-flight)', async () => {
|
||||
const { transport, getRequestCount } = createPendingTransport();
|
||||
const source = axios.CancelToken.source();
|
||||
|
||||
const request = axios.get('http://example.com/resource', {
|
||||
cancelToken: source.token,
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
source.cancel('Operation canceled by the user.');
|
||||
|
||||
const error = await request.catch((err) => err);
|
||||
|
||||
expect(axios.isCancel(error)).toBe(true);
|
||||
expect(error.code).toBe('ERR_CANCELED');
|
||||
expect(getRequestCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
139
tests/smoke/esm/tests/error.smoke.test.js
Normal file
139
tests/smoke/esm/tests/error.smoke.test.js
Normal file
@ -0,0 +1,139 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createTransport = (config) => {
|
||||
const opts = config || {};
|
||||
|
||||
return {
|
||||
request(options, onResponse) {
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
|
||||
req.setTimeout = (_ms, cb) => {
|
||||
if (opts.timeout) {
|
||||
req._timeoutCallback = cb;
|
||||
}
|
||||
};
|
||||
|
||||
req.end = () => {
|
||||
if (opts.error) {
|
||||
req.emit('error', opts.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.timeout && req._timeoutCallback) {
|
||||
req._timeoutCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode =
|
||||
opts.response && opts.response.statusCode !== undefined ? opts.response.statusCode : 200;
|
||||
res.statusMessage =
|
||||
opts.response && opts.response.statusMessage ? opts.response.statusMessage : 'OK';
|
||||
res.headers =
|
||||
opts.response && opts.response.headers
|
||||
? opts.response.headers
|
||||
: { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
res.end(
|
||||
opts.response && opts.response.body !== undefined ? opts.response.body : '{"ok":true}'
|
||||
);
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('error compat (dist export only)', () => {
|
||||
it('rejects with AxiosError for non-2xx responses by default', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/fail', {
|
||||
proxy: false,
|
||||
transport: createTransport({
|
||||
response: {
|
||||
statusCode: 500,
|
||||
statusMessage: 'Internal Server Error',
|
||||
body: '{"error":"boom"}',
|
||||
},
|
||||
}),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).toBe(true);
|
||||
expect(err.response.status).toBe(500);
|
||||
expect(err.message).toContain('500');
|
||||
});
|
||||
|
||||
it('resolves when validateStatus allows non-2xx responses', async () => {
|
||||
const response = await axios.get('http://example.com/allowed', {
|
||||
proxy: false,
|
||||
validateStatus: () => true,
|
||||
transport: createTransport({
|
||||
response: {
|
||||
statusCode: 500,
|
||||
statusMessage: 'Internal Server Error',
|
||||
body: '{"ok":false}',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.data).toEqual({ ok: false });
|
||||
});
|
||||
|
||||
it('wraps transport errors as AxiosError', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/network', {
|
||||
proxy: false,
|
||||
transport: createTransport({
|
||||
error: new Error('socket hang up'),
|
||||
}),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).toBe(true);
|
||||
expect(err.message).toContain('socket hang up');
|
||||
expect(err.toJSON).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
it('rejects with ECONNABORTED on timeout', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: 10,
|
||||
transport: createTransport({ timeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).toBe(true);
|
||||
expect(err.code).toBe('ECONNABORTED');
|
||||
expect(err.message).toBe('timeout of 10ms exceeded');
|
||||
});
|
||||
|
||||
it('uses timeoutErrorMessage when provided', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: 25,
|
||||
timeoutErrorMessage: 'custom timeout message',
|
||||
transport: createTransport({ timeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).toBe(true);
|
||||
expect(err.code).toBe('ECONNABORTED');
|
||||
expect(err.message).toBe('custom timeout message');
|
||||
});
|
||||
});
|
||||
142
tests/smoke/esm/tests/fetch.smoke.test.js
Normal file
142
tests/smoke/esm/tests/fetch.smoke.test.js
Normal file
@ -0,0 +1,142 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
const createFetchMock = (responseFactory) => {
|
||||
const calls = [];
|
||||
|
||||
const mockFetch = async (input, init) => {
|
||||
calls.push({ input, init: init || {} });
|
||||
|
||||
if (responseFactory) {
|
||||
return responseFactory(input, init || {});
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
mockFetch,
|
||||
getCalls: () => calls,
|
||||
};
|
||||
};
|
||||
|
||||
describe('fetch compat (dist export only)', () => {
|
||||
it('uses fetch adapter and resolves JSON response', async () => {
|
||||
const { mockFetch, getCalls } = createFetchMock();
|
||||
|
||||
const response = await axios.get('https://example.com/users', {
|
||||
adapter: 'fetch',
|
||||
env: {
|
||||
fetch: mockFetch,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ ok: true });
|
||||
expect(response.status).toBe(200);
|
||||
expect(getCalls()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('sends method, headers and body for post requests', async () => {
|
||||
const { mockFetch, getCalls } = createFetchMock(async (input, init) => {
|
||||
const requestInit = init || {};
|
||||
const isRequest = input && typeof input !== 'string';
|
||||
const method = isRequest ? input.method : requestInit.method;
|
||||
|
||||
const body =
|
||||
isRequest && typeof input.clone === 'function'
|
||||
? await input.clone().text()
|
||||
: requestInit.body;
|
||||
|
||||
let contentType;
|
||||
if (isRequest && input.headers) {
|
||||
contentType = input.headers.get('content-type');
|
||||
} else if (requestInit.headers) {
|
||||
contentType = requestInit.headers['Content-Type'] || requestInit.headers['content-type'];
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
url: typeof input === 'string' ? input : input.url,
|
||||
method,
|
||||
contentType,
|
||||
body,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const response = await axios.post(
|
||||
'https://example.com/items',
|
||||
{ name: 'widget' },
|
||||
{
|
||||
adapter: 'fetch',
|
||||
env: {
|
||||
fetch: mockFetch,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(getCalls()).toHaveLength(1);
|
||||
expect(response.data.url).toBe('https://example.com/items');
|
||||
expect(response.data.method).toBe('POST');
|
||||
expect(response.data.contentType).toContain('application/json');
|
||||
expect(response.data.body).toBe(JSON.stringify({ name: 'widget' }));
|
||||
});
|
||||
|
||||
it('rejects non-2xx fetch responses by default', async () => {
|
||||
const { mockFetch } = createFetchMock(
|
||||
() =>
|
||||
new Response(JSON.stringify({ error: 'boom' }), {
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
);
|
||||
|
||||
const err = await axios
|
||||
.get('https://example.com/fail', {
|
||||
adapter: 'fetch',
|
||||
env: {
|
||||
fetch: mockFetch,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).toBe(true);
|
||||
expect(err.response.status).toBe(500);
|
||||
});
|
||||
|
||||
it('supports cancellation with AbortController in fetch mode', async () => {
|
||||
const { mockFetch } = createFetchMock();
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
|
||||
const err = await axios
|
||||
.get('https://example.com/cancel', {
|
||||
adapter: 'fetch',
|
||||
signal: controller.signal,
|
||||
env: {
|
||||
fetch: mockFetch,
|
||||
Request,
|
||||
Response,
|
||||
},
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isCancel(err)).toBe(true);
|
||||
expect(err.code).toBe('ERR_CANCELED');
|
||||
});
|
||||
});
|
||||
115
tests/smoke/esm/tests/files.smoke.test.js
Normal file
115
tests/smoke/esm/tests/files.smoke.test.js
Normal file
@ -0,0 +1,115 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { PassThrough, Readable, Writable } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createCaptureTransport = (buildResponse) => {
|
||||
return {
|
||||
request(options, onResponse) {
|
||||
const chunks = [];
|
||||
|
||||
const req = new Writable({
|
||||
write(chunk, _encoding, callback) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.close = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
|
||||
const originalDestroy = req.destroy.bind(req);
|
||||
req.destroy = (...args) => {
|
||||
req.destroyed = true;
|
||||
return originalDestroy(...args);
|
||||
};
|
||||
|
||||
const originalEnd = req.end.bind(req);
|
||||
req.end = (...args) => {
|
||||
originalEnd(...args);
|
||||
|
||||
const body = Buffer.concat(chunks);
|
||||
const response = buildResponse ? buildResponse(body, options) : {};
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = response.statusCode !== undefined ? response.statusCode : 200;
|
||||
res.statusMessage = response.statusMessage || 'OK';
|
||||
res.headers = response.headers || { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
res.end(response.body || JSON.stringify({ size: body.length }));
|
||||
};
|
||||
|
||||
req.on('error', () => {});
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('files compat (dist export only)', () => {
|
||||
it('supports posting Buffer payloads', async () => {
|
||||
const source = Buffer.from('binary-\x00-data', 'utf8');
|
||||
|
||||
const response = await axios.post('http://example.com/upload', source, {
|
||||
proxy: false,
|
||||
transport: createCaptureTransport((body) => ({
|
||||
body: JSON.stringify({ echoed: body.toString('base64') }),
|
||||
})),
|
||||
});
|
||||
|
||||
expect(response.data.echoed).toBe(source.toString('base64'));
|
||||
});
|
||||
|
||||
it('supports posting Uint8Array payloads', async () => {
|
||||
const source = Uint8Array.from([1, 2, 3, 4, 255]);
|
||||
|
||||
const response = await axios.post('http://example.com/upload', source, {
|
||||
proxy: false,
|
||||
transport: createCaptureTransport((body) => ({
|
||||
body: JSON.stringify({ echoed: Array.from(body.values()) }),
|
||||
})),
|
||||
});
|
||||
|
||||
expect(response.data.echoed).toEqual([1, 2, 3, 4, 255]);
|
||||
});
|
||||
|
||||
it('supports posting Readable stream payloads', async () => {
|
||||
const streamData = ['hello ', 'stream ', 'world'];
|
||||
const source = Readable.from(streamData);
|
||||
|
||||
const response = await axios.post('http://example.com/upload', source, {
|
||||
proxy: false,
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
transport: createCaptureTransport((body, options) => ({
|
||||
body: JSON.stringify({
|
||||
text: body.toString('utf8'),
|
||||
contentType:
|
||||
options.headers && (options.headers['Content-Type'] || options.headers['content-type']),
|
||||
}),
|
||||
})),
|
||||
});
|
||||
|
||||
expect(response.data.text).toBe('hello stream world');
|
||||
expect(response.data.contentType).toContain('application/octet-stream');
|
||||
});
|
||||
|
||||
it('supports binary downloads with responseType=arraybuffer', async () => {
|
||||
const binary = Buffer.from([0xde, 0xad, 0xbe, 0xef]);
|
||||
|
||||
const response = await axios.get('http://example.com/file.bin', {
|
||||
proxy: false,
|
||||
responseType: 'arraybuffer',
|
||||
transport: createCaptureTransport(() => ({
|
||||
headers: { 'content-type': 'application/octet-stream' },
|
||||
body: binary,
|
||||
})),
|
||||
});
|
||||
|
||||
expect(Buffer.isBuffer(response.data)).toBe(true);
|
||||
expect(response.data.equals(binary)).toBe(true);
|
||||
});
|
||||
});
|
||||
107
tests/smoke/esm/tests/formData.smoke.test.js
Normal file
107
tests/smoke/esm/tests/formData.smoke.test.js
Normal file
@ -0,0 +1,107 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { Writable, PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createCaptureTransport = (buildResponse) => {
|
||||
return {
|
||||
request(options, onResponse) {
|
||||
const chunks = [];
|
||||
|
||||
const req = new Writable({
|
||||
write(chunk, _encoding, callback) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = req.write.bind(req);
|
||||
req.close = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
|
||||
const originalDestroy = req.destroy.bind(req);
|
||||
req.destroy = (...args) => {
|
||||
req.destroyed = true;
|
||||
return originalDestroy(...args);
|
||||
};
|
||||
|
||||
const originalEnd = req.end.bind(req);
|
||||
req.end = (...args) => {
|
||||
originalEnd(...args);
|
||||
|
||||
const body = Buffer.concat(chunks);
|
||||
const response = buildResponse ? buildResponse(body, options) : {};
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = response.statusCode !== undefined ? response.statusCode : 200;
|
||||
res.statusMessage = response.statusMessage || 'OK';
|
||||
res.headers = response.headers || { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
res.end(response.body || JSON.stringify({ ok: true }));
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const bodyAsUtf8 = (value) => {
|
||||
return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);
|
||||
};
|
||||
|
||||
describe('formData compat (dist export only)', () => {
|
||||
it('supports posting FormData instances', async () => {
|
||||
const form = new FormData();
|
||||
form.append('username', 'janedoe');
|
||||
form.append('role', 'admin');
|
||||
|
||||
const response = await axios.post('http://example.com/form', form, {
|
||||
proxy: false,
|
||||
transport: createCaptureTransport((body, options) => ({
|
||||
body: JSON.stringify({
|
||||
contentType:
|
||||
options.headers && (options.headers['Content-Type'] || options.headers['content-type']),
|
||||
payload: bodyAsUtf8(body),
|
||||
}),
|
||||
})),
|
||||
});
|
||||
|
||||
expect(response.data.contentType).toContain('multipart/form-data');
|
||||
expect(response.data.payload).toContain('name="username"');
|
||||
expect(response.data.payload).toContain('janedoe');
|
||||
expect(response.data.payload).toContain('name="role"');
|
||||
expect(response.data.payload).toContain('admin');
|
||||
});
|
||||
|
||||
it('supports axios.postForm helper', async () => {
|
||||
const response = await axios.postForm(
|
||||
'http://example.com/post-form',
|
||||
{
|
||||
project: 'axios',
|
||||
mode: 'compat',
|
||||
},
|
||||
{
|
||||
proxy: false,
|
||||
transport: createCaptureTransport((body, options) => ({
|
||||
body: JSON.stringify({
|
||||
contentType:
|
||||
options.headers &&
|
||||
(options.headers['Content-Type'] || options.headers['content-type']),
|
||||
payload: bodyAsUtf8(body),
|
||||
}),
|
||||
})),
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.data.contentType).toContain('multipart/form-data');
|
||||
expect(response.data.payload).toContain('name="project"');
|
||||
expect(response.data.payload).toContain('axios');
|
||||
expect(response.data.payload).toContain('name="mode"');
|
||||
expect(response.data.payload).toContain('compat');
|
||||
});
|
||||
});
|
||||
125
tests/smoke/esm/tests/headers.smoke.test.js
Normal file
125
tests/smoke/esm/tests/headers.smoke.test.js
Normal file
@ -0,0 +1,125 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const normalizeHeaders = (headers) => {
|
||||
const result = {};
|
||||
|
||||
Object.entries(headers || {}).forEach(([key, value]) => {
|
||||
result[key.toLowerCase()] = value;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const createTransportCapture = () => {
|
||||
let capturedOptions;
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
capturedOptions = options;
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end('{"ok":true}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
};
|
||||
};
|
||||
|
||||
describe('headers compat (dist export only)', () => {
|
||||
it('sends default Accept header', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.get('http://example.com/default-headers', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers.accept).toBe('application/json, text/plain, */*');
|
||||
});
|
||||
|
||||
it('supports custom headers', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.get('http://example.com/custom-headers', {
|
||||
transport,
|
||||
proxy: false,
|
||||
headers: {
|
||||
'X-Trace-Id': 'trace-123',
|
||||
Authorization: 'Bearer token-abc',
|
||||
},
|
||||
});
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers['x-trace-id']).toBe('trace-123');
|
||||
expect(headers.authorization).toBe('Bearer token-abc');
|
||||
});
|
||||
|
||||
it('treats header names as case-insensitive when overriding', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.get('http://example.com/case-insensitive', {
|
||||
transport,
|
||||
proxy: false,
|
||||
headers: {
|
||||
authorization: 'Bearer old-token',
|
||||
AuThOrIzAtIoN: 'Bearer new-token',
|
||||
},
|
||||
});
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers.authorization).toBe('Bearer new-token');
|
||||
});
|
||||
|
||||
it('sets content-type for json post payloads', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.post(
|
||||
'http://example.com/post-json',
|
||||
{ name: 'widget' },
|
||||
{
|
||||
transport,
|
||||
proxy: false,
|
||||
}
|
||||
);
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers['content-type']).toContain('application/json');
|
||||
});
|
||||
|
||||
it('does not force content-type for get requests without body', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
await axios.get('http://example.com/get-no-body', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
const headers = normalizeHeaders(getCapturedOptions().headers);
|
||||
expect(headers['content-type']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
86
tests/smoke/esm/tests/http2.smoke.test.js
Normal file
86
tests/smoke/esm/tests/http2.smoke.test.js
Normal file
@ -0,0 +1,86 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
describe('http2 compat (dist export only)', () => {
|
||||
it('keeps instance-level httpVersion and http2Options in request config', async () => {
|
||||
const client = axios.create({
|
||||
baseURL: 'https://example.com',
|
||||
httpVersion: 2,
|
||||
http2Options: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.get('/resource', {
|
||||
adapter: async (config) => ({
|
||||
data: {
|
||||
httpVersion: config.httpVersion,
|
||||
http2Options: config.http2Options,
|
||||
},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.httpVersion).toBe(2);
|
||||
expect(response.data.http2Options).toEqual({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('merges request http2Options with instance http2Options', async () => {
|
||||
const client = axios.create({
|
||||
baseURL: 'https://example.com',
|
||||
httpVersion: 2,
|
||||
http2Options: {
|
||||
rejectUnauthorized: false,
|
||||
sessionTimeout: 1000,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.get('/resource', {
|
||||
http2Options: {
|
||||
sessionTimeout: 5000,
|
||||
customFlag: true,
|
||||
},
|
||||
adapter: async (config) => ({
|
||||
data: config.http2Options,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({
|
||||
rejectUnauthorized: false,
|
||||
sessionTimeout: 5000,
|
||||
customFlag: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('allows request-level httpVersion override', async () => {
|
||||
const client = axios.create({
|
||||
baseURL: 'https://example.com',
|
||||
httpVersion: 2,
|
||||
});
|
||||
|
||||
const response = await client.get('/resource', {
|
||||
httpVersion: 1,
|
||||
adapter: async (config) => ({
|
||||
data: {
|
||||
httpVersion: config.httpVersion,
|
||||
},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.httpVersion).toBe(1);
|
||||
});
|
||||
});
|
||||
146
tests/smoke/esm/tests/instance.smoke.test.js
Normal file
146
tests/smoke/esm/tests/instance.smoke.test.js
Normal file
@ -0,0 +1,146 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createTransportCapture = (responseBody) => {
|
||||
const calls = [];
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
calls.push(options);
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end(responseBody ? responseBody : '{"ok":true}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCalls: () => calls,
|
||||
};
|
||||
};
|
||||
|
||||
describe('instance compat (dist export only)', () => {
|
||||
it('creates isolated instances with separate defaults', async () => {
|
||||
const { transport, getCalls } = createTransportCapture();
|
||||
|
||||
const clientA = axios.create({
|
||||
baseURL: 'http://example.com/api-a',
|
||||
headers: {
|
||||
'X-App': 'A',
|
||||
},
|
||||
});
|
||||
const clientB = axios.create({
|
||||
baseURL: 'http://example.com/api-b',
|
||||
headers: {
|
||||
'X-App': 'B',
|
||||
},
|
||||
});
|
||||
|
||||
await clientA.get('/users', { transport, proxy: false });
|
||||
await clientB.get('/users', { transport, proxy: false });
|
||||
|
||||
const [callA, callB] = getCalls();
|
||||
expect(callA.path).toBe('/api-a/users');
|
||||
expect(callB.path).toBe('/api-b/users');
|
||||
expect(callA.headers['X-App']).toBe('A');
|
||||
expect(callB.headers['X-App']).toBe('B');
|
||||
});
|
||||
|
||||
it('supports callable instance form instance(config)', async () => {
|
||||
const { transport, getCalls } = createTransportCapture();
|
||||
const client = axios.create({
|
||||
baseURL: 'http://example.com',
|
||||
});
|
||||
|
||||
await client({
|
||||
url: '/status',
|
||||
method: 'get',
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(getCalls()).toHaveLength(1);
|
||||
expect(getCalls()[0].method).toBe('GET');
|
||||
expect(getCalls()[0].path).toBe('/status');
|
||||
});
|
||||
|
||||
it('applies instance request interceptors', async () => {
|
||||
const { transport, getCalls } = createTransportCapture();
|
||||
const client = axios.create({
|
||||
baseURL: 'http://example.com',
|
||||
});
|
||||
|
||||
client.interceptors.request.use((config) => {
|
||||
config.headers = config.headers || {};
|
||||
config.headers['X-From-Interceptor'] = 'yes';
|
||||
return config;
|
||||
});
|
||||
|
||||
await client.get('/intercepted', { transport, proxy: false });
|
||||
|
||||
expect(getCalls()).toHaveLength(1);
|
||||
expect(getCalls()[0].headers['X-From-Interceptor']).toBe('yes');
|
||||
});
|
||||
|
||||
it('applies instance response interceptors', async () => {
|
||||
const { transport } = createTransportCapture('{"name":"axios"}');
|
||||
const client = axios.create({
|
||||
baseURL: 'http://example.com',
|
||||
});
|
||||
|
||||
client.interceptors.response.use((response) => {
|
||||
response.data = Object.assign({}, response.data, {
|
||||
intercepted: true,
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
||||
const response = await client.get('/response-interceptor', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({
|
||||
name: 'axios',
|
||||
intercepted: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('builds URLs with getUri from instance defaults and request params', () => {
|
||||
const client = axios.create({
|
||||
baseURL: 'http://example.com/api',
|
||||
params: {
|
||||
apiKey: 'abc',
|
||||
},
|
||||
});
|
||||
|
||||
const uri = client.getUri({
|
||||
url: '/users',
|
||||
params: {
|
||||
page: 2,
|
||||
},
|
||||
});
|
||||
|
||||
expect(uri).toBe('http://example.com/api/users?apiKey=abc&page=2');
|
||||
});
|
||||
});
|
||||
149
tests/smoke/esm/tests/interceptors.smoke.test.js
Normal file
149
tests/smoke/esm/tests/interceptors.smoke.test.js
Normal file
@ -0,0 +1,149 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createTransport = (responseBody) => {
|
||||
const calls = [];
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
calls.push(options);
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end(responseBody ? responseBody : '{"value":"ok"}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCalls: () => calls,
|
||||
};
|
||||
};
|
||||
|
||||
describe('interceptors compat (dist export only)', () => {
|
||||
it('applies request interceptors before dispatch', async () => {
|
||||
const { transport, getCalls } = createTransport();
|
||||
const client = axios.create();
|
||||
|
||||
client.interceptors.request.use((config) => {
|
||||
config.headers = config.headers || {};
|
||||
config.headers['X-One'] = '1';
|
||||
return config;
|
||||
});
|
||||
|
||||
client.interceptors.request.use((config) => {
|
||||
config.headers['X-Two'] = '2';
|
||||
return config;
|
||||
});
|
||||
|
||||
await client.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(getCalls()).toHaveLength(1);
|
||||
expect(getCalls()[0].headers['X-One']).toBe('1');
|
||||
expect(getCalls()[0].headers['X-Two']).toBe('2');
|
||||
});
|
||||
|
||||
it('applies response interceptors in registration order', async () => {
|
||||
const { transport } = createTransport('{"n":1}');
|
||||
const client = axios.create();
|
||||
|
||||
client.interceptors.response.use((response) => {
|
||||
response.data.n += 1;
|
||||
return response;
|
||||
});
|
||||
|
||||
client.interceptors.response.use((response) => {
|
||||
response.data.n *= 10;
|
||||
return response;
|
||||
});
|
||||
|
||||
const response = await client.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(response.data.n).toBe(20);
|
||||
});
|
||||
|
||||
it('supports ejecting request interceptors', async () => {
|
||||
const { transport, getCalls } = createTransport();
|
||||
const client = axios.create();
|
||||
|
||||
const id = client.interceptors.request.use((config) => {
|
||||
config.headers = config.headers || {};
|
||||
config.headers['X-Ejected'] = 'yes';
|
||||
return config;
|
||||
});
|
||||
|
||||
client.interceptors.request.eject(id);
|
||||
|
||||
await client.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(getCalls()).toHaveLength(1);
|
||||
expect(getCalls()[0].headers['X-Ejected']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('supports async request interceptors', async () => {
|
||||
const { transport, getCalls } = createTransport();
|
||||
const client = axios.create();
|
||||
|
||||
client.interceptors.request.use(async (config) => {
|
||||
await Promise.resolve();
|
||||
config.headers = config.headers || {};
|
||||
config.headers['X-Async'] = 'true';
|
||||
return config;
|
||||
});
|
||||
|
||||
await client.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
});
|
||||
|
||||
expect(getCalls()[0].headers['X-Async']).toBe('true');
|
||||
});
|
||||
|
||||
it('propagates errors thrown by request interceptors', async () => {
|
||||
const { transport, getCalls } = createTransport();
|
||||
const client = axios.create();
|
||||
|
||||
client.interceptors.request.use(() => {
|
||||
throw new Error('blocked-by-interceptor');
|
||||
});
|
||||
|
||||
const err = await client
|
||||
.get('http://example.com/resource', {
|
||||
transport,
|
||||
proxy: false,
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(err).toBeInstanceOf(Error);
|
||||
expect(err.message).toContain('blocked-by-interceptor');
|
||||
expect(getCalls()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
113
tests/smoke/esm/tests/progress.smoke.test.js
Normal file
113
tests/smoke/esm/tests/progress.smoke.test.js
Normal file
@ -0,0 +1,113 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { Readable, Writable, PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createProgressTransport = (config) => {
|
||||
const opts = config || {};
|
||||
const responseChunks = opts.responseChunks || ['ok'];
|
||||
const responseHeaders = opts.responseHeaders || {};
|
||||
|
||||
return {
|
||||
request(_options, onResponse) {
|
||||
const req = new Writable({
|
||||
write(_chunk, _encoding, callback) {
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.close = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
|
||||
const originalDestroy = req.destroy.bind(req);
|
||||
req.destroy = (...args) => {
|
||||
req.destroyed = true;
|
||||
return originalDestroy(...args);
|
||||
};
|
||||
|
||||
const originalEnd = req.end.bind(req);
|
||||
req.end = (...args) => {
|
||||
originalEnd(...args);
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = Object.assign(
|
||||
{
|
||||
'content-type': 'text/plain',
|
||||
},
|
||||
responseHeaders
|
||||
);
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
|
||||
responseChunks.forEach((chunk) => {
|
||||
res.write(chunk);
|
||||
});
|
||||
res.end();
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('progress compat (dist export only)', () => {
|
||||
it('emits upload progress events for stream payloads', async () => {
|
||||
const samples = [];
|
||||
const payload = ['abc', 'def', 'ghi'];
|
||||
const total = payload.join('').length;
|
||||
|
||||
await axios.post('http://example.com/upload', Readable.from(payload), {
|
||||
proxy: false,
|
||||
headers: {
|
||||
'Content-Length': String(total),
|
||||
},
|
||||
onUploadProgress: ({ loaded, total: reportedTotal, upload }) => {
|
||||
samples.push({ loaded, total: reportedTotal, upload });
|
||||
},
|
||||
transport: createProgressTransport({
|
||||
responseChunks: ['uploaded'],
|
||||
}),
|
||||
});
|
||||
|
||||
expect(samples.length).toBeGreaterThan(0);
|
||||
expect(samples[samples.length - 1]).toMatchObject({
|
||||
loaded: total,
|
||||
total,
|
||||
upload: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('emits download progress events', async () => {
|
||||
const samples = [];
|
||||
const chunks = ['ab', 'cd', 'ef'];
|
||||
const total = chunks.join('').length;
|
||||
|
||||
const response = await axios.get('http://example.com/download', {
|
||||
proxy: false,
|
||||
responseType: 'text',
|
||||
onDownloadProgress: ({ loaded, total: reportedTotal, download }) => {
|
||||
samples.push({ loaded, total: reportedTotal, download });
|
||||
},
|
||||
transport: createProgressTransport({
|
||||
responseChunks: chunks,
|
||||
responseHeaders: {
|
||||
'content-length': String(total),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data).toBe('abcdef');
|
||||
expect(samples.length).toBeGreaterThan(0);
|
||||
expect(samples[samples.length - 1]).toMatchObject({
|
||||
loaded: total,
|
||||
total,
|
||||
download: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
105
tests/smoke/esm/tests/rateLimit.smoke.test.js
Normal file
105
tests/smoke/esm/tests/rateLimit.smoke.test.js
Normal file
@ -0,0 +1,105 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createTransportCapture = () => {
|
||||
let capturedOptions;
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
capturedOptions = options;
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.write = () => true;
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.end = () => {
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end('{"ok":true}');
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
};
|
||||
};
|
||||
|
||||
describe('rateLimit compat (dist export only)', () => {
|
||||
it('accepts numeric maxRate config', async () => {
|
||||
const response = await axios.get('http://example.com/rate', {
|
||||
maxRate: 1024,
|
||||
adapter: async (config) => ({
|
||||
data: { maxRate: config.maxRate },
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.maxRate).toBe(1024);
|
||||
});
|
||||
|
||||
it('accepts tuple maxRate config [upload, download]', async () => {
|
||||
const response = await axios.get('http://example.com/rate', {
|
||||
maxRate: [2048, 4096],
|
||||
adapter: async (config) => ({
|
||||
data: { maxRate: config.maxRate },
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.maxRate).toEqual([2048, 4096]);
|
||||
});
|
||||
|
||||
it('merges instance and request maxRate values', async () => {
|
||||
const client = axios.create({
|
||||
maxRate: [1000, 2000],
|
||||
});
|
||||
|
||||
const response = await client.get('http://example.com/rate', {
|
||||
maxRate: [3000, 4000],
|
||||
adapter: async (config) => ({
|
||||
data: { maxRate: config.maxRate },
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.data.maxRate).toEqual([3000, 4000]);
|
||||
});
|
||||
|
||||
it('supports maxRate in node transport flow without errors', async () => {
|
||||
const { transport, getCapturedOptions } = createTransportCapture();
|
||||
|
||||
const response = await axios.get('http://example.com/rate', {
|
||||
proxy: false,
|
||||
maxRate: [1500, 2500],
|
||||
transport,
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(getCapturedOptions().method).toBe('GET');
|
||||
expect(getCapturedOptions().path).toBe('/rate');
|
||||
});
|
||||
});
|
||||
116
tests/smoke/esm/tests/timeout.smoke.test.js
Normal file
116
tests/smoke/esm/tests/timeout.smoke.test.js
Normal file
@ -0,0 +1,116 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createTransport = (config) => {
|
||||
const opts = config || {};
|
||||
|
||||
return {
|
||||
request(_options, onResponse) {
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req._timeoutCallback = null;
|
||||
|
||||
req.setTimeout = (_ms, callback) => {
|
||||
req._timeoutCallback = callback;
|
||||
};
|
||||
|
||||
req.write = () => true;
|
||||
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
|
||||
req.end = () => {
|
||||
if (opts.triggerTimeout && req._timeoutCallback) {
|
||||
req._timeoutCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
|
||||
onResponse(res);
|
||||
res.end(opts.body === undefined ? '{"ok":true}' : opts.body);
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('timeout compat (dist export only)', () => {
|
||||
it('rejects with ECONNABORTED on timeout', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: 25,
|
||||
transport: createTransport({ triggerTimeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).toBe(true);
|
||||
expect(err.code).toBe('ECONNABORTED');
|
||||
expect(err.message).toBe('timeout of 25ms exceeded');
|
||||
});
|
||||
|
||||
it('uses timeoutErrorMessage when provided', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: 25,
|
||||
timeoutErrorMessage: 'custom timeout',
|
||||
transport: createTransport({ triggerTimeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).toBe(true);
|
||||
expect(err.code).toBe('ECONNABORTED');
|
||||
expect(err.message).toBe('custom timeout');
|
||||
});
|
||||
|
||||
it('accepts timeout as a numeric string', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: '30',
|
||||
transport: createTransport({ triggerTimeout: true }),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).toBe(true);
|
||||
expect(err.code).toBe('ECONNABORTED');
|
||||
expect(err.message).toBe('timeout of 30ms exceeded');
|
||||
});
|
||||
|
||||
it('rejects with ERR_BAD_OPTION_VALUE when timeout is not parsable', async () => {
|
||||
const err = await axios
|
||||
.get('http://example.com/timeout', {
|
||||
proxy: false,
|
||||
timeout: { invalid: true },
|
||||
transport: createTransport(),
|
||||
})
|
||||
.catch((e) => e);
|
||||
|
||||
expect(axios.isAxiosError(err)).toBe(true);
|
||||
expect(err.code).toBe('ERR_BAD_OPTION_VALUE');
|
||||
expect(err.message).toBe('error trying to parse `config.timeout` to int');
|
||||
});
|
||||
|
||||
it('does not time out when timeout is 0', async () => {
|
||||
const response = await axios.get('http://example.com/no-timeout', {
|
||||
proxy: false,
|
||||
timeout: 0,
|
||||
transport: createTransport({ body: '{"ok":true}' }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data).toEqual({ ok: true });
|
||||
});
|
||||
});
|
||||
146
tests/smoke/esm/tests/urlencode.smoke.test.js
Normal file
146
tests/smoke/esm/tests/urlencode.smoke.test.js
Normal file
@ -0,0 +1,146 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { PassThrough } from 'stream';
|
||||
import axios from 'axios';
|
||||
|
||||
const createEchoTransport = () => {
|
||||
let capturedOptions;
|
||||
|
||||
const transport = {
|
||||
request(options, onResponse) {
|
||||
capturedOptions = options;
|
||||
const chunks = [];
|
||||
|
||||
const req = new EventEmitter();
|
||||
req.destroyed = false;
|
||||
req.setTimeout = () => {};
|
||||
req.destroy = () => {
|
||||
req.destroyed = true;
|
||||
};
|
||||
req.close = req.destroy;
|
||||
req.write = (chunk) => {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
return true;
|
||||
};
|
||||
req.end = (chunk) => {
|
||||
if (chunk) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
}
|
||||
|
||||
const res = new PassThrough();
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.headers = { 'content-type': 'application/json' };
|
||||
res.req = req;
|
||||
onResponse(res);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
path: options.path,
|
||||
body: Buffer.concat(chunks).toString('utf8'),
|
||||
contentType:
|
||||
options.headers &&
|
||||
(options.headers['Content-Type'] || options.headers['content-type']),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return req;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
transport,
|
||||
getCapturedOptions: () => capturedOptions,
|
||||
};
|
||||
};
|
||||
|
||||
describe('urlencode compat (dist export only)', () => {
|
||||
it('serializes params into request URL', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
|
||||
const response = await axios.get('http://example.com/search', {
|
||||
proxy: false,
|
||||
transport,
|
||||
params: {
|
||||
q: 'axios docs',
|
||||
page: 2,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.data.path).toBe('/search?q=axios+docs&page=2');
|
||||
});
|
||||
|
||||
it('supports custom paramsSerializer function', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
|
||||
const response = await axios.get('http://example.com/search', {
|
||||
proxy: false,
|
||||
transport,
|
||||
params: { q: 'ignored' },
|
||||
paramsSerializer: () => 'fixed=1',
|
||||
});
|
||||
|
||||
expect(response.data.path).toBe('/search?fixed=1');
|
||||
});
|
||||
|
||||
it('supports URLSearchParams payloads', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
const payload = new URLSearchParams();
|
||||
payload.append('name', 'axios');
|
||||
payload.append('mode', 'compat');
|
||||
|
||||
const response = await axios.post('http://example.com/form', payload, {
|
||||
proxy: false,
|
||||
transport,
|
||||
});
|
||||
|
||||
expect(response.data.body).toBe('name=axios&mode=compat');
|
||||
expect(response.data.contentType).toContain('application/x-www-form-urlencoded');
|
||||
});
|
||||
|
||||
it('serializes object payload when content-type is application/x-www-form-urlencoded', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
|
||||
const response = await axios.post(
|
||||
'http://example.com/form',
|
||||
{
|
||||
name: 'axios',
|
||||
mode: 'compat',
|
||||
},
|
||||
{
|
||||
proxy: false,
|
||||
transport,
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.data.body).toBe('name=axios&mode=compat');
|
||||
expect(response.data.contentType).toContain('application/x-www-form-urlencoded');
|
||||
});
|
||||
|
||||
it('respects formSerializer options for index formatting', async () => {
|
||||
const { transport } = createEchoTransport();
|
||||
|
||||
const response = await axios.post(
|
||||
'http://example.com/form',
|
||||
{
|
||||
arr: ['1', '2'],
|
||||
},
|
||||
{
|
||||
proxy: false,
|
||||
transport,
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
formSerializer: {
|
||||
indexes: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(response.data.body).toBe('arr%5B0%5D=1&arr%5B1%5D=2');
|
||||
});
|
||||
});
|
||||
17
tests/smoke/esm/vitest.config.js
Normal file
17
tests/smoke/esm/vitest.config.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
testTimeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
test: {
|
||||
name: 'smoke',
|
||||
environment: 'node',
|
||||
include: ['tests/**/*.smoke.test.js'],
|
||||
setupFiles: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
@ -1,9 +1,8 @@
|
||||
import { afterEach, describe, it, vi } from 'vitest';
|
||||
import { describe, it, vi } from 'vitest';
|
||||
import assert from 'assert';
|
||||
import {
|
||||
startHTTPServer,
|
||||
stopHTTPServer,
|
||||
LOCAL_SERVER_URL,
|
||||
setTimeoutAsync,
|
||||
makeReadableStream,
|
||||
generateReadable,
|
||||
@ -15,6 +14,9 @@ import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill.js';
|
||||
import util from 'util';
|
||||
import NodeFormData from 'form-data';
|
||||
|
||||
const SERVER_PORT = 8010;
|
||||
const LOCAL_SERVER_URL = `http://localhost:${SERVER_PORT}`;
|
||||
|
||||
const pipelineAsync = util.promisify(stream.pipeline);
|
||||
|
||||
const fetchAxios = axios.create({
|
||||
@ -22,69 +24,85 @@ const fetchAxios = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
let server;
|
||||
|
||||
describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () => {
|
||||
afterEach(async () => {
|
||||
await stopHTTPServer(server);
|
||||
|
||||
server = null;
|
||||
});
|
||||
|
||||
describe('responses', () => {
|
||||
it('should support text response type', async () => {
|
||||
const originalData = 'my data';
|
||||
|
||||
server = await startHTTPServer((req, res) => res.end(originalData));
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
responseType: 'text',
|
||||
const server = await startHTTPServer((req, res) => res.end(originalData), {
|
||||
port: SERVER_PORT,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(data, originalData);
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'text',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(data, originalData);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should support arraybuffer response type', async () => {
|
||||
const originalData = 'my data';
|
||||
|
||||
server = await startHTTPServer((req, res) => res.end(originalData));
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
responseType: 'arraybuffer',
|
||||
const server = await startHTTPServer((req, res) => res.end(originalData), {
|
||||
port: SERVER_PORT,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(
|
||||
data,
|
||||
Uint8Array.from(await new TextEncoder().encode(originalData)).buffer
|
||||
);
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(
|
||||
data,
|
||||
Uint8Array.from(await new TextEncoder().encode(originalData)).buffer
|
||||
);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should support blob response type', async () => {
|
||||
const originalData = 'my data';
|
||||
|
||||
server = await startHTTPServer((req, res) => res.end(originalData));
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
responseType: 'blob',
|
||||
const server = await startHTTPServer((req, res) => res.end(originalData), {
|
||||
port: SERVER_PORT,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(data, new Blob([originalData]));
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(data, new Blob([originalData]));
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should support stream response type', async () => {
|
||||
const originalData = 'my data';
|
||||
|
||||
server = await startHTTPServer((req, res) => res.end(originalData));
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
responseType: 'stream',
|
||||
const server = await startHTTPServer((req, res) => res.end(originalData), {
|
||||
port: SERVER_PORT,
|
||||
});
|
||||
|
||||
assert.ok(data instanceof ReadableStream, 'data is not instanceof ReadableStream');
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'stream',
|
||||
});
|
||||
|
||||
const response = new Response(data);
|
||||
assert.ok(data instanceof ReadableStream, 'data is not instanceof ReadableStream');
|
||||
|
||||
assert.deepStrictEqual(await response.text(), originalData);
|
||||
const response = new Response(data);
|
||||
|
||||
assert.deepStrictEqual(await response.text(), originalData);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should support formData response type', async () => {
|
||||
@ -92,295 +110,382 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
|
||||
originalData.append('x', '123');
|
||||
|
||||
server = await startHTTPServer(async (req, res) => {
|
||||
const response = await new Response(originalData);
|
||||
const server = await startHTTPServer(
|
||||
async (req, res) => {
|
||||
const response = await new Response(originalData);
|
||||
|
||||
res.setHeader('Content-Type', response.headers.get('Content-Type'));
|
||||
res.setHeader('Content-Type', response.headers.get('Content-Type'));
|
||||
|
||||
res.end(await response.text());
|
||||
});
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
responseType: 'formdata',
|
||||
});
|
||||
|
||||
assert.ok(data instanceof FormData, 'data is not instanceof FormData');
|
||||
|
||||
assert.deepStrictEqual(
|
||||
Object.fromEntries(data.entries()),
|
||||
Object.fromEntries(originalData.entries())
|
||||
res.end(await response.text());
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'formdata',
|
||||
});
|
||||
|
||||
assert.ok(data instanceof FormData, 'data is not instanceof FormData');
|
||||
|
||||
assert.deepStrictEqual(
|
||||
Object.fromEntries(data.entries()),
|
||||
Object.fromEntries(originalData.entries())
|
||||
);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
it('should support json response type', async () => {
|
||||
const originalData = { x: 'my data' };
|
||||
|
||||
server = await startHTTPServer((req, res) => res.end(JSON.stringify(originalData)));
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
responseType: 'json',
|
||||
const server = await startHTTPServer((req, res) => res.end(JSON.stringify(originalData)), {
|
||||
port: SERVER_PORT,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(data, originalData);
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(data, originalData);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('progress', () => {
|
||||
describe('upload', () => {
|
||||
it('should support upload progress capturing', async () => {
|
||||
server = await startHTTPServer({
|
||||
rate: 100 * 1024,
|
||||
});
|
||||
|
||||
let content = '';
|
||||
const count = 10;
|
||||
const chunk = 'test';
|
||||
const chunkLength = Buffer.byteLength(chunk);
|
||||
const contentLength = count * chunkLength;
|
||||
|
||||
const readable = stream.Readable.from(
|
||||
(async function* () {
|
||||
let i = count;
|
||||
|
||||
while (i-- > 0) {
|
||||
await setTimeoutAsync(1100);
|
||||
content += chunk;
|
||||
yield chunk;
|
||||
}
|
||||
})()
|
||||
const server = await startHTTPServer(
|
||||
{
|
||||
rate: 100 * 1024,
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const samples = [];
|
||||
try {
|
||||
let content = '';
|
||||
const count = 10;
|
||||
const chunk = 'test';
|
||||
const chunkLength = Buffer.byteLength(chunk);
|
||||
const contentLength = count * chunkLength;
|
||||
|
||||
const { data } = await fetchAxios.post('/', readable, {
|
||||
onUploadProgress: ({ loaded, total, progress, bytes, upload }) => {
|
||||
console.log(
|
||||
`Upload Progress ${loaded} from ${total} bytes (${(progress * 100).toFixed(1)}%)`
|
||||
);
|
||||
const readable = stream.Readable.from(
|
||||
(async function* () {
|
||||
let i = count;
|
||||
|
||||
samples.push({
|
||||
loaded,
|
||||
total,
|
||||
progress,
|
||||
bytes,
|
||||
upload,
|
||||
});
|
||||
},
|
||||
headers: {
|
||||
'Content-Length': contentLength,
|
||||
},
|
||||
responseType: 'text',
|
||||
});
|
||||
|
||||
await setTimeoutAsync(500);
|
||||
|
||||
assert.strictEqual(data, content);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
samples,
|
||||
Array.from(
|
||||
(function* () {
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
yield {
|
||||
loaded: chunkLength * i,
|
||||
total: contentLength,
|
||||
progress: (chunkLength * i) / contentLength,
|
||||
bytes: 4,
|
||||
upload: true,
|
||||
};
|
||||
while (i-- > 0) {
|
||||
await setTimeoutAsync(1100);
|
||||
content += chunk;
|
||||
yield chunk;
|
||||
}
|
||||
})()
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
const samples = [];
|
||||
|
||||
const { data } = await fetchAxios.post(
|
||||
`http://localhost:${server.address().port}/`,
|
||||
readable,
|
||||
{
|
||||
onUploadProgress: ({ loaded, total, progress, bytes, upload }) => {
|
||||
console.log(
|
||||
`Upload Progress ${loaded} from ${total} bytes (${(progress * 100).toFixed(1)}%)`
|
||||
);
|
||||
|
||||
samples.push({
|
||||
loaded,
|
||||
total,
|
||||
progress,
|
||||
bytes,
|
||||
upload,
|
||||
});
|
||||
},
|
||||
headers: {
|
||||
'Content-Length': contentLength,
|
||||
},
|
||||
responseType: 'text',
|
||||
}
|
||||
);
|
||||
|
||||
await setTimeoutAsync(500);
|
||||
|
||||
assert.strictEqual(data, content);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
samples,
|
||||
Array.from(
|
||||
(function* () {
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
yield {
|
||||
loaded: chunkLength * i,
|
||||
total: contentLength,
|
||||
progress: (chunkLength * i) / contentLength,
|
||||
bytes: 4,
|
||||
upload: true,
|
||||
};
|
||||
}
|
||||
})()
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
}, 15000);
|
||||
|
||||
it('should not fail with get method', async () => {
|
||||
server = await startHTTPServer((req, res) => res.end('OK'));
|
||||
const server = await startHTTPServer((req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
onUploadProgress() {},
|
||||
});
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
onUploadProgress() {},
|
||||
});
|
||||
|
||||
assert.strictEqual(data, 'OK');
|
||||
assert.strictEqual(data, 'OK');
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('download', () => {
|
||||
it('should support download progress capturing', async () => {
|
||||
server = await startHTTPServer({
|
||||
rate: 100 * 1024,
|
||||
});
|
||||
|
||||
let content = '';
|
||||
const count = 10;
|
||||
const chunk = 'test';
|
||||
const chunkLength = Buffer.byteLength(chunk);
|
||||
const contentLength = count * chunkLength;
|
||||
|
||||
const readable = stream.Readable.from(
|
||||
(async function* () {
|
||||
let i = count;
|
||||
|
||||
while (i-- > 0) {
|
||||
await setTimeoutAsync(1100);
|
||||
content += chunk;
|
||||
yield chunk;
|
||||
}
|
||||
})()
|
||||
const server = await startHTTPServer(
|
||||
{
|
||||
rate: 100 * 1024,
|
||||
},
|
||||
{
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
const samples = [];
|
||||
try {
|
||||
let content = '';
|
||||
const count = 10;
|
||||
const chunk = 'test';
|
||||
const chunkLength = Buffer.byteLength(chunk);
|
||||
const contentLength = count * chunkLength;
|
||||
|
||||
const { data } = await fetchAxios.post('/', readable, {
|
||||
onDownloadProgress: ({ loaded, total, progress, bytes, download }) => {
|
||||
console.log(
|
||||
`Download Progress ${loaded} from ${total} bytes (${(progress * 100).toFixed(1)}%)`
|
||||
);
|
||||
const readable = stream.Readable.from(
|
||||
(async function* () {
|
||||
let i = count;
|
||||
|
||||
samples.push({
|
||||
loaded,
|
||||
total,
|
||||
progress,
|
||||
bytes,
|
||||
download,
|
||||
});
|
||||
},
|
||||
headers: {
|
||||
'Content-Length': contentLength,
|
||||
},
|
||||
responseType: 'text',
|
||||
maxRedirects: 0,
|
||||
});
|
||||
|
||||
await setTimeoutAsync(500);
|
||||
|
||||
assert.strictEqual(data, content);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
samples,
|
||||
Array.from(
|
||||
(function* () {
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
yield {
|
||||
loaded: chunkLength * i,
|
||||
total: contentLength,
|
||||
progress: (chunkLength * i) / contentLength,
|
||||
bytes: 4,
|
||||
download: true,
|
||||
};
|
||||
while (i-- > 0) {
|
||||
await setTimeoutAsync(1100);
|
||||
content += chunk;
|
||||
yield chunk;
|
||||
}
|
||||
})()
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
const samples = [];
|
||||
|
||||
const { data } = await fetchAxios.post(
|
||||
`http://localhost:${server.address().port}/`,
|
||||
readable,
|
||||
{
|
||||
onDownloadProgress: ({ loaded, total, progress, bytes, download }) => {
|
||||
console.log(
|
||||
`Download Progress ${loaded} from ${total} bytes (${(progress * 100).toFixed(1)}%)`
|
||||
);
|
||||
|
||||
samples.push({
|
||||
loaded,
|
||||
total,
|
||||
progress,
|
||||
bytes,
|
||||
download,
|
||||
});
|
||||
},
|
||||
headers: {
|
||||
'Content-Length': contentLength,
|
||||
},
|
||||
responseType: 'text',
|
||||
maxRedirects: 0,
|
||||
}
|
||||
);
|
||||
|
||||
await setTimeoutAsync(500);
|
||||
|
||||
assert.strictEqual(data, content);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
samples,
|
||||
Array.from(
|
||||
(function* () {
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
yield {
|
||||
loaded: chunkLength * i,
|
||||
total: contentLength,
|
||||
progress: (chunkLength * i) / contentLength,
|
||||
bytes: 4,
|
||||
download: true,
|
||||
};
|
||||
}
|
||||
})()
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
}, 15000);
|
||||
});
|
||||
});
|
||||
|
||||
it('should support basic auth', async () => {
|
||||
server = await startHTTPServer((req, res) => res.end(req.headers.authorization));
|
||||
const server = await startHTTPServer((req, res) => res.end(req.headers.authorization), {
|
||||
port: SERVER_PORT,
|
||||
});
|
||||
|
||||
const user = 'foo';
|
||||
const headers = { Authorization: 'Bearer 1234' };
|
||||
const res = await axios.get(`http://${user}@localhost:4444/`, { headers });
|
||||
try {
|
||||
const user = 'foo';
|
||||
const headers = { Authorization: 'Bearer 1234' };
|
||||
const res = await axios.get(`http://${user}@localhost:${server.address().port}/`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
const base64 = Buffer.from(`${user}:`, 'utf8').toString('base64');
|
||||
assert.equal(res.data, `Basic ${base64}`);
|
||||
const base64 = Buffer.from(`${user}:`, 'utf8').toString('base64');
|
||||
assert.equal(res.data, `Basic ${base64}`);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should support stream.Readable as a payload', async () => {
|
||||
server = await startHTTPServer();
|
||||
const server = await startHTTPServer(async (req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
|
||||
const { data } = await fetchAxios.post('/', stream.Readable.from('OK'));
|
||||
try {
|
||||
const { data } = await fetchAxios.post(
|
||||
`http://localhost:${server.address().port}/`,
|
||||
stream.Readable.from('OK')
|
||||
);
|
||||
|
||||
assert.strictEqual(data, 'OK');
|
||||
assert.strictEqual(data, 'OK');
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
describe('request aborting', () => {
|
||||
it('should be able to abort the request stream', async () => {
|
||||
server = await startHTTPServer({
|
||||
rate: 100000,
|
||||
useBuffering: true,
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
{
|
||||
rate: 100000,
|
||||
useBuffering: true,
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const controller = new AbortController();
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
|
||||
setTimeout(() => {
|
||||
controller.abort();
|
||||
}, 500);
|
||||
setTimeout(() => {
|
||||
controller.abort();
|
||||
}, 500);
|
||||
|
||||
await assert.rejects(async () => {
|
||||
await fetchAxios.post('/', makeReadableStream(), {
|
||||
responseType: 'stream',
|
||||
signal: controller.signal,
|
||||
});
|
||||
}, /CanceledError/);
|
||||
await assert.rejects(async () => {
|
||||
await fetchAxios.post(
|
||||
`http://localhost:${server.address().port}/`,
|
||||
makeReadableStream(),
|
||||
{
|
||||
responseType: 'stream',
|
||||
signal: controller.signal,
|
||||
}
|
||||
);
|
||||
}, /CanceledError/);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should be able to abort the response stream', async () => {
|
||||
server = await startHTTPServer((req, res) => {
|
||||
pipelineAsync(generateReadable(10000, 10), res).catch(() => {
|
||||
// Client-side abort intentionally closes the stream early in this test.
|
||||
const server = await startHTTPServer(
|
||||
(req, res) => {
|
||||
pipelineAsync(generateReadable(10000, 10), res).catch(() => {
|
||||
// Client-side abort intentionally closes the stream early in this test.
|
||||
});
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
|
||||
setTimeout(() => {
|
||||
controller.abort(new Error('test'));
|
||||
}, 800);
|
||||
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'stream',
|
||||
signal: controller.signal,
|
||||
});
|
||||
});
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
setTimeout(() => {
|
||||
controller.abort(new Error('test'));
|
||||
}, 800);
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
responseType: 'stream',
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
await assert.rejects(async () => {
|
||||
await data.pipeTo(makeEchoStream());
|
||||
}, /^(AbortError|CanceledError):/);
|
||||
await assert.rejects(async () => {
|
||||
await data.pipeTo(makeEchoStream());
|
||||
}, /^(AbortError|CanceledError):/);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should support a timeout', async () => {
|
||||
server = await startHTTPServer(async (req, res) => {
|
||||
await setTimeoutAsync(1000);
|
||||
res.end('OK');
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
async (req, res) => {
|
||||
await setTimeoutAsync(1000);
|
||||
res.end('OK');
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const timeout = 500;
|
||||
try {
|
||||
const timeout = 500;
|
||||
|
||||
const ts = Date.now();
|
||||
const ts = Date.now();
|
||||
|
||||
await assert.rejects(async () => {
|
||||
await fetchAxios('/', {
|
||||
timeout,
|
||||
});
|
||||
}, /timeout/);
|
||||
await assert.rejects(async () => {
|
||||
await fetchAxios(`http://localhost:${server.address().port}/`, {
|
||||
timeout,
|
||||
});
|
||||
}, /timeout/);
|
||||
|
||||
const passed = Date.now() - ts;
|
||||
const passed = Date.now() - ts;
|
||||
|
||||
assert.ok(passed >= timeout - 5, `early cancellation detected (${passed} ms)`);
|
||||
assert.ok(passed >= timeout - 5, `early cancellation detected (${passed} ms)`);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should combine baseURL and url', async () => {
|
||||
server = await startHTTPServer();
|
||||
const server = await startHTTPServer(async (req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
try {
|
||||
const res = await fetchAxios('/foo');
|
||||
|
||||
const res = await fetchAxios('/foo');
|
||||
|
||||
assert.equal(res.config.baseURL, LOCAL_SERVER_URL);
|
||||
assert.equal(res.config.url, '/foo');
|
||||
assert.equal(res.config.baseURL, LOCAL_SERVER_URL);
|
||||
assert.equal(res.config.url, '/foo');
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should support params', async () => {
|
||||
server = await startHTTPServer((req, res) => res.end(req.url));
|
||||
const server = await startHTTPServer((req, res) => res.end(req.url), { port: SERVER_PORT });
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/?test=1`, {
|
||||
params: {
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
},
|
||||
});
|
||||
|
||||
const { data } = await fetchAxios.get('/?test=1', {
|
||||
params: {
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(data, '/?test=1&foo=1&bar=2');
|
||||
assert.strictEqual(data, '/?test=1&foo=1&bar=2');
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle fetch failed error as an AxiosError with ERR_NETWORK code', async () => {
|
||||
@ -394,16 +499,23 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
});
|
||||
|
||||
it('should get response headers', async () => {
|
||||
server = await startHTTPServer((req, res) => {
|
||||
res.setHeader('foo', 'bar');
|
||||
res.end(req.url);
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
(req, res) => {
|
||||
res.setHeader('foo', 'bar');
|
||||
res.end(req.url);
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const { headers } = await fetchAxios.get('/', {
|
||||
responseType: 'stream',
|
||||
});
|
||||
try {
|
||||
const { headers } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'stream',
|
||||
});
|
||||
|
||||
assert.strictEqual(headers.get('foo'), 'bar');
|
||||
assert.strictEqual(headers.get('foo'), 'bar');
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
describe('fetch adapter - Content-Type handling', () => {
|
||||
@ -411,13 +523,20 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
const form = new NodeFormData();
|
||||
form.append('foo', 'bar');
|
||||
|
||||
server = await startHTTPServer((req, res) => {
|
||||
const contentType = req.headers['content-type'];
|
||||
assert.match(contentType, /^multipart\/form-data; boundary=/i);
|
||||
res.end('OK');
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
(req, res) => {
|
||||
const contentType = req.headers['content-type'];
|
||||
assert.match(contentType, /^multipart\/form-data; boundary=/i);
|
||||
res.end('OK');
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
await fetchAxios.post('/form', form);
|
||||
try {
|
||||
await fetchAxios.post(`http://localhost:${server.address().port}/form`, form);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -490,15 +609,19 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
});
|
||||
|
||||
it('should fallback to the global on undefined env value', async () => {
|
||||
server = await startHTTPServer((req, res) => res.end('OK'));
|
||||
const server = await startHTTPServer((req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
env: {
|
||||
fetch: undefined,
|
||||
},
|
||||
});
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
env: {
|
||||
fetch: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(data, 'OK');
|
||||
assert.strictEqual(data, 'OK');
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
|
||||
it('should use current global fetch when env fetch is not specified', async () => {
|
||||
@ -513,10 +636,10 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
server = await startHTTPServer((req, res) => res.end('OK'));
|
||||
const server = await startHTTPServer((req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
|
||||
const { data } = await fetchAxios.get('/', {
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
env: {
|
||||
fetch: undefined,
|
||||
},
|
||||
@ -525,6 +648,7 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
assert.strictEqual(data, 'global');
|
||||
} finally {
|
||||
vi.stubGlobal('fetch', globalFetch);
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -31,6 +31,11 @@ import bodyParser from 'body-parser';
|
||||
import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill.js';
|
||||
import { lookup } from 'dns';
|
||||
|
||||
const OPEN_WEB_PORT = 80;
|
||||
const SERVER_PORT = 8020;
|
||||
const PROXY_PORT = 8030;
|
||||
const ALTERNATE_SERVER_PORT = 8040;
|
||||
|
||||
describe('supports http with nodejs', () => {
|
||||
const adaptersTestsDir = path.join(process.cwd(), 'tests/unit/adapters');
|
||||
const thisTestFilePath = path.join(adaptersTestsDir, 'http.test.js');
|
||||
@ -59,7 +64,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(data));
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -82,7 +87,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(data));
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -102,7 +107,7 @@ describe('supports http with nodejs', () => {
|
||||
res.end();
|
||||
}, 1000);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -128,7 +133,7 @@ describe('supports http with nodejs', () => {
|
||||
res.end();
|
||||
}, 1000);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -154,7 +159,7 @@ describe('supports http with nodejs', () => {
|
||||
res.end();
|
||||
}, 1000);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -180,7 +185,7 @@ describe('supports http with nodejs', () => {
|
||||
res.end();
|
||||
}, 1000);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -212,7 +217,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(data));
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -237,7 +242,7 @@ describe('supports http with nodejs', () => {
|
||||
const jsonBuffer = Buffer.from(JSON.stringify(data));
|
||||
res.end(Buffer.concat([bomBuffer, jsonBuffer]));
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -261,7 +266,7 @@ describe('supports http with nodejs', () => {
|
||||
|
||||
res.end(expectedResponse);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -283,7 +288,7 @@ describe('supports http with nodejs', () => {
|
||||
res.statusCode = 302;
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -311,7 +316,7 @@ describe('supports http with nodejs', () => {
|
||||
res.end();
|
||||
i++;
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -333,7 +338,7 @@ describe('supports http with nodejs', () => {
|
||||
res.statusCode = 302;
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -362,12 +367,12 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
requestCount += 1;
|
||||
if (requestCount <= totalRedirectCount) {
|
||||
res.setHeader('Location', 'http://localhost:8080');
|
||||
res.setHeader('Location', `http://localhost:${SERVER_PORT}`);
|
||||
res.writeHead(302);
|
||||
}
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const proxy = await startHTTPServer(
|
||||
@ -392,13 +397,13 @@ describe('supports http with nodejs', () => {
|
||||
res.end();
|
||||
});
|
||||
},
|
||||
{ port: 4000 }
|
||||
{ port: PROXY_PORT }
|
||||
);
|
||||
|
||||
await axios.get(`http://localhost:${server.address().port}/`, {
|
||||
proxy: {
|
||||
host: 'localhost',
|
||||
port: 4000,
|
||||
port: PROXY_PORT,
|
||||
},
|
||||
maxRedirects: totalRedirectCount,
|
||||
beforeRedirect: (options) => {
|
||||
@ -419,7 +424,7 @@ describe('supports http with nodejs', () => {
|
||||
res.statusCode = 400;
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -455,7 +460,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -496,7 +501,7 @@ describe('supports http with nodejs', () => {
|
||||
res.end();
|
||||
}
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -532,7 +537,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
res.end(zipped);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -552,7 +557,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
res.end('invalid response');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -584,7 +589,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
res.end(zipped);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -661,10 +666,13 @@ describe('supports http with nodejs', () => {
|
||||
|
||||
describe(`${typeName} decompression`, () => {
|
||||
it('should support decompression', async () => {
|
||||
const server = await startHTTPServer(async (req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.end(await zipped);
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
async (req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.end(await zipped);
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(`http://localhost:${server.address().port}`);
|
||||
@ -675,11 +683,14 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
|
||||
it(`should not fail if response content-length header is missing (${type})`, async () => {
|
||||
const server = await startHTTPServer(async (req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.removeHeader('Content-Length');
|
||||
res.end(await zipped);
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
async (req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.removeHeader('Content-Length');
|
||||
res.end(await zipped);
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(`http://localhost:${server.address().port}`);
|
||||
@ -690,13 +701,16 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
|
||||
it('should not fail with chunked responses (without Content-Length header)', async () => {
|
||||
const server = await startHTTPServer(async (req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.setHeader('Transfer-Encoding', 'chunked');
|
||||
res.removeHeader('Content-Length');
|
||||
res.write(await zipped);
|
||||
res.end();
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
async (req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.setHeader('Transfer-Encoding', 'chunked');
|
||||
res.removeHeader('Content-Length');
|
||||
res.write(await zipped);
|
||||
res.end();
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(`http://localhost:${server.address().port}`);
|
||||
@ -707,11 +721,14 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
|
||||
it('should not fail with an empty response without content-length header (Z_BUF_ERROR)', async () => {
|
||||
const server = await startHTTPServer((req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.removeHeader('Content-Length');
|
||||
res.end();
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
(req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.removeHeader('Content-Length');
|
||||
res.end();
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(`http://localhost:${server.address().port}`);
|
||||
@ -722,10 +739,13 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
|
||||
it('should not fail with an empty response with content-length header (Z_BUF_ERROR)', async () => {
|
||||
const server = await startHTTPServer((req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.end();
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
(req, res) => {
|
||||
res.setHeader('Content-Encoding', type);
|
||||
res.end();
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
await axios.get(`http://localhost:${server.address().port}`);
|
||||
@ -746,7 +766,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end(str);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -762,7 +782,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
res.end(req.headers.authorization);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -783,7 +803,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
res.end(req.headers.authorization);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -805,7 +825,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
res.end(req.headers['user-agent']);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -824,7 +844,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
res.end(req.headers['user-agent']);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -842,7 +862,7 @@ describe('supports http with nodejs', () => {
|
||||
assert.strictEqual(req.headers['content-length'], '42');
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -859,7 +879,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end(Array(5000).join('#'));
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -891,7 +911,7 @@ describe('supports http with nodejs', () => {
|
||||
res.statusCode = 302;
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -916,7 +936,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -953,7 +973,7 @@ describe('supports http with nodejs', () => {
|
||||
res.end('OK');
|
||||
});
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -967,7 +987,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
|
||||
it('should display error while parsing params', async () => {
|
||||
const server = await startHTTPServer(() => {}, { port: 8080 });
|
||||
const server = await startHTTPServer(() => {}, { port: SERVER_PORT });
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
@ -1044,7 +1064,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
req.pipe(res);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -1077,7 +1097,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
|
||||
it('should pass errors for a failed stream', async () => {
|
||||
const server = await startHTTPServer(() => {}, { port: 8080 });
|
||||
const server = await startHTTPServer(() => {}, { port: SERVER_PORT });
|
||||
const notExistPath = path.join(adaptersTestsDir, 'does_not_exist');
|
||||
|
||||
try {
|
||||
@ -1148,7 +1168,7 @@ describe('supports http with nodejs', () => {
|
||||
assert.strictEqual(req.headers['content-length'], buf.length.toString());
|
||||
req.pipe(res);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -1182,7 +1202,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end('12345');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const proxy = await startHTTPServer(
|
||||
@ -1207,7 +1227,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
});
|
||||
},
|
||||
{ port: 0 }
|
||||
{ port: PROXY_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -1251,9 +1271,9 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end('12345');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
)
|
||||
.listen(8080, () => resolve(httpsServer));
|
||||
.listen(SERVER_PORT, () => resolve(httpsServer));
|
||||
|
||||
httpsServer.on('error', reject);
|
||||
});
|
||||
@ -1290,9 +1310,9 @@ describe('supports http with nodejs', () => {
|
||||
response.end();
|
||||
});
|
||||
},
|
||||
{ port: 8081 }
|
||||
{ port: PROXY_PORT }
|
||||
)
|
||||
.listen(8081, () => resolve(httpsProxy));
|
||||
.listen(PROXY_PORT, () => resolve(httpsProxy));
|
||||
|
||||
httpsProxy.on('error', reject);
|
||||
});
|
||||
@ -1324,7 +1344,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end('123456789');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -1355,7 +1375,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end('4567');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const proxy = await startHTTPServer(
|
||||
@ -1380,7 +1400,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
});
|
||||
},
|
||||
{ port: 8081 }
|
||||
{ port: PROXY_PORT }
|
||||
);
|
||||
|
||||
const proxyUrl = `http://localhost:${proxy.address().port}/`;
|
||||
@ -1458,9 +1478,9 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end('12345');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
)
|
||||
.listen(8080, () => resolve(httpsServer));
|
||||
.listen(SERVER_PORT, () => resolve(httpsServer));
|
||||
|
||||
httpsServer.on('error', reject);
|
||||
});
|
||||
@ -1497,9 +1517,9 @@ describe('supports http with nodejs', () => {
|
||||
response.end();
|
||||
});
|
||||
},
|
||||
{ port: 8081 }
|
||||
{ port: PROXY_PORT }
|
||||
)
|
||||
.listen(8081, () => resolve(httpsProxy));
|
||||
.listen(PROXY_PORT, () => resolve(httpsProxy));
|
||||
|
||||
httpsProxy.on('error', reject);
|
||||
});
|
||||
@ -1561,7 +1581,7 @@ describe('supports http with nodejs', () => {
|
||||
res.statusCode = 302;
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const proxy = await startHTTPServer(
|
||||
@ -1597,7 +1617,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
});
|
||||
},
|
||||
{ port: 8081 }
|
||||
{ port: PROXY_PORT }
|
||||
);
|
||||
|
||||
const proxyUrl = `http://localhost:${proxy.address().port}`;
|
||||
@ -1651,7 +1671,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end('4567');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const proxy = await startHTTPServer(
|
||||
@ -1676,7 +1696,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
});
|
||||
},
|
||||
{ port: 8081 }
|
||||
{ port: PROXY_PORT }
|
||||
);
|
||||
|
||||
const noProxyValue = 'foo.com, localhost,bar.net , , quix.co';
|
||||
@ -1730,7 +1750,7 @@ describe('supports http with nodejs', () => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end('4567');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const proxy = await startHTTPServer(
|
||||
@ -1755,7 +1775,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
});
|
||||
},
|
||||
{ port: 8081 }
|
||||
{ port: PROXY_PORT }
|
||||
);
|
||||
|
||||
const noProxyValue = 'foo.com, ,bar.net , quix.co';
|
||||
@ -1803,7 +1823,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const proxy = await startHTTPServer(
|
||||
@ -1825,7 +1845,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
});
|
||||
},
|
||||
{ port: 8081 }
|
||||
{ port: PROXY_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -1858,7 +1878,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
const proxy = await startHTTPServer(
|
||||
@ -1880,7 +1900,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
});
|
||||
},
|
||||
{ port: 8081 }
|
||||
{ port: PROXY_PORT }
|
||||
);
|
||||
|
||||
const proxyUrl = `http://user:pass@localhost:${proxy.address().port}/`;
|
||||
@ -1932,7 +1952,7 @@ describe('supports http with nodejs', () => {
|
||||
const proxy = {
|
||||
protocol: 'http:',
|
||||
host: 'hostname.abc.xyz',
|
||||
port: 3300,
|
||||
port: PROXY_PORT,
|
||||
auth: {
|
||||
username: '',
|
||||
password: '',
|
||||
@ -1954,23 +1974,48 @@ describe('supports http with nodejs', () => {
|
||||
const testCases = [
|
||||
{
|
||||
description: 'hostname and trailing colon in protocol',
|
||||
proxyConfig: { hostname: '127.0.0.1', protocol: 'http:', port: 80 },
|
||||
expectedOptions: { host: '127.0.0.1', protocol: 'http:', port: 80, path: destination },
|
||||
proxyConfig: { hostname: '127.0.0.1', protocol: 'http:', port: OPEN_WEB_PORT },
|
||||
expectedOptions: {
|
||||
host: '127.0.0.1',
|
||||
protocol: 'http:',
|
||||
port: OPEN_WEB_PORT,
|
||||
path: destination,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'hostname and no trailing colon in protocol',
|
||||
proxyConfig: { hostname: '127.0.0.1', protocol: 'http', port: 80 },
|
||||
expectedOptions: { host: '127.0.0.1', protocol: 'http:', port: 80, path: destination },
|
||||
proxyConfig: { hostname: '127.0.0.1', protocol: 'http', port: OPEN_WEB_PORT },
|
||||
expectedOptions: {
|
||||
host: '127.0.0.1',
|
||||
protocol: 'http:',
|
||||
port: OPEN_WEB_PORT,
|
||||
path: destination,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'both hostname and host -> hostname takes precedence',
|
||||
proxyConfig: { hostname: '127.0.0.1', host: '0.0.0.0', protocol: 'http', port: 80 },
|
||||
expectedOptions: { host: '127.0.0.1', protocol: 'http:', port: 80, path: destination },
|
||||
proxyConfig: {
|
||||
hostname: '127.0.0.1',
|
||||
host: '0.0.0.0',
|
||||
protocol: 'http',
|
||||
port: OPEN_WEB_PORT,
|
||||
},
|
||||
expectedOptions: {
|
||||
host: '127.0.0.1',
|
||||
protocol: 'http:',
|
||||
port: OPEN_WEB_PORT,
|
||||
path: destination,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'only host and https protocol',
|
||||
proxyConfig: { host: '0.0.0.0', protocol: 'https', port: 80 },
|
||||
expectedOptions: { host: '0.0.0.0', protocol: 'https:', port: 80, path: destination },
|
||||
proxyConfig: { host: '0.0.0.0', protocol: 'https', port: OPEN_WEB_PORT },
|
||||
expectedOptions: {
|
||||
host: '0.0.0.0',
|
||||
protocol: 'https:',
|
||||
port: OPEN_WEB_PORT,
|
||||
path: destination,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -1994,7 +2039,7 @@ describe('supports http with nodejs', () => {
|
||||
// Call cancel() when the request has been sent but no response received.
|
||||
source.cancel('Operation has been canceled.');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2024,7 +2069,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2046,7 +2091,7 @@ describe('supports http with nodejs', () => {
|
||||
res.end();
|
||||
}, 1000);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2072,9 +2117,9 @@ describe('supports http with nodejs', () => {
|
||||
res.end();
|
||||
}, 1000);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
)
|
||||
.listen(8080, () => resolve(httpsServer));
|
||||
.listen(SERVER_PORT, () => resolve(httpsServer));
|
||||
|
||||
httpsServer.on('error', reject);
|
||||
});
|
||||
@ -2120,7 +2165,7 @@ describe('supports http with nodejs', () => {
|
||||
assert.equal(req.headers['user-agent'], `axios/${axios.VERSION}`);
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2137,7 +2182,7 @@ describe('supports http with nodejs', () => {
|
||||
assert.equal('User-Agent' in req.headers, false);
|
||||
res.end();
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2165,7 +2210,7 @@ describe('supports http with nodejs', () => {
|
||||
res.destroy();
|
||||
}, 200);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2190,7 +2235,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
res.end('ok');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2259,7 +2304,7 @@ describe('supports http with nodejs', () => {
|
||||
);
|
||||
});
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2297,7 +2342,7 @@ describe('supports http with nodejs', () => {
|
||||
})
|
||||
);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2355,7 +2400,7 @@ describe('supports http with nodejs', () => {
|
||||
const expressServer = app.listen(0, () => resolve(expressServer));
|
||||
expressServer.on('error', reject);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2391,7 +2436,7 @@ describe('supports http with nodejs', () => {
|
||||
async (req, res) => {
|
||||
res.end(await getStream(req));
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2433,7 +2478,7 @@ describe('supports http with nodejs', () => {
|
||||
const expressServer = app.listen(0, () => resolve(expressServer));
|
||||
expressServer.on('error', reject);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2475,7 +2520,7 @@ describe('supports http with nodejs', () => {
|
||||
(req, res) => {
|
||||
req.pipe(res);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2541,7 +2586,7 @@ describe('supports http with nodejs', () => {
|
||||
{
|
||||
rate: 100 * 1024,
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2610,7 +2655,7 @@ describe('supports http with nodejs', () => {
|
||||
{
|
||||
rate: 100 * 1024,
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -2783,10 +2828,13 @@ describe('supports http with nodejs', () => {
|
||||
|
||||
describe('request aborting', () => {
|
||||
it('should be able to abort the response stream', async () => {
|
||||
const server = await startHTTPServer({
|
||||
rate: 100000,
|
||||
useBuffering: true,
|
||||
});
|
||||
const server = await startHTTPServer(
|
||||
{
|
||||
rate: 100000,
|
||||
useBuffering: true,
|
||||
},
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
const buf = Buffer.alloc(1024 * 1024);
|
||||
@ -2832,7 +2880,7 @@ describe('supports http with nodejs', () => {
|
||||
});
|
||||
|
||||
it('should support function as paramsSerializer value', async () => {
|
||||
const server = await startHTTPServer((req, res) => res.end(req.url));
|
||||
const server = await startHTTPServer((req, res) => res.end(req.url), { port: SERVER_PORT });
|
||||
|
||||
try {
|
||||
const { data } = await axios.post(`http://localhost:${server.address().port}`, 'test', {
|
||||
@ -2989,7 +3037,7 @@ describe('supports http with nodejs', () => {
|
||||
})
|
||||
);
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
@ -3043,7 +3091,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3060,7 +3108,7 @@ describe('supports http with nodejs', () => {
|
||||
it('should support request payload', async () => {
|
||||
const server = await startHTTPServer(null, {
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
});
|
||||
|
||||
try {
|
||||
@ -3092,7 +3140,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3134,7 +3182,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3165,7 +3213,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3210,7 +3258,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3251,7 +3299,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3302,7 +3350,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3339,7 +3387,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3351,7 +3399,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8081,
|
||||
port: ALTERNATE_SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3389,7 +3437,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3428,7 +3476,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3470,7 +3518,7 @@ describe('supports http with nodejs', () => {
|
||||
},
|
||||
{
|
||||
useHTTP2: true,
|
||||
port: 8080,
|
||||
port: SERVER_PORT,
|
||||
}
|
||||
);
|
||||
|
||||
@ -3542,7 +3590,7 @@ describe('supports http with nodejs', () => {
|
||||
|
||||
res.end('ok');
|
||||
},
|
||||
{ port: 8080 }
|
||||
{ port: SERVER_PORT }
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
97
tests/unit/api.test.js
Normal file
97
tests/unit/api.test.js
Normal file
@ -0,0 +1,97 @@
|
||||
import { describe, it } from 'vitest';
|
||||
import assert from 'assert';
|
||||
import axios from '../../index.js';
|
||||
|
||||
describe('static api', () => {
|
||||
it('should have request method helpers', () => {
|
||||
assert.strictEqual(typeof axios.request, 'function');
|
||||
assert.strictEqual(typeof axios.get, 'function');
|
||||
assert.strictEqual(typeof axios.head, 'function');
|
||||
assert.strictEqual(typeof axios.options, 'function');
|
||||
assert.strictEqual(typeof axios.delete, 'function');
|
||||
assert.strictEqual(typeof axios.post, 'function');
|
||||
assert.strictEqual(typeof axios.put, 'function');
|
||||
assert.strictEqual(typeof axios.patch, 'function');
|
||||
});
|
||||
|
||||
it('should have promise method helpers', async () => {
|
||||
const promise = axios.request({
|
||||
url: '/test',
|
||||
adapter: (config) =>
|
||||
Promise.resolve({
|
||||
data: null,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
request: {},
|
||||
}),
|
||||
});
|
||||
|
||||
assert.strictEqual(typeof promise.then, 'function');
|
||||
assert.strictEqual(typeof promise.catch, 'function');
|
||||
|
||||
await promise;
|
||||
});
|
||||
|
||||
it('should have defaults', () => {
|
||||
assert.strictEqual(typeof axios.defaults, 'object');
|
||||
assert.strictEqual(typeof axios.defaults.headers, 'object');
|
||||
});
|
||||
|
||||
it('should have interceptors', () => {
|
||||
assert.strictEqual(typeof axios.interceptors.request, 'object');
|
||||
assert.strictEqual(typeof axios.interceptors.response, 'object');
|
||||
});
|
||||
|
||||
it('should have all/spread helpers', () => {
|
||||
assert.strictEqual(typeof axios.all, 'function');
|
||||
assert.strictEqual(typeof axios.spread, 'function');
|
||||
});
|
||||
|
||||
it('should have factory method', () => {
|
||||
assert.strictEqual(typeof axios.create, 'function');
|
||||
});
|
||||
|
||||
it('should have CanceledError, CancelToken, and isCancel properties', () => {
|
||||
assert.strictEqual(typeof axios.Cancel, 'function');
|
||||
assert.strictEqual(typeof axios.CancelToken, 'function');
|
||||
assert.strictEqual(typeof axios.isCancel, 'function');
|
||||
});
|
||||
|
||||
it('should have getUri method', () => {
|
||||
assert.strictEqual(typeof axios.getUri, 'function');
|
||||
});
|
||||
|
||||
it('should have isAxiosError properties', () => {
|
||||
assert.strictEqual(typeof axios.isAxiosError, 'function');
|
||||
});
|
||||
|
||||
it('should have mergeConfig properties', () => {
|
||||
assert.strictEqual(typeof axios.mergeConfig, 'function');
|
||||
});
|
||||
|
||||
it('should have getAdapter properties', () => {
|
||||
assert.strictEqual(typeof axios.getAdapter, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance api', () => {
|
||||
const instance = axios.create();
|
||||
|
||||
it('should have request methods', () => {
|
||||
assert.strictEqual(typeof instance.request, 'function');
|
||||
assert.strictEqual(typeof instance.get, 'function');
|
||||
assert.strictEqual(typeof instance.options, 'function');
|
||||
assert.strictEqual(typeof instance.head, 'function');
|
||||
assert.strictEqual(typeof instance.delete, 'function');
|
||||
assert.strictEqual(typeof instance.post, 'function');
|
||||
assert.strictEqual(typeof instance.put, 'function');
|
||||
assert.strictEqual(typeof instance.patch, 'function');
|
||||
});
|
||||
|
||||
it('should have interceptors', () => {
|
||||
assert.strictEqual(typeof instance.interceptors.request, 'object');
|
||||
assert.strictEqual(typeof instance.interceptors.response, 'object');
|
||||
});
|
||||
});
|
||||
23
tests/unit/cancel/canceledError.test.js
Normal file
23
tests/unit/cancel/canceledError.test.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { isNativeError } from 'node:util/types';
|
||||
import CanceledError from '../../../lib/cancel/CanceledError.js';
|
||||
|
||||
describe('cancel::CanceledError', () => {
|
||||
describe('toString', () => {
|
||||
it('returns the default message when message is not specified', () => {
|
||||
const cancel = new CanceledError();
|
||||
|
||||
expect(cancel.toString()).toBe('CanceledError: canceled');
|
||||
});
|
||||
|
||||
it('returns the provided message when message is specified', () => {
|
||||
const cancel = new CanceledError('Operation has been canceled.');
|
||||
|
||||
expect(cancel.toString()).toBe('CanceledError: Operation has been canceled.');
|
||||
});
|
||||
});
|
||||
|
||||
it('is recognized as a native error by Node util/types', () => {
|
||||
expect(isNativeError(new CanceledError('My Canceled Error'))).toBe(true);
|
||||
});
|
||||
});
|
||||
13
tests/unit/cancel/isCancel.test.js
Normal file
13
tests/unit/cancel/isCancel.test.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import isCancel from '../../../lib/cancel/isCancel.js';
|
||||
import CanceledError from '../../../lib/cancel/CanceledError.js';
|
||||
|
||||
describe('cancel::isCancel', () => {
|
||||
it('returns true when value is a CanceledError', () => {
|
||||
expect(isCancel(new CanceledError())).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when value is not canceled', () => {
|
||||
expect(isCancel({ foo: 'bar' })).toBe(false);
|
||||
});
|
||||
});
|
||||
102
tests/unit/core/AxiosError.test.js
Normal file
102
tests/unit/core/AxiosError.test.js
Normal file
@ -0,0 +1,102 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { isNativeError } from 'node:util/types';
|
||||
import AxiosError from '../../../lib/core/AxiosError.js';
|
||||
|
||||
describe('core::AxiosError', () => {
|
||||
it('creates an error with message, config, code, request, response, stack and isAxiosError', () => {
|
||||
const request = { path: '/foo' };
|
||||
const response = { status: 200, data: { foo: 'bar' } };
|
||||
const error = new AxiosError('Boom!', 'ESOMETHING', { foo: 'bar' }, request, response);
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toBe('Boom!');
|
||||
expect(error.config).toEqual({ foo: 'bar' });
|
||||
expect(error.code).toBe('ESOMETHING');
|
||||
expect(error.request).toBe(request);
|
||||
expect(error.response).toBe(response);
|
||||
expect(error.isAxiosError).toBe(true);
|
||||
expect(error.stack).toBeDefined();
|
||||
});
|
||||
|
||||
it('serializes to JSON safely', () => {
|
||||
// request/response are intentionally omitted from the serialized shape
|
||||
// to avoid circular-reference problems.
|
||||
const request = { path: '/foo' };
|
||||
const response = { status: 200, data: { foo: 'bar' } };
|
||||
const error = new AxiosError('Boom!', 'ESOMETHING', { foo: 'bar' }, request, response);
|
||||
const json = error.toJSON();
|
||||
|
||||
expect(json.message).toBe('Boom!');
|
||||
expect(json.config).toEqual({ foo: 'bar' });
|
||||
expect(json.code).toBe('ESOMETHING');
|
||||
expect(json.status).toBe(200);
|
||||
expect(json.request).toBeUndefined();
|
||||
expect(json.response).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('AxiosError.from', () => {
|
||||
it('adds config, code, request and response to the wrapped error', () => {
|
||||
const error = new Error('Boom!');
|
||||
const request = { path: '/foo' };
|
||||
const response = { status: 200, data: { foo: 'bar' } };
|
||||
|
||||
const axiosError = AxiosError.from(error, 'ESOMETHING', { foo: 'bar' }, request, response);
|
||||
|
||||
expect(axiosError.config).toEqual({ foo: 'bar' });
|
||||
expect(axiosError.code).toBe('ESOMETHING');
|
||||
expect(axiosError.request).toBe(request);
|
||||
expect(axiosError.response).toBe(response);
|
||||
expect(axiosError.isAxiosError).toBe(true);
|
||||
});
|
||||
|
||||
it('returns an AxiosError instance', () => {
|
||||
const axiosError = AxiosError.from(new Error('Boom!'), 'ESOMETHING', { foo: 'bar' });
|
||||
|
||||
expect(axiosError).toBeInstanceOf(AxiosError);
|
||||
});
|
||||
|
||||
it('preserves status from the original error when response is not provided', () => {
|
||||
const error = new Error('Network Error');
|
||||
error.status = 404;
|
||||
|
||||
const axiosError = AxiosError.from(error, 'ERR_NETWORK', { foo: 'bar' });
|
||||
|
||||
expect(axiosError.status).toBe(404);
|
||||
});
|
||||
|
||||
it('prefers response.status over error.status when response is provided', () => {
|
||||
const error = new Error('Error');
|
||||
error.status = 500;
|
||||
const response = { status: 404 };
|
||||
|
||||
const axiosError = AxiosError.from(error, 'ERR_BAD_REQUEST', {}, null, response);
|
||||
|
||||
expect(axiosError.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
it('is recognized as a native error by Node util/types', () => {
|
||||
expect(isNativeError(new AxiosError('My Axios Error'))).toBe(true);
|
||||
});
|
||||
|
||||
it('supports static error-code properties', () => {
|
||||
const error = new AxiosError('My Axios Error', AxiosError.ECONNABORTED);
|
||||
|
||||
expect(error.code).toBe(AxiosError.ECONNABORTED);
|
||||
});
|
||||
|
||||
it('sets status when response is passed to constructor', () => {
|
||||
const error = new AxiosError('test', 'foo', {}, {}, { status: 400 });
|
||||
|
||||
expect(error.status).toBe(400);
|
||||
});
|
||||
|
||||
it('keeps message enumerable for backward compatibility', () => {
|
||||
const error = new AxiosError('Test error message', 'ERR_TEST', { foo: 'bar' });
|
||||
|
||||
expect(Object.keys(error)).toContain('message');
|
||||
expect(Object.entries(error).find(([key]) => key === 'message')?.[1]).toBe('Test error message');
|
||||
expect({ ...error }.message).toBe('Test error message');
|
||||
expect(Object.getOwnPropertyDescriptor(error, 'message')?.enumerable).toBe(true);
|
||||
});
|
||||
});
|
||||
34
tests/unit/core/buildFullPath.test.js
Normal file
34
tests/unit/core/buildFullPath.test.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import buildFullPath from '../../../lib/core/buildFullPath.js';
|
||||
|
||||
describe('core::buildFullPath', () => {
|
||||
it('combines URLs when the requested URL is relative', () => {
|
||||
expect(buildFullPath('https://api.github.com', '/users')).toBe('https://api.github.com/users');
|
||||
});
|
||||
|
||||
it('does not combine URLs when the requested URL is absolute', () => {
|
||||
expect(buildFullPath('https://api.github.com', 'https://api.example.com/users')).toBe(
|
||||
'https://api.example.com/users'
|
||||
);
|
||||
});
|
||||
|
||||
it('combines URLs when requested URL is absolute and allowAbsoluteUrls is false', () => {
|
||||
expect(buildFullPath('https://api.github.com', 'https://api.example.com/users', false)).toBe(
|
||||
'https://api.github.com/https://api.example.com/users'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not combine URLs when baseURL is missing and allowAbsoluteUrls is false', () => {
|
||||
expect(buildFullPath(undefined, 'https://api.example.com/users', false)).toBe(
|
||||
'https://api.example.com/users'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not combine URLs when baseURL is not configured', () => {
|
||||
expect(buildFullPath(undefined, '/users')).toBe('/users');
|
||||
});
|
||||
|
||||
it('combines URLs when baseURL and requested URL are both relative', () => {
|
||||
expect(buildFullPath('/api', '/users')).toBe('/api/users');
|
||||
});
|
||||
});
|
||||
357
tests/unit/core/mergeConfig.test.js
Normal file
357
tests/unit/core/mergeConfig.test.js
Normal file
@ -0,0 +1,357 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import defaults from '../../../lib/defaults/index.js';
|
||||
import mergeConfig from '../../../lib/core/mergeConfig.js';
|
||||
import { AxiosHeaders } from '../../../index.js';
|
||||
|
||||
describe('core::mergeConfig', () => {
|
||||
it('accepts undefined for second argument', () => {
|
||||
expect(mergeConfig(defaults, undefined)).toEqual(defaults);
|
||||
});
|
||||
|
||||
it('accepts an object for second argument', () => {
|
||||
expect(mergeConfig(defaults, {})).toEqual(defaults);
|
||||
});
|
||||
|
||||
it('does not leave references', () => {
|
||||
const merged = mergeConfig(defaults, {});
|
||||
|
||||
expect(merged).not.toBe(defaults);
|
||||
expect(merged.headers).not.toBe(defaults.headers);
|
||||
});
|
||||
|
||||
it('allows setting request options', () => {
|
||||
const config = {
|
||||
url: '__sample url__',
|
||||
method: '__sample method__',
|
||||
params: '__sample params__',
|
||||
data: { foo: true },
|
||||
};
|
||||
const merged = mergeConfig(defaults, config);
|
||||
|
||||
expect(merged.url).toBe(config.url);
|
||||
expect(merged.method).toBe(config.method);
|
||||
expect(merged.params).toBe(config.params);
|
||||
expect(merged.data).toEqual(config.data);
|
||||
});
|
||||
|
||||
it('does not inherit request options', () => {
|
||||
const localDefaults = {
|
||||
method: '__sample method__',
|
||||
data: { foo: true },
|
||||
};
|
||||
const merged = mergeConfig(localDefaults, {});
|
||||
|
||||
expect(merged.method).toBeUndefined();
|
||||
expect(merged.data).toBeUndefined();
|
||||
});
|
||||
|
||||
for (const key of ['auth', 'headers', 'params', 'proxy']) {
|
||||
it(`sets new config for ${key} without default`, () => {
|
||||
const config1 = { [key]: undefined };
|
||||
const config2 = { [key]: { user: 'foo', pass: 'test' } };
|
||||
const expected = { [key]: { user: 'foo', pass: 'test' } };
|
||||
|
||||
expect(mergeConfig(config1, config2)).toEqual(expected);
|
||||
});
|
||||
|
||||
it(`merges ${key} with defaults`, () => {
|
||||
const config1 = { [key]: { user: 'foo', pass: 'bar' } };
|
||||
const config2 = { [key]: { pass: 'test' } };
|
||||
const expected = { [key]: { user: 'foo', pass: 'test' } };
|
||||
|
||||
expect(mergeConfig(config1, config2)).toEqual(expected);
|
||||
});
|
||||
|
||||
it.each([false, null, 123])(`overwrites default ${key} with %p`, (value) => {
|
||||
const config1 = { [key]: { user: 'foo', pass: 'test' } };
|
||||
const config2 = { [key]: value };
|
||||
const expected = { [key]: value };
|
||||
|
||||
expect(mergeConfig(config1, config2)).toEqual(expected);
|
||||
});
|
||||
}
|
||||
|
||||
it('allows setting other options', () => {
|
||||
const merged = mergeConfig(defaults, { timeout: 123 });
|
||||
|
||||
expect(merged.timeout).toBe(123);
|
||||
});
|
||||
|
||||
it('allows setting custom options', () => {
|
||||
const merged = mergeConfig(defaults, { foo: 'bar' });
|
||||
|
||||
expect(merged.foo).toBe('bar');
|
||||
});
|
||||
|
||||
it('allows setting custom default options', () => {
|
||||
const merged = mergeConfig({ foo: 'bar' }, {});
|
||||
|
||||
expect(merged.foo).toBe('bar');
|
||||
});
|
||||
|
||||
it('allows merging custom objects in config', () => {
|
||||
const merged = mergeConfig(
|
||||
{
|
||||
nestedConfig: {
|
||||
propertyOnDefaultConfig: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
nestedConfig: {
|
||||
propertyOnRequestConfig: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(merged.nestedConfig.propertyOnDefaultConfig).toBe(true);
|
||||
expect(merged.nestedConfig.propertyOnRequestConfig).toBe(true);
|
||||
});
|
||||
|
||||
describe('headers', () => {
|
||||
it('allows merging with AxiosHeaders instances', () => {
|
||||
const merged = mergeConfig(
|
||||
{
|
||||
headers: new AxiosHeaders({
|
||||
x: 1,
|
||||
y: 2,
|
||||
}),
|
||||
},
|
||||
{
|
||||
headers: new AxiosHeaders({
|
||||
X: 1,
|
||||
Y: 2,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
expect(merged.headers).toEqual({
|
||||
x: '1',
|
||||
y: '2',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('valueFromConfig2Keys', () => {
|
||||
const config1 = { url: '/foo', method: 'post', data: { a: 3 } };
|
||||
|
||||
it('skips if config2 does not define the key', () => {
|
||||
expect(mergeConfig(config1, {})).toEqual({});
|
||||
});
|
||||
|
||||
it('clones config2 when it is a plain object', () => {
|
||||
const data = { a: 1, b: 2 };
|
||||
const merged = mergeConfig(config1, { data });
|
||||
|
||||
expect(merged.data).toEqual(data);
|
||||
expect(merged.data).not.toBe(data);
|
||||
});
|
||||
|
||||
it('clones config2 when it is an array', () => {
|
||||
const data = [1, 2, 3];
|
||||
const merged = mergeConfig(config1, { data });
|
||||
|
||||
expect(merged.data).toEqual(data);
|
||||
expect(merged.data).not.toBe(data);
|
||||
});
|
||||
|
||||
it('sets config2 value directly for non-mergeable values', () => {
|
||||
const obj = Object.create({});
|
||||
|
||||
expect(mergeConfig(config1, { data: 1 }).data).toBe(1);
|
||||
expect(mergeConfig(config1, { data: 'str' }).data).toBe('str');
|
||||
expect(mergeConfig(config1, { data: obj }).data).toBe(obj);
|
||||
expect(mergeConfig(config1, { data: null }).data).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeDeepPropertiesKeys', () => {
|
||||
it('skips when both config1 and config2 values are undefined', () => {
|
||||
expect(mergeConfig({ headers: undefined }, { headers: undefined })).toEqual({});
|
||||
});
|
||||
|
||||
it('merges when both values are plain objects', () => {
|
||||
expect(mergeConfig({ headers: { a: 1, b: 1 } }, { headers: { b: 2, c: 2 } })).toEqual({
|
||||
headers: { a: 1, b: 2, c: 2 },
|
||||
});
|
||||
});
|
||||
|
||||
it('clones config2 when it is a plain object', () => {
|
||||
const config1 = { headers: [1, 2, 3] };
|
||||
const config2 = { headers: { a: 1, b: 2 } };
|
||||
const merged = mergeConfig(config1, config2);
|
||||
|
||||
expect(merged.headers).toEqual(config2.headers);
|
||||
expect(merged.headers).not.toBe(config2.headers);
|
||||
});
|
||||
|
||||
it('clones config2 when it is an array', () => {
|
||||
const config1 = { headers: { a: 1, b: 1 } };
|
||||
const config2 = { headers: [1, 2, 3] };
|
||||
const merged = mergeConfig(config1, config2);
|
||||
|
||||
expect(merged.headers).toEqual(config2.headers);
|
||||
expect(merged.headers).not.toBe(config2.headers);
|
||||
});
|
||||
|
||||
it('sets config2 value directly for non-mergeable values', () => {
|
||||
const config1 = { headers: { a: 1, b: 1 } };
|
||||
const obj = Object.create({});
|
||||
|
||||
expect(mergeConfig(config1, { headers: 1 }).headers).toBe(1);
|
||||
expect(mergeConfig(config1, { headers: 'str' }).headers).toBe('str');
|
||||
expect(mergeConfig(config1, { headers: obj }).headers).toBe(obj);
|
||||
expect(mergeConfig(config1, { headers: null }).headers).toBe(null);
|
||||
});
|
||||
|
||||
it('clones config1 when it is a plain object', () => {
|
||||
const config1 = { headers: { a: 1, b: 2 } };
|
||||
const merged = mergeConfig(config1, {});
|
||||
|
||||
expect(merged.headers).toEqual(config1.headers);
|
||||
expect(merged.headers).not.toBe(config1.headers);
|
||||
});
|
||||
|
||||
it('clones config1 when it is an array', () => {
|
||||
const config1 = { headers: [1, 2, 3] };
|
||||
const merged = mergeConfig(config1, {});
|
||||
|
||||
expect(merged.headers).toEqual(config1.headers);
|
||||
expect(merged.headers).not.toBe(config1.headers);
|
||||
});
|
||||
|
||||
it('sets config1 value directly for non-mergeable values', () => {
|
||||
const obj = Object.create({});
|
||||
|
||||
expect(mergeConfig({ headers: 1 }, {}).headers).toBe(1);
|
||||
expect(mergeConfig({ headers: 'str' }, {}).headers).toBe('str');
|
||||
expect(mergeConfig({ headers: obj }, {}).headers).toBe(obj);
|
||||
expect(mergeConfig({ headers: null }, {}).headers).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultToConfig2Keys', () => {
|
||||
it('skips when both config1 and config2 values are undefined', () => {
|
||||
expect(mergeConfig({ transformRequest: undefined }, { transformRequest: undefined })).toEqual(
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('clones config2 when both values are plain objects', () => {
|
||||
const config1 = { transformRequest: { a: 1, b: 1 } };
|
||||
const config2 = { transformRequest: { b: 2, c: 2 } };
|
||||
const merged = mergeConfig(config1, config2);
|
||||
|
||||
expect(merged.transformRequest).toEqual(config2.transformRequest);
|
||||
expect(merged.transformRequest).not.toBe(config2.transformRequest);
|
||||
});
|
||||
|
||||
it('clones config2 when it is an array', () => {
|
||||
const config1 = { transformRequest: { a: 1, b: 1 } };
|
||||
const config2 = { transformRequest: [1, 2, 3] };
|
||||
const merged = mergeConfig(config1, config2);
|
||||
|
||||
expect(merged.transformRequest).toEqual(config2.transformRequest);
|
||||
expect(merged.transformRequest).not.toBe(config2.transformRequest);
|
||||
});
|
||||
|
||||
it('sets config2 value directly for non-mergeable values', () => {
|
||||
const config1 = { transformRequest: { a: 1, b: 1 } };
|
||||
const obj = Object.create({});
|
||||
|
||||
expect(mergeConfig(config1, { transformRequest: 1 }).transformRequest).toBe(1);
|
||||
expect(mergeConfig(config1, { transformRequest: 'str' }).transformRequest).toBe('str');
|
||||
expect(mergeConfig(config1, { transformRequest: obj }).transformRequest).toBe(obj);
|
||||
expect(mergeConfig(config1, { transformRequest: null }).transformRequest).toBe(null);
|
||||
});
|
||||
|
||||
it('clones config1 when it is a plain object', () => {
|
||||
const config1 = { transformRequest: { a: 1, b: 2 } };
|
||||
const merged = mergeConfig(config1, {});
|
||||
|
||||
expect(merged.transformRequest).toEqual(config1.transformRequest);
|
||||
expect(merged.transformRequest).not.toBe(config1.transformRequest);
|
||||
});
|
||||
|
||||
it('clones config1 when it is an array', () => {
|
||||
const config1 = { transformRequest: [1, 2, 3] };
|
||||
const merged = mergeConfig(config1, {});
|
||||
|
||||
expect(merged.transformRequest).toEqual(config1.transformRequest);
|
||||
expect(merged.transformRequest).not.toBe(config1.transformRequest);
|
||||
});
|
||||
|
||||
it('sets config1 value directly for non-mergeable values', () => {
|
||||
const obj = Object.create({});
|
||||
|
||||
expect(mergeConfig({ transformRequest: 1 }, {}).transformRequest).toBe(1);
|
||||
expect(mergeConfig({ transformRequest: 'str' }, {}).transformRequest).toBe('str');
|
||||
expect(mergeConfig({ transformRequest: obj }, {}).transformRequest).toBe(obj);
|
||||
expect(mergeConfig({ transformRequest: null }, {}).transformRequest).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('directMergeKeys', () => {
|
||||
it('merges when config2 defines the key', () => {
|
||||
expect(mergeConfig({}, { validateStatus: undefined })).toEqual({ validateStatus: undefined });
|
||||
});
|
||||
|
||||
it('merges when both values are plain objects', () => {
|
||||
expect(
|
||||
mergeConfig({ validateStatus: { a: 1, b: 1 } }, { validateStatus: { b: 2, c: 2 } })
|
||||
).toEqual({ validateStatus: { a: 1, b: 2, c: 2 } });
|
||||
});
|
||||
|
||||
it('clones config2 when it is a plain object', () => {
|
||||
const config1 = { validateStatus: [1, 2, 3] };
|
||||
const config2 = { validateStatus: { a: 1, b: 2 } };
|
||||
const merged = mergeConfig(config1, config2);
|
||||
|
||||
expect(merged.validateStatus).toEqual(config2.validateStatus);
|
||||
expect(merged.validateStatus).not.toBe(config2.validateStatus);
|
||||
});
|
||||
|
||||
it('clones config2 when it is an array', () => {
|
||||
const config1 = { validateStatus: { a: 1, b: 2 } };
|
||||
const config2 = { validateStatus: [1, 2, 3] };
|
||||
const merged = mergeConfig(config1, config2);
|
||||
|
||||
expect(merged.validateStatus).toEqual(config2.validateStatus);
|
||||
expect(merged.validateStatus).not.toBe(config2.validateStatus);
|
||||
});
|
||||
|
||||
it('sets config2 value directly for non-mergeable values', () => {
|
||||
const config1 = { validateStatus: { a: 1, b: 2 } };
|
||||
const obj = Object.create({});
|
||||
|
||||
expect(mergeConfig(config1, { validateStatus: 1 }).validateStatus).toBe(1);
|
||||
expect(mergeConfig(config1, { validateStatus: 'str' }).validateStatus).toBe('str');
|
||||
expect(mergeConfig(config1, { validateStatus: obj }).validateStatus).toBe(obj);
|
||||
expect(mergeConfig(config1, { validateStatus: null }).validateStatus).toBe(null);
|
||||
});
|
||||
|
||||
it('clones config1 when it is a plain object', () => {
|
||||
const config1 = { validateStatus: { a: 1, b: 2 } };
|
||||
const merged = mergeConfig(config1, {});
|
||||
|
||||
expect(merged.validateStatus).toEqual(config1.validateStatus);
|
||||
expect(merged.validateStatus).not.toBe(config1.validateStatus);
|
||||
});
|
||||
|
||||
it('clones config1 when it is an array', () => {
|
||||
const config1 = { validateStatus: [1, 2, 3] };
|
||||
const merged = mergeConfig(config1, {});
|
||||
|
||||
expect(merged.validateStatus).toEqual(config1.validateStatus);
|
||||
expect(merged.validateStatus).not.toBe(config1.validateStatus);
|
||||
});
|
||||
|
||||
it('sets config1 value directly for non-mergeable values', () => {
|
||||
const obj = Object.create({});
|
||||
|
||||
expect(mergeConfig({ validateStatus: 1 }, {}).validateStatus).toBe(1);
|
||||
expect(mergeConfig({ validateStatus: 'str' }, {}).validateStatus).toBe('str');
|
||||
expect(mergeConfig({ validateStatus: obj }, {}).validateStatus).toBe(obj);
|
||||
expect(mergeConfig({ validateStatus: null }, {}).validateStatus).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
49
tests/unit/core/transformData.test.js
Normal file
49
tests/unit/core/transformData.test.js
Normal file
@ -0,0 +1,49 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import transformData from '../../../lib/core/transformData.js';
|
||||
|
||||
describe('core::transformData', () => {
|
||||
it('supports a single transformer', () => {
|
||||
const data = transformData.call({}, (value) => {
|
||||
value = 'foo';
|
||||
return value;
|
||||
});
|
||||
|
||||
expect(data).toBe('foo');
|
||||
});
|
||||
|
||||
it('supports an array of transformers', () => {
|
||||
const data = transformData.call({ data: '' }, [
|
||||
(value) => value + 'f',
|
||||
(value) => value + 'o',
|
||||
(value) => value + 'o',
|
||||
]);
|
||||
|
||||
expect(data).toBe('foo');
|
||||
});
|
||||
|
||||
it('passes headers through to transformers', () => {
|
||||
const headers = {
|
||||
'content-type': 'foo/bar',
|
||||
};
|
||||
|
||||
const data = transformData.call(
|
||||
{
|
||||
data: '',
|
||||
headers,
|
||||
},
|
||||
[(value, currentHeaders) => value + currentHeaders['content-type']]
|
||||
);
|
||||
|
||||
expect(data).toBe('foo/bar');
|
||||
});
|
||||
|
||||
it('passes status code through to transformers', () => {
|
||||
const data = transformData.call(
|
||||
{},
|
||||
[(value, _headers, status) => value + status],
|
||||
{ data: '', status: 200 }
|
||||
);
|
||||
|
||||
expect(data).toBe('200');
|
||||
});
|
||||
});
|
||||
13
tests/unit/helpers/bind.test.js
Normal file
13
tests/unit/helpers/bind.test.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import bind from '../../../lib/helpers/bind.js';
|
||||
|
||||
describe('bind', () => {
|
||||
it('should bind an object to a function', () => {
|
||||
const o = { val: 123 };
|
||||
const f = bind(function (num) {
|
||||
return this.val * num;
|
||||
}, o);
|
||||
|
||||
expect(f(2)).toEqual(246);
|
||||
});
|
||||
});
|
||||
126
tests/unit/helpers/buildURL.test.js
Normal file
126
tests/unit/helpers/buildURL.test.js
Normal file
@ -0,0 +1,126 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import buildURL from '../../../lib/helpers/buildURL.js';
|
||||
|
||||
describe('helpers::buildURL', () => {
|
||||
it('should support null params', () => {
|
||||
expect(buildURL('/foo')).toEqual('/foo');
|
||||
});
|
||||
|
||||
it('should support params', () => {
|
||||
expect(
|
||||
buildURL('/foo', {
|
||||
foo: 'bar',
|
||||
isUndefined: undefined,
|
||||
isNull: null,
|
||||
})
|
||||
).toEqual('/foo?foo=bar');
|
||||
});
|
||||
|
||||
it('should support sending raw params to custom serializer func', () => {
|
||||
const serializer = vi.fn().mockReturnValue('foo=bar');
|
||||
const params = { foo: 'bar' };
|
||||
const options = {
|
||||
serialize: serializer,
|
||||
};
|
||||
expect(
|
||||
buildURL(
|
||||
'/foo',
|
||||
{
|
||||
foo: 'bar',
|
||||
},
|
||||
options
|
||||
)
|
||||
).toEqual('/foo?foo=bar');
|
||||
expect(serializer).toHaveBeenCalledTimes(1);
|
||||
expect(serializer).toHaveBeenCalledWith(params, options);
|
||||
});
|
||||
|
||||
it('should support object params', () => {
|
||||
expect(
|
||||
buildURL('/foo', {
|
||||
foo: {
|
||||
bar: 'baz',
|
||||
},
|
||||
})
|
||||
).toEqual('/foo?foo%5Bbar%5D=baz');
|
||||
});
|
||||
|
||||
it('should support date params', () => {
|
||||
const date = new Date();
|
||||
|
||||
expect(
|
||||
buildURL('/foo', {
|
||||
date,
|
||||
})
|
||||
).toEqual('/foo?date=' + date.toISOString());
|
||||
});
|
||||
|
||||
it('should support array params with encode', () => {
|
||||
expect(
|
||||
buildURL('/foo', {
|
||||
foo: ['bar', 'baz'],
|
||||
})
|
||||
).toEqual('/foo?foo%5B%5D=bar&foo%5B%5D=baz');
|
||||
});
|
||||
|
||||
it('should support special char params', () => {
|
||||
expect(
|
||||
buildURL('/foo', {
|
||||
foo: ':$, ',
|
||||
})
|
||||
).toEqual('/foo?foo=:$,+');
|
||||
});
|
||||
|
||||
it('should support existing params', () => {
|
||||
expect(
|
||||
buildURL('/foo?foo=bar', {
|
||||
bar: 'baz',
|
||||
})
|
||||
).toEqual('/foo?foo=bar&bar=baz');
|
||||
});
|
||||
|
||||
it('should support "length" parameter', () => {
|
||||
expect(
|
||||
buildURL('/foo', {
|
||||
query: 'bar',
|
||||
start: 0,
|
||||
length: 5,
|
||||
})
|
||||
).toEqual('/foo?query=bar&start=0&length=5');
|
||||
});
|
||||
|
||||
it('should correct discard url hash mark', () => {
|
||||
expect(
|
||||
buildURL('/foo?foo=bar#hash', {
|
||||
query: 'baz',
|
||||
})
|
||||
).toEqual('/foo?foo=bar&query=baz');
|
||||
});
|
||||
|
||||
it('should support URLSearchParams', () => {
|
||||
expect(buildURL('/foo', new URLSearchParams('bar=baz'))).toEqual('/foo?bar=baz');
|
||||
});
|
||||
|
||||
it('should support custom serialize function', () => {
|
||||
const params = {
|
||||
x: 1,
|
||||
};
|
||||
|
||||
const options = {
|
||||
serialize: (thisParams, thisOptions) => {
|
||||
expect(thisParams).toEqual(params);
|
||||
expect(thisOptions).toEqual(options);
|
||||
return 'rendered';
|
||||
},
|
||||
};
|
||||
|
||||
expect(buildURL('/foo', params, options)).toEqual('/foo?rendered');
|
||||
|
||||
const customSerializer = (thisParams) => {
|
||||
expect(thisParams).toEqual(params);
|
||||
return 'rendered';
|
||||
};
|
||||
|
||||
expect(buildURL('/foo', params, customSerializer)).toEqual('/foo?rendered');
|
||||
});
|
||||
});
|
||||
24
tests/unit/helpers/combineURLs.test.js
Normal file
24
tests/unit/helpers/combineURLs.test.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import combineURLs from '../../../lib/helpers/combineURLs.js';
|
||||
|
||||
describe('helpers::combineURLs', () => {
|
||||
it('should combine URLs', () => {
|
||||
expect(combineURLs('https://api.github.com', '/users')).toBe('https://api.github.com/users');
|
||||
});
|
||||
|
||||
it('should remove duplicate slashes', () => {
|
||||
expect(combineURLs('https://api.github.com/', '/users')).toBe('https://api.github.com/users');
|
||||
});
|
||||
|
||||
it('should insert missing slash', () => {
|
||||
expect(combineURLs('https://api.github.com', 'users')).toBe('https://api.github.com/users');
|
||||
});
|
||||
|
||||
it('should not insert slash when relative url missing/empty', () => {
|
||||
expect(combineURLs('https://api.github.com/users', '')).toBe('https://api.github.com/users');
|
||||
});
|
||||
|
||||
it('should allow a single slash for relative url', () => {
|
||||
expect(combineURLs('https://api.github.com/users', '/')).toBe('https://api.github.com/users/');
|
||||
});
|
||||
});
|
||||
72
tests/unit/helpers/formDataToJSON.test.js
Normal file
72
tests/unit/helpers/formDataToJSON.test.js
Normal file
@ -0,0 +1,72 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import formDataToJSON from '../../../lib/helpers/formDataToJSON.js';
|
||||
|
||||
describe('formDataToJSON', () => {
|
||||
it('should convert a FormData Object to JSON Object', () => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('foo[bar][baz]', '123');
|
||||
|
||||
expect(formDataToJSON(formData)).toEqual({
|
||||
foo: {
|
||||
bar: {
|
||||
baz: '123',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert repeatable values as an array', () => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('foo', '1');
|
||||
formData.append('foo', '2');
|
||||
|
||||
expect(formDataToJSON(formData)).toEqual({
|
||||
foo: ['1', '2'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert props with empty brackets to arrays', () => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('foo[]', '1');
|
||||
formData.append('foo[]', '2');
|
||||
|
||||
expect(formDataToJSON(formData)).toEqual({
|
||||
foo: ['1', '2'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should supported indexed arrays', () => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('foo[0]', '1');
|
||||
formData.append('foo[1]', '2');
|
||||
|
||||
expect(formDataToJSON(formData)).toEqual({
|
||||
foo: ['1', '2'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should resist prototype pollution CVE', () => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('foo[0]', '1');
|
||||
formData.append('foo[1]', '2');
|
||||
formData.append('__proto__.x', 'hack');
|
||||
formData.append('constructor.prototype.y', 'value');
|
||||
|
||||
expect(formDataToJSON(formData)).toEqual({
|
||||
foo: ['1', '2'],
|
||||
constructor: {
|
||||
prototype: {
|
||||
y: 'value',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect({}.x).toEqual(undefined);
|
||||
expect({}.y).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
24
tests/unit/helpers/isAbsoluteURL.test.js
Normal file
24
tests/unit/helpers/isAbsoluteURL.test.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import isAbsoluteURL from '../../../lib/helpers/isAbsoluteURL.js';
|
||||
|
||||
describe('helpers::isAbsoluteURL', () => {
|
||||
it('should return true if URL begins with valid scheme name', () => {
|
||||
expect(isAbsoluteURL('https://api.github.com/users')).toBe(true);
|
||||
expect(isAbsoluteURL('custom-scheme-v1.0://example.com/')).toBe(true);
|
||||
expect(isAbsoluteURL('HTTP://example.com/')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if URL begins with invalid scheme name', () => {
|
||||
expect(isAbsoluteURL('123://example.com/')).toBe(false);
|
||||
expect(isAbsoluteURL('!valid://example.com/')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if URL is protocol-relative', () => {
|
||||
expect(isAbsoluteURL('//example.com/')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if URL is relative', () => {
|
||||
expect(isAbsoluteURL('/foo')).toBe(false);
|
||||
expect(isAbsoluteURL('foo')).toBe(false);
|
||||
});
|
||||
});
|
||||
21
tests/unit/helpers/isAxiosError.test.js
Normal file
21
tests/unit/helpers/isAxiosError.test.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import AxiosError from '../../../lib/core/AxiosError.js';
|
||||
import isAxiosError from '../../../lib/helpers/isAxiosError.js';
|
||||
|
||||
describe('helpers::isAxiosError', () => {
|
||||
it('should return true if the error is created by core::createError', () => {
|
||||
expect(isAxiosError(new AxiosError('Boom!', null, { foo: 'bar' }))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the error is enhanced by core::enhanceError', () => {
|
||||
expect(isAxiosError(AxiosError.from(new Error('Boom!'), null, { foo: 'bar' }))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the error is a normal Error instance', () => {
|
||||
expect(isAxiosError(new Error('Boom!'))).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if the error is null', () => {
|
||||
expect(isAxiosError(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
43
tests/unit/helpers/parseHeaders.test.js
Normal file
43
tests/unit/helpers/parseHeaders.test.js
Normal file
@ -0,0 +1,43 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import parseHeaders from '../../../lib/helpers/parseHeaders.js';
|
||||
|
||||
describe('helpers::parseHeaders', () => {
|
||||
it('should parse headers', () => {
|
||||
const date = new Date();
|
||||
const parsed = parseHeaders(
|
||||
'Date: ' +
|
||||
date.toISOString() +
|
||||
'\n' +
|
||||
'Content-Type: application/json\n' +
|
||||
'Connection: keep-alive\n' +
|
||||
'Transfer-Encoding: chunked'
|
||||
);
|
||||
|
||||
expect(parsed.date).toEqual(date.toISOString());
|
||||
expect(parsed['content-type']).toEqual('application/json');
|
||||
expect(parsed.connection).toEqual('keep-alive');
|
||||
expect(parsed['transfer-encoding']).toEqual('chunked');
|
||||
});
|
||||
|
||||
it('should use array for set-cookie', () => {
|
||||
const parsedZero = parseHeaders('');
|
||||
const parsedSingle = parseHeaders('Set-Cookie: key=val;');
|
||||
const parsedMulti = parseHeaders('Set-Cookie: key=val;\n' + 'Set-Cookie: key2=val2;\n');
|
||||
|
||||
expect(parsedZero['set-cookie']).toBeUndefined();
|
||||
expect(parsedSingle['set-cookie']).toEqual(['key=val;']);
|
||||
expect(parsedMulti['set-cookie']).toEqual(['key=val;', 'key2=val2;']);
|
||||
});
|
||||
|
||||
it('should handle duplicates', () => {
|
||||
const parsed = parseHeaders(
|
||||
'Age: age-a\n' +
|
||||
'Age: age-b\n' +
|
||||
'Foo: foo-a\n' +
|
||||
'Foo: foo-b\n'
|
||||
);
|
||||
|
||||
expect(parsed.age).toEqual('age-a');
|
||||
expect(parsed.foo).toEqual('foo-a, foo-b');
|
||||
});
|
||||
});
|
||||
21
tests/unit/helpers/spread.test.js
Normal file
21
tests/unit/helpers/spread.test.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import spread from '../../../lib/helpers/spread.js';
|
||||
|
||||
describe('helpers::spread', () => {
|
||||
it('should spread array to arguments', () => {
|
||||
let value = 0;
|
||||
spread((a, b) => {
|
||||
value = a * b;
|
||||
})([5, 10]);
|
||||
|
||||
expect(value).toEqual(50);
|
||||
});
|
||||
|
||||
it('should return callback result', () => {
|
||||
const value = spread((a, b) => {
|
||||
return a * b;
|
||||
})([5, 10]);
|
||||
|
||||
expect(value).toEqual(50);
|
||||
});
|
||||
});
|
||||
67
tests/unit/helpers/validator.test.js
Normal file
67
tests/unit/helpers/validator.test.js
Normal file
@ -0,0 +1,67 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import AxiosError from '../../../lib/core/AxiosError.js';
|
||||
import validator from '../../../lib/helpers/validator.js';
|
||||
|
||||
describe('validator::assertOptions', () => {
|
||||
it('should throw only if unknown an option was passed', () => {
|
||||
let error;
|
||||
try {
|
||||
validator.assertOptions(
|
||||
{
|
||||
x: true,
|
||||
},
|
||||
{
|
||||
y: validator.validators.boolean,
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).toBeInstanceOf(AxiosError);
|
||||
expect(error.message).toBe('Unknown option x');
|
||||
expect(error.code).toBe(AxiosError.ERR_BAD_OPTION);
|
||||
|
||||
expect(() => {
|
||||
validator.assertOptions(
|
||||
{
|
||||
x: true,
|
||||
},
|
||||
{
|
||||
x: validator.validators.boolean,
|
||||
y: validator.validators.boolean,
|
||||
}
|
||||
);
|
||||
}).not.toThrow(new Error('Unknown option x'));
|
||||
});
|
||||
|
||||
it("should throw TypeError only if option type doesn't match", () => {
|
||||
let error;
|
||||
try {
|
||||
validator.assertOptions(
|
||||
{
|
||||
x: 123,
|
||||
},
|
||||
{
|
||||
x: validator.validators.boolean,
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).toBeInstanceOf(AxiosError);
|
||||
expect(error.message).toBe('option x must be a boolean');
|
||||
expect(error.code).toBe(AxiosError.ERR_BAD_OPTION_VALUE);
|
||||
|
||||
expect(() => {
|
||||
validator.assertOptions(
|
||||
{
|
||||
x: true,
|
||||
},
|
||||
{
|
||||
x: validator.validators.boolean,
|
||||
y: validator.validators.boolean,
|
||||
}
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
13
tests/unit/utils/endsWith.test.js
Normal file
13
tests/unit/utils/endsWith.test.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
const { kindOf } = utils;
|
||||
|
||||
describe('utils::kindOf', () => {
|
||||
it('should return object tag', () => {
|
||||
expect(kindOf({})).toEqual('object');
|
||||
// cached result
|
||||
expect(kindOf({})).toEqual('object');
|
||||
expect(kindOf([])).toEqual('array');
|
||||
});
|
||||
});
|
||||
40
tests/unit/utils/extend.test.js
Normal file
40
tests/unit/utils/extend.test.js
Normal file
@ -0,0 +1,40 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
const { extend } = utils;
|
||||
|
||||
describe('utils::extend', () => {
|
||||
it('should be mutable', () => {
|
||||
const a = {};
|
||||
const b = { foo: 123 };
|
||||
|
||||
extend(a, b);
|
||||
|
||||
expect(a.foo).toEqual(b.foo);
|
||||
});
|
||||
|
||||
it('should extend properties', () => {
|
||||
let a = { foo: 123, bar: 456 };
|
||||
const b = { bar: 789 };
|
||||
|
||||
a = extend(a, b);
|
||||
|
||||
expect(a.foo).toEqual(123);
|
||||
expect(a.bar).toEqual(789);
|
||||
});
|
||||
|
||||
it('should bind to thisArg', () => {
|
||||
const a = {};
|
||||
const b = {
|
||||
getFoo: function getFoo() {
|
||||
return this.foo;
|
||||
},
|
||||
};
|
||||
const thisArg = { foo: 'barbaz' };
|
||||
|
||||
extend(a, b, thisArg);
|
||||
|
||||
expect(typeof a.getFoo).toEqual('function');
|
||||
expect(a.getFoo()).toEqual(thisArg.foo);
|
||||
});
|
||||
});
|
||||
69
tests/unit/utils/forEach.test.js
Normal file
69
tests/unit/utils/forEach.test.js
Normal file
@ -0,0 +1,69 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
const { forEach } = utils;
|
||||
|
||||
describe('utils::forEach', () => {
|
||||
it('should loop over an array', () => {
|
||||
let sum = 0;
|
||||
|
||||
forEach([1, 2, 3, 4, 5], (val) => {
|
||||
sum += val;
|
||||
});
|
||||
|
||||
expect(sum).toEqual(15);
|
||||
});
|
||||
|
||||
it('should loop over object keys', () => {
|
||||
let keys = '';
|
||||
let vals = 0;
|
||||
const obj = {
|
||||
b: 1,
|
||||
a: 2,
|
||||
r: 3,
|
||||
};
|
||||
|
||||
forEach(obj, (v, k) => {
|
||||
keys += k;
|
||||
vals += v;
|
||||
});
|
||||
|
||||
expect(keys).toEqual('bar');
|
||||
expect(vals).toEqual(6);
|
||||
});
|
||||
|
||||
it('should handle undefined gracefully', () => {
|
||||
let count = 0;
|
||||
|
||||
forEach(undefined, () => {
|
||||
count++;
|
||||
});
|
||||
|
||||
expect(count).toEqual(0);
|
||||
});
|
||||
|
||||
it('should make an array out of non-array argument', () => {
|
||||
let count = 0;
|
||||
|
||||
forEach(
|
||||
() => {},
|
||||
() => {
|
||||
count++;
|
||||
}
|
||||
);
|
||||
|
||||
expect(count).toEqual(1);
|
||||
});
|
||||
|
||||
it('should handle non object prototype gracefully', () => {
|
||||
let count = 0;
|
||||
const data = Object.create(null);
|
||||
data.foo = 'bar';
|
||||
|
||||
forEach(data, () => {
|
||||
count++;
|
||||
});
|
||||
|
||||
expect(count).toEqual(1);
|
||||
});
|
||||
});
|
||||
80
tests/unit/utils/isX.test.js
Normal file
80
tests/unit/utils/isX.test.js
Normal file
@ -0,0 +1,80 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
describe('utils::isX', () => {
|
||||
it('should validate Array', () => {
|
||||
expect(utils.isArray([])).toEqual(true);
|
||||
expect(utils.isArray({ length: 5 })).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate ArrayBuffer', () => {
|
||||
expect(utils.isArrayBuffer(new ArrayBuffer(2))).toEqual(true);
|
||||
expect(utils.isArrayBuffer({})).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate ArrayBufferView', () => {
|
||||
expect(utils.isArrayBufferView(new DataView(new ArrayBuffer(2)))).toEqual(true);
|
||||
});
|
||||
|
||||
it('should validate FormData', () => {
|
||||
expect(utils.isFormData(new FormData())).toEqual(true);
|
||||
});
|
||||
|
||||
it('should validate Blob', () => {
|
||||
expect(utils.isBlob(new Blob())).toEqual(true);
|
||||
});
|
||||
|
||||
it('should validate String', () => {
|
||||
expect(utils.isString('')).toEqual(true);
|
||||
expect(
|
||||
utils.isString({
|
||||
toString: function () {
|
||||
return '';
|
||||
},
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate Number', () => {
|
||||
expect(utils.isNumber(123)).toEqual(true);
|
||||
expect(utils.isNumber('123')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate Undefined', () => {
|
||||
expect(utils.isUndefined()).toEqual(true);
|
||||
expect(utils.isUndefined(null)).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate Object', () => {
|
||||
expect(utils.isObject({})).toEqual(true);
|
||||
expect(utils.isObject([])).toEqual(true);
|
||||
expect(utils.isObject(null)).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate plain Object', () => {
|
||||
expect(utils.isPlainObject({})).toEqual(true);
|
||||
expect(utils.isPlainObject([])).toEqual(false);
|
||||
expect(utils.isPlainObject(null)).toEqual(false);
|
||||
expect(utils.isPlainObject(Object.create({}))).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate Date', () => {
|
||||
expect(utils.isDate(new Date())).toEqual(true);
|
||||
expect(utils.isDate(Date.now())).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate Function', () => {
|
||||
expect(utils.isFunction(function () {})).toEqual(true);
|
||||
expect(utils.isFunction('function')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate URLSearchParams', () => {
|
||||
expect(utils.isURLSearchParams(new URLSearchParams())).toEqual(true);
|
||||
expect(utils.isURLSearchParams('foo=1&bar=2')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should validate TypedArray instance', () => {
|
||||
expect(utils.isTypedArray(new Uint8Array([1, 2, 3]))).toEqual(true);
|
||||
expect(utils.isTypedArray([1, 2, 3])).toEqual(false);
|
||||
});
|
||||
});
|
||||
13
tests/unit/utils/kindOf.test.js
Normal file
13
tests/unit/utils/kindOf.test.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
const { kindOf } = utils;
|
||||
|
||||
describe('utils::kindOf', () => {
|
||||
it('should return object tag', () => {
|
||||
expect(kindOf({})).toEqual('object');
|
||||
// cached result
|
||||
expect(kindOf({})).toEqual('object');
|
||||
expect(kindOf([])).toEqual('array');
|
||||
});
|
||||
});
|
||||
12
tests/unit/utils/kindOfTest.test.js
Normal file
12
tests/unit/utils/kindOfTest.test.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
describe('utils::kindOfTest', () => {
|
||||
it('should return true if the type is matched', () => {
|
||||
const { kindOfTest } = utils;
|
||||
const test = kindOfTest('number');
|
||||
|
||||
expect(test(123)).toEqual(true);
|
||||
expect(test('123')).toEqual(false);
|
||||
});
|
||||
});
|
||||
97
tests/unit/utils/merge.test.js
Normal file
97
tests/unit/utils/merge.test.js
Normal file
@ -0,0 +1,97 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
const { merge } = utils;
|
||||
|
||||
describe('utils::merge', () => {
|
||||
it('should be immutable', () => {
|
||||
const a = {};
|
||||
const b = { foo: 123 };
|
||||
const c = { bar: 456 };
|
||||
|
||||
merge(a, b, c);
|
||||
|
||||
expect(typeof a.foo).toEqual('undefined');
|
||||
expect(typeof a.bar).toEqual('undefined');
|
||||
expect(typeof b.bar).toEqual('undefined');
|
||||
expect(typeof c.foo).toEqual('undefined');
|
||||
});
|
||||
|
||||
it('should merge properties', () => {
|
||||
const a = { foo: 123 };
|
||||
const b = { bar: 456 };
|
||||
const c = { foo: 789 };
|
||||
const d = merge(a, b, c);
|
||||
|
||||
expect(d.foo).toEqual(789);
|
||||
expect(d.bar).toEqual(456);
|
||||
});
|
||||
|
||||
it('should merge recursively', () => {
|
||||
const a = { foo: { bar: 123 } };
|
||||
const b = { foo: { baz: 456 }, bar: { qux: 789 } };
|
||||
|
||||
expect(merge(a, b)).toEqual({
|
||||
foo: {
|
||||
bar: 123,
|
||||
baz: 456,
|
||||
},
|
||||
bar: {
|
||||
qux: 789,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove all references from nested objects', () => {
|
||||
const a = { foo: { bar: 123 } };
|
||||
const b = {};
|
||||
const d = merge(a, b);
|
||||
|
||||
expect(d).toEqual({
|
||||
foo: {
|
||||
bar: 123,
|
||||
},
|
||||
});
|
||||
|
||||
expect(d.foo).not.toBe(a.foo);
|
||||
});
|
||||
|
||||
it('handles null and undefined arguments', () => {
|
||||
expect(merge(undefined, undefined)).toEqual({});
|
||||
expect(merge(undefined, { foo: 123 })).toEqual({ foo: 123 });
|
||||
expect(merge({ foo: 123 }, undefined)).toEqual({ foo: 123 });
|
||||
|
||||
expect(merge(null, null)).toEqual({});
|
||||
expect(merge(null, { foo: 123 })).toEqual({ foo: 123 });
|
||||
expect(merge({ foo: 123 }, null)).toEqual({ foo: 123 });
|
||||
});
|
||||
|
||||
it('should replace properties with null', () => {
|
||||
expect(merge({}, { a: null })).toEqual({ a: null });
|
||||
expect(merge({ a: null }, {})).toEqual({ a: null });
|
||||
});
|
||||
|
||||
it('should replace properties with arrays', () => {
|
||||
expect(merge({}, { a: [1, 2, 3] })).toEqual({ a: [1, 2, 3] });
|
||||
expect(merge({ a: 2 }, { a: [1, 2, 3] })).toEqual({ a: [1, 2, 3] });
|
||||
expect(merge({ a: { b: 2 } }, { a: [1, 2, 3] })).toEqual({ a: [1, 2, 3] });
|
||||
});
|
||||
|
||||
it('should replace properties with cloned arrays', () => {
|
||||
const a = [1, 2, 3];
|
||||
const d = merge({}, { a });
|
||||
|
||||
expect(d).toEqual({ a: [1, 2, 3] });
|
||||
expect(d.a).not.toBe(a);
|
||||
});
|
||||
|
||||
it('should support caseless option', () => {
|
||||
const a = { x: 1 };
|
||||
const b = { X: 2 };
|
||||
const merged = merge.call({ caseless: true }, a, b);
|
||||
|
||||
expect(merged).toEqual({
|
||||
x: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
13
tests/unit/utils/toArray.test.js
Normal file
13
tests/unit/utils/toArray.test.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
const { toArray } = utils;
|
||||
|
||||
describe('utils::toArray', () => {
|
||||
it('should return null or an array copy depending on input', () => {
|
||||
expect(toArray()).toEqual(null);
|
||||
expect(toArray([])).toEqual([]);
|
||||
expect(toArray([1])).toEqual([1]);
|
||||
expect(toArray([1, 2, 3])).toEqual([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
13
tests/unit/utils/toFlatObject.test.js
Normal file
13
tests/unit/utils/toFlatObject.test.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
const { toFlatObject } = utils;
|
||||
|
||||
describe('utils::toFlatObject', () => {
|
||||
it('should resolve object proto chain to a flat object representation', () => {
|
||||
const a = { x: 1 };
|
||||
const b = Object.create(a, { y: { value: 2 } });
|
||||
const c = Object.create(b, { z: { value: 3 } });
|
||||
expect(toFlatObject(c)).toEqual({ x: 1, y: 2, z: 3 });
|
||||
});
|
||||
});
|
||||
12
tests/unit/utils/trim.test.js
Normal file
12
tests/unit/utils/trim.test.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import utils from '../../../lib/utils.js';
|
||||
|
||||
describe('utils::trim', () => {
|
||||
it('should trim spaces', () => {
|
||||
expect(utils.trim(' foo ')).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should trim tabs', () => {
|
||||
expect(utils.trim('\tfoo\t')).toEqual('foo');
|
||||
});
|
||||
});
|
||||
@ -25,6 +25,22 @@ export default defineConfig({
|
||||
setupFiles: ['tests/setup/browser.setup.js'],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: {
|
||||
name: 'browser-headless',
|
||||
include: ['tests/browser/**/*.browser.test.js'],
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: playwright(),
|
||||
instances: [
|
||||
{ browser: 'chromium', headless: true },
|
||||
{ browser: 'firefox', headless: true },
|
||||
{ browser: 'webkit', headless: true },
|
||||
],
|
||||
},
|
||||
setupFiles: ['tests/setup/browser.setup.js'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user