mirror of
https://github.com/tenrok/axios.git
synced 2026-06-17 19:21:29 +03:00
f39203dcbe
* feat: add QUERY HTTP method support Add support for the HTTP QUERY method as defined in draft-ietf-httpbis-safe-method-w-body. QUERY is a safe, idempotent method like GET but carries a request body, making it suitable for complex queries that cannot be expressed in a URL. Changes: - Add axios.query(url, data, config) and axios.queryForm() shorthands - Register 'query' in default headers initialization - Include 'query' in header cleanup during request dispatch - Add 'QUERY' to Method type in TypeScript definitions (d.ts and d.cts) - Add query/queryForm signatures to Axios class type definitions - Add 'query' to HeadersDefaults interface - Update unit and module typing tests Closes #5465 Signed-off-by: Pierluigi Lenoci <pierluigilenoci@gmail.com> * test: add thorough QUERY method tests Add comprehensive tests for the QUERY HTTP method covering: - Request method correctness (via mock adapter and real HTTP server) - Request body support (QUERY accepts a body like POST/PUT/PATCH) - Custom headers handling - baseURL configuration with instances - Content-Type auto-detection (application/json for objects) - Instance method and defaults merging - Generic request form axios({ method: 'query' }) - queryForm() multipart/form-data support - Integration tests against a real HTTP server verifying the QUERY method string arrives correctly on the wire Signed-off-by: Pierluigi Lenoci <pierluigilenoci@gmail.com> * chore: updated docs with all translations * chore: drop formquery method as this is probably not a real use case * chore: remove un-needed file --------- Signed-off-by: Pierluigi Lenoci <pierluigilenoci@gmail.com> Co-authored-by: Jay <jasonsaayman@gmail.com>
269 lines
7.8 KiB
JavaScript
269 lines
7.8 KiB
JavaScript
import { describe, it } from 'vitest';
|
|
import assert from 'assert';
|
|
import axios from '../../index.js';
|
|
import { startHTTPServer, stopHTTPServer } from '../setup/server.js';
|
|
|
|
describe('QUERY method', () => {
|
|
describe('static axios.query()', () => {
|
|
it('should make a request with the QUERY HTTP method', async () => {
|
|
const response = await axios.query('/test', null, {
|
|
adapter: (config) => {
|
|
assert.strictEqual(config.method, 'query');
|
|
return Promise.resolve({
|
|
data: null,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
config,
|
|
request: {},
|
|
});
|
|
},
|
|
});
|
|
|
|
assert.strictEqual(response.status, 200);
|
|
});
|
|
|
|
it('should support a request body', async () => {
|
|
const requestBody = { selector: 'field1, field2', filter: { active: true } };
|
|
|
|
await axios.query('/search', requestBody, {
|
|
adapter: (config) => {
|
|
assert.deepStrictEqual(config.data, JSON.stringify(requestBody));
|
|
return Promise.resolve({
|
|
data: null,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
config,
|
|
request: {},
|
|
});
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should support custom headers', async () => {
|
|
await axios.query('/test', null, {
|
|
headers: {
|
|
'X-Custom-Header': 'custom-value',
|
|
Authorization: 'Bearer token-abc',
|
|
},
|
|
adapter: (config) => {
|
|
assert.strictEqual(config.headers.get('X-Custom-Header'), 'custom-value');
|
|
assert.strictEqual(config.headers.get('Authorization'), 'Bearer token-abc');
|
|
return Promise.resolve({
|
|
data: null,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
config,
|
|
request: {},
|
|
});
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should work with baseURL configuration', async () => {
|
|
const instance = axios.create({ baseURL: 'http://example.com/api' });
|
|
|
|
await instance.query('/resources', { fields: ['name'] }, {
|
|
adapter: (config) => {
|
|
assert.strictEqual(config.baseURL, 'http://example.com/api');
|
|
assert.strictEqual(config.url, '/resources');
|
|
assert.strictEqual(config.method, 'query');
|
|
return Promise.resolve({
|
|
data: null,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
config,
|
|
request: {},
|
|
});
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should set Content-Type to application/json for object bodies', async () => {
|
|
await axios.query('/test', { key: 'value' }, {
|
|
adapter: (config) => {
|
|
assert.ok(
|
|
config.headers.get('Content-Type').includes('application/json'),
|
|
'Expected Content-Type to include application/json'
|
|
);
|
|
return Promise.resolve({
|
|
data: null,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
config,
|
|
request: {},
|
|
});
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('instance.query()', () => {
|
|
it('should make a request with the QUERY HTTP method on an instance', async () => {
|
|
const instance = axios.create();
|
|
|
|
const response = await instance.query('/test', null, {
|
|
adapter: (config) => {
|
|
assert.strictEqual(config.method, 'query');
|
|
return Promise.resolve({
|
|
data: null,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
config,
|
|
request: {},
|
|
});
|
|
},
|
|
});
|
|
|
|
assert.strictEqual(response.status, 200);
|
|
});
|
|
|
|
it('should merge instance defaults with request config', async () => {
|
|
const instance = axios.create({
|
|
headers: { 'X-Instance-Header': 'from-instance' },
|
|
});
|
|
|
|
await instance.query('/test', null, {
|
|
headers: { 'X-Request-Header': 'from-request' },
|
|
adapter: (config) => {
|
|
assert.strictEqual(config.headers.get('X-Instance-Header'), 'from-instance');
|
|
assert.strictEqual(config.headers.get('X-Request-Header'), 'from-request');
|
|
return Promise.resolve({
|
|
data: null,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
config,
|
|
request: {},
|
|
});
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('axios({ method: "query" })', () => {
|
|
it('should support the generic request form', async () => {
|
|
const response = await axios({
|
|
method: 'query',
|
|
url: '/test',
|
|
data: { selector: '*' },
|
|
adapter: (config) => {
|
|
assert.strictEqual(config.method, 'query');
|
|
assert.deepStrictEqual(config.data, JSON.stringify({ selector: '*' }));
|
|
return Promise.resolve({
|
|
data: { result: 'ok' },
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
config,
|
|
request: {},
|
|
});
|
|
},
|
|
});
|
|
|
|
assert.deepStrictEqual(response.data, { result: 'ok' });
|
|
});
|
|
});
|
|
|
|
describe('with HTTP server', () => {
|
|
it('should send QUERY requests with a body to a real server', async () => {
|
|
const server = await startHTTPServer(
|
|
(req, res) => {
|
|
let body = '';
|
|
req.on('data', (chunk) => { body += chunk; });
|
|
req.on('end', () => {
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.end(JSON.stringify({
|
|
method: req.method,
|
|
url: req.url,
|
|
body,
|
|
headers: req.headers,
|
|
}));
|
|
});
|
|
},
|
|
{ port: 0 }
|
|
);
|
|
|
|
try {
|
|
const { data } = await axios.query(
|
|
`http://localhost:${server.address().port}/search`,
|
|
{ selector: 'field1' }
|
|
);
|
|
|
|
assert.strictEqual(data.method, 'QUERY');
|
|
assert.strictEqual(data.url, '/search');
|
|
|
|
const parsedBody = JSON.parse(data.body);
|
|
assert.deepStrictEqual(parsedBody, { selector: 'field1' });
|
|
assert.ok(
|
|
data.headers['content-type'].includes('application/json'),
|
|
'Expected server to receive application/json content-type'
|
|
);
|
|
} finally {
|
|
await stopHTTPServer(server);
|
|
}
|
|
});
|
|
|
|
it('should send QUERY requests with custom headers to a real server', async () => {
|
|
const server = await startHTTPServer(
|
|
(req, res) => {
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.end(JSON.stringify({
|
|
method: req.method,
|
|
headers: req.headers,
|
|
}));
|
|
},
|
|
{ port: 0 }
|
|
);
|
|
|
|
try {
|
|
const { data } = await axios.query(
|
|
`http://localhost:${server.address().port}/test`,
|
|
null,
|
|
{
|
|
headers: {
|
|
'X-Custom': 'test-value',
|
|
},
|
|
}
|
|
);
|
|
|
|
assert.strictEqual(data.method, 'QUERY');
|
|
assert.strictEqual(data.headers['x-custom'], 'test-value');
|
|
} finally {
|
|
await stopHTTPServer(server);
|
|
}
|
|
});
|
|
|
|
it('should send QUERY requests with baseURL to a real server', async () => {
|
|
const server = await startHTTPServer(
|
|
(req, res) => {
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.end(JSON.stringify({
|
|
method: req.method,
|
|
url: req.url,
|
|
}));
|
|
},
|
|
{ port: 0 }
|
|
);
|
|
|
|
try {
|
|
const instance = axios.create({
|
|
baseURL: `http://localhost:${server.address().port}/api`,
|
|
});
|
|
|
|
const { data } = await instance.query('/resources', { fields: ['name'] });
|
|
|
|
assert.strictEqual(data.method, 'QUERY');
|
|
assert.strictEqual(data.url, '/api/resources');
|
|
} finally {
|
|
await stopHTTPServer(server);
|
|
}
|
|
});
|
|
});
|
|
});
|