mirror of
https://github.com/axios/axios.git
synced 2026-04-11 02:11:50 +08:00
feat: add checks to support deno and bun (#10652)
* feat: added smoke tests for deno * feat: added bun smoke tests * chore: added workflows for deno and bun * chore: swap workflow implementation * chore: apply ai suggestion * chore: test alt install of bun deps * chore: deno install * chore: map bun file install * chore: try a different approach for bun * chore: unpack and then install for bun * chore: remove un-needed step * chore: try with tgx again for bun * chore: alternative zip approach * ci: full ci added back
This commit is contained in:
parent
23fcd5f278
commit
2f52f6b13b
57
.github/workflows/release-branch.yml
vendored
57
.github/workflows/release-branch.yml
vendored
@ -189,9 +189,64 @@ jobs:
|
|||||||
working-directory: tests/module/esm
|
working-directory: tests/module/esm
|
||||||
run: npm run test:module:esm
|
run: npm run test:module:esm
|
||||||
|
|
||||||
|
bun-smoke-tests:
|
||||||
|
name: Bun smoke tests
|
||||||
|
needs: build-and-run-vitest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Setup bun
|
||||||
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||||
|
- name: Download npm pack artifact
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: axios-tarball
|
||||||
|
path: artifacts
|
||||||
|
- name: Install packed axios
|
||||||
|
env:
|
||||||
|
TMPDIR: ${{ runner.temp }}
|
||||||
|
BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-cache
|
||||||
|
run: |
|
||||||
|
mkdir -p "$BUN_INSTALL_CACHE_DIR"
|
||||||
|
mv artifacts/axios-*.tgz artifacts/axios.tgz
|
||||||
|
cd tests/smoke/bun
|
||||||
|
bun add file:../../../artifacts/axios.tgz
|
||||||
|
- name: Run Bun smoke tests
|
||||||
|
working-directory: tests/smoke/bun
|
||||||
|
run: bun test
|
||||||
|
|
||||||
|
deno-smoke-tests:
|
||||||
|
name: Deno smoke tests
|
||||||
|
needs: build-and-run-vitest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Setup deno
|
||||||
|
uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
|
||||||
|
- name: Download npm pack artifact
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: axios-tarball
|
||||||
|
path: artifacts
|
||||||
|
- name: Prepare packed axios dist
|
||||||
|
run: mkdir -p dist && tar -xzf artifacts/axios-*.tgz -C artifacts && cp -R artifacts/package/dist/. ./dist
|
||||||
|
- name: Install Deno smoke test dependencies
|
||||||
|
working-directory: tests/smoke/deno
|
||||||
|
run: deno install
|
||||||
|
- name: Run Deno smoke tests
|
||||||
|
working-directory: tests/smoke/deno
|
||||||
|
run: deno task test
|
||||||
|
|
||||||
bump-version-and-create-pr:
|
bump-version-and-create-pr:
|
||||||
name: Bump version and create PR
|
name: Bump version and create PR
|
||||||
needs: [build-and-run-vitest, cjs-smoke-tests, esm-smoke-tests]
|
needs:
|
||||||
|
[build-and-run-vitest, cjs-smoke-tests, esm-smoke-tests, bun-smoke-tests, deno-smoke-tests]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
54
.github/workflows/run-ci.yml
vendored
54
.github/workflows/run-ci.yml
vendored
@ -182,3 +182,57 @@ jobs:
|
|||||||
- name: Run ESM module tests
|
- name: Run ESM module tests
|
||||||
working-directory: tests/module/esm
|
working-directory: tests/module/esm
|
||||||
run: npm run test:module:esm
|
run: npm run test:module:esm
|
||||||
|
|
||||||
|
bun-smoke-tests:
|
||||||
|
name: Bun smoke tests
|
||||||
|
needs: build-and-run-vitest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Setup bun
|
||||||
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||||
|
- name: Download npm pack artifact
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: axios-tarball
|
||||||
|
path: artifacts
|
||||||
|
- name: Install packed axios
|
||||||
|
env:
|
||||||
|
TMPDIR: ${{ runner.temp }}
|
||||||
|
BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-cache
|
||||||
|
run: |
|
||||||
|
mkdir -p "$BUN_INSTALL_CACHE_DIR"
|
||||||
|
mv artifacts/axios-*.tgz artifacts/axios.tgz
|
||||||
|
cd tests/smoke/bun
|
||||||
|
bun add file:../../../artifacts/axios.tgz
|
||||||
|
- name: Run Bun smoke tests
|
||||||
|
working-directory: tests/smoke/bun
|
||||||
|
run: bun test
|
||||||
|
|
||||||
|
deno-smoke-tests:
|
||||||
|
name: Deno smoke tests
|
||||||
|
needs: build-and-run-vitest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Setup deno
|
||||||
|
uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3
|
||||||
|
- name: Download npm pack artifact
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: axios-tarball
|
||||||
|
path: artifacts
|
||||||
|
- name: Prepare packed axios dist
|
||||||
|
run: mkdir -p dist && tar -xzf artifacts/axios-*.tgz -C artifacts && cp -R artifacts/package/dist/. ./dist
|
||||||
|
- name: Install Deno smoke test dependencies
|
||||||
|
working-directory: tests/smoke/deno
|
||||||
|
run: deno install
|
||||||
|
- name: Run Deno smoke tests
|
||||||
|
working-directory: tests/smoke/deno
|
||||||
|
run: deno task test
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,5 +14,6 @@ backup/
|
|||||||
dist/
|
dist/
|
||||||
.vscode/
|
.vscode/
|
||||||
openspec/
|
openspec/
|
||||||
|
.opencode/
|
||||||
docs/.vitepress/dist
|
docs/.vitepress/dist
|
||||||
docs/.vitepress/cache
|
docs/.vitepress/cache
|
||||||
|
|||||||
@ -109,6 +109,8 @@
|
|||||||
"test:vitest:watch": "vitest",
|
"test:vitest:watch": "vitest",
|
||||||
"test:smoke:cjs:vitest": "npm --prefix tests/smoke/cjs run test:smoke:cjs:mocha",
|
"test:smoke:cjs:vitest": "npm --prefix tests/smoke/cjs run test:smoke:cjs:mocha",
|
||||||
"test:smoke:esm:vitest": "npm --prefix tests/smoke/esm run test:smoke:esm:vitest",
|
"test:smoke:esm:vitest": "npm --prefix tests/smoke/esm run test:smoke:esm:vitest",
|
||||||
|
"test:smoke:deno": "deno task --cwd tests/smoke/deno test",
|
||||||
|
"test:smoke:bun": "bun test --cwd tests/smoke/bun",
|
||||||
"test:module:cjs": "npm --prefix tests/module/cjs run test:module:cjs",
|
"test:module:cjs": "npm --prefix tests/module/cjs run test:module:cjs",
|
||||||
"test:module:esm": "npm --prefix tests/module/esm run test:module:esm",
|
"test:module:esm": "npm --prefix tests/module/esm run test:module:esm",
|
||||||
"docs:dev": "cd docs && npm run docs:dev",
|
"docs:dev": "cd docs && npm run docs:dev",
|
||||||
|
|||||||
1695
tests/smoke/bun/bun.lock
Normal file
1695
tests/smoke/bun/bun.lock
Normal file
File diff suppressed because it is too large
Load Diff
11
tests/smoke/bun/package.json
Normal file
11
tests/smoke/bun/package.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "@axios/bun-smoke-tests",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
84
tests/smoke/bun/tests/cancel.smoke.test.ts
Normal file
84
tests/smoke/bun/tests/cancel.smoke.test.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const env = (fetch: typeof globalThis.fetch) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cancellation', () => {
|
||||||
|
test('pre-aborted AbortController cancels before fetch is called', async () => {
|
||||||
|
let fetchCallCount = 0;
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
fetchCallCount += 1;
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ ok: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
controller.abort();
|
||||||
|
|
||||||
|
const err = await axios
|
||||||
|
.get('https://example.com/cancel', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
signal: controller.signal,
|
||||||
|
env: env(fetch),
|
||||||
|
})
|
||||||
|
.catch((e: any) => e);
|
||||||
|
|
||||||
|
expect(axios.isCancel(err)).toBe(true);
|
||||||
|
expect(err.code).toBe('ERR_CANCELED');
|
||||||
|
expect(fetchCallCount).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('in-flight AbortController abort cancels the request', async () => {
|
||||||
|
const fetch = (_input: unknown, init?: RequestInit) =>
|
||||||
|
new Promise<Response>((_resolve, reject) => {
|
||||||
|
const abortError = () =>
|
||||||
|
reject(new DOMException('The operation was aborted', 'AbortError'));
|
||||||
|
|
||||||
|
const timeout = setTimeout(abortError, 20);
|
||||||
|
|
||||||
|
if (init?.signal) {
|
||||||
|
if (init.signal.aborted) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
abortError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
init.signal.addEventListener(
|
||||||
|
'abort',
|
||||||
|
() => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
abortError();
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
const request = axios.get('https://example.com/in-flight', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
signal: controller.signal,
|
||||||
|
env: env(fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.abort();
|
||||||
|
|
||||||
|
const err = await request.catch((e: any) => e);
|
||||||
|
|
||||||
|
expect(axios.isCancel(err)).toBe(true);
|
||||||
|
expect(err.code).toBe('ERR_CANCELED');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('axios.isCancel returns false for a plain Error', () => {
|
||||||
|
expect(axios.isCancel(new Error('random'))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
33
tests/smoke/bun/tests/error.smoke.test.ts
Normal file
33
tests/smoke/bun/tests/error.smoke.test.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const env = (fetch: typeof globalThis.fetch) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('errors', () => {
|
||||||
|
test('non-2xx response rejects with AxiosError and status 404', async () => {
|
||||||
|
const fetch = async () =>
|
||||||
|
new Response(JSON.stringify({ error: 'missing' }), {
|
||||||
|
status: 404,
|
||||||
|
statusText: 'Not Found',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const err = await axios
|
||||||
|
.get('https://example.com/missing', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(fetch),
|
||||||
|
})
|
||||||
|
.catch((e: any) => e);
|
||||||
|
|
||||||
|
expect(axios.isAxiosError(err)).toBe(true);
|
||||||
|
expect(err.response.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('axios.isAxiosError returns false for a plain Error', () => {
|
||||||
|
expect(axios.isAxiosError(new Error('plain'))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
126
tests/smoke/bun/tests/fetch.smoke.test.ts
Normal file
126
tests/smoke/bun/tests/fetch.smoke.test.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const createFetchMock = (
|
||||||
|
responseFactory?: (input: unknown, init: RequestInit) => Response | Promise<Response>
|
||||||
|
) => {
|
||||||
|
const calls: Array<{ input: unknown; init: RequestInit }> = [];
|
||||||
|
|
||||||
|
const mockFetch = async (input: unknown, init: RequestInit = {}) => {
|
||||||
|
calls.push({ input, 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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRequestMeta = async (input: unknown, init: RequestInit = {}) => {
|
||||||
|
const request = input instanceof Request ? input : new Request(input as string, init);
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: request.url,
|
||||||
|
method: request.method,
|
||||||
|
body:
|
||||||
|
request.method === 'GET' || request.method === 'HEAD'
|
||||||
|
? undefined
|
||||||
|
: await request.clone().text(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const env = (fetch: typeof globalThis.fetch) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetch adapter', () => {
|
||||||
|
test('GET resolves JSON response via fetch adapter', async () => {
|
||||||
|
const { mockFetch, getCalls } = createFetchMock();
|
||||||
|
|
||||||
|
const response = await axios.get('https://example.com/users', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.data).toEqual({ ok: true });
|
||||||
|
expect(getCalls()).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('POST serializes JSON body via fetch adapter', async () => {
|
||||||
|
const { mockFetch, getCalls } = createFetchMock();
|
||||||
|
|
||||||
|
await axios.post(
|
||||||
|
'https://example.com/items',
|
||||||
|
{ name: 'widget' },
|
||||||
|
{
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { input, init } = getCalls()[0];
|
||||||
|
const meta = await getRequestMeta(input, init);
|
||||||
|
expect(meta.body).toBe(JSON.stringify({ name: 'widget' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('HTTP methods are forwarded correctly', async () => {
|
||||||
|
const run = async (
|
||||||
|
method: 'delete' | 'head' | 'options' | 'put' | 'patch',
|
||||||
|
expected: string
|
||||||
|
) => {
|
||||||
|
const { mockFetch, getCalls } = createFetchMock();
|
||||||
|
|
||||||
|
if (method === 'put' || method === 'patch') {
|
||||||
|
await axios[method](
|
||||||
|
'https://example.com/items',
|
||||||
|
{ name: 'widget' },
|
||||||
|
{
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await axios[method]('https://example.com/items', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { input, init } = getCalls()[0];
|
||||||
|
const meta = await getRequestMeta(input, init);
|
||||||
|
expect(meta.method).toBe(expected);
|
||||||
|
};
|
||||||
|
|
||||||
|
await run('delete', 'DELETE');
|
||||||
|
await run('head', 'HEAD');
|
||||||
|
await run('options', 'OPTIONS');
|
||||||
|
await run('put', 'PUT');
|
||||||
|
await run('patch', 'PATCH');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('full URL is preserved in the fetch request', async () => {
|
||||||
|
const { mockFetch, getCalls } = createFetchMock();
|
||||||
|
|
||||||
|
await axios.get('https://example.com/users', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { input, init } = getCalls()[0];
|
||||||
|
const meta = await getRequestMeta(input, init);
|
||||||
|
|
||||||
|
expect(meta.url).toBe('https://example.com/users');
|
||||||
|
});
|
||||||
|
});
|
||||||
110
tests/smoke/bun/tests/formData.smoke.test.ts
Normal file
110
tests/smoke/bun/tests/formData.smoke.test.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import { PassThrough, Writable } from 'node:stream';
|
||||||
|
import FormDataPackage from 'form-data';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const createTransportMock = (
|
||||||
|
responseFactory?: (body: Buffer, options: Record<string, any>) => Record<string, any>
|
||||||
|
) => {
|
||||||
|
const transport = {
|
||||||
|
request(options: Record<string, any>, onResponse: (res: PassThrough) => void) {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
|
||||||
|
const req = new Writable({
|
||||||
|
write(chunk, _encoding, callback) {
|
||||||
|
chunks.push(Buffer.from(chunk));
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
}) as Writable & Record<string, any>;
|
||||||
|
|
||||||
|
req.destroyed = false;
|
||||||
|
req.setTimeout = () => {};
|
||||||
|
req.write = req.write.bind(req);
|
||||||
|
req.destroy = () => {
|
||||||
|
req.destroyed = true;
|
||||||
|
return req;
|
||||||
|
};
|
||||||
|
req.close = req.destroy;
|
||||||
|
const originalEnd = req.end.bind(req);
|
||||||
|
req.end = (...args: unknown[]) => {
|
||||||
|
originalEnd(...(args as Parameters<Writable['end']>));
|
||||||
|
|
||||||
|
const body = Buffer.concat(chunks);
|
||||||
|
const response = responseFactory ? responseFactory(body, options) : {};
|
||||||
|
|
||||||
|
const res = new PassThrough() as PassThrough & Record<string, any>;
|
||||||
|
res.statusCode = 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
return req;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return { transport };
|
||||||
|
};
|
||||||
|
|
||||||
|
const bodyAsUtf8 = (value: unknown) => {
|
||||||
|
return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('form data', () => {
|
||||||
|
test('native Bun FormData body produces multipart/form-data content-type', async () => {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('username', 'janedoe');
|
||||||
|
form.append('role', 'admin');
|
||||||
|
|
||||||
|
const { transport } = createTransportMock((body, options) => ({
|
||||||
|
body: JSON.stringify({
|
||||||
|
contentType:
|
||||||
|
options.headers && (options.headers['Content-Type'] || options.headers['content-type']),
|
||||||
|
payload: bodyAsUtf8(body),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = await axios.post('http://example.com/form', form, {
|
||||||
|
adapter: 'http',
|
||||||
|
proxy: false,
|
||||||
|
transport,
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('npm form-data package instance is serialized correctly', async () => {
|
||||||
|
const form = new FormDataPackage();
|
||||||
|
form.append('project', 'axios');
|
||||||
|
form.append('mode', 'compat');
|
||||||
|
|
||||||
|
const { transport } = createTransportMock((body, options) => ({
|
||||||
|
body: JSON.stringify({
|
||||||
|
contentType:
|
||||||
|
options.headers && (options.headers['Content-Type'] || options.headers['content-type']),
|
||||||
|
payload: bodyAsUtf8(body),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = await axios.post('http://example.com/npm-form-data', form as any, {
|
||||||
|
adapter: 'http',
|
||||||
|
proxy: false,
|
||||||
|
transport,
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
62
tests/smoke/bun/tests/headers.smoke.test.ts
Normal file
62
tests/smoke/bun/tests/headers.smoke.test.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const createFetchCapture = () => {
|
||||||
|
const calls: Request[] = [];
|
||||||
|
|
||||||
|
const fetch = async (input: unknown, init?: RequestInit) => {
|
||||||
|
const request = input instanceof Request ? input : new Request(input as string, init);
|
||||||
|
calls.push(request);
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ ok: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetch,
|
||||||
|
getCalls: () => calls,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const env = (fetch: typeof globalThis.fetch) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('headers', () => {
|
||||||
|
test('custom X-Custom header is forwarded to mock fetch (case-insensitive)', async () => {
|
||||||
|
const { fetch, getCalls } = createFetchCapture();
|
||||||
|
|
||||||
|
await axios.get('https://example.com/custom-headers', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
headers: {
|
||||||
|
'X-Custom': 'trace-123',
|
||||||
|
},
|
||||||
|
env: env(fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
const request = getCalls()[0];
|
||||||
|
expect(request.headers.get('x-custom')).toBe('trace-123');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('content-type application/json is inferred for JSON POST body', async () => {
|
||||||
|
const { fetch, getCalls } = createFetchCapture();
|
||||||
|
|
||||||
|
await axios.post(
|
||||||
|
'https://example.com/post-json',
|
||||||
|
{ name: 'widget' },
|
||||||
|
{
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(fetch),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const request = getCalls()[0];
|
||||||
|
const contentType = request.headers.get('content-type') || '';
|
||||||
|
|
||||||
|
expect(contentType.includes('application/json')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
104
tests/smoke/bun/tests/http.smoke.test.ts
Normal file
104
tests/smoke/bun/tests/http.smoke.test.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import { EventEmitter } from 'node:events';
|
||||||
|
import { PassThrough } from 'node:stream';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
type TransportCall = {
|
||||||
|
options: Record<string, any>;
|
||||||
|
body: Buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createTransportMock = (
|
||||||
|
responseFactory?: (body: Buffer, options: Record<string, any>) => Record<string, any>
|
||||||
|
) => {
|
||||||
|
const calls: TransportCall[] = [];
|
||||||
|
|
||||||
|
const transport = {
|
||||||
|
request(options: Record<string, any>, onResponse: (res: PassThrough) => void) {
|
||||||
|
const req = new EventEmitter() as Record<string, any>;
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
|
||||||
|
req.destroyed = false;
|
||||||
|
req.setTimeout = () => {};
|
||||||
|
req.write = (chunk?: unknown) => {
|
||||||
|
if (chunk !== undefined) {
|
||||||
|
chunks.push(Buffer.from(chunk as string));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
req.destroy = () => {
|
||||||
|
req.destroyed = true;
|
||||||
|
};
|
||||||
|
req.close = req.destroy;
|
||||||
|
req.end = (chunk?: unknown) => {
|
||||||
|
if (chunk !== undefined) {
|
||||||
|
chunks.push(Buffer.from(chunk as string));
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = Buffer.concat(chunks);
|
||||||
|
calls.push({ options, body });
|
||||||
|
|
||||||
|
const response = responseFactory ? responseFactory(body, options) : {};
|
||||||
|
const res = new PassThrough() as PassThrough & Record<string, any>;
|
||||||
|
res.statusCode = 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;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
transport,
|
||||||
|
getCalls: () => calls,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('http adapter', () => {
|
||||||
|
test('GET via http adapter returns mocked response data', async () => {
|
||||||
|
const { transport, getCalls } = createTransportMock();
|
||||||
|
|
||||||
|
const response = await axios.get('http://example.com/users', {
|
||||||
|
adapter: 'http',
|
||||||
|
proxy: false,
|
||||||
|
transport,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.data).toEqual({ ok: true });
|
||||||
|
expect(getCalls()).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('POST sends JSON-serialized body via http adapter', async () => {
|
||||||
|
const { transport, getCalls } = createTransportMock();
|
||||||
|
|
||||||
|
await axios.post(
|
||||||
|
'http://example.com/items',
|
||||||
|
{ name: 'widget' },
|
||||||
|
{
|
||||||
|
adapter: 'http',
|
||||||
|
proxy: false,
|
||||||
|
transport,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { body } = getCalls()[0];
|
||||||
|
expect(body.toString('utf8')).toBe(JSON.stringify({ name: 'widget' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default adapter selection in Bun routes through http adapter', async () => {
|
||||||
|
const { transport, getCalls } = createTransportMock();
|
||||||
|
|
||||||
|
await axios.get('http://example.com/default-adapter', {
|
||||||
|
proxy: false,
|
||||||
|
transport,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getCalls()).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
19
tests/smoke/bun/tests/import.smoke.test.ts
Normal file
19
tests/smoke/bun/tests/import.smoke.test.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
describe('Bun importing', () => {
|
||||||
|
test('default export is callable', () => {
|
||||||
|
expect(typeof axios).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('named exports are present', async () => {
|
||||||
|
const exports = (await import('axios')) as Record<string, any>;
|
||||||
|
|
||||||
|
expect(typeof (exports.axios ?? exports.default)).toBe('function');
|
||||||
|
expect(typeof (exports.create ?? exports.default.create)).toBe('function');
|
||||||
|
expect(typeof exports.isCancel).toBe('function');
|
||||||
|
expect(typeof exports.isAxiosError).toBe('function');
|
||||||
|
expect(typeof exports.CancelToken).toBe('function');
|
||||||
|
expect(typeof exports.VERSION).toBe('string');
|
||||||
|
});
|
||||||
|
});
|
||||||
65
tests/smoke/bun/tests/interceptors.smoke.test.ts
Normal file
65
tests/smoke/bun/tests/interceptors.smoke.test.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const createFetchCapture = () => {
|
||||||
|
const calls: Request[] = [];
|
||||||
|
|
||||||
|
const fetch = async (input: unknown, init?: RequestInit) => {
|
||||||
|
const request = input instanceof Request ? input : new Request(input as string, init);
|
||||||
|
calls.push(request);
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ value: 'ok' }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetch,
|
||||||
|
getCalls: () => calls,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const env = (fetch: typeof globalThis.fetch) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('interceptors', () => {
|
||||||
|
test('request interceptor header is forwarded to fetch', async () => {
|
||||||
|
const { fetch, getCalls } = createFetchCapture();
|
||||||
|
const client = axios.create({
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
client.interceptors.request.use((config: any) => {
|
||||||
|
config.headers = config.headers || {};
|
||||||
|
config.headers['X-Added'] = 'yes';
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.get('https://example.com/interceptor-request');
|
||||||
|
|
||||||
|
expect(getCalls()).toHaveLength(1);
|
||||||
|
expect(getCalls()[0].headers.get('x-added')).toBe('yes');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('response interceptor transform is reflected in resolved value', async () => {
|
||||||
|
const { fetch } = createFetchCapture();
|
||||||
|
const client = axios.create({
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
client.interceptors.response.use((response: any) => {
|
||||||
|
response.data.value = String(response.data.value).toUpperCase();
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await client.get('https://example.com/interceptor-response');
|
||||||
|
|
||||||
|
expect(response.data).toEqual({ value: 'OK' });
|
||||||
|
});
|
||||||
|
});
|
||||||
45
tests/smoke/bun/tests/progress.smoke.test.ts
Normal file
45
tests/smoke/bun/tests/progress.smoke.test.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const env = (fetch: typeof globalThis.fetch) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('progress', () => {
|
||||||
|
test('onDownloadProgress fires with loaded > 0 for streaming fetch response', async () => {
|
||||||
|
const samples: number[] = [];
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(new TextEncoder().encode('ab'));
|
||||||
|
controller.enqueue(new TextEncoder().encode('cd'));
|
||||||
|
controller.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response(stream, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
'Content-Length': '4',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await axios.get('https://example.com/download', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
responseType: 'text',
|
||||||
|
onDownloadProgress: ({ loaded }: { loaded: number }) => {
|
||||||
|
samples.push(loaded);
|
||||||
|
},
|
||||||
|
env: env(fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.data).toBe('abcd');
|
||||||
|
expect(samples.length).toBeGreaterThan(0);
|
||||||
|
expect(samples.some((loaded) => loaded > 0)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
50
tests/smoke/bun/tests/timeout.smoke.test.ts
Normal file
50
tests/smoke/bun/tests/timeout.smoke.test.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const env = (fetch: typeof globalThis.fetch) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createAbortedError = () => {
|
||||||
|
const error = new Error('The operation was aborted') as Error & { code?: string; name: string };
|
||||||
|
error.name = 'AbortError';
|
||||||
|
error.code = 'ECONNABORTED';
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('timeout', () => {
|
||||||
|
test('timeout: 50 with never-resolving fetch mock rejects with ECONNABORTED', async () => {
|
||||||
|
const fetch = (input: unknown, init?: RequestInit) =>
|
||||||
|
new Promise<Response>((_resolve, reject) => {
|
||||||
|
const signal = init?.signal || (input instanceof Request ? input.signal : undefined);
|
||||||
|
|
||||||
|
if (signal) {
|
||||||
|
if (signal.aborted) {
|
||||||
|
reject(createAbortedError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal.addEventListener(
|
||||||
|
'abort',
|
||||||
|
() => {
|
||||||
|
reject(createAbortedError());
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const err = await axios
|
||||||
|
.get('https://example.com/timeout', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
timeout: 50,
|
||||||
|
env: env(fetch),
|
||||||
|
})
|
||||||
|
.catch((e: any) => e);
|
||||||
|
|
||||||
|
expect(axios.isAxiosError(err)).toBe(true);
|
||||||
|
expect(err.code).toBe('ECONNABORTED');
|
||||||
|
});
|
||||||
|
});
|
||||||
12
tests/smoke/deno/deno.json
Normal file
12
tests/smoke/deno/deno.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"@std/assert": "jsr:@std/assert@1.0.19",
|
||||||
|
"axios": "../../../dist/esm/axios.js"
|
||||||
|
},
|
||||||
|
"tasks": {
|
||||||
|
"test": "deno test --allow-read tests/"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"include": ["tests/**/*.smoke.test.ts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
23
tests/smoke/deno/deno.lock
generated
Normal file
23
tests/smoke/deno/deno.lock
generated
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"specifiers": {
|
||||||
|
"jsr:@std/assert@1.0.19": "1.0.19",
|
||||||
|
"jsr:@std/internal@^1.0.12": "1.0.12"
|
||||||
|
},
|
||||||
|
"jsr": {
|
||||||
|
"@std/assert@1.0.19": {
|
||||||
|
"integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/internal"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@std/internal@1.0.12": {
|
||||||
|
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/assert@1.0.19"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
81
tests/smoke/deno/tests/cancel.smoke.test.ts
Normal file
81
tests/smoke/deno/tests/cancel.smoke.test.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { assertEquals } from '@std/assert';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const env = (fetch: any) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('cancel: pre-aborted AbortController cancels request', async () => {
|
||||||
|
let fetchCallCount = 0;
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
fetchCallCount += 1;
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ ok: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
controller.abort();
|
||||||
|
|
||||||
|
const err = await axios
|
||||||
|
.get('https://example.com/cancel', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
signal: controller.signal,
|
||||||
|
env: env(fetch),
|
||||||
|
})
|
||||||
|
.catch((e: any) => e);
|
||||||
|
|
||||||
|
assertEquals(axios.isCancel(err), true);
|
||||||
|
assertEquals(err.code, 'ERR_CANCELED');
|
||||||
|
assertEquals(fetchCallCount, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('cancel: in-flight abort cancels request', async () => {
|
||||||
|
const fetch = (_input: any, init?: any) =>
|
||||||
|
new Promise<Response>((_resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
reject(new DOMException('The operation was aborted', 'AbortError'));
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
if (init?.signal) {
|
||||||
|
if (init.signal.aborted) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(new DOMException('The operation was aborted', 'AbortError'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
init.signal.addEventListener(
|
||||||
|
'abort',
|
||||||
|
() => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(new DOMException('The operation was aborted', 'AbortError'));
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
const request = axios.get('https://example.com/in-flight', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
signal: controller.signal,
|
||||||
|
env: env(fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.abort();
|
||||||
|
|
||||||
|
const err = await request.catch((e: any) => e);
|
||||||
|
|
||||||
|
assertEquals(axios.isCancel(err), true);
|
||||||
|
assertEquals(err.code, 'ERR_CANCELED');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('cancel: isCancel returns false for plain Error', () => {
|
||||||
|
assertEquals(axios.isCancel(new Error('random')), false);
|
||||||
|
});
|
||||||
51
tests/smoke/deno/tests/error.smoke.test.ts
Normal file
51
tests/smoke/deno/tests/error.smoke.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { assertEquals } from '@std/assert';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const env = (fetch: any) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('errors: rejects with AxiosError for 500', async () => {
|
||||||
|
const fetch = async () =>
|
||||||
|
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: env(fetch),
|
||||||
|
})
|
||||||
|
.catch((e: any) => e);
|
||||||
|
|
||||||
|
assertEquals(axios.isAxiosError(err), true);
|
||||||
|
assertEquals(err.response.status, 500);
|
||||||
|
assertEquals(err.response.data, { error: 'boom' });
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('errors: rejects with AxiosError for 404', async () => {
|
||||||
|
const fetch = async () =>
|
||||||
|
new Response(JSON.stringify({ error: 'missing' }), {
|
||||||
|
status: 404,
|
||||||
|
statusText: 'Not Found',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const err = await axios
|
||||||
|
.get('https://example.com/missing', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(fetch),
|
||||||
|
})
|
||||||
|
.catch((e: any) => e);
|
||||||
|
|
||||||
|
assertEquals(axios.isAxiosError(err), true);
|
||||||
|
assertEquals(err.response.status, 404);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('errors: isAxiosError returns false for plain Error', () => {
|
||||||
|
assertEquals(axios.isAxiosError(new Error('plain')), false);
|
||||||
|
});
|
||||||
126
tests/smoke/deno/tests/fetch.smoke.test.ts
Normal file
126
tests/smoke/deno/tests/fetch.smoke.test.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { assertEquals } from '@std/assert';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const createFetchMock = (
|
||||||
|
responseFactory?: (input: any, init: any) => Response | Promise<Response>
|
||||||
|
) => {
|
||||||
|
const calls: Array<{ input: any; init: any }> = [];
|
||||||
|
|
||||||
|
const mockFetch = async (input: any, init: any = {}) => {
|
||||||
|
calls.push({ input, 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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRequestMeta = async (input: any, init: any) => {
|
||||||
|
const request = input instanceof Request ? input : new Request(input, init);
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: request.url,
|
||||||
|
method: request.method,
|
||||||
|
body:
|
||||||
|
request.method === 'GET' || request.method === 'HEAD'
|
||||||
|
? undefined
|
||||||
|
: await request.clone().text(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const env = (fetch: any) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('fetch adapter: GET resolves JSON response', async () => {
|
||||||
|
const { mockFetch, getCalls } = createFetchMock();
|
||||||
|
|
||||||
|
const response = await axios.get('https://example.com/users', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(response.status, 200);
|
||||||
|
assertEquals(response.data, { ok: true });
|
||||||
|
assertEquals(getCalls().length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('fetch adapter: forwards HTTP methods', async () => {
|
||||||
|
const run = async (
|
||||||
|
method: 'delete' | 'head' | 'options' | 'post' | 'put' | 'patch',
|
||||||
|
expected: string
|
||||||
|
) => {
|
||||||
|
const { mockFetch, getCalls } = createFetchMock();
|
||||||
|
|
||||||
|
if (method === 'post' || method === 'put' || method === 'patch') {
|
||||||
|
await axios[method](
|
||||||
|
'https://example.com/items',
|
||||||
|
{ name: 'widget' },
|
||||||
|
{
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await axios[method]('https://example.com/items', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { input, init } = getCalls()[0];
|
||||||
|
const meta = await getRequestMeta(input, init);
|
||||||
|
assertEquals(meta.method, expected);
|
||||||
|
};
|
||||||
|
|
||||||
|
await run('delete', 'DELETE');
|
||||||
|
await run('head', 'HEAD');
|
||||||
|
await run('options', 'OPTIONS');
|
||||||
|
await run('post', 'POST');
|
||||||
|
await run('put', 'PUT');
|
||||||
|
await run('patch', 'PATCH');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('fetch adapter: serializes JSON body for POST', async () => {
|
||||||
|
const { mockFetch, getCalls } = createFetchMock();
|
||||||
|
|
||||||
|
await axios.post(
|
||||||
|
'https://example.com/items',
|
||||||
|
{ name: 'widget' },
|
||||||
|
{
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { input, init } = getCalls()[0];
|
||||||
|
const meta = await getRequestMeta(input, init);
|
||||||
|
|
||||||
|
assertEquals(meta.body, JSON.stringify({ name: 'widget' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('fetch adapter: forwards full URL', async () => {
|
||||||
|
const { mockFetch, getCalls } = createFetchMock();
|
||||||
|
|
||||||
|
await axios.get('https://example.com/users', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(mockFetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { input, init } = getCalls()[0];
|
||||||
|
const meta = await getRequestMeta(input, init);
|
||||||
|
|
||||||
|
assertEquals(meta.url, 'https://example.com/users');
|
||||||
|
});
|
||||||
85
tests/smoke/deno/tests/headers.smoke.test.ts
Normal file
85
tests/smoke/deno/tests/headers.smoke.test.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { assertEquals } from '@std/assert';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const createFetchCapture = () => {
|
||||||
|
const calls: Request[] = [];
|
||||||
|
|
||||||
|
const fetch = async (input: any, init?: any) => {
|
||||||
|
const request = input instanceof Request ? input : new Request(input, init);
|
||||||
|
calls.push(request);
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ ok: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetch,
|
||||||
|
getCalls: () => calls,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const env = (fetch: any) => ({
|
||||||
|
fetch,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('headers: default Accept header is sent', async () => {
|
||||||
|
const { fetch, getCalls } = createFetchCapture();
|
||||||
|
|
||||||
|
await axios.get('https://example.com/default-headers', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
const request = getCalls()[0];
|
||||||
|
assertEquals(request.headers.get('accept'), 'application/json, text/plain, */*');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('headers: custom headers are forwarded', async () => {
|
||||||
|
const { fetch, getCalls } = createFetchCapture();
|
||||||
|
|
||||||
|
await axios.get('https://example.com/custom-headers', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
headers: {
|
||||||
|
'X-Trace-Id': 'trace-123',
|
||||||
|
Authorization: 'Bearer token-abc',
|
||||||
|
},
|
||||||
|
env: env(fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
const request = getCalls()[0];
|
||||||
|
assertEquals(request.headers.get('x-trace-id'), 'trace-123');
|
||||||
|
assertEquals(request.headers.get('authorization'), 'Bearer token-abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('headers: content-type is set for JSON POST payload', async () => {
|
||||||
|
const { fetch, getCalls } = createFetchCapture();
|
||||||
|
|
||||||
|
await axios.post(
|
||||||
|
'https://example.com/post-json',
|
||||||
|
{ name: 'widget' },
|
||||||
|
{
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(fetch),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const request = getCalls()[0];
|
||||||
|
const contentType = request.headers.get('content-type') || '';
|
||||||
|
assertEquals(contentType.includes('application/json'), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('headers: content-type is absent for bodyless GET', async () => {
|
||||||
|
const { fetch, getCalls } = createFetchCapture();
|
||||||
|
|
||||||
|
await axios.get('https://example.com/get-no-body', {
|
||||||
|
adapter: 'fetch',
|
||||||
|
env: env(fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
const request = getCalls()[0];
|
||||||
|
assertEquals(request.headers.get('content-type'), null);
|
||||||
|
});
|
||||||
18
tests/smoke/deno/tests/import.smoke.test.ts
Normal file
18
tests/smoke/deno/tests/import.smoke.test.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { assertEquals } from '@std/assert';
|
||||||
|
import axios, { AxiosError, AxiosHeaders, CanceledError } from 'axios';
|
||||||
|
|
||||||
|
Deno.test('Deno importing: default export is callable', () => {
|
||||||
|
assertEquals(typeof axios, 'function');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('Deno importing: named exports are functions', () => {
|
||||||
|
assertEquals(typeof AxiosError, 'function');
|
||||||
|
assertEquals(typeof CanceledError, 'function');
|
||||||
|
assertEquals(typeof AxiosHeaders, 'function');
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test('Deno importing: named exports match axios properties', () => {
|
||||||
|
assertEquals(axios.AxiosError, AxiosError);
|
||||||
|
assertEquals(axios.CanceledError, CanceledError);
|
||||||
|
assertEquals(axios.AxiosHeaders, AxiosHeaders);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user