pact-foundation / pact-jvm

JVM 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://docs.pact.io
Apache License 2.0
1.08k stars 479 forks source link

`eachKeyLike` does not allow type matchers, only value matches and works unexpectedly with regexes. #1813

Closed nlandjev closed 1 month ago

nlandjev commented 3 months ago

We are using Pact to verify a JSON response payload that satisfies the following properties:

  1. The payload is an object that has a property called "a".
  2. The value corresponding to the property "a" is also an object.
  3. Each key inside the nested object matches a specific regular expression
  4. The value corresponding to each nested object is also an object that has a key called "value" and a value of type string.

For example this object is valid (provided prop1 and prop2 match the regular expression):

{
  "a": {
    "prop1": {
       "value": "x"
    },
    "prop2": {
      "value": "y"
    }
 }

We use the following code to generate the Pact JSON file (snippet just for the object):

final var expectedBody = newJsonBody(body ->
    body.object("a", aObj -> {
        aObj.eachKeyLike("prop1", PactDslJsonRootValue.stingMatcher(REGEX, "prop1"));
        aObj.eachKeyLike("prop1", propObj -> proObj.stringType("value", "x"));
    }));

This generates the following Pact file (snippet just for the object):

"matchingRules": {
  "body": {
    "$.a": {
      "combine": "AND",
      "matchers": [
        {
          "match": "values"
        },
        {
          "match": "values"
        }
      ]
    },
    "$.a.*": {
      "combine": "AND",
      "matchers": [
        {
          "match": "regex",
          "regex": "<REGEX>"
        }
      ]
    },
    "$.a.*.value": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type"
        }
      ]
    }
  }
}

This leads to a problem on the producer side that the body in the example above get accepted even if prop1 and prop2 don't match the regular expression. If we change the matchers in line 7 and 10 in the Pact JSON above to "match": "type", then the body gets rejected as expected. Is there a way to specify this with eachKeyLike? Or is there another way to specify a regex for the keys to match?

Regards, Nikolay

Relevant versions from the pom file: au.com.dius.pact.consumer:junit5 -> 4.6.6 au.com.dius.pact.provider:junit5spring -> 4.6.6

github-actions[bot] commented 3 months ago

🤖 Great news! We've labeled this issue as smartbear-supported and created a tracking ticket in PactFlow's Jira (PACT-2182). We'll keep work public and post updates here. Meanwhile, feel free to check out our docs. Thanks for your patience!

rholshausen commented 3 months ago

The problem is that eachKeyLike doesn't do what you think. It applies the matching rules to the values, not the keys.

I'm going to deprecate those methods, and add ones that actually act on the keys.

rholshausen commented 3 months ago

I've added two new methods, so you can do something like

newJsonBody(body ->
        body.object("a", aObj -> {
          aObj.eachKeyMatching(Matchers.regexp("prop\\d+", "prop1"));
          aObj.eachValueMatching("prop1", propObj -> propObj.stringType("value", "x"));
        }))

I think this is what you want.

YOU54F commented 2 months ago

Hey @nlandjev, are you able to test with the current version and confirm?

nlandjev commented 2 months ago

Hi,

Thanks for the quick fix. It looks correct but I can't get the project to build, so I'm waiting for the next 4.6 release. When is it planned for?

rholshausen commented 2 months ago

I'll release it soon (some time this week). I have one more change I want to get into the release first.

rholshausen commented 2 months ago

4.6.12 is released

nlandjev commented 2 months ago

Thank you, I will check it out next week and close the issue.

nlandjev commented 1 month ago

We've tested this and it works as expected. I'm closing the issue. Thanks for implementing :)