Skip to content

MockAgent intercept().reply() provides headers in flat array form #1556

@meyfa

Description

@meyfa

Bug Description

MockAgent allows us to generate a reply dynamically. The request's options are passed into that function. Undici's type definitions specify that a Headers object will be used for options.headers, but instead a flattened array is given. This is entirely unusable without splitting it into two-tuple chunks first and manually wrapping it in a new Headers() call.

Note that it is also possible to access the headers inside a matcher function passed to intercept(). This was a useful workaround for me, but keep in mind that for some reason, the matcher gets called twice which you can also see in the output below.

Reproducible By

import { fetch, MockAgent, setGlobalDispatcher } from 'undici'

const mockAgent = new MockAgent()
const mockPool = mockAgent.get('http://localhost')

setGlobalDispatcher(mockAgent)

mockPool.intercept({
  path: '/foo',
  method: 'GET',
  headers: (headers) => {
    console.log('headers in intercept:', Object.prototype.toString.call(headers), headers)
    return true
  }
}).reply(200, (options) => {
  console.log('headers in reply:', Object.prototype.toString.call(options.headers), options.headers)
  return {}
})

await fetch('http://localhost/foo')

Expected Behavior

The included type definitions specify that the parameter to the headers matcher in intercept() should receive Record<string, string>. For reply(), they specify options.headers to be a Headers object.
As such I would expect the following output:

headers in intercept: [object Object] {
  accept: '*/*',
  'accept-language': '*',
  'sec-fetch-mode': 'cors',
  'user-agent': 'undici',
  'accept-encoding': 'gzip, deflate'
}
headers in reply: [object Headers] HeadersList {
  [Symbol(headers map)]: Map(5) {
    'accept' => '*/*',
    'accept-language' => '*',
    'sec-fetch-mode' => 'cors',
    'user-agent' => 'undici',
    'accept-encoding' => 'gzip, deflate'
  },
  [Symbol(headers map sorted)]: null
}

Logs & Screenshots

Instead of the above, the following is logged:

headers in intercept: [object Object] {
  accept: '*/*',
  'accept-language': '*',
  'sec-fetch-mode': 'cors',
  'user-agent': 'undici',
  'accept-encoding': 'gzip, deflate'
}
headers in reply: [object Array] [
  'accept',
  '*/*',
  'accept-language',
  '*',
  'sec-fetch-mode',
  'cors',
  'user-agent',
  'undici',
  'accept-encoding',
  'gzip, deflate'
]
headers in intercept: [object Object] {
  accept: '*/*',
  'accept-language': '*',
  'sec-fetch-mode': 'cors',
  'user-agent': 'undici',
  'accept-encoding': 'gzip, deflate'
}

Environment

  • Windows 11, additionally tried in WSL running Ubuntu 20.04.4 LTS with same behavior
  • Node v18.6.0
  • npm 8.13.2
  • Undici 5.8.0

Additional context

  • Relevant type definitions for options.headers in reply():
    headers: Headers;
  • I didn't yet have the time to figure out where in the source code the headers array is coming from, unfortunately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions