axios-axios/test/unit/adapters/fetch.js
Jay fa337332b9
Update unit testing flows as part of migration to vitest (#7484)
* chore: small fixes to tests

* feat: transitional move to vitests

* feat: moving unit tests in progress

* feat: moving more unit tests over

* feat: more tests moved

* feat: updated more sections of the http test

* chore: wip http tests

* chore: wip http tests

* chore: more http tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: tests

* chore: remove un-needed docs

* chore: update package lock

* chore: update lock
2026-03-06 20:42:14 +02:00

542 lines
14 KiB
JavaScript

/* eslint-env mocha */
import assert from 'assert';
import {
startHTTPServer,
stopHTTPServer,
LOCAL_SERVER_URL,
setTimeoutAsync,
makeReadableStream,
generateReadable,
makeEchoStream,
} from '../../helpers/server.js';
import axios from '../../../index.js';
import stream from 'stream';
import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill.js';
import util from 'util';
const pipelineAsync = util.promisify(stream.pipeline);
const fetchAxios = axios.create({
baseURL: LOCAL_SERVER_URL,
adapter: 'fetch',
});
let server;
describe('supports fetch with nodejs', function () {
before(function () {
if (typeof fetch !== 'function') {
this.skip();
}
});
afterEach(async function () {
await stopHTTPServer(server);
server = null;
});
describe('responses', async () => {
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',
});
assert.deepStrictEqual(data, originalData);
});
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',
});
assert.deepStrictEqual(
data,
Uint8Array.from(await new TextEncoder().encode(originalData)).buffer
);
});
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',
});
assert.deepStrictEqual(data, new Blob([originalData]));
});
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',
});
assert.ok(data instanceof ReadableStream, 'data is not instanceof ReadableStream');
let response = new Response(data);
assert.deepStrictEqual(await response.text(), originalData);
});
it(`should support formData response type`, async function () {
this.timeout(5000);
const originalData = new FormData();
originalData.append('x', '123');
server = await startHTTPServer(async (req, res) => {
const response = await new Response(originalData);
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())
);
});
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',
});
assert.deepStrictEqual(data, originalData);
});
});
describe('progress', () => {
describe('upload', function () {
it('should support upload progress capturing', async function () {
this.timeout(15000);
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 samples = [];
const { data } = await fetchAxios.post('/', 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,
};
}
})()
)
);
});
it('should not fail with get method', async () => {
server = await startHTTPServer((req, res) => res.end('OK'));
const { data } = await fetchAxios.get('/', {
onUploadProgress() {},
});
assert.strictEqual(data, 'OK');
});
});
describe('download', function () {
it('should support download progress capturing', async function () {
this.timeout(15000);
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 samples = [];
const { data } = await fetchAxios.post('/', 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,
};
}
})()
)
);
});
});
});
it('should support basic auth', async () => {
server = await startHTTPServer((req, res) => res.end(req.headers.authorization));
const user = 'foo';
const headers = { Authorization: 'Bearer 1234' };
const res = await axios.get('http://' + user + '@localhost:4444/', { headers: headers });
const base64 = Buffer.from(user + ':', 'utf8').toString('base64');
assert.equal(res.data, 'Basic ' + base64);
});
it('should support stream.Readable as a payload', async () => {
server = await startHTTPServer();
const { data } = await fetchAxios.post('/', stream.Readable.from('OK'));
assert.strictEqual(data, 'OK');
});
describe('request aborting', function () {
it('should be able to abort the request stream', async function () {
server = await startHTTPServer({
rate: 100000,
useBuffering: true,
});
const controller = new AbortController();
setTimeout(() => {
controller.abort();
}, 500);
await assert.rejects(async () => {
await fetchAxios.post('/', makeReadableStream(), {
responseType: 'stream',
signal: controller.signal,
});
}, /CanceledError/);
});
it('should be able to abort the response stream', async function () {
server = await startHTTPServer((req, res) => {
pipelineAsync(generateReadable(10000, 10), res);
});
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):/);
});
});
it('should support a timeout', async () => {
server = await startHTTPServer(async (req, res) => {
await setTimeoutAsync(1000);
res.end('OK');
});
const timeout = 500;
const ts = Date.now();
await assert.rejects(async () => {
await fetchAxios('/', {
timeout,
});
}, /timeout/);
const passed = Date.now() - ts;
assert.ok(passed >= timeout - 5, `early cancellation detected (${passed} ms)`);
});
it('should combine baseURL and url', async () => {
server = await startHTTPServer();
const res = await fetchAxios('/foo');
assert.equal(res.config.baseURL, LOCAL_SERVER_URL);
assert.equal(res.config.url, '/foo');
});
it('should support params', async () => {
server = await startHTTPServer((req, res) => res.end(req.url));
const { data } = await fetchAxios.get('/?test=1', {
params: {
foo: 1,
bar: 2,
},
});
assert.strictEqual(data, '/?test=1&foo=1&bar=2');
});
it('should handle fetch failed error as an AxiosError with ERR_NETWORK code', async () => {
try {
await fetchAxios('http://notExistsUrl.in.nowhere');
assert.fail('should fail');
} catch (err) {
assert.strictEqual(String(err), 'AxiosError: Network Error');
assert.strictEqual(err.cause && err.cause.code, 'ENOTFOUND');
}
});
it('should get response headers', async () => {
server = await startHTTPServer((req, res) => {
res.setHeader('foo', 'bar');
res.end(req.url);
});
const { headers } = await fetchAxios.get('/', {
responseType: 'stream',
});
assert.strictEqual(headers.get('foo'), 'bar');
});
describe('fetch adapter - Content-Type handling', function () {
it('should set correct Content-Type for FormData automatically', async function () {
const FormData = (await import('form-data')).default; // Node FormData
const form = new FormData();
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');
});
await fetchAxios.post('/form', form);
});
});
describe('env config', () => {
it('should respect env fetch API configuration', async () => {
const { data, headers } = await fetchAxios.get('/', {
env: {
fetch() {
return {
headers: {
foo: '1',
},
text: async () => 'test',
};
},
},
});
assert.strictEqual(headers.get('foo'), '1');
assert.strictEqual(data, 'test');
});
it('should be able to request with lack of Request object', async () => {
const form = new FormData();
form.append('x', '1');
const { data, headers } = await fetchAxios.post('/', form, {
onUploadProgress() {
// dummy listener to activate streaming
},
env: {
Request: null,
fetch() {
return {
headers: {
foo: '1',
},
text: async () => 'test',
};
},
},
});
assert.strictEqual(headers.get('foo'), '1');
assert.strictEqual(data, 'test');
});
it('should be able to handle response with lack of Response object', async () => {
const { data, headers } = await fetchAxios.get('/', {
onDownloadProgress() {
// dummy listener to activate streaming
},
env: {
Request: null,
Response: null,
fetch() {
return {
headers: {
foo: '1',
},
text: async () => 'test',
};
},
},
});
assert.strictEqual(headers.get('foo'), '1');
assert.strictEqual(data, 'test');
});
it('should fallback to the global on undefined env value', async () => {
server = await startHTTPServer((req, res) => res.end('OK'));
const { data } = await fetchAxios.get('/', {
env: {
fetch: undefined,
},
});
assert.strictEqual(data, 'OK');
});
it('should use current global fetch when env fetch is not specified', async () => {
const globalFetch = fetch;
fetch = async () => {
return {
headers: {
foo: '1',
},
text: async () => 'global',
};
};
try {
server = await startHTTPServer((req, res) => res.end('OK'));
const { data } = await fetchAxios.get('/', {
env: {
fetch: undefined,
},
});
assert.strictEqual(data, 'global');
} finally {
fetch = globalFetch;
}
});
});
});