Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions lib/mock/mock-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const {
kOriginalClose,
kOrigin,
kOriginalDispatch,
kConnected
kConnected,
kIgnoreTrailingSlash
} = require('./mock-symbols')
const { MockInterceptor } = require('./mock-interceptor')
const Symbols = require('../core/symbols')
Expand All @@ -29,6 +30,7 @@ class MockClient extends Client {

this[kMockAgent] = opts.agent
this[kOrigin] = origin
this[kIgnoreTrailingSlash] = opts.ignoreTrailingSlash ?? false
this[kDispatches] = []
this[kConnected] = 1
this[kOriginalDispatch] = this.dispatch
Expand All @@ -46,7 +48,10 @@ class MockClient extends Client {
* Sets up the base interceptor for mocking replies from undici.
*/
intercept (opts) {
return new MockInterceptor(opts, this[kDispatches])
return new MockInterceptor(
opts && { ignoreTrailingSlash: this[kIgnoreTrailingSlash], ...opts },
this[kDispatches]
)
}

async [kClose] () {
Expand Down
9 changes: 7 additions & 2 deletions lib/mock/mock-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const {
kOriginalClose,
kOrigin,
kOriginalDispatch,
kConnected
kConnected,
kIgnoreTrailingSlash
} = require('./mock-symbols')
const { MockInterceptor } = require('./mock-interceptor')
const Symbols = require('../core/symbols')
Expand All @@ -29,6 +30,7 @@ class MockPool extends Pool {

this[kMockAgent] = opts.agent
this[kOrigin] = origin
this[kIgnoreTrailingSlash] = opts.ignoreTrailingSlash ?? false
this[kDispatches] = []
this[kConnected] = 1
this[kOriginalDispatch] = this.dispatch
Expand All @@ -46,7 +48,10 @@ class MockPool extends Pool {
* Sets up the base interceptor for mocking replies from undici.
*/
intercept (opts) {
return new MockInterceptor(opts, this[kDispatches])
return new MockInterceptor(
opts && { ignoreTrailingSlash: this[kIgnoreTrailingSlash], ...opts },
this[kDispatches]
)
}

async [kClose] () {
Expand Down
3 changes: 2 additions & 1 deletion lib/mock/mock-symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ module.exports = {
kIsMockActive: Symbol('is mock active'),
kNetConnect: Symbol('net connect'),
kGetNetConnect: Symbol('get net connect'),
kConnected: Symbol('connected')
kConnected: Symbol('connected'),
kIgnoreTrailingSlash: Symbol('ignore trailing slash')
}
19 changes: 16 additions & 3 deletions lib/mock/mock-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const {
kMockAgent,
kOriginalDispatch,
kOrigin,
kGetNetConnect
kGetNetConnect,
kIgnoreTrailingSlash
} = require('./mock-symbols')
const { serializePathWithQuery } = require('../core/util')
const { STATUS_CODES } = require('node:http')
Expand Down Expand Up @@ -182,7 +183,19 @@ function deleteMockDispatch (mockDispatches, key) {
}

function buildKey (opts) {
const { path, method, body, headers, query } = opts
let { path, method, body, headers, query, ignoreTrailingSlash = false } = opts

// normalize path if it is a string
if (ignoreTrailingSlash && typeof path === 'string') {
while (path.endsWith('/')) {
path = path.slice(0, -1)
}

if (path.length === 0) {
path = '/'
}
}

return {
path,
method,
Expand Down Expand Up @@ -231,7 +244,7 @@ async function getResponse (body) {
*/
function mockDispatch (opts, handler) {
// Get mock dispatch from built key
const key = buildKey(opts)
const key = buildKey({ ignoreTrailingSlash: this[kIgnoreTrailingSlash], ...opts })
const mockDispatch = getMockDispatch(this[kDispatches], key)

mockDispatch.timesInvoked++
Expand Down
42 changes: 42 additions & 0 deletions test/mock-interceptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,48 @@ describe('MockInterceptor - replyContentLength', () => {
})
})

describe('https://github.com/nodejs/undici/issues/3649', () => {
[
['/api/some-path', '/api/some-path'],
['/api/some-path/', '/api/some-path'],
['/api/some-path', '/api/some-path/'],
['/api/some-path/', '/api/some-path/'],
['/api/some-path////', '/api/some-path//'],
['', ''],
['/', ''],
['', '/'],
['/', '/']
].forEach(([interceptPath, fetchedPath], index) => {
test(`MockAgent should match with or without trailing slash by setting ignoreTrailingSlash as MockAgent option /${index}`, async (t) => {
t = tspl(t, { plan: 1 })

const mockAgent = new MockAgent({ ignoreTrailingSlash: true })
mockAgent.disableNetConnect()
mockAgent
.get('https://localhost')
.intercept({ path: interceptPath }).reply(200, { ok: true })

const res = await fetch(new URL(fetchedPath, 'https://localhost'), { dispatcher: mockAgent })

t.deepStrictEqual(await res.json(), { ok: true })
})
})

test('MockAgent should match with or without trailing slash /6', async (t) => {
t = tspl(t, { plan: 1 })

const mockAgent = new MockAgent()
mockAgent.disableNetConnect()
mockAgent
.get('https://localhost')
.intercept({ path: '/' }).reply(200, { ok: true })

const res = await fetch(new URL('', 'https://localhost'), { dispatcher: mockAgent })

t.deepStrictEqual(await res.json(), { ok: true })
})
})

describe('MockInterceptor - different payloads', () => {
[
// Buffer
Expand Down
3 changes: 3 additions & 0 deletions types/mock-agent.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,8 @@ declare namespace MockAgent {
export interface Options extends Agent.Options {
/** A custom agent to be encapsulated by the MockAgent. */
agent?: Dispatcher;

/** Ignore trailing slashes in the path */
ignoreTrailingSlash?: boolean;
}
}
Loading