hey there, I'm having trouble matching my queries ...
# pactflow
m
hey there, I'm having trouble matching my queries to the oas files in bidirectional contract testing, details in thread
open api spec for the query is as follows:
Copy code
"parameters": [
          {
            "name": "request",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/QuoteRequest"
            }
          }
        ]
ref for QuoteRequest:
Copy code
"QuoteRequest": {
        "required": [
          "amount",
          "buy_currency",
          "fixed_side",
          "sell_currency",
          "tenor"
        ],
        "type": "object",
        "properties": {
          "amount": {
            "type": "number"
          },
          "buy_currency": {
            "maxLength": 3,
            "minLength": 3,
            "type": "string"
          },
          "sell_currency": {
            "maxLength": 3,
            "minLength": 3,
            "type": "string"
          },
          "fixed_side": {
            "type": "string",
            "enum": [
              "buy",
              "sell"
            ]
          },
          "delivery_date": {
            "type": "string"
          },
          "tenor": {
            "pattern": "tod|tom|spot|forward",
            "type": "string"
          }
        }
      }
I generate the pact contract based on a wiremock stub using a custom tool, so that is what I'm trying to tweak
the generated contract currently:
Copy code
"query": {
          "amount": [
            900000
          ],
          "buy_currency": [
            "RON"
          ],
          "sell_currency": [
            "USD"
          ],
          "fixed_side": [
            "sell"
          ],
          "delivery_date": [
            "2024-03-12"
          ],
          "tenor": [
            "tod"
          ]
        }
and matchers
Copy code
"matchingRules": {
          "query": {
            "$.amount": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            },
            "$.buy_currency": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            },
            "$.sell_currency": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            },
            "$.fixed_side": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            },
            "$.delivery_date": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            },
            "$.tenor": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            }
          }
        }
      }
the error I'm getting is
Value is incompatible with the parameter defined in the spec file: must have required property 'value'
I tried the matchers for enum values to be
equality
rather than
type
but that did not fix the error (unless I used the wrong matcher, with the intention of matching an enum value described in oas)
it is also completely not recognising the query values, i.e.
Query parameter is not defined in the spec file: sell_currency
for all parameters
any help massively appreciated!
b
Just sheer curiosity: why are you building a custom tool to do this? What should it do that https://bitbucket.org/atlassian/wiremock-pact-generator/src/master/ doesn’t?
I’m happy to have a look but I’d hate it if you were just reinventing the wheel :)
m
hey there! Valid point, we did look at the available tools first of course, but we were unable to use that one because it's java-only based, limits one provider per wiremock server and has not been actively supported for seemingly a few years
I am looking into my company's policies to make the tool I'm working on open source as well, for those facing the same issues with existing tooling, but that's for further down the line
b
Gotcha! Just wanted to make sure you've seen that. Are you working on a general-purpose WireMock adapter (because you mentioned Java-only)? The not-so-active support is a valid point, indeed. I'll ask around and see if people know more about this. I'm running a test with the latest WireMock version right now just to see if it still works as it used to.
Just ran a test with WireMock 3.4.2 and it still seems to work fine, but that doesn't invalidate your point (and I'm drifting away from your original question)
Isn't the problem caused by the fact that your generated contract seems to prescribe that element values are arrays, e.g.
Copy code
"amount": [
            900000
          ]
whereas the spec says they should be literals, e.g.
Copy code
"amount": {
            "type": "number"
          },
