Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add option ignoreTrailingSlash to MockAgent and .intercept() #3655

Merged
merged 9 commits into from
Oct 4, 2024
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