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
Conversation
Hi! Thanks for opening this. What's the expected behavior for the repro program?
I just played with |
@paulmelnikow Thank you for your speedy reply! I think this is the
The outgoing request that the |
@paulmelnikow let me do a little more digging and see if I can pin down what is happening a bit better. |
When I run the test I see "goodbye" in the terminal. Is that the expected behavior? |
@paulmelnikow yes that is the expected behavior. I did a bit more playing around to make a minimal example and this is a working minimal example. import httpProxy from 'http-proxy';
import nock from 'nock';
nock('http://example.com', {allowUnmocked: true})
.matchHeader('any-string', val => true)
.get('/hello')
.reply(200, 'goodbye');
let proxyServer = httpProxy.createProxyServer({
changeOrigin: true,
selfHandleResponse: false,
target: 'http://example.com',
});
proxyServer.listen(8080); What I have found is that if run without |
The first hit to
... and then goes to The second
... and then goes to When allowUnmocked is true the second call is never triggered and the first call appears broken. I think this shows two bugs but you can better diagnose. Two things are surprising though.
|
I'm getting "goodbye" without any code changes. I guess I'm confused about what the bug is. The Could you clarify the expected + observed behavior of the test? |
@paulmelnikow Does that include my recent comments with |
Sorry, I'm not sure what you're asking! 😀 I'd like to understand what the bug is first. Otherwise it's hard to make sense of the fix / implementation change. |
Let me try to restate and see if that makes it more clear.
This is different from the original example because I added the allowUnmocked: true setting. |
@paulmelnikow I've added a test as well. If you just add the test I added you will also see the bug. |
Gooooot it! The test in the top post actually passes and is working as expected; it's the one with I am not convinced that I hate to spread more layers on top of existing cruft. What would you think about removing these lines: Lines 101 to 103 in 75cceb6
And proceeding from there, to get the matching working again? |
lib/interceptor.js
Outdated
return ( | ||
options.getHeader && | ||
common.matchStringOrRegexp(options.getHeader(header.name), header.value) | ||
if (_.isFunction(header.value)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a bug. When this condition is true, the function returns without executing common.matchStringOrRegexp()
.
lib/intercept.js
Outdated
@@ -388,7 +388,17 @@ function activate() { | |||
options = urlParse(options.toString()) | |||
} | |||
options.proto = proto | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned I'm not sure we need to insert getHeader
into options
at all. I've found no evidence that this is for parity with an existing API or compatibility with some existing library.
Could you remove this code, and update the code in the places where options.getHeader
is called, instead? It's fine to delegate the repeated code to a helper function, though let's not unnecessarily stick that helper function into options
.
tests/test_header_matching.js
Outdated
@@ -95,6 +95,21 @@ test('match headers with function', async t => { | |||
scope.done() | |||
}) | |||
|
|||
test('match headers with function and allowUnmocked', async t => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you merge master into this file, there are some new tests which are a bit clearer and more specific. If you'd like, you could update this to use one of those.
Currently, the header filter fails if there is not a `getHeader` method present on the options object. This sometimes appears to be the case. This is a simple node program written that should work as a repo ```typescript import httpProxy from 'http-proxy'; import nock from 'nock'; let proxyServer = httpProxy.createProxyServer({ changeOrigin: true, selfHandleResponse: false, target: 'http://example.com', }); proxyServer.listen(8080); nock('http://example.com') .matchHeader('any-string', val => true) .get('hello'), reply(200, 'goodbye') ``` ```bash curl -X GET http://localhost:8080/hello ``` This change also make the two checkHeader methods consistent. Where one previously respected functions and the other did not.
Fix lint errors.
…he incomming object * Reverted changes to intercept.js * Moved shared header logic to a helper function in interceptor.js * Added test to cover header checks with allowUnmocked set to true
@paulmelnikow I believe these changes meet your criteria. I suspect there is still a bug on body filters with |
Fix typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good.
t.equal(statusCode, 200) | ||
t.equal(body, 'Hello World!') | ||
scope.done() | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
t.equal(statusCode, 200) | ||
t.equal(body, 'Hello World!') | ||
scope.done() | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
lib/interceptor.js
Outdated
common.matchStringOrRegexp(options.getHeader(header.name), header.value) | ||
) | ||
const checkHeaders = function(filter) { | ||
return filterMatches(filter, options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great!
tests/test_header_matching.js
Outdated
@@ -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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
.matchHeader()
with allowUnmocked
.matchHeader()
with allowUnmocked
.matchHeader()
with allowUnmocked
@ww-daniel-mora Hi! Thanks for your patience! I've moved the header check out of Before I ship it, would you like to have another look and/or take it for a spin? If you don't have time, no worries! I know you've worked hard on this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to give OP a chance to look at this if he wants!
@paulmelnikow looks good. Thanks for your help! |
Coverage % is down in |
🎉 This PR is included in version 11.0.0-beta.9 🎉 The release is available on: Your semantic-release bot 📦🚀 |
🎉 This PR is included in version 11.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
- Move header checks into `match()`. - Rename internal method `matchIndependentOfBody()` to `matchAddress()`
- Move header checks into `match()`. - Rename internal method `matchIndependentOfBody()` to `matchAddress()`
- Move header checks into `match()`. - Rename internal method `matchIndependentOfBody()` to `matchAddress()`
Currently, the header filter fails if there is not a
getHeader
method present on theoptions
object. This sometimes appears to be the case. This is a simple node program written that should work as a repoThis change also makes the two checkHeader methods consistent. Where one previously respected functions and the other did not.