?
m
hey, I had an issue with the contract not being valid without the values placed in arrays, as it seems to have been (or still is) a bug with the validation in pact, it was raised here a while back. Or do you mean I should put the matcher in an array as well? even though the query object in the OAS file is not in array for each value?
I also tried removing the array part but I was getting the old issue where it was breaking from pact. validation POV
b
Just for reference, this is the contract that I just generated, and Pactflow accepts it:
Copy code
{
  "consumer": {
    "name": "order_consumer"
  },
  "provider": {
    "name": "payment_provider"
  },
  "interactions": [
    {
      "description": "GET /payment/this_is_not_a_valid_payment_id -> 400",
      "request": {
        "method": "GET",
        "path": "/payment/this_is_not_a_valid_payment_id",
        "headers": {
          "content-length": "0",
          "connection": "close",
          "accept-encoding": "gzip, x-gzip, deflate",
          "accept": "application/json, application/*+json",
          "user-agent": "Apache-HttpClient/5.2.1 (Java/17.0.3.1)"
        }
      },
      "response": {
        "status": 400
      }
    },
    {
      "description": "GET /payment/00000000-0000-0000-0000-000000000000 -> 404",
      "request": {
        "method": "GET",
        "path": "/payment/00000000-0000-0000-0000-000000000000",
        "headers": {
          "content-length": "0",
          "connection": "close",
          "accept-encoding": "gzip, x-gzip, deflate",
          "accept": "application/json, application/*+json",
          "user-agent": "Apache-HttpClient/5.2.1 (Java/17.0.3.1)"
        }
      },
      "response": {
        "status": 404
      }
    },
    {
      "description": "GET /payment/228aa55c-393c-411b-9410-4a995480e78e -> 200",
      "request": {
        "method": "GET",
        "path": "/payment/228aa55c-393c-411b-9410-4a995480e78e",
        "headers": {
          "content-length": "0",
          "connection": "close",
          "accept-encoding": "gzip, x-gzip, deflate",
          "accept": "application/json, application/*+json",
          "user-agent": "Apache-HttpClient/5.2.1 (Java/17.0.3.1)"
        }
      },
      "response": {
        "status": 200,
        "headers": {
          "content-type": "application/json"
        },
        "body": {
          "id": "8383a7c3-f831-4f4d-a0a9-015165148af5",
          "orderId": "228aa55c-393c-411b-9410-4a995480e78e",
          "status": "payment_complete",
          "amount": 42,
          "description": "Payment for order 228aa55c-393c-411b-9410-4a995480e78e"
        }
      }
    }
  ]
}
Without more specific details about element value matching rules, I assume Pactflow matches on type.
👍 1
EDIT: it does. Changed amount value from number to string and now I see this:
So if type matching is all you need, I think your contract creation logic can be much more straightforward. But from your examples I guess you'll want more specific matching (e.g. on a fixed set of allowed values), too?
y
matching rules are not considered in the bi directional comparison so adding them to the contract is unnecessary
💡 2
m
Bas, I'm not seeing a query in your contract, which is what I'm having the issue with
good to know about matching rules not being considered, was not aware of that
so I guess it's not the matching that's the issue here but rather comparing the query parameters in the contract vs how they're described in the OAS file
b
By
query
you're referring to query parameters, right? Sorry, I'm not fluent in OpenAPI spec 🙂
m
sorry yes, query parameters
anything in url following a
?
in the open api spec described as an object of key value pairs
b
Yes, I'm familiar with them, just not with how they're described in OpenAPI
m
removing the array surrounds around query values in the contract leads to the old
requestQuery[queryName].join is not a function
issue, which was raised in the past
matching rules are not considered in the bi directional comparison so adding them to the contract is unnecessary
@Yousaf Nabi (pactflow.io) is it possible to force check for values for those that require the value to be in an enum for the openapi spec? wondering if that's whats causing this problem here?
issues being:
or could the issue be the forcing of query values to be wrapped in arrays for pact to take them in properly, while the query definition in OAS does not have those arrays
or both 🫠
b
I'm trying to reproduce this in my own project right now, just to see what happens 🙂
🙌 2
y
my gen debugging process 1. get pact and oas 2. check oas is valid 3. check pact is valid 4. run against swagger-mock-validator repo directly https://github.com/pactflow/swagger-mock-validator 5. I would also check the pact contract generated with pact framework itself just to ensure no issues with our manually generated one can then hack on the smv codebase to demonstrate issue and tweak code to support, if it doesn’t already
b
Changed path to query parameter, this is what the contract generated by WireMockPactGenerator looks like now:
Copy code
{
  "consumer": {
    "name": "order_consumer"
  },
  "provider": {
    "name": "payment_provider"
  },
  "interactions": [
    {
      "description": "GET /payment -> 400",
      "request": {
        "method": "GET",
        "path": "/payment",
        "query": "order=this_is_not_a_valid_payment_id",
        "headers": {
          "content-length": "0",
          "connection": "close",
          "accept-encoding": "gzip, x-gzip, deflate",
          "accept": "application/json, application/*+json",
          "user-agent": "Apache-HttpClient/5.2.1 (Java/17.0.3.1)"
        }
      },
      "response": {
        "status": 400
      }
    },
    {
      "description": "GET /payment -> 404",
      "request": {
        "method": "GET",
        "path": "/payment",
        "query": "order=00000000-0000-0000-0000-000000000000",
        "headers": {
          "content-length": "0",
          "connection": "close",
          "accept-encoding": "gzip, x-gzip, deflate",
          "accept": "application/json, application/*+json",
          "user-agent": "Apache-HttpClient/5.2.1 (Java/17.0.3.1)"
        }
      },
      "response": {
        "status": 404
      }
    },
    {
      "description": "GET /payment -> 200",
      "request": {
        "method": "GET",
        "path": "/payment",
        "query": "order=228aa55c-393c-411b-9410-4a995480e78e",
        "headers": {
          "content-length": "0",
          "connection": "close",
          "accept-encoding": "gzip, x-gzip, deflate",
          "accept": "application/json, application/*+json",
          "user-agent": "Apache-HttpClient/5.2.1 (Java/17.0.3.1)"
        }
      },
      "response": {
        "status": 200,
        "headers": {
          "content-type": "application/json"
        },
        "body": {
          "id": "8383a7c3-f831-4f4d-a0a9-015165148af5",
          "orderId": "228aa55c-393c-411b-9410-4a995480e78e",
          "status": "payment_complete",
          "amount": 42,
          "description": "Payment for order 228aa55c-393c-411b-9410-4a995480e78e"
        }
      }
    }
  ]
}
Let me see if I can validate that against an updated OAS
m
thanks Bas - if it works on your end I'll update mine to include the query as just a string rather than object ; curious how it treats multiple parameters though? assuming
&
would be accepted in this case
b
Happy to test that too once provider side verification works
❤️ 1
Seems to work! OpenAPI:
Pactflow:
Will do the multiple query parameters later today!
m
Thanks a lot Bas, I'll try it out on my end and see how goes
really appreciate the help
🙌 1
y
Legend as always @Bas Dijkstra!
b
I'm learning here, too 🙂
Multiple query parameters end up in the contract like this (as you expected):
"query": "order=228aa55c-393c-411b-9410-4a995480e78e&status=complete",
Validation is successful again once I add the same parameter to the OpenAPI spec
m
yep tried the multiple ones, in pactflow they are happily coming through as an object, but it's still not matching to the object as defined in OAS upside down tears
OAs is defined by
springdoc-openapi-starter-webmvc-ui
FWIW
b
When I remove the second query parameter from the consumer-side contract (so that only has one) but the OAS requires both, I get a validation error (as expected):
The error message is a little cryptic, though
m
aha
so that's what i see, but all the properties are there
b
Yes, that's what I thought
m
all the required properties that is - the main difference I assume is that some properties have enum/pattern defined there
rather than just matched by type
b
I think so, too
m
well then that's a bummer - @Yousaf Nabi (pactflow.io) I can see from the link that it's not currently comparing beyond primitive types, and guessing there's no way to force it?
ie force using custom matchers
m
Yeah. Pact Matchers are not used in BDCT comparison. All that is matched are the example values in the pact file against the schema in the BDCT.
Do you happen have a minimal OAS and pact file we can take a look at Milda?
the must have required property of “value” is confusing me, so I suspect there is something not quite right about the OAS structure, or something the comparison tool is not liking about it.
m
hey Matt, are the snippets are posted earlier not sufficient?
also Bas had the same 'value' error when he removed the 2nd one thinking2
b
Yes, I can see how that error message is confusing, I reproduced this by using a provider OAS that requires two query parameters and then providing only one on the consumer side. The other way around worked fine (sending two as the consumer, requiring only one as the provider) but that is as expected.
The name of the query parameter in my case was NOT
value
by the way :)
👍 1
m
little update, I have removed all enum/pattern/min max definitions from the OAS file, but the error still remains and it's not matching up the parameters. The other theory is maybe the queries defined as objects are not working? will rewrite it as individual params and check
ok it is because it's wrapped in an object, specifically this part:
Copy code
"parameters": [
          {
            "name": "request",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/QuoteRequest"
            }
          }
        ]
