How to define pact-specification matching rule for single string body? - pact

I am setting up a test for a put request uploading a file. The request body in my pact-file consists of a single string, containing a mime boundary that changes for every test run. I am trying to define a regex matching rule for the request body string, but it won't match. A similar matching rule for the header content-type does match.
How should I define the matching rule for the body if the body is only a string?
I'm using a reference implementation of Pact in Rust. The Pact-Specification version is 3.
"request": {
"headers": {
"Content-Length": "206",
"Host": "127.0.0.1:1234",
"Connection": "Close",
"Content-Type": "multipart/form-data; boundary=\"MIME_boundary_4FBA8D0826C707B6\""
},
"body": "--MIME_boundary_4FBA8D0826C707B6\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test_file.txt\"\r\nContent-Type: application/octet-stream\r\n\r\nContent of test file.\r\n--MIME_boundary_4FBA8D0826C707B6--\r\n",
"matchingRules": {
"header": {
"$.Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "multipart/form-data; boundary=\"MIME_boundary_[A-Z0-9]{16}\""
}
]
}
},
"body": {
"$": {
"matchers": [
{
"match": "regex",
"regex": "--MIME_boundary_[A-Z0-9]{16}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test_file.txt\"\r\nContent-Type: application/octet-stream\r\n\r\nContent of test file.\r\n--MIME_boundary_[A-Z0-9]{16}--\r\n"
}
]
}
}
}
}
The code above is part of the pact file used in the test. The test results in a BodyMismatch error. Comparing the expected and received bodies shows that they only differ in mime boundary, so the regex matching is not working.

Through Pact's Slack channel we got the answer that the current Pact code does not support this type of matching. We created a feature request issue on GitHub:
https://github.com/pact-foundation/pact-reference/issues/43

The mime boundary value will always change. Writing a regular expression to match that will be quite challenging. It would be better to have a matching implementation that understands multi-part bodies. Pact-JVM supports this (see https://github.com/DiUS/pact-jvm/blob/master/consumer/pact-jvm-consumer-junit/src/test/groovy/au/com/dius/pact/consumer/junit/ExampleFileUploadSpec.groovy), so it would not be too hard to implement in Pact-Rust.

Related

Media type ignored in open api response

I'm trying to include a bad request (400) response schemas in the open api document for the web api I'm building in .NET 6. The problem is, the media type always defaults to application/octet-stream even though I explicitly set it to application/json.
My controller is decorated with the following attribute:
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestResult), (int)HttpStatusCode.BadRequest, "application/json")]
But, the media type for the response 400 is incorrect in the generated open api document:
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Foo"
}
}
}
},
"400": {
"description": "",
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
}
Am I missing something or is this a bug in NSwag?
So this is no bug, I was using ObjectResult as the response type instead of ProblemDetails. Apparently this class could not be serialized or something and that produced a different response media type.
So no bug!

K6 how do i make a raw (non encoded) post

I'm trying to use K6 to load test prometheus pushgateway and it wants posts in the following format.
http_request_duration_seconds_bucket{le="0.05"} 24054
http_request_duration_seconds_bucket{le="0.1"} 33444
http_request_duration_seconds_bucket{le="0.2"} 100392
there has to be a linefeed at the end of each line (and one extra at the end) - however I seem to only get url encoded strings like %20 for space etc. Is it possible somehow to post raw strings?
If you just construct the body as a string yourself and pass it that way to http.post(), it should be sent as-is, without any modifications. This code should illustrate this, using httpbin.org:
import http from "k6/http";
import crypto from "k6/crypto";
let payload = `http_request_duration_seconds_bucket{le="0.05"} 24054
http_request_duration_seconds_bucket{le="0.1"} 33444
http_request_duration_seconds_bucket{le="0.2"} 100392
`;
export default function (data) {
console.log(crypto.sha256(payload, "hex"));
let resp = http.post("https://httpbin.org/anything", payload);
console.log(crypto.sha256(resp.json().data, "hex"));
console.log(resp.body);
}
it will output something like this:
INFO[0000] 773f0d81713fca0663ad7a01135bf674b93b0859854b2248368125af3f070d29
INFO[0001] 773f0d81713fca0663ad7a01135bf674b93b0859854b2248368125af3f070d29
INFO[0001] {
"args": {},
"data": "http_request_duration_seconds_bucket{le=\"0.05\"} 24054\nhttp_request_duration_seconds_bucket{le=\"0.1\"} 33444\nhttp_request_duration_seconds_bucket{le=\"0.2\"} 100392\n",
"files": {},
"form": {},
"headers": {
"Connection": "close",
"Content-Length": "161",
"Host": "httpbin.org",
"User-Agent": "k6/0.23.1 (https://k6.io/)"
},
"json": null,
"method": "POST",
"url": "https://httpbin.org/anything"
}

Spring Cloud Contract returns "cursor" in the response payload for elements with regex

I have a java based service as the provider and a node JS app as the consumer.
I used a stub runner here https://github.com/spring-cloud-samples/stub-runner-boot for Node JS to run against the wiremock. But whether it's Node JS, browser or curl as client I get this "cursor" text in place of generated string from regex elements.
This is the contract:
request {
method GET()
url value(consumer(regex('/v2/accounts/[0-9]+')))
}
response {
status 200
headers {
contentType(applicationJson())
}
body (
"firstName": regex('[a-zA-Z]*'),
"lastName": regex('[a-zA-Z]*'),
"kycStatus": regex('FAILED|PASSED|PENDING|ERROR'),
"address": [
"streetAddress" : "3244 jackson street",
"city" : "City",
"state" : regex('[a-zA-Z]{2}'),
"zipcode": regex('^\\d{5}\$')
]
)
}
This is the actual response from wiremock:
Response:
HTTP/1.1 200
Content-Type: [application/json]
{
"firstName": {
"cursor": 9
},
"lastName": {
"cursor": 9
},
"kycStatus": {
"cursor": 27
},
"address": {
"streetAddress": "3244 jackson street",
"city": "City",
"state": {
"cursor": 11
},
"zipcode": {
"cursor": 7
}
}
}
I noticed that your cursor values are actually the number of characters in your regex. So that told me something was definitely wrong. I've never ran into this before.
I think you need to wrap your regex with value()
request {
method GET()
url value(consumer(regex('/v2/accounts/[0-9]+')))
}
response {
status 200
headers {
contentType(applicationJson())
}
body (
"firstName": value(producer(regex('[a-zA-Z]*'))),
"lastName": value(producer(regex('[a-zA-Z]*'))),
"kycStatus": value(producer(regex('FAILED|PASSED|PENDING|ERROR'))),
"address": [
"streetAddress" : "3244 jackson street",
"city" : "City",
"state" : value(producer(regex('[a-zA-Z]{2}'))),
"zipcode": value(producer(regex('^\\d{5}\$')))
]
)
}
I came across another example that affects request payloads in the same way, when generating wiremock stubs.
If I do not add at least one "field" to the request body, for example:
request{
// ...
body (
value(consumer(regex("[a-zA-Z0-9]+")), producer("derp"))
)
}
the request payload is required to be
{
"cursor" : 12
}
as seen by this wiremock stub generated in target/META-INF/.../mappings/myContract.json
"bodyPatterns" : [ {
"equalToJson" : "{\"cursor\":12}",
"ignoreArrayOrder" : false,
"ignoreExtraElements" : false
} ]
Solution
All I needed to do was add at least one field to the request body
body (
aMadeUpField: value(consumer(regex("[a-zA-Z0-9]+")), producer("derp"))
)
And the regex will now work for that field, as seen by my regenerated wiremock stub
"bodyPatterns" : [ {
"matchesJsonPath" : "$[?(#.['aMadeUpField'] =~ /[a-zA-Z0-9]+/)]"
} ]
This is probably why there is a line hidden in the documentation saying only JSON is supported for the request body.
Edit: Another thing to check is "anyBoolean()" which should be changed to "aBoolean()" and "anInteger()" which should be "anyInteger()" (SCC is i guess really inconsistent with naming...). I double check if they are right in IntelliJ by ctrl + hovering over the groovy methods and making sure they return DslProperty or ClientDslProperty
And, as the other poster said, be sure to wrap any regex() with value() and play around with consumer() and producer() if needed.

Why assert in #PactVerification?

I don't understand the use of assert in #PactVerification. To me it seams more like a complicated way of saying 1 == 1. For example:
import static org.assertj.core.api.Assertions.assertThat;
public class PactConsumerDrivenContractUnitTest {
#Rule
public PactProviderRuleMk2 mockProvider
= new PactProviderRuleMk2("test_provider", "localhost", 8080, this);
#Pact(consumer = "test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
return builder
.given("test GET ")
.uponReceiving("GET REQUEST")
.path("/")
.method("GET")
.willRespondWith()
.body("{\"condition\": true, \"name\": \"tom\"}")
}
#Test
#PactVerification()
public void givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody() {
//when
ResponseEntity<String> response
= new RestTemplate().getForEntity(mockProvider.getUrl(), String.class);
//then
assertThat(response.getBody()).contains("condition", "true", "name", "tom");
}
}
So first in "createPact" we state
body("{\"condition\": true, \"name\": \"tom\"}")
Then in givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody annotated #PactVerification we do this
assertThat(response.getBody()).contains("condition", "true", "name", "tom");
But why? We just said that! As far as I can see the assertion does not show up in the generated Pact file. It seams to fill no purpose?
In addition to that, I thought that the idea of contract testing was to reduce the need for integration test since they can break for example if test data changes. But here we still depend on test data. If there are no "Tom" in the Provider, then the test will fail. I primarily wanted to test if the contract is broken, not if the test data has changed.
The example given is a contrived one. In real life using Pact, you wouldn't do this. Your PactVerification would invoke a collaboration method/class/thing which is responsible for the external call to the service you are mocking.
So your assertions are then on what the collaborating function is doing.
Eg. A User Service might create an object with certain properties, that you know only are populated by that external call.
Testing assertions in your #PactVerification test method is not mandatory, yet still it might be very helpful. E.g. you may make a typo in your JSON body string and you wont be able to catch it in your test and it will break provider's pipeline. Assertions in this case have nothing to do with generated Pact file, they play role of a guard that checks in the end if the contract you have just defined (RequestResponsePact) matches all your expectations (assertions).
Also it is worth mentioning that your consumer contract tests should break only if provider tries to release a change that makes your expectations broken. And this is consumer's responsibility to write good contract tests. In your example you have defined following expectation:
#Pact(consumer = "test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
return builder
.given("test GET ")
.uponReceiving("GET REQUEST")
.path("/")
.method("GET")
.willRespondWith()
.body("{\"condition\": true, \"name\": \"tom\"}")
}
This contract will be satisfied as long as condition == true and name == tom. This is over-specification of a response. You could define more flexible response with PactDslJsonBody DSL instead:
#Pact(consumer = "test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
final DslPart body = new PactDslJsonBody()
.stringType("name", "tom")
.booleanType("condition", true);
return builder
.given("test GET ")
.uponReceiving("GET REQUEST")
.path("/")
.method("GET")
.willRespondWith()
.body(body)
.toPact();
}
This fragment will generate Pact file like:
{
"provider": {
"name": "providerA"
},
"consumer": {
"name": "test_consumer"
},
"interactions": [
{
"description": "GET REQUEST",
"request": {
"method": "GET",
"path": "/"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json; charset=UTF-8"
},
"body": {
"condition": true,
"name": "tom"
},
"matchingRules": {
"body": {
"$.name": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.condition": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
},
"providerStates": [
{
"name": "test GET "
}
]
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.10"
}
}
}
The main difference is that this Pact file uses matchingRules to test if:
type of condition field is boolean
type of name field is String
For strings you can also use PactDslJsonBody.stringMatcher(name, regex, value) method if needed. It allows you to define regular expression that will be tested using current field value.

Worklight WL.Server.invokeHttp() with DELETE method doesn't accept query param

I have a Worklight adapter that calls a RESTful method through WL.Server.invokeHttp(). When an http DELETE method is used, the query string parameters do not get added. I'm on Worklight 6.0.
The input is setup like so:
{
"headers": {
"Accept": "application\/json",
"Authorization": "Bearer xxxxxxxxxxxxxxxx",
"Content-Type": "application\/json"
},
"method": "delete",
"parameters": {
"messageIds": "r11118,r11119"
},
"path": "\/myMessages\/v2\/messages"
}
and called like: var result=WL.Server.invokeHttp(input);
But I can see from Wireshark that the query params don't get added:
DELETE /myMessages/v2/messages HTTP/1.1\r\n
If all I do is change the method to a GET, the params are there on Wireshark:
GET /myMessages/v2/messages?messageIds=r11118%2Cr11119 HTTP/1.1\r\n
Sounds like a bug. We'll investigate it and fix in next releases if confirmed.

Resources