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: Fix .matchHeader() with allowUnmocked #1480

Merged
merged 13 commits into from
Apr 15, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 29 additions & 14 deletions lib/interceptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,31 @@ Interceptor.prototype.replyWithFile = function replyWithFile(
return this.reply(statusCode, readStream, headers)
}

/**
* Test to see if the headers match a filter. If the function cannot determin where the headers are
* it returns false.
* @name filterMatches
* @param {Object} filter - A filter object containing the header name we want to filter on and the
* filter value which can be a string, regex, or predicate.
* @param {Object} obj - An object containing the headers we wish to check. This is either the
* request options or the outgoing request.
* @returns {boolean} True if the headers contain a value that matches the filter.
*/
function filterMatches(filter, obj) {
let headerValue
if (obj.getHeader) {
headerValue = obj.getHeader(filter.name)
} else if (obj.headers) {
headerValue = obj.headers[filter.name]
} else {
return false
}
if (_.isFunction(filter.value)) {
return filter.value(headerValue)
}
return common.matchStringOrRegexp(headerValue, filter.value)
}

// Also match request headers
// https://github.com/pgte/nock/issues/163
Interceptor.prototype.reqheaderMatches = function reqheaderMatches(
Expand Down Expand Up @@ -227,14 +252,8 @@ Interceptor.prototype.match = function match(options, body, hostNameOnly) {
body = this.scope.transformRequestBodyFunction(body, this._requestBody)
}

const checkHeaders = function(header) {
if (_.isFunction(header.value)) {
return header.value(options.getHeader(header.name))
}
return common.matchStringOrRegexp(
options.getHeader(header.name),
header.value
)
const checkHeaders = function(filter) {
return filterMatches(filter, options)
}

if (
Expand Down Expand Up @@ -393,12 +412,8 @@ Interceptor.prototype.matchIndependentOfBody = function matchIndependentOfBody(
if (this.scope.transformPathFunction) {
path = this.scope.transformPathFunction(path)
}

const checkHeaders = function(header) {
return (
options.getHeader &&
common.matchStringOrRegexp(options.getHeader(header.name), header.value)
)
const checkHeaders = function(filter) {
return filterMatches(filter, options)
Copy link
Member

Choose a reason for hiding this comment

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

This looks great!

}

if (
Expand Down
36 changes: 34 additions & 2 deletions tests/test_header_matching.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ test('match headers on number with regexp', async t => {
})

test('match header on scope with function: gets the expected argument', async t => {
t.plan(3)
t.plan(4) // The check in matchHeader should run twice
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, is there a reason it should? It seems confusing for the check to run more than once per request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I ran the debug and what is happening is it is called once from matchIndependentOfBody and once from match. Previously the two functions used different header matching logic. Now they use the same logic so line 92 will be called twice.

Copy link
Member

Choose a reason for hiding this comment

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

Okay. I can dig into it a bit more and see if I can get all the tests passing with the function only being called once. Ideally I'd like to fix that before merging because I don't think there's a good reason to evaluate it twice and it seems confusing for the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So matchIndependantOfBody is used here https://github.com/nock/nock/blob/beta/lib/intercept.js#L398 to detect if we should let the request through. But if all the check pass on matchIndependantOfBody we don't really do anything with it because we wait for the body when we execute match. I think what we would basically need to do to get down to 3 executions rather than 4 is to drop the whole matchIndependantOfBody line of execution and only executing matching once. Inside the match method. This would simplify nock but it looked like a lot more than I was really willing to bite off.

Copy link
Member

Choose a reason for hiding this comment

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

Gotcha. Thank you so much for looking into it this far! I can pick it up from here.

Since the headers aren't part of the body, it seems like the check belongs in matchIndependentOfBody, though maybe doing it just once inside of match will be a more reasonable incremental change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You can't only check in the first because there might be I rejecting body filter. So you could only check in the second method when you have all the data. As an alternative, you could store the results of matchIndependant... on the object and then use the result in the match call rather than rerunning all the checks.

As a contributor I do find the separate matches confusing so executing the checks once would read better, but, it was so odd I assumed it was done for good reason.

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 possible you could also consider merging this change in as is and addressing the extra call separately. We would trade the duplicate call for working header matching.


const scope = nock('http://example.test')
.get('/')
Expand Down Expand Up @@ -118,6 +118,21 @@ test('match header on scope with function: matches when match accepted', async t
scope.done()
})

test('match header on scope with function and allow unmocked: matches when match accepted', async t => {
const scope = nock('http://example.test', { allowUnmocked: true })
.get('/')
.matchHeader('x-my-headers', val => true)
.reply(200, 'Hello World!')

const { statusCode, body } = await got('http://example.test/', {
headers: { 'X-My-Headers': 456 },
})

t.equal(statusCode, 200)
t.equal(body, 'Hello World!')
scope.done()
})
Copy link
Member

Choose a reason for hiding this comment

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

👍


test('match header on scope with function: does not match when match declined', async t => {
nock('http://example.test')
.get('/')
Expand Down Expand Up @@ -152,7 +167,7 @@ test('match header on scope with function: does not consume mock request when ma
})

test('match header on intercept with function: gets the expected argument', async t => {
t.plan(3)
t.plan(4) // The check in matchHeader should run twice

const scope = nock('http://example.test')
.matchHeader('x-my-headers', val => {
Expand Down Expand Up @@ -192,6 +207,23 @@ test('match header on interceptor with function: matches when match accepted', a
scope.done()
})

test('match header on interceptor with function: matches when match accepted', async t => {
const scope = nock('http://example.test', { allowUnmocked: true })
.matchHeader('x-my-headers', val => true)
// `.matchHeader()` is called on the interceptor. It precedes the call to
// `.get()`.
.get('/')
.reply(200, 'Hello World!')

const { statusCode, body } = await got('http://example.test/', {
headers: { 'X-My-Headers': 456 },
})

t.equal(statusCode, 200)
t.equal(body, 'Hello World!')
scope.done()
})
Copy link
Member

Choose a reason for hiding this comment

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

👍


test('match header on interceptor with function: does not match when match declined', async t => {
nock('http://example.test')
.matchHeader('x-my-headers', val => false)
Expand Down