where the name "request" is the key and the schema ref defining the parameters is the value. how should the name be defined here in order to make use of the reference being a query object?
Copy code
"QuoteRequest": {
        "required": [
          "amount",
          "buy_currency",
          "fixed_side",
          "sell_currency",
          "tenor"
        ],
        "type": "object",
        "properties": {
          "amount": {
            "type": "number"
          },
          "buy_currency": {
            "maxLength": 3,
            "minLength": 3,
            "type": "string"
          },
          "sell_currency": {
            "maxLength": 3,
            "minLength": 3,
            "type": "string"
          },
          "fixed_side": {
            "type": "string",
            "enum": [
              "buy",
              "sell"
            ]
          },
          "delivery_date": {
            "type": "string"
          },
          "tenor": {
            "pattern": "tod|tom|spot|forward",
            "type": "string"
          }
        }
      },
should all query params in this case be
request.amount=90000&request.buy_currency=GBP
and such?
^ request.something doesn't seem to work
b
Do you have an example of a working request / endpoint?
m
parameters defined without a reference work:
Copy code
"parameters": [
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "buy_currency",
            "in": "query",
            "required": true,
            "schema": {
              "maxLength": 3,
              "minLength": 3,
              "type": "string"
            }
          },
          {
            "name": "sell_currency",
            "in": "query",
            "required": true,
            "schema": {
              "maxLength": 3,
              "minLength": 3,
              "type": "string"
            }
          },
          {
            "name": "fixed_side",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "buy",
                "sell"
              ]
            }
          },
          {
            "name": "delivery_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tenor",
            "in": "query",
            "required": true,
            "schema": {
              "pattern": "tod|tom|spot|forward",
              "type": "string"
            }
          }
        ]
