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

mock: improve mock interceptor #3062

Merged
merged 1 commit into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
35 changes: 18 additions & 17 deletions lib/mock/mock-interceptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class MockInterceptor {
this[kContentLength] = false
}

createMockScopeDispatchData (statusCode, data, responseOptions = {}) {
createMockScopeDispatchData ({ statusCode, data, responseOptions }) {
const responseData = getResponseData(data)
const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {}
const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers }
Expand All @@ -99,43 +99,40 @@ class MockInterceptor {
return { statusCode, data, headers, trailers }
}

validateReplyParameters (statusCode, data, responseOptions) {
if (typeof statusCode === 'undefined') {
validateReplyParameters (replyParameters) {
if (typeof replyParameters.statusCode === 'undefined') {
throw new InvalidArgumentError('statusCode must be defined')
}
if (typeof data === 'undefined') {
throw new InvalidArgumentError('data must be defined')
}
if (typeof responseOptions !== 'object' || responseOptions === null) {
if (typeof replyParameters.responseOptions !== 'object' || replyParameters.responseOptions === null) {
throw new InvalidArgumentError('responseOptions must be an object')
}
}

/**
* Mock an undici request with a defined reply.
*/
reply (replyData) {
reply (replyOptionsCallbackOrStatusCode) {
// Values of reply aren't available right now as they
// can only be available when the reply callback is invoked.
if (typeof replyData === 'function') {
if (typeof replyOptionsCallbackOrStatusCode === 'function') {
// We'll first wrap the provided callback in another function,
// this function will properly resolve the data from the callback
// when invoked.
const wrappedDefaultsCallback = (opts) => {
// Our reply options callback contains the parameter for statusCode, data and options.
const resolvedData = replyData(opts)
const resolvedData = replyOptionsCallbackOrStatusCode(opts)

// Check if it is in the right format
if (typeof resolvedData !== 'object') {
if (typeof resolvedData !== 'object' || resolvedData === null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we dont do this, than null would result in destructuring issues in the original code

throw new InvalidArgumentError('reply options callback must return an object')
}

const { statusCode, data = '', responseOptions = {} } = resolvedData
this.validateReplyParameters(statusCode, data, responseOptions)
const replyParameters = { data: '', responseOptions: {}, ...resolvedData }
this.validateReplyParameters(replyParameters)
// Since the values can be obtained immediately we return them
// from this higher order function that will be resolved later.
return {
...this.createMockScopeDispatchData(statusCode, data, responseOptions)
...this.createMockScopeDispatchData(replyParameters)
}
}

Expand All @@ -148,11 +145,15 @@ class MockInterceptor {
// we should have 1-3 parameters. So we spread the arguments of
// this function to obtain the parameters, since replyData will always
// just be the statusCode.
const [statusCode, data = '', responseOptions = {}] = [...arguments]
this.validateReplyParameters(statusCode, data, responseOptions)
const replyParameters = {
statusCode: replyOptionsCallbackOrStatusCode,
data: arguments[1] === undefined ? '' : arguments[1],
responseOptions: arguments[2] === undefined ? {} : arguments[2]
}
this.validateReplyParameters(replyParameters)

// Send in-already provided data like usual
const dispatchData = this.createMockScopeDispatchData(statusCode, data, responseOptions)
const dispatchData = this.createMockScopeDispatchData(replyParameters)
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData)
return new MockScope(newMockDispatch)
}
Expand Down
20 changes: 17 additions & 3 deletions test/mock-interceptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('MockInterceptor - reply options callback', () => {
})

test('should error if passed options invalid', async (t) => {
t = tspl(t, { plan: 3 })
t = tspl(t, { plan: 4 })

const baseUrl = 'http://localhost:9999'
const mockAgent = new MockAgent()
Expand All @@ -116,10 +116,15 @@ describe('MockInterceptor - reply options callback', () => {
const mockPool = mockAgent.get(baseUrl)

mockPool.intercept({
path: '/test',
path: '/test-return-undefined',
method: 'GET'
}).reply(() => { })

mockPool.intercept({
path: '/test-return-null',
method: 'GET'
}).reply(() => { return null })

mockPool.intercept({
path: '/test3',
method: 'GET'
Expand All @@ -138,7 +143,16 @@ describe('MockInterceptor - reply options callback', () => {
}))

t.throws(() => mockPool.dispatch({
path: '/test',
path: '/test-return-undefined',
method: 'GET'
}, {
onHeaders: () => { },
onData: () => { },
onComplete: () => { }
}), new InvalidArgumentError('reply options callback must return an object'))

t.throws(() => mockPool.dispatch({
path: '/test-return-null',
method: 'GET'
}, {
onHeaders: () => { },
Expand Down
Loading