pact-js icon indicating copy to clipboard operation
pact-js copied to clipboard

Modify Content-Type header of the response when verifying HTTP provider

Open avanmalleghem opened this issue 2 years ago • 8 comments

Hello everyone,

Seems there is an issue when you try to modify the 'Content-Type' header using a requestFilter.

Software versions

Expected behaviour

The test should pass, the 'Content-Type' header of the response should be set to 'application/json'.

Actual behaviour

Expected header "content-type" to equal "application/json", but was "text/html; charset=utf-8". It seems there is an issue with this content-type header. I can add any header but this one.

Steps to reproduce

I added a requestFilter in the options of the Verifier constructor.

  requestFilter: (req, res, next) => {
    next();
    res.header('test', 'valuetest'); // example to show it works
    res.header('Content-Type', 'application/json');
  }

Logs :

[2022-05-30 16:41:18.123 +0000] DEBUG (52303 on NGG568): [email protected]: I, [2022-05-30T18:41:18.123823 #52326]  INFO -- request: POST http://localhost:52088/_pactSetup

[2022-05-30 16:41:18.124 +0000] DEBUG (52303 on NGG568): [email protected]: D, [2022-05-30T18:41:18.123870 #52326] DEBUG -- request: User-Agent: "Faraday v0.17.5"
Content-Type: "application/json"

[2022-05-30 16:41:18.184 +0000] DEBUG (52303 on NGG568): [email protected]: I, [2022-05-30T18:41:18.184099 #52326]  INFO -- response: Status 200

[2022-05-30 16:41:18.184 +0000] DEBUG (52303 on NGG568): [email protected]: D, [2022-05-30T18:41:18.184170 #52326] DEBUG -- response: x-powered-by: "Express"
test: "valuetest"
content-type: "text/plain; charset=utf-8"
content-length: "2"
etag: "W/\"2-nOO9QiTIwXgNtWtBJezz8kv3SLc\""
date: "Mon, 30 May 2022 16:41:18 GMT"
connection: "close"

You can see that the test header has been added but the content-type header hasn't been set correctly.

Thanks in advance for your answer.

avanmalleghem avatar May 30 '22 16:05 avanmalleghem

Hey, what happens if you set res.header('content-type', 'application/json'); to match the case.

also I would have expected next() to have been called after setting your options, as its a callback

see this example

https://github.com/pact-foundation/pact-js#modify-requests-prior-to-verification-request-filters

let token
let opts = {
  provider: 'Animal Profile Service',
  ...
  stateHandlers: {
    "is authenticated": () => {
      token = "1234"
      Promise.resolve(`Valid bearer token generated`)
    },
    "is not authenticated": () => {
      token = ""
      Promise.resolve(`Expired bearer token generated`)
    }
  },

  // this middleware is executed for each request, allowing `token` to change between invocations
  // it is common to pair this with `stateHandlers` as per above, that can set/expire the token
  // for different test cases
  requestFilter: (req, res, next) => {
    req.headers["Authorization"] = `Bearer: ${token}`
    next()
  },

  // This header will always be sent for each and every request, and can't be dynamic
  // (i.e. passing a variable instead of the bearer token)
  customProviderHeaders: ["Authorization: Bearer 1234"]
}

return new Verifier(opts).verifyProvider().then(...)

YOU54F avatar May 30 '22 19:05 YOU54F

Hey, what happens if you set res.header('content-type', 'application/json'); to match the case.

also I would have expected next() to have been called after setting your options, as its a callback

see this example

https://github.com/pact-foundation/pact-js#modify-requests-prior-to-verification-request-filters

let token
let opts = {
  provider: 'Animal Profile Service',
  ...
  stateHandlers: {
    "is authenticated": () => {
      token = "1234"
      Promise.resolve(`Valid bearer token generated`)
    },
    "is not authenticated": () => {
      token = ""
      Promise.resolve(`Expired bearer token generated`)
    }
  },

  // this middleware is executed for each request, allowing `token` to change between invocations
  // it is common to pair this with `stateHandlers` as per above, that can set/expire the token
  // for different test cases
  requestFilter: (req, res, next) => {
    req.headers["Authorization"] = `Bearer: ${token}`
    next()
  },

  // This header will always be sent for each and every request, and can't be dynamic
  // (i.e. passing a variable instead of the bearer token)
  customProviderHeaders: ["Authorization: Bearer 1234"]
}

return new Verifier(opts).verifyProvider().then(...)

I tried but same result ! As suggested, I forked the pact-js repository and modified an example in order to reproduce the issue. You can find my fork here. To reproduce it :

  • You have first to generate the pact contract using npm run test:consumer in the pact-js/examples/e2e directory.
  • You can then execute npm run test:provider (same directory). You will see that the test fails because the header is not correctly set.

avanmalleghem avatar May 31 '22 07:05 avanmalleghem

@YOU54F , any insight on this one ?

avanmalleghem avatar Jun 14 '22 06:06 avanmalleghem

Can I ask - why do you want to change the content type header of the provider? This is a fairly crucial header as far as matching is concerned, so overriding it is quite dangerous.

I'm not sure why we can't override the content-type header here, it's probably something to do with the way express works. You can certainly add others.

I don't have time to look into this specific issue. If you really need to do this and can't find a way to modify the current Pact JS code / use the express interface provided, I'd suggest you add a test only middleware on your provider code base and modify it there (or roll your own proxy).

mefellows avatar Jul 29 '22 23:07 mefellows

That's indeed a good question ! In fact, this api will run in an AWS lambda. Consequently, I deploy the code on localstack during my CI step and run the Pact contract on it. The problem is that by default AWS Lambda returns this header correctly when you don't specify anything BUT an AWS lambda on localstack doesn't. I don't want to modify the code for a test purpose. Fortunately, I opened a ticket on the localstack project and they solved the issue on their side.

avanmalleghem avatar Aug 05 '22 13:08 avanmalleghem

Glad to hear! I'm going to leave this open as a feature request in the meantime.

mefellows avatar Aug 06 '22 11:08 mefellows

I have my consumer in java and my provider in Nestjs, the problem is with the content type uppercase, any idea how to solve that?

image

nicolasariza avatar Dec 15 '22 22:12 nicolasariza

@nicolasariza this is a different issue to the one reported here. You could add a regex matcher on the response header that was case insensitive or ignored the charset component.

mefellows avatar Dec 15 '22 22:12 mefellows