diff --git a/lib/mock/mock-utils.js b/lib/mock/mock-utils.js index 80052223f8f..58bee2a5612 100644 --- a/lib/mock/mock-utils.js +++ b/lib/mock/mock-utils.js @@ -51,15 +51,20 @@ function getHeaderByName (headers, key) { } } +/** @param {string[]} headers */ +function buildHeadersFromArray (headers) { // fetch HeadersList + const clone = headers.slice() + const entries = [] + for (let index = 0; index < clone.length; index += 2) { + entries.push([clone[index], clone[index + 1]]) + } + return Object.fromEntries(entries) +} + function matchHeaders (mockDispatch, headers) { if (typeof mockDispatch.headers === 'function') { if (Array.isArray(headers)) { // fetch HeadersList - const clone = headers.slice() - const entries = [] - for (let index = 0; index < clone.length; index += 2) { - entries.push([clone[index], clone[index + 1]]) - } - headers = Object.fromEntries(entries) + headers = buildHeadersFromArray(headers) } return mockDispatch.headers(headers ? lowerCaseEntries(headers) : {}) } @@ -284,7 +289,13 @@ function mockDispatch (opts, handler) { } function handleReply (mockDispatches) { - const responseData = getResponseData(typeof data === 'function' ? data(opts) : data) + // fetch's HeadersList is a 1D string array + const optsHeaders = Array.isArray(opts.headers) + ? buildHeadersFromArray(opts.headers) + : opts.headers + const responseData = getResponseData( + typeof data === 'function' ? data({ ...opts, headers: optsHeaders }) : data + ) const responseHeaders = generateKeyValues(headers) const responseTrailers = generateKeyValues(trailers) diff --git a/test/mock-agent.js b/test/mock-agent.js index aa6e4ef1101..dc1150058f6 100644 --- a/test/mock-agent.js +++ b/test/mock-agent.js @@ -2431,3 +2431,35 @@ test('MockAgent - using fetch yields correct statusText', { skip: nodeMajor < 16 t.end() }) + +// https://github.com/nodejs/undici/issues/1556 +test('MockAgent - using fetch yields a headers object in the reply callback', { skip: nodeMajor < 16 }, async (t) => { + const { fetch } = require('..') + + const mockAgent = new MockAgent() + mockAgent.disableNetConnect() + t.teardown(mockAgent.close.bind(mockAgent)) + + const mockPool = mockAgent.get('http://localhost:3000') + + mockPool.intercept({ + path: '/headers', + method: 'GET' + }).reply(200, (opts) => { + t.same(opts.headers, { + accept: '*/*', + 'accept-language': '*', + 'sec-fetch-mode': 'cors', + 'user-agent': 'undici', + 'accept-encoding': 'gzip, deflate' + }) + + return {} + }) + + await fetch('http://localhost:3000/headers', { + dispatcher: mockAgent + }) + + t.end() +}) diff --git a/test/types/mock-interceptor.test-d.ts b/test/types/mock-interceptor.test-d.ts index ed0c0512529..24d29e11206 100644 --- a/test/types/mock-interceptor.test-d.ts +++ b/test/types/mock-interceptor.test-d.ts @@ -36,6 +36,10 @@ expectAssignable(mockResponseCall expectAssignable(mockInterceptor.reply(() => ({ statusCode: 200, data: { foo: 'bar' }, responseOptions: { trailers: { foo: 'bar' } }}))) + mockInterceptor.reply((options) => { + expectAssignable(options.headers); + return { statusCode: 200, data: { foo: 'bar' } } + }) // replyWithError class CustomError extends Error { diff --git a/types/mock-interceptor.d.ts b/types/mock-interceptor.d.ts index 8812960573f..87eedcd4060 100644 --- a/types/mock-interceptor.d.ts +++ b/types/mock-interceptor.d.ts @@ -74,7 +74,7 @@ declare namespace MockInterceptor { origin: string; method: string; body?: BodyInit | Dispatcher.DispatchOptions['body']; - headers: Headers; + headers: Headers | Record; maxRedirections: number; }