and this fails
Copy code
"parameters": [
          {
            "name": "request",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/QuoteRequest"
            }
          }
        ]
because of that
name: request
being (presumably) treated as a key for the query "object"
b
Yes, that's what I think, too. But I also think the two schemas are different, so it's only logical. One defines a single, complex query parameter (complex meaning having an object value) and the other having multiple, simple query parameters (simple meaning having a primitive value). It's only logical that only one of them works, not the other. That's why I was asking if you could show (the relevant part of) an actual, working request endpoint for your provider, partly out of curiosity (never worked with complex query parameters), partly because it helps define the consumer expectations in the right way, partly because then we can see which of the two OAS variants is the right one
👍 1
m
host:port/v1/quotes?amount=113&buy_currency=SGD&sell_currency=AUD&fixed_side=buy&delivery_date=2024-04-15&tenor=spot' \
+ headers normally works
ie just the query params as normal 🫠
and the OAS schema is generated from code 🤷‍♀️
b
Assuming you're using OpenAPI 3.x, I think the problem might be (I'm cautious here) that from the schema the processor doesn't know how to process the query parameter. See this SO answer here: https://stackoverflow.com/a/51277233 If you add
"style": "form"
and
"explode": true
to the schema, would that make it work?
m
thanks Das, I'll test it out!
🙌 1
m
s/Das/Bas 😛
hey Matt, are the snippets are posted earlier not sufficient?
forgive me, but we spend a lot of time helping people out on forums. Whilst I could probably put together an example OAS/pact file from the snippets it would improve the likelihood if a repro was provided.
I’ve managed to merge it into the
parameters
example here: https://github.com/pactflow/bdct-oas-examples (see README) I think Bas is on the right track. My assumption is that there is a bug (or perhaps, it just never considered) in resolving query parameters in this fashion. It’s worth a quick play around, but I suspect the additional level of indirection is what’s making it look for
value
rather than seeing the
$ref
directly
You could take a look at https://github.com/pactflow/swagger-mock-validator/ to see if it’s easily fixed
b
Would love to hear if adding those fields to the spec made it work, @Milda :)
m
sorry guys having a day off today, but the explode parameter didn't work on Friday - I'll have more of a look tomorrow though
👍 1