fix: preserve axios default Accept header when setting User-Agent (#7820)

Assigning `defaults.headers.common = { 'User-Agent': ... }` replaced the
entire common headers object, nuking axios's built-in default:

  Accept: application/json, text/plain, */*

This caused servers relying on content-negotiation to receive requests
with no Accept header. Fix by extending the existing object with a
property assignment instead.

Add regression tests for both electron and CLI axios instances verifying
that Accept is preserved and User-Agent is set correctly.

Co-authored-by: Pragadesh-45 <temporaryg7904@gmail.com>
This commit is contained in:
James
2026-05-05 17:08:12 +01:00
committed by GitHub
parent ab7dd1ff26
commit b91f9ba5be
5 changed files with 97 additions and 8 deletions

View File

@@ -92,10 +92,11 @@ function makeAxiosInstance({
headers: {}
});
// Set User-Agent manually (using transformRequest to delete headers instead)
instance.defaults.headers.common = {
'User-Agent': `bruno-runtime/${CLI_VERSION}`
};
// Extend common headers with User-Agent rather than replacing the object.
// axios.create() preserves defaults.headers.common = { Accept: 'application/json, text/plain, */*' }.
// Assigning a new object (= { 'User-Agent': ... }) would nuke that default, causing servers that
// rely on content-negotiation to receive requests with no Accept header.
instance.defaults.headers.common['User-Agent'] = `bruno-runtime/${CLI_VERSION}`;
instance.interceptors.request.use((config) => {
config.headers['request-start-time'] = Date.now();

View File

@@ -0,0 +1,37 @@
const { describe, it, expect } = require('@jest/globals');
const { makeAxiosInstance } = require('../../src/utils/axios-instance');
function createStubAdapter() {
let capturedConfig = null;
const adapter = (config) => {
capturedConfig = config;
return Promise.resolve({ data: {}, status: 200, statusText: 'OK', headers: {}, config });
};
adapter.getConfig = () => capturedConfig;
return adapter;
}
describe('makeAxiosInstance', () => {
it('setting User-Agent does not clobber the axios default Accept header', async () => {
const stubAdapter = createStubAdapter();
const instance = makeAxiosInstance();
await instance({ url: 'https://api.example.com/test', method: 'get', adapter: stubAdapter });
// axios.create() sets Accept by default; assigning a new object to defaults.headers.common
// would nuke it. Guard against that regression.
expect(stubAdapter.getConfig().headers['Accept']).toMatch(/application\/json/);
});
it('sets User-Agent header to bruno-runtime version', async () => {
const stubAdapter = createStubAdapter();
const instance = makeAxiosInstance();
await instance({ url: 'https://api.example.com/test', method: 'get', adapter: stubAdapter });
expect(stubAdapter.getConfig().headers['User-Agent']).toMatch(/^bruno-runtime\//);
});
});

View File

@@ -99,10 +99,11 @@ function makeAxiosInstance({
headers: {}
});
// Set User-Agent manually (using transformRequest to delete headers instead)
instance.defaults.headers.common = {
'User-Agent': `bruno-runtime/${version}`
};
// Extend common headers with User-Agent rather than replacing the object.
// axios.create() preserves defaults.headers.common = { Accept: 'application/json, text/plain, */*' }.
// Assigning a new object (= { 'User-Agent': ... }) would nuke that default, causing servers that
// rely on content-negotiation to receive requests with no Accept header.
instance.defaults.headers.common['User-Agent'] = `bruno-runtime/${version}`;
instance.interceptors.request.use(async (config) => {
const url = URL.parse(config.url);

View File

@@ -51,6 +51,28 @@ function createStubAdapter() {
return adapter;
}
describe('axios-instance: default headers', () => {
test('setting User-Agent does not clobber the axios default Accept header', async () => {
const stubAdapter = createStubAdapter();
const instance = makeAxiosInstance();
await instance({ url: 'https://api.example.com/test', method: 'get', adapter: stubAdapter });
// axios.create() sets Accept by default; assigning a new object to defaults.headers.common
// would nuke it. Guard against that regression.
expect(stubAdapter.getConfig().headers['Accept']).toMatch(/application\/json/);
});
test('sets User-Agent header to bruno-runtime version', async () => {
const stubAdapter = createStubAdapter();
const instance = makeAxiosInstance();
await instance({ url: 'https://api.example.com/test', method: 'get', adapter: stubAdapter });
expect(stubAdapter.getConfig().headers['User-Agent']).toMatch(/^bruno-runtime\//);
});
});
describe('axios-instance: DNS lookup behavior (GitHub #7343)', () => {
let axiosInstance;

View File

@@ -0,0 +1,28 @@
meta {
name: echo default request headers
type: http
seq: 14
}
post {
url: {{echo-host}}
body: none
auth: none
}
tests {
test("sends Accept header with application/json by default", function() {
// The echo server reflects request headers back as response headers.
// Verifies that axios's default Accept header is not clobbered when
// setting User-Agent (regression for: defaults.headers.common object replacement).
const accept = res.getHeaders()["accept"];
expect(accept).to.be.a("string");
expect(accept).to.include("application/json");
});
test("sends User-Agent header with bruno-runtime prefix", function() {
const userAgent = res.getHeaders()["user-agent"];
expect(userAgent).to.be.a("string");
expect(userAgent).to.match(/^bruno-runtime\//);
});
}