pact-foundation / pact-net

.NET version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
https://pact.io
MIT License
842 stars 231 forks source link

Matching rules on response headers do not work #184

Closed rafsojka closed 2 years ago

rafsojka commented 5 years ago

Hello,

I have a pact containing:

"response": {
                "status": 404,
                "headers": {
                    "Content-Type": "application/json; charset=UTF-8"
                },
                "body": {
                    // body sample
                },
                "matchingRules": {
                    "$.header.Content-Type": {
                        "match": "regex",
                        "regex": "^application\\/json; charset=(utf|UTF)-8$"
                    },
                   // body matching rules
            },

when running verification I am getting:

Failure/Error: expect(header_value).to match_header(name, expected_header_value)
       Expected header "Content-Type" to equal "application/json; charset=UTF-8", but was "application/json; charset=utf-8"

The regex provided clearly matches both "application/json; charset=utf-8" (lowercase utf) and "application/json; charset=UTF-8" (uppercase UTF).

Does that mean that regex matching rules are not supported for headers?

Edit:

I have just spotted that I am also getting a warning in verification output:

WARN: Ignoring unsupported matching rules {"match"=>"regex", "regex"=>"^application\\/json; charset=(utf|UTF)-8$"} for path $['header']['Content-Type']

Similar warnings for query parameters and match"=>"number.

I understand that all these things are not supported in .NET. Is there somewhere a complete, up-to-date list of what is supported in pact-net?

I am working on an API written in aspnetcore consumed from mobile apps. We are trying to employ Pact for consumer driven contract testing so I am getting pact files generated in other technologies which as it seems, have greater capabilities. Therefore a complete list of supported features that I can point client developers to would save lots of time on both ends.

neilcampbell commented 5 years ago

Hi @rafsojka. It supports those use cases, there are examples of those in the Samples. Under the covers Pact-Net uses the ruby core, so for the most part, if the ruby engine supports it, we do.

I just noticed that your matching rule is $.header.Content-Type, it should be $.headers.Content-Type.

What pact library are you using for the consumer that generates that pact file?

rafsojka commented 5 years ago

Hi @neilcampbell thanks for your response.

You are right about $.header vs $.headers I missed that. When generating pact files using pact-net I get matching rules on $.headers so they work well.

The client team uses pact-jvm-consumer-java8_2.12-3.6.0. I asked them to confirm if it is possible or not to put $.headers in generated pact file.

Other issues with their pact file:

  1. "query": "languageCode=en-GB",
    "$.query.languageCode": {
                        "match": "regex",
                        "regex": "^(([a-z]{2}-[A-Z]{2})|[a-z]{2})$"
                    },

    results in

WARN: Ignoring unsupported matching rules {"match"=>"regex", "regex"=>"^(([a-z]{2}-[A-Z]{2})|[a-z]{2})$"} for path $['query']['languageCode']

however

"$.query": {
            "match": "regex",
            "regex": "^languageCode=[a-z]{2}-[A-z]{2}"
          }

is accepted without warnings. Can pact match individual query parameters separately or just entire query string?

  1. "body": {
                    "geo": {
                        "latitude": 38.898648,
                        "longitude": 77.037692
                    },
    "$.body.geo.latitude": {
                        "match": "number"
                    },
                    "$.body.geo.longitude": {
                        "match": "number"
                    },

    result in WARN: Ignoring unsupported matching rules {"match"=>"number"} for path $['body']['geo']['latitude'] WARN: Ignoring unsupported matching rules {"match"=>"number"} for path $['body']['geo']['longitude']

Is number matcher supported in pact-net? If yes how to make it to work? I verified that “type”, “regex” and “min” matchers available in pact-net are fine.

Many thanks for your help!

koolsanjeev commented 5 years ago

I went through the source code of "PactDslResponse" class and found that the header matching rule uses "header" key for headers instead of "headers". Snapshot is attached here.

image

rafsojka commented 5 years ago

@neilcampbell could you kindly answer my above questions:

  1. Can pact-net match individual query parameters separately or just entire query string?

  2. Is number matcher supported in pact-net? If yes how to make it to work?

Many thanks for your help!

neilcampbell commented 5 years ago

@rafsojka

  1. I believe it is just the full query string
  2. You can use the type matcher for this

Probably your best bet is to download the .NET samples and create your consumer contracts using the samples rather than manually crafting the json contract (it sounds like that is what you are doing). The real benefit of using Pact, is when both parties can write/verify the contract via an automated test.

rafsojka commented 5 years ago

Thanks for your answers @neilcampbell

FYI We are not "manually crafting the json contract". The consumer party is a remote team and generates the contracts using pact-jvm. My team (the provider party) tests APIs based on consumer's contracts using pact-net.

When there are errors/warnings I try to set generate compatible contract using pact-net. So above snippets are comparisons between output from pact-jvm and pact-net. Seems thera are discrepancies between Java na .NET implementations which triggered my questions.

I am happy to close the issue unless you have any further comments/tips for us.

bethesque commented 5 years ago

Can pact match individual query parameters separately or just entire query string?

You can match on individual parameters. There is a very annoying bug with the query params and that warning that I haven't yet worked out how to fix. I'm pretty sure the logic still runs correctly even if there is a warning though. Tell me if it doesn't.

neilcampbell commented 5 years ago

@rafsojka Ah right sorry about that I misunderstood/didn't read properly, contracts generated by pact-jvm should be compatible with pact-net. Pact-net uses the ruby standalone, which many other pact libraries use. I'm pretty sure that pact-jvm doesn't though. If the contracts generated by pact-jvm aren't compatible with pact-net, they likely aren't compatible with quite a few of the other pact libraries either. @bethesque Have you seen this before between pact-jvm and the ruby pact?

rafsojka commented 5 years ago

@bethesque First of all I am not clear how to confirm if the logic is run or not since the warning says the rule is ignored. Then even if it runs the warning says opposite which is even worse to me.

I am trying to adopt Pact in a commercial environment to be used between distributed teams. Hopefully you agree that the explanation "something works although it says it does not" does not sell well in an enterprise environment.

zestzero commented 3 years ago

Sorry this is my first post. I'm also facing the same issue even I do not add any header matching rules. This happen only when I run pact test against mock (not actual service) and you can see from the message below that the different between expected and actual value is an extra space after application/json; . But from the response I've already checked that there is space 😓

server: "Kestrel"
transfer-encoding: "chunked"
          has status code 200
          has a matching body
          includes headers
            "Content-Type" which equals "application/json; charset=utf-8" (FAILED - 1)

Failures:

  1) Verifying a pact between Consumer and Provider Given Status Response Contract Test Combined Status_0 with POST /BookingsV3/Status returns a response which includes headers "Content-Type" which equals "application/json; charset=utf-8"
     Failure/Error: expect(header_value).to match_header(name, expected_header_value)
       Expected header "Content-Type" to equal "application/json; charset=utf-8", but was "application/json;charset=utf-8"

1 interaction, 1 failure
adamrodger commented 3 years ago

@zestzero that appears to be correctly identifying a slight mismatch. Your contract says it expects a space after the semicolon but your provider doesn't include one.

zestzero commented 3 years ago

@zestzero that appears to be correctly identifying a slight mismatch. Your contract says it expects a space after the semicolon but your provider doesn't include one.

That's what it should be but my response also contain a space in that place already. 😅

adamrodger commented 2 years ago

Response header matching rules appear to work correctly in 4.x, so closing for now. We can reopen if that turns out not to be the case.