From f7a3b5e0f7e5e127b97defa92a132fbf1b55cf15 Mon Sep 17 00:00:00 2001 From: Dmitriy Mozgovoy Date: Wed, 23 Apr 2025 19:24:08 +0300 Subject: [PATCH] fix(headers): fixed support for setting multiple header values from an iterated source; (#6885) --- lib/core/AxiosHeaders.js | 7 ++++++- test/helpers/retry.js | 18 ++++++++++++++++++ test/specs/formdata.spec.js | 15 +++++++++------ test/unit/core/AxiosHeaders.js | 22 ++++++++++++++++++++++ 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 test/helpers/retry.js diff --git a/lib/core/AxiosHeaders.js b/lib/core/AxiosHeaders.js index 42a10ca..6744581 100644 --- a/lib/core/AxiosHeaders.js +++ b/lib/core/AxiosHeaders.js @@ -101,12 +101,17 @@ class AxiosHeaders { } else if(utils.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) { setHeaders(parseHeaders(header), valueOrRewrite); } else if (utils.isObject(header) && utils.isIterable(header)) { + let obj = {}, dest, key; for (const entry of header) { if (!utils.isArray(entry)) { throw TypeError('Object iterator must return a key-value pair'); } - setHeader(entry[1], entry[0], rewrite); + + obj[key = entry[0]] = (dest = obj[key]) ? + (utils.isArray(dest) ? [...dest, entry[1]] : [dest, entry[1]]) : entry[1]; } + + setHeaders(obj, valueOrRewrite) } else { header != null && setHeader(valueOrRewrite, header, rewrite); } diff --git a/test/helpers/retry.js b/test/helpers/retry.js new file mode 100644 index 0000000..a4ccfbd --- /dev/null +++ b/test/helpers/retry.js @@ -0,0 +1,18 @@ + +export const retryNetwork = async (fn, retries = 3, delay = 1000) => { + let attempt = 0, sleep; + + do { + try { + return await fn() + } catch (err) { + if (err.code === 'ERR_NETWORK' && attempt++ < retries) { + sleep = attempt * attempt * delay; + console.warn(`[ERR_NETWORK]: Attempt ${attempt}/${retries}${err.config ? ' [' + err.config.url + ']' : ''} sleep [${sleep}ms]`); + await new Promise(resolve => setTimeout(resolve, sleep)); + } else { + throw err; + } + } + } while (true); +} diff --git a/test/specs/formdata.spec.js b/test/specs/formdata.spec.js index 5c8cca7..b43fdef 100644 --- a/test/specs/formdata.spec.js +++ b/test/specs/formdata.spec.js @@ -1,13 +1,16 @@ +import {retryNetwork} from "../helpers/retry.js"; describe('FormData', function() { - it('should allow FormData posting', function () { - return axios.postForm('http://httpbin.org/post', { - a: 'foo', - b: 'bar' - }).then(({data}) => { - expect(data.form).toEqual({ + it('should allow FormData posting', async () => { + await retryNetwork(() => { + return axios.postForm('http://httpbin.org/post', { a: 'foo', b: 'bar' + }).then(({data}) => { + expect(data.form).toEqual({ + a: 'foo', + b: 'bar' + }); }); }); }); diff --git a/test/unit/core/AxiosHeaders.js b/test/unit/core/AxiosHeaders.js index 0c5258c..2551fd6 100644 --- a/test/unit/core/AxiosHeaders.js +++ b/test/unit/core/AxiosHeaders.js @@ -1,6 +1,7 @@ import AxiosHeaders from '../../../lib/core/AxiosHeaders.js'; import assert from 'assert'; +const [nodeMajorVersion] = process.versions.node.split('.').map(v => parseInt(v, 10)); describe('AxiosHeaders', function () { it('should support headers argument', function () { @@ -82,6 +83,27 @@ describe('AxiosHeaders', function () { assert.strictEqual(headers.get('x'), '123'); }); + + it('should support setting multiple header values from an iterable source', function () { + if (nodeMajorVersion < 18) { + this.skip(); + return; + } + + const headers = new AxiosHeaders(); + + const nativeHeaders = new Headers(); + + nativeHeaders.append('set-cookie', 'foo'); + nativeHeaders.append('set-cookie', 'bar'); + nativeHeaders.append('set-cookie', 'baz'); + nativeHeaders.append('y', 'qux'); + + headers.set(nativeHeaders); + + assert.deepStrictEqual(headers.get('set-cookie'), ['foo', 'bar', 'baz']); + assert.strictEqual(headers.get('y'), 'qux'); + }); }); it('should support uppercase name mapping for names overlapped by class methods', () => {