Support for Pact v3 (#569)
* Upgraded pact-jvm-model to 3.5.13 * Enhanced the conversion of Spring Cloud contracts to Pact contracts using the v3 spec. Introduces breaking change returning a list of `Pact`s instead of one. * Enhanced the conversion of Pact contracts using the v3 spec tp Spring Cloud contracts. * Implemented conversion of Pact v3 messaging to/from Spring Cloud contracts * Updated code based on comments in the PR * Added null matcher * Added missing null matcher conversion from SCC to Pact * Added number, integer and decimal matchers * Added support for multiple matchers for the same json path. Currently only the AND rule logic is supported. * Added value generator functionality * Refactored `stubMatchers` and `testMatchers` to support multiple types of "matcher groups", eg. header, path & query * Refactored e37a8c5d82f96da6e1a698331afc62e6cf747bd6 in favor of a less invasive change using deprecation * Added header matcher conversion * Add/updated the ASF license header * Fixed an issue with the header matchers while updating the documentation * Updated the documentation * Made some last minute changes to the `bodyMatchers`: `byNull()` isn't supported by WireMock Closes #583 Fixes #595
This commit is contained in:
committed by
Marcin Grzejszczak
parent
ff8fd0a16c
commit
de529e222e
@@ -371,8 +371,11 @@ want to force the consumers to stub their clocks to always return the same value
|
||||
so that it gets matched by the stub.
|
||||
|
||||
For Groovy DSL you can provide the dynamic parts in your contracts
|
||||
in two ways: pass them directly in the body or set them in separate sections called
|
||||
`testMatchers` and `stubMatchers`.
|
||||
in two ways: pass them directly in the body or set them in a separate section called
|
||||
`bodyMatchers`.
|
||||
|
||||
NOTE: Before 2.0.0 these were set using `testMatchers` and `stubMatchers`,
|
||||
check out the https://github.com/spring-cloud/spring-cloud-contract/wiki/Spring-Cloud-Contract-2.0-Migration-Guide[migration guide] for more information.
|
||||
|
||||
For YAML you can only use the `matchers` section.
|
||||
|
||||
@@ -732,11 +735,12 @@ If you work with https://docs.pact.io/[Pact], the following discussion may seem
|
||||
Quite a few users are used to having a separation between the body and setting the
|
||||
dynamic parts of a contract.
|
||||
|
||||
You can use two separate sections:
|
||||
You can use the `bodyMatchers` section for two reasons:
|
||||
|
||||
* `stubMatchers`, which lets you define the dynamic values that should end up in a stub.
|
||||
* Define the dynamic values that should end up in a stub.
|
||||
You can set it in the `request` or `inputMessage` part of your contract.
|
||||
* `testMatchers`, which is present in the `response` or `outputMessage` side of the
|
||||
* Verify the result of your test.
|
||||
This section is present in the `response` or `outputMessage` side of the
|
||||
contract.
|
||||
|
||||
Currently, Spring Cloud Contract Verifier supports only JSON Path-based matchers with the
|
||||
@@ -744,7 +748,7 @@ following matching possibilities:
|
||||
|
||||
.Groovy DSL
|
||||
|
||||
* For `stubMatchers`:
|
||||
* For the stubs:
|
||||
** `byEquality()`: The value taken from the response via the provided JSON Path must be
|
||||
equal to the value provided in the contract.
|
||||
** `byRegex(...)`: The value taken from the response via the provided JSON Path must
|
||||
@@ -755,7 +759,7 @@ match the regex for an ISO Date value.
|
||||
match the regex for an ISO DateTime value.
|
||||
** `byTime()`: The value taken from the response via the provided JSON Path must
|
||||
match the regex for an ISO Time value.
|
||||
* For `testMatchers`:
|
||||
* For the verification:
|
||||
** `byEquality()`: The value taken from the response via the provided JSON Path must be
|
||||
equal to the provided value in the contract.
|
||||
** `byRegex(...)`: The value taken from the response via the provided JSON Path must
|
||||
@@ -781,6 +785,7 @@ following, depending on the JSON path:
|
||||
*** `Map`: If you point to a `Map`.
|
||||
*** `Number`: If you point to `Integer`, `Double`, or other kind of number.
|
||||
*** `Boolean`: If you point to a `Boolean`.
|
||||
** `byNull()`: The value taken from the response via the provided JSON Path must be null
|
||||
|
||||
.YAML
|
||||
|
||||
@@ -824,6 +829,7 @@ Below you can find the allowed list of `type`s.
|
||||
** `by_type`
|
||||
*** there are 2 additional fields accepted: `minOccurrence` and `maxOccurrence`.
|
||||
** `by_command`
|
||||
** `by_null`
|
||||
|
||||
Consider the following example:
|
||||
|
||||
@@ -846,7 +852,7 @@ contain are explicitly set. For the `valueWithoutAMatcher`, the verification tak
|
||||
in the same way as without the use of matchers. In that case, the test performs an
|
||||
equality check.
|
||||
|
||||
For the response side in the `testMatchers` section, we define the dynamic parts in a
|
||||
For the response side in the `bodyMatchers` section, we define the dynamic parts in a
|
||||
similar manner. The only difference is that the `byType` matchers are also present. The
|
||||
verifier engine checks four fields to verify whether the response from the test
|
||||
has a value for which the JSON path matches the given field, is of the same type as the one
|
||||
@@ -868,7 +874,7 @@ separates the autogenerated assertions and the assertion from matchers):
|
||||
// given:
|
||||
MockMvcRequestSpecification request = given()
|
||||
.header("Content-Type", "application/json")
|
||||
.body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\"}");
|
||||
.body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\",\"key\":{\"complex.key\":\"foo\"}}");
|
||||
|
||||
// when:
|
||||
ResponseOptions response = given().spec(request)
|
||||
@@ -879,29 +885,30 @@ separates the autogenerated assertions and the assertion from matchers):
|
||||
assertThat(response.header("Content-Type")).matches("application/json.*");
|
||||
// and:
|
||||
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
|
||||
assertThatJson(parsedJson).field("valueWithoutAMatcher").isEqualTo("foo");
|
||||
assertThatJson(parsedJson).field("['valueWithoutAMatcher']").isEqualTo("foo");
|
||||
// and:
|
||||
assertThat(parsedJson.read("$.duck", String.class)).matches("[0-9]{3}");
|
||||
assertThat(parsedJson.read("$.duck", Integer.class)).isEqualTo(123);
|
||||
assertThat(parsedJson.read("$.alpha", String.class)).matches("[\\p{L}]*");
|
||||
assertThat(parsedJson.read("$.alpha", String.class)).isEqualTo("abc");
|
||||
assertThat(parsedJson.read("$.number", String.class)).matches("-?\\d*(\\.\\d+)?");
|
||||
assertThat(parsedJson.read("$.number", String.class)).matches("-?(\\d*\\.\\d+|\\d+)");
|
||||
assertThat(parsedJson.read("$.aBoolean", String.class)).matches("(true|false)");
|
||||
assertThat(parsedJson.read("$.date", String.class)).matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])");
|
||||
assertThat(parsedJson.read("$.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])");
|
||||
assertThat(parsedJson.read("$.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])");
|
||||
assertThat((Object) parsedJson.read("$.valueWithTypeMatch")).isInstanceOf(java.lang.String.class);
|
||||
assertThat((Object) parsedJson.read("$.valueWithMin")).isInstanceOf(java.util.List.class);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMin", java.util.Collection.class)).hasSizeGreaterThanOrEqualTo(1);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMin", java.util.Collection.class)).as("$.valueWithMin").hasSizeGreaterThanOrEqualTo(1);
|
||||
assertThat((Object) parsedJson.read("$.valueWithMax")).isInstanceOf(java.util.List.class);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMax", java.util.Collection.class)).hasSizeLessThanOrEqualTo(3);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMax", java.util.Collection.class)).as("$.valueWithMax").hasSizeLessThanOrEqualTo(3);
|
||||
assertThat((Object) parsedJson.read("$.valueWithMinMax")).isInstanceOf(java.util.List.class);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMinMax", java.util.Collection.class)).hasSizeBetween(1, 3);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMinMax", java.util.Collection.class)).as("$.valueWithMinMax").hasSizeBetween(1, 3);
|
||||
assertThat((Object) parsedJson.read("$.valueWithMinEmpty")).isInstanceOf(java.util.List.class);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMinEmpty", java.util.Collection.class)).hasSizeGreaterThanOrEqualTo(0);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMinEmpty", java.util.Collection.class)).as("$.valueWithMinEmpty").hasSizeGreaterThanOrEqualTo(0);
|
||||
assertThat((Object) parsedJson.read("$.valueWithMaxEmpty")).isInstanceOf(java.util.List.class);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMaxEmpty", java.util.Collection.class)).hasSizeLessThanOrEqualTo(0);
|
||||
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMaxEmpty", java.util.Collection.class)).as("$.valueWithMaxEmpty").hasSizeLessThanOrEqualTo(0);
|
||||
assertThatValueIsANumber(parsedJson.read("$.duck"));
|
||||
assertThat(parsedJson.read("$.['key'].['complex.key']", String.class)).isEqualTo("foo");
|
||||
----
|
||||
|
||||
IMPORTANT: Notice that, for the `byCommand` method, the example calls the
|
||||
@@ -917,7 +924,7 @@ The resulting WireMock stub is in the following example:
|
||||
include::{plugins_path}/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy[tags=matchers,indent=0]
|
||||
----
|
||||
|
||||
IMPORTANT: If you use a `matcher`, then the part of the request aned response that the
|
||||
IMPORTANT: If you use a `matcher`, then the part of the request and response that the
|
||||
`matcher` addresses with the JSON Path gets removed from the assertion. In the case of
|
||||
verifying a collection, you must create matchers for *all* the elements of the
|
||||
collection.
|
||||
@@ -944,7 +951,7 @@ Contract.make {
|
||||
]
|
||||
]
|
||||
)
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.events[0].operation', byRegex('.+'))
|
||||
jsonPath('$.events[0].eventId', byRegex('^([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$'))
|
||||
jsonPath('$.events[0].status', byRegex('.+'))
|
||||
@@ -999,7 +1006,7 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract
|
||||
|
||||
If you're using asynchronous communication on the server side (your controllers are
|
||||
returning `Callable`, `DeferredResult`, and so on), then, inside your contract, you must
|
||||
provide a `sync()` method in the `response` section. The following code shows an example:
|
||||
provide an `async()` method in the `response` section. The following code shows an example:
|
||||
|
||||
.Groovy DSL
|
||||
[source,groovy,indent=0]
|
||||
@@ -1378,8 +1385,15 @@ org.springframework.cloud.contract.verifier.converter.YamlContractConverter
|
||||
==== Pact Converter
|
||||
|
||||
Spring Cloud Contract includes support for https://docs.pact.io/[Pact] representation of
|
||||
contracts. Instead of using the Groovy DSL, you can use Pact files. In this section, we
|
||||
present how to add Pact support for your project.
|
||||
contracts up until v4. Instead of using the Groovy DSL, you can use Pact files. In this section, we
|
||||
present how to add Pact support for your project. Note however that not all functionality is supported.
|
||||
Starting with v3 you can combine multiple matcher for the same element;
|
||||
you can use matchers for the body, headers, request and path; and you can use value generators.
|
||||
Spring Cloud Contract currently only supports multiple matchers that are combined using the AND rule logic.
|
||||
Next to that the request and path matchers are skipped during the conversion.
|
||||
When using a date, time or datetime value generator with a given format,
|
||||
the given format will be skipped and the ISO format will be used.
|
||||
|
||||
|
||||
==== Pact Contract
|
||||
|
||||
@@ -1395,7 +1409,7 @@ The remainder of this section about using Pact refers to the preceding file.
|
||||
|
||||
==== Pact for Producers
|
||||
|
||||
On the producer side, you mustadd two additional dependencies to your plugin
|
||||
On the producer side, you must add two additional dependencies to your plugin
|
||||
configuration. One is the Spring Cloud Contract Pact support, and the other represents
|
||||
the current Pact version that you use.
|
||||
|
||||
@@ -1429,10 +1443,10 @@ test might be as follows:
|
||||
|
||||
// then:
|
||||
assertThat(response.statusCode()).isEqualTo(200);
|
||||
assertThat(response.header("Content-Type")).isEqualTo("application/vnd.fraud.v1+json;charset=UTF-8");
|
||||
assertThat(response.header("Content-Type")).matches("application/vnd\\.fraud\\.v1\\+json.*");
|
||||
// and:
|
||||
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
|
||||
assertThatJson(parsedJson).field("rejectionReason").isEqualTo("Amount too high");
|
||||
assertThatJson(parsedJson).field("['rejectionReason']").isEqualTo("Amount too high");
|
||||
// and:
|
||||
assertThat(parsedJson.read("$.fraudCheckStatus", String.class)).matches("FRAUD");
|
||||
}
|
||||
@@ -1443,17 +1457,18 @@ The corresponding generated stub might be as follows:
|
||||
[source,javascript,indent=0]
|
||||
----
|
||||
{
|
||||
"id" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
|
||||
"uuid" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
|
||||
"request" : {
|
||||
"url" : "/fraudcheck",
|
||||
"method" : "PUT",
|
||||
"headers" : {
|
||||
"Content-Type" : {
|
||||
"equalTo" : "application/vnd.fraud.v1+json"
|
||||
"matches" : "application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
},
|
||||
"bodyPatterns" : [ {
|
||||
"matchesJsonPath" : "$[?(@.loanAmount == 99999)]"
|
||||
"matchesJsonPath" : "$[?(@.['loanAmount'] == 99999)]"
|
||||
}, {
|
||||
"matchesJsonPath" : "$[?(@.clientId =~ /([0-9]{10})/)]"
|
||||
} ]
|
||||
@@ -1463,8 +1478,9 @@ The corresponding generated stub might be as follows:
|
||||
"body" : "{\"fraudCheckStatus\":\"FRAUD\",\"rejectionReason\":\"Amount too high\"}",
|
||||
"headers" : {
|
||||
"Content-Type" : "application/vnd.fraud.v1+json;charset=UTF-8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"transformers" : [ "response-template" ]
|
||||
},
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
20
pom.xml
20
pom.xml
@@ -24,6 +24,7 @@
|
||||
|
||||
<properties>
|
||||
<checkstyle.version>2.17</checkstyle.version>
|
||||
<pact.version>3.5.13</pact.version>
|
||||
<jsch-agent.version>0.0.9</jsch-agent.version>
|
||||
<spring-cloud-build.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-build.version>
|
||||
<spring-cloud-zookeeper.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-zookeeper.version>
|
||||
@@ -111,10 +112,27 @@
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>${commons-text.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-consumer-java8_2.12</artifactId>
|
||||
<version>${pact.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-model</artifactId>
|
||||
<version>2.4.18</version>
|
||||
<version>${pact.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.jknack</groupId>
|
||||
|
||||
@@ -40,7 +40,7 @@ dependencies {
|
||||
testCompile "org.springframework.cloud:spring-cloud-starter-contract-stub-runner"
|
||||
//tag::pact_dependency[]
|
||||
testCompile "org.springframework.cloud:spring-cloud-contract-spec-pact"
|
||||
testCompile 'au.com.dius:pact-jvm-model:2.4.18'
|
||||
testCompile 'au.com.dius:pact-jvm-model:3.5.13'
|
||||
//end::pact_dependency[]
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-model</artifactId>
|
||||
<version>2.4.18</version>
|
||||
<version>3.5.13</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- end::pact_dependency[] -->
|
||||
|
||||
@@ -11,7 +11,7 @@ buildscript {
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${findProperty('verifierVersion') ?: verifierVersion}"
|
||||
//tag::pact_dependency[]
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-spec-pact:${findProperty('verifierVersion') ?: verifierVersion}"
|
||||
classpath 'au.com.dius:pact-jvm-model:2.4.18'
|
||||
classpath 'au.com.dius:pact-jvm-model:3.5.13'
|
||||
//end::pact_dependency[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-model</artifactId>
|
||||
<version>2.4.18</version>
|
||||
<version>3.5.13</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
|
||||
@@ -18,10 +18,36 @@
|
||||
"clientId": "1234567890",
|
||||
"loanAmount": 99999
|
||||
},
|
||||
"generators": {
|
||||
"body": {
|
||||
"$.clientId": {
|
||||
"type": "Regex",
|
||||
"regex": "[0-9]{10}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"matchingRules": {
|
||||
"$.body.clientId": {
|
||||
"match": "regex",
|
||||
"regex": "[0-9]{10}"
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body" : {
|
||||
"$.clientId": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[0-9]{10}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -35,9 +61,27 @@
|
||||
"rejectionReason": "Amount too high"
|
||||
},
|
||||
"matchingRules": {
|
||||
"$.body.fraudCheckStatus": {
|
||||
"match": "regex",
|
||||
"regex": "FRAUD"
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.fraudCheckStatus": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "FRAUD"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,10 +89,10 @@
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
@@ -27,8 +29,11 @@ import repackaged.nl.flotsam.xeger.Xeger
|
||||
* Represents an input for messaging. The input can be a message or some
|
||||
* action inside the application.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@TypeChecked
|
||||
@EqualsAndHashCode
|
||||
@ToString(includePackage = false, includeNames = true)
|
||||
@@ -41,7 +46,7 @@ class Input extends Common {
|
||||
Headers messageHeaders = new Headers()
|
||||
BodyType messageBody
|
||||
ExecutionProperty assertThat
|
||||
BodyMatchers matchers
|
||||
BodyMatchers bodyMatchers
|
||||
|
||||
Input() {}
|
||||
|
||||
@@ -107,9 +112,18 @@ class Input extends Common {
|
||||
this.assertThat = new ExecutionProperty(assertThat)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too
|
||||
*/
|
||||
@Deprecated
|
||||
void stubMatchers(@DelegatesTo(BodyMatchers) Closure closure) {
|
||||
this.matchers = new BodyMatchers()
|
||||
closure.delegate = this.matchers
|
||||
log.warn("stubMatchers method is deprecated. Please use bodyMatchers instead")
|
||||
bodyMatchers(closure)
|
||||
}
|
||||
|
||||
void bodyMatchers(@DelegatesTo(BodyMatchers) Closure closure) {
|
||||
this.bodyMatchers = new BodyMatchers()
|
||||
closure.delegate = this.bodyMatchers
|
||||
closure()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
@@ -7,6 +22,7 @@ import groovy.transform.CompileStatic
|
||||
* the body of the request or response.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 1.0.3
|
||||
*/
|
||||
@CompileStatic
|
||||
@@ -42,10 +58,14 @@ enum MatchingType {
|
||||
/**
|
||||
* The user can provide custom command to execute
|
||||
*/
|
||||
COMMAND
|
||||
COMMAND,
|
||||
/**
|
||||
* Verification if the value for the given path is null
|
||||
*/
|
||||
NULL
|
||||
|
||||
static boolean regexRelated(MatchingType type) {
|
||||
if (type == EQUALITY || type == TYPE || type == COMMAND ) {
|
||||
if (type == EQUALITY || type == TYPE || type == COMMAND || type == NULL ) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,11 +20,20 @@ import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.ToString
|
||||
import groovy.transform.TypeChecked
|
||||
import groovy.util.logging.Slf4j
|
||||
import org.apache.commons.lang3.StringEscapeUtils
|
||||
import repackaged.nl.flotsam.xeger.Xeger
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Represents an output for messaging. Used for verifying
|
||||
* the body and headers that are sent.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@TypeChecked
|
||||
@EqualsAndHashCode
|
||||
@ToString(includePackage = false, includeNames = true)
|
||||
@@ -36,7 +45,7 @@ class OutputMessage extends Common {
|
||||
Headers headers
|
||||
DslProperty body
|
||||
ExecutionProperty assertThat
|
||||
ResponseBodyMatchers matchers
|
||||
ResponseBodyMatchers bodyMatchers
|
||||
|
||||
OutputMessage() {}
|
||||
|
||||
@@ -80,9 +89,18 @@ class OutputMessage extends Common {
|
||||
return new DslProperty(value, server.serverValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too
|
||||
*/
|
||||
@Deprecated
|
||||
void testMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) {
|
||||
this.matchers = new ResponseBodyMatchers()
|
||||
closure.delegate = this.matchers
|
||||
log.warn("testMatchers method is deprecated. Please use bodyMatchers instead")
|
||||
bodyMatchers(closure)
|
||||
}
|
||||
|
||||
void bodyMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) {
|
||||
this.bodyMatchers = new ResponseBodyMatchers()
|
||||
closure.delegate = this.bodyMatchers
|
||||
closure()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import org.apache.commons.lang3.RandomStringUtils
|
||||
|
||||
import java.util.regex.Pattern
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
@PackageScope
|
||||
@CompileStatic
|
||||
@@ -40,10 +57,19 @@ abstract class PatternValueDslProperty<T extends DslProperty> {
|
||||
RandomStringGenerator.randomString(20))
|
||||
}
|
||||
|
||||
T anyAlphaNumeric() {
|
||||
return createAndValidateProperty(RegexPatterns.ALPHA_NUMERIC,
|
||||
RandomStringUtils.randomAlphanumeric(20))
|
||||
}
|
||||
|
||||
T anyNumber() {
|
||||
return createAndValidateProperty(RegexPatterns.NUMBER, this.random.nextInt())
|
||||
}
|
||||
|
||||
T anyInteger() {
|
||||
return createAndValidateProperty(RegexPatterns.INTEGER, this.random.nextInt())
|
||||
}
|
||||
|
||||
T anyPositiveInt() {
|
||||
return createAndValidateProperty(RegexPatterns.POSITIVE_INT, Math.abs(this.random.nextInt() + 1))
|
||||
}
|
||||
@@ -52,6 +78,11 @@ abstract class PatternValueDslProperty<T extends DslProperty> {
|
||||
return createAndValidateProperty(RegexPatterns.DOUBLE, this.random.nextInt(100) + this.random.nextDouble())
|
||||
}
|
||||
|
||||
T anyHex() {
|
||||
return createAndValidateProperty(RegexPatterns.HEX,
|
||||
RandomStringUtils.random(10, "0123456789abcdef"))
|
||||
}
|
||||
|
||||
T aBoolean() {
|
||||
return createAndValidateProperty(RegexPatterns.TRUE_OR_FALSE)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,8 @@ import java.util.regex.Pattern
|
||||
/**
|
||||
* Contains most common regular expression patterns
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@CompileStatic
|
||||
@@ -34,10 +36,13 @@ class RegexPatterns {
|
||||
|
||||
// tag::regexps[]
|
||||
protected static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
|
||||
protected static final Pattern ALPHA_NUMERIC = Pattern.compile('[a-zA-Z0-9]+')
|
||||
protected static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/)
|
||||
protected static final Pattern NUMBER = Pattern.compile('-?(\\d*\\.\\d+|\\d+)')
|
||||
protected static final Pattern INTEGER = Pattern.compile('-?(\\d+)')
|
||||
protected static final Pattern POSITIVE_INT = Pattern.compile('([1-9]\\d*)')
|
||||
protected static final Pattern DOUBLE = Pattern.compile('-?(\\d*\\.\\d+)')
|
||||
protected static final Pattern HEX = Pattern.compile('[a-fA-F0-9]+')
|
||||
protected static final Pattern IP_ADDRESS = Pattern.compile('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])')
|
||||
protected static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):/)/?([^:/\\s]+)(:[0-9]{1,5})?')
|
||||
protected static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}')
|
||||
@@ -59,6 +64,10 @@ class RegexPatterns {
|
||||
return ONLY_ALPHA_UNICODE
|
||||
}
|
||||
|
||||
Pattern alphaNumeric() {
|
||||
return ALPHA_NUMERIC
|
||||
}
|
||||
|
||||
Pattern number() {
|
||||
return NUMBER
|
||||
}
|
||||
@@ -71,6 +80,10 @@ class RegexPatterns {
|
||||
return TRUE_OR_FALSE
|
||||
}
|
||||
|
||||
Pattern anInteger() {
|
||||
return INTEGER
|
||||
}
|
||||
|
||||
Pattern aDouble() {
|
||||
return DOUBLE
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,7 @@ import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.ToString
|
||||
import groovy.transform.TypeChecked
|
||||
import groovy.util.logging.Slf4j
|
||||
import org.apache.commons.lang3.StringEscapeUtils
|
||||
|
||||
import org.springframework.cloud.contract.spec.util.RegexpUtils
|
||||
@@ -29,8 +30,11 @@ import java.util.regex.Pattern
|
||||
/**
|
||||
* Represents the request side of the HTTP communication
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@TypeChecked
|
||||
@EqualsAndHashCode
|
||||
@ToString(includePackage = false, includeNames = true)
|
||||
@@ -45,7 +49,7 @@ class Request extends Common {
|
||||
Headers headers
|
||||
Body body
|
||||
Multipart multipart
|
||||
BodyMatchers matchers
|
||||
BodyMatchers bodyMatchers
|
||||
|
||||
Request() {
|
||||
}
|
||||
@@ -207,9 +211,18 @@ class Request extends Common {
|
||||
return value(client)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too
|
||||
*/
|
||||
@Deprecated
|
||||
void stubMatchers(@DelegatesTo(BodyMatchers) Closure closure) {
|
||||
this.matchers = new BodyMatchers()
|
||||
closure.delegate = this.matchers
|
||||
log.warn("stubMatchers method is deprecated. Please use bodyMatchers instead")
|
||||
bodyMatchers(closure)
|
||||
}
|
||||
|
||||
void bodyMatchers(@DelegatesTo(BodyMatchers) Closure closure) {
|
||||
this.bodyMatchers = new BodyMatchers()
|
||||
closure.delegate = this.bodyMatchers
|
||||
closure()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
@@ -28,8 +30,11 @@ import org.springframework.cloud.contract.spec.util.RegexpUtils
|
||||
/**
|
||||
* Represents the response side of the HTTP communication
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@TypeChecked
|
||||
@EqualsAndHashCode
|
||||
@ToString(includePackage = false, includeFields = true)
|
||||
@@ -43,7 +48,7 @@ class Response extends Common {
|
||||
Headers headers
|
||||
Body body
|
||||
boolean async
|
||||
ResponseBodyMatchers matchers
|
||||
ResponseBodyMatchers bodyMatchers
|
||||
|
||||
Response() {
|
||||
}
|
||||
@@ -114,9 +119,18 @@ class Response extends Common {
|
||||
return value(server)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated in favor of bodyMatchers to support other future bodyMatchers too
|
||||
*/
|
||||
@Deprecated
|
||||
void testMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) {
|
||||
this.matchers = new ResponseBodyMatchers()
|
||||
closure.delegate = this.matchers
|
||||
log.warn("testMatchers method is deprecated. Please use bodyMatchers instead")
|
||||
bodyMatchers(closure)
|
||||
}
|
||||
|
||||
void bodyMatchers(@DelegatesTo(ResponseBodyMatchers) Closure closure) {
|
||||
this.bodyMatchers = new ResponseBodyMatchers()
|
||||
closure.delegate = this.bodyMatchers
|
||||
closure()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
@@ -8,6 +23,7 @@ import groovy.transform.ToString
|
||||
* Body matchers for the response side (output message, REST response)
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 1.0.3
|
||||
*/
|
||||
@CompileStatic
|
||||
@@ -28,6 +44,10 @@ class ResponseBodyMatchers extends BodyMatchers {
|
||||
closure()
|
||||
return matchingTypeValue.matchingTypeValue
|
||||
}
|
||||
|
||||
MatchingTypeValue byNull() {
|
||||
return new MatchingTypeValue(MatchingType.NULL, null)
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
|
||||
@@ -2,6 +2,9 @@ package org.springframework.cloud.contract.spec.internal
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import spock.lang.Specification
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
@@ -291,4 +294,38 @@ then:
|
||||
}
|
||||
a == b
|
||||
}
|
||||
|
||||
def 'should support deprecated testMatchers and stubMatchers'() {
|
||||
given:
|
||||
def contract = Contract.make {
|
||||
request {
|
||||
method 'GET'
|
||||
url '/path'
|
||||
body(
|
||||
id: [value: '132']
|
||||
)
|
||||
stubMatchers {
|
||||
jsonPath('$.id.value', byRegex(anInteger()))
|
||||
}
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body(
|
||||
id: [value: '132'],
|
||||
surname: 'Kowalsky',
|
||||
name: 'Jan',
|
||||
created: '2014-02-02 12:23:43'
|
||||
)
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
testMatchers {
|
||||
jsonPath('$.created', byTimestamp())
|
||||
}
|
||||
}
|
||||
}
|
||||
expect:
|
||||
assertThat(contract.request.bodyMatchers.hasMatchers()).isTrue()
|
||||
assertThat(contract.response.bodyMatchers.hasMatchers()).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,6 +40,7 @@ import com.toomuchcoding.jsonassert.JsonAssertion;
|
||||
* Passes through a message that matches the one defined in the DSL
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class StubRunnerIntegrationMessageSelector implements MessageSelector {
|
||||
|
||||
@@ -56,7 +57,7 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector {
|
||||
return false;
|
||||
}
|
||||
Object inputMessage = message.getPayload();
|
||||
BodyMatchers matchers = this.groovyDsl.getInput().getMatchers();
|
||||
BodyMatchers matchers = this.groovyDsl.getInput().getBodyMatchers();
|
||||
Object dslBody = MapConverter.getStubSideValues(this.groovyDsl.getInput().getMessageBody());
|
||||
Object matchingInputMessage = JsonToJsonPathsConverter
|
||||
.removeMatchingJsonPaths(dslBody, matchers);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,6 +40,7 @@ import com.toomuchcoding.jsonassert.JsonAssertion;
|
||||
* Passes through a message that matches the one defined in the DSL
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class StubRunnerStreamMessageSelector implements MessageSelector {
|
||||
|
||||
@@ -56,7 +57,7 @@ class StubRunnerStreamMessageSelector implements MessageSelector {
|
||||
return false;
|
||||
}
|
||||
Object inputMessage = message.getPayload();
|
||||
BodyMatchers matchers = this.groovyDsl.getInput().getMatchers();
|
||||
BodyMatchers matchers = this.groovyDsl.getInput().getBodyMatchers();
|
||||
Object dslBody = MapConverter.getStubSideValues(this.groovyDsl.getInput().getMessageBody());
|
||||
Object matchingInputMessage = JsonToJsonPathsConverter
|
||||
.removeMatchingJsonPaths(dslBody, matchers);
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.stubrunner.messaging.integration
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
@@ -61,7 +76,7 @@ class StubRunnerIntegrationMessageSelectorSpec extends Specification {
|
||||
header("foo", 123)
|
||||
}
|
||||
messageBody(foo: "non matching stuff")
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.foo', byRegex("[0-9]{3}"))
|
||||
}
|
||||
}
|
||||
@@ -111,7 +126,7 @@ class StubRunnerIntegrationMessageSelectorSpec extends Specification {
|
||||
header("foo", 123)
|
||||
}
|
||||
messageBody(foo: 123)
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.foo', byRegex("[0-9]{3}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.stubrunner.messaging.stream
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
@@ -62,7 +77,7 @@ class StubRunnerStreamMessageSelectorSpec extends Specification {
|
||||
header("foo", 123)
|
||||
}
|
||||
messageBody(foo: "non matching stuff")
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.foo', byRegex("[0-9]{3}"))
|
||||
}
|
||||
}
|
||||
@@ -112,7 +127,7 @@ class StubRunnerStreamMessageSelectorSpec extends Specification {
|
||||
header("foo", 123)
|
||||
}
|
||||
messageBody(foo: 123)
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.foo', byRegex("[0-9]{3}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -583,7 +583,7 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
]
|
||||
]
|
||||
])
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.duck', byRegex("[0-9]{3}"))
|
||||
jsonPath('$.duck', byEquality())
|
||||
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
|
||||
@@ -621,7 +621,7 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
1,2,3
|
||||
],
|
||||
])
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
// asserts the jsonpath value against manual regex
|
||||
jsonPath('$.duck', byRegex("[0-9]{3}"))
|
||||
jsonPath('$.duck', byEquality())
|
||||
@@ -789,7 +789,7 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
email: 'abc@abc.com',
|
||||
callback_url: 'http://partners.com'
|
||||
)
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.[\\'email\\']', byRegex(email()))
|
||||
jsonPath('$.[\\'callback_url\\']', byRegex(hostname()))
|
||||
}
|
||||
@@ -803,7 +803,7 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
code: "123123",
|
||||
message: "User not found by email == [not.existing@user.com]"
|
||||
)
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.code', byRegex("123123"))
|
||||
jsonPath('$.message', byRegex("User not found by email == ${email()}"))
|
||||
}
|
||||
@@ -875,7 +875,7 @@ class DslToWireMockClientConverterSpec extends Specification {
|
||||
email: 'abc@abc.com',
|
||||
callback_url: 'http://partners.com'
|
||||
)
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.[\\'email\\']', byRegex(email()))
|
||||
jsonPath('$.[\\'callback_url\\']', byRegex(hostname()))
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-model</artifactId>
|
||||
<version>2.4.18</version>
|
||||
<version>3.5.13</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-model</artifactId>
|
||||
<optional>true</optional>
|
||||
<artifactId>pact-jvm-consumer-java8_2.12</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.consumer.dsl.DslPart
|
||||
import au.com.dius.pact.consumer.dsl.PactDslJsonArray
|
||||
import au.com.dius.pact.consumer.dsl.PactDslJsonBody
|
||||
import au.com.dius.pact.model.OptionalBody
|
||||
import au.com.dius.pact.model.Request
|
||||
import au.com.dius.pact.model.Response
|
||||
import au.com.dius.pact.model.generators.Generator
|
||||
import au.com.dius.pact.model.v3.messaging.Message
|
||||
import com.jayway.jsonpath.Configuration
|
||||
import com.jayway.jsonpath.internal.EvaluationContext
|
||||
import com.jayway.jsonpath.internal.Path
|
||||
import com.jayway.jsonpath.internal.PathRef
|
||||
import com.jayway.jsonpath.internal.path.PathCompiler
|
||||
import groovy.json.JsonException
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import org.springframework.cloud.contract.spec.internal.Body
|
||||
import org.springframework.cloud.contract.spec.internal.ClientDslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.ServerDslProperty
|
||||
import org.springframework.cloud.contract.verifier.util.ContentUtils
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
* @Since 2.0.0
|
||||
*/
|
||||
@CompileStatic
|
||||
@PackageScope
|
||||
class BodyConverter {
|
||||
|
||||
private static final JsonSlurper jsonSlurper = new JsonSlurper()
|
||||
|
||||
static DslPart toPactBody(Body body, Closure dslPropertyValueExtractor) {
|
||||
return traverse(body, null, dslPropertyValueExtractor)
|
||||
}
|
||||
|
||||
static DslPart toPactBody(DslProperty dslProperty, Closure dslPropertyValueExtractor) {
|
||||
return traverse(dslProperty, null, dslPropertyValueExtractor)
|
||||
}
|
||||
|
||||
private static DslPart traverse(Object value, DslPart parent, Closure dslPropertyValueExtractor) {
|
||||
boolean isRoot = parent == null
|
||||
Object v = value
|
||||
if (v instanceof DslProperty) {
|
||||
v = dslPropertyValueExtractor(v)
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue(v, dslPropertyValueExtractor)
|
||||
}
|
||||
if (v instanceof String) {
|
||||
v = v.trim()
|
||||
if (v.startsWith("{") && v.endsWith("}")) {
|
||||
try {
|
||||
v = jsonSlurper.parseText(v as String)
|
||||
} catch (JsonException ex) { /*it wasn't a JSON string after all...*/
|
||||
}
|
||||
}
|
||||
}
|
||||
DslPart p = isRoot ? createRootDslPart(v) : parent
|
||||
if (v instanceof Map) {
|
||||
if (!isRoot) {
|
||||
p = p.object()
|
||||
}
|
||||
processMap(v as Map, p as PactDslJsonBody, dslPropertyValueExtractor)
|
||||
if (!isRoot) {
|
||||
p = p.closeObject()
|
||||
}
|
||||
} else if (v instanceof Collection) {
|
||||
if (!isRoot) {
|
||||
p = p.array()
|
||||
}
|
||||
processCollection(v as Collection, p as PactDslJsonArray, dslPropertyValueExtractor)
|
||||
if (!isRoot) {
|
||||
p = p.closeArray()
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
private static DslPart createRootDslPart(Object value) {
|
||||
return value instanceof Collection ? new PactDslJsonArray() : new PactDslJsonBody()
|
||||
}
|
||||
|
||||
private static void processCollection(Collection values, PactDslJsonArray jsonArray, Closure dslPropertyValueExtractor) {
|
||||
values.forEach({
|
||||
Object v = it
|
||||
if (v instanceof DslProperty) {
|
||||
v = dslPropertyValueExtractor(v)
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue(v, dslPropertyValueExtractor)
|
||||
}
|
||||
if (v == null) {
|
||||
jsonArray.nullValue()
|
||||
} else if (v instanceof String) {
|
||||
jsonArray.string(v)
|
||||
} else if (v instanceof Number) {
|
||||
jsonArray.number(v)
|
||||
} else {
|
||||
traverse(it, jsonArray, dslPropertyValueExtractor)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private static void processMap(Map<String, Object> values, PactDslJsonBody jsonObject, Closure dslPropertyValueExtractor) {
|
||||
values.forEach({ String k, Object v ->
|
||||
if (v instanceof DslProperty) {
|
||||
v = dslPropertyValueExtractor(v)
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue(v, dslPropertyValueExtractor)
|
||||
}
|
||||
if (v == null) {
|
||||
jsonObject.nullValue(k)
|
||||
} else if (v instanceof String) {
|
||||
jsonObject.stringType(k, v)
|
||||
} else if (v instanceof Number) {
|
||||
jsonObject.numberValue(k, v)
|
||||
} else {
|
||||
PactDslJsonBody current = jsonObject.object(k)
|
||||
traverse(v, current, dslPropertyValueExtractor)
|
||||
current.closeObject()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static def toSCCBody(Request request) {
|
||||
def body = parseBody(request.body)
|
||||
if (request.generators.isNotEmpty() && request.generators.categories.containsKey(au.com.dius.pact.model.generators.Category.BODY)) {
|
||||
applyGenerators(body, request.generators.categories.get(au.com.dius.pact.model.generators.Category.BODY)) { Object currentValue, Pattern pattern, Object generatedValue ->
|
||||
return new DslProperty<Object>(new ClientDslProperty(pattern, generatedValue), currentValue)
|
||||
}
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
static def toSCCBody(Response response) {
|
||||
def body = parseBody(response.body)
|
||||
if (response.generators.isNotEmpty() && response.generators.categories.containsKey(au.com.dius.pact.model.generators.Category.BODY)) {
|
||||
applyGenerators(body, response.generators.categories.get(au.com.dius.pact.model.generators.Category.BODY)) { Object currentValue, Pattern pattern, Object generatedValue ->
|
||||
return new DslProperty<Object>(currentValue, new ServerDslProperty(pattern, generatedValue))
|
||||
}
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
static def toSCCBody(Message message) {
|
||||
def body = parseBody(message.contents)
|
||||
if (message.generators.isNotEmpty() && message.generators.categories.containsKey(au.com.dius.pact.model.generators.Category.BODY)) {
|
||||
applyGenerators(body, message.generators.categories.get(au.com.dius.pact.model.generators.Category.BODY)) { Object currentValue, Pattern pattern, Object generatedValue ->
|
||||
return new DslProperty<Object>(new ClientDslProperty(pattern, generatedValue), currentValue)
|
||||
}
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
private static def parseBody(OptionalBody optionalBody) {
|
||||
if (optionalBody.present) {
|
||||
return new JsonSlurper().parseText(optionalBody.value)
|
||||
} else {
|
||||
return optionalBody.value
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyGenerators(def body, Map<String, Generator> generatorsPerPath, Closure<DslProperty> dslPropertyProvider) {
|
||||
Configuration configuration = Configuration.defaultConfiguration()
|
||||
generatorsPerPath.each { String path, Generator generator ->
|
||||
Path compiledPath = PathCompiler.compile(path)
|
||||
EvaluationContext evaluationContext = compiledPath.evaluate(body, body, configuration, true)
|
||||
evaluationContext.updateOperations().each { PathRef pathRef ->
|
||||
pathRef.convert({ Object currentValue, Configuration config ->
|
||||
return ValueGeneratorConverter.convert(generator) { Pattern pattern, Object generatedValue ->
|
||||
return dslPropertyProvider(currentValue, pattern, generatedValue)
|
||||
}
|
||||
}, configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.model.matchingrules.Category
|
||||
import au.com.dius.pact.model.matchingrules.DateMatcher
|
||||
import au.com.dius.pact.model.matchingrules.EqualsMatcher
|
||||
import au.com.dius.pact.model.matchingrules.MaxTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.MinMaxTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.MinTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.NullMatcher
|
||||
import au.com.dius.pact.model.matchingrules.NumberTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.RegexMatcher
|
||||
import au.com.dius.pact.model.matchingrules.TimeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.TimestampMatcher
|
||||
import au.com.dius.pact.model.matchingrules.TypeMatcher
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import org.springframework.cloud.contract.spec.internal.BodyMatcher
|
||||
import org.springframework.cloud.contract.spec.internal.BodyMatchers
|
||||
import org.springframework.cloud.contract.spec.internal.MatchingType
|
||||
import org.springframework.cloud.contract.spec.internal.RegexPatterns
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
* @Since 2.0.0
|
||||
*/
|
||||
@CompileStatic
|
||||
@PackageScope
|
||||
class MatchingRulesConverter {
|
||||
|
||||
private static final RegexPatterns regexPatterns = new RegexPatterns()
|
||||
|
||||
static Category matchingRulesForBody(BodyMatchers bodyMatchers) {
|
||||
return matchingRulesFor("body", bodyMatchers)
|
||||
}
|
||||
|
||||
private static Category matchingRulesFor(String categoryName, BodyMatchers bodyMatchers) {
|
||||
Category category = new Category(categoryName)
|
||||
bodyMatchers.jsonPathMatchers().forEach({ BodyMatcher it ->
|
||||
String key = getMatcherKey(it.path())
|
||||
MatchingType matchingType = it.matchingType()
|
||||
switch (matchingType) {
|
||||
case MatchingType.NULL:
|
||||
category.addRule(key, NullMatcher.INSTANCE)
|
||||
break
|
||||
case MatchingType.EQUALITY:
|
||||
category.addRule(key, EqualsMatcher.INSTANCE)
|
||||
break
|
||||
case MatchingType.TYPE:
|
||||
if (it.minTypeOccurrence() && it.maxTypeOccurrence()) {
|
||||
category.addRule(key, new MinMaxTypeMatcher(it.minTypeOccurrence(), it.maxTypeOccurrence()))
|
||||
} else if (it.minTypeOccurrence()) {
|
||||
category.addRule(key, new MinTypeMatcher(it.minTypeOccurrence()))
|
||||
} else if (it.maxTypeOccurrence()) {
|
||||
category.addRule(key, new MaxTypeMatcher(it.maxTypeOccurrence()))
|
||||
} else {
|
||||
category.addRule(key, TypeMatcher.INSTANCE)
|
||||
}
|
||||
break
|
||||
case MatchingType.DATE:
|
||||
category.addRule(key, new DateMatcher())
|
||||
break
|
||||
case MatchingType.TIME:
|
||||
category.addRule(key, new TimeMatcher())
|
||||
break
|
||||
case MatchingType.TIMESTAMP:
|
||||
category.addRule(key, new TimestampMatcher())
|
||||
break
|
||||
case MatchingType.REGEX:
|
||||
String pattern = it.value().toString()
|
||||
if (pattern.equals(regexPatterns.number().pattern())) {
|
||||
category.addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.NUMBER))
|
||||
} else if (pattern.equals(regexPatterns.anInteger().pattern())) {
|
||||
category.addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.INTEGER))
|
||||
} else if (pattern.equals(regexPatterns.aDouble().pattern())) {
|
||||
category.addRule(key, new NumberTypeMatcher(NumberTypeMatcher.NumberType.DECIMAL))
|
||||
} else {
|
||||
category.addRule(key, new RegexMatcher(pattern))
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
return category
|
||||
}
|
||||
|
||||
private static String getMatcherKey(String path) {
|
||||
return "${path.startsWith('$') ? path.substring(1) : path}"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.consumer.MessagePactBuilder
|
||||
import au.com.dius.pact.consumer.dsl.DslPart
|
||||
import au.com.dius.pact.model.v3.messaging.MessagePact
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.Header
|
||||
import org.springframework.cloud.contract.spec.internal.Headers
|
||||
import org.springframework.cloud.contract.spec.internal.Input
|
||||
import org.springframework.cloud.contract.spec.internal.OutputMessage
|
||||
import org.springframework.cloud.contract.verifier.util.ContentUtils
|
||||
|
||||
/**
|
||||
* Creator of {@link MessagePact} instances
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@CompileStatic
|
||||
@PackageScope
|
||||
class MessagePactCreator {
|
||||
|
||||
private static final Closure clientValueExtractor = { DslProperty property -> property.clientValue }
|
||||
|
||||
MessagePact createFromContract(Contract contract) {
|
||||
MessagePactBuilder messagePactBuilder = MessagePactBuilder.consumer("Consumer")
|
||||
.hasPactWith("Provider")
|
||||
.given(getGiven(contract.input))
|
||||
.expectsToReceive(getOutcome(contract))
|
||||
if (contract.outputMessage) {
|
||||
OutputMessage message = contract.outputMessage
|
||||
if (message.body) {
|
||||
DslPart pactResponseBody = BodyConverter.toPactBody(message.body, clientValueExtractor)
|
||||
if (message.bodyMatchers) {
|
||||
pactResponseBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(message.bodyMatchers))
|
||||
}
|
||||
pactResponseBody.setGenerators(ValueGeneratorConverter.extract(message, { DslProperty dslProperty -> dslProperty.serverValue }))
|
||||
messagePactBuilder = messagePactBuilder.withContent(pactResponseBody)
|
||||
}
|
||||
if (message.headers) {
|
||||
messagePactBuilder = messagePactBuilder.withMetadata(getMetadata(message.headers))
|
||||
}
|
||||
}
|
||||
return messagePactBuilder.toPact()
|
||||
}
|
||||
|
||||
private String getGiven(Input input) {
|
||||
if (input.triggeredBy) {
|
||||
return input.triggeredBy.executionCommand
|
||||
} else if (input.messageFrom) {
|
||||
return "received message from " + clientValueExtractor.call(input.messageFrom)
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private String getOutcome(Contract contract) {
|
||||
if (contract.outputMessage) {
|
||||
OutputMessage message = contract.outputMessage
|
||||
return "message sent to " + clientValueExtractor.call(message.sentTo)
|
||||
} else {
|
||||
return "assert that " + contract.input.assertThat.executionCommand
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> getMetadata(Headers headers) {
|
||||
return headers.entries.collectEntries({ Header header ->
|
||||
return ["$header.name": extractValue(header)]
|
||||
})
|
||||
}
|
||||
|
||||
private String extractValue(Object value) {
|
||||
Object v = value
|
||||
if (v instanceof DslProperty) {
|
||||
v = clientValueExtractor.call(v)
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue(v, clientValueExtractor)
|
||||
}
|
||||
if (v instanceof String) {
|
||||
return v
|
||||
} else {
|
||||
return v.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.model.matchingrules.Category
|
||||
import au.com.dius.pact.model.matchingrules.DateMatcher
|
||||
import au.com.dius.pact.model.matchingrules.MatchingRule
|
||||
import au.com.dius.pact.model.matchingrules.MatchingRuleGroup
|
||||
import au.com.dius.pact.model.matchingrules.MaxTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.MinMaxTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.MinTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.NullMatcher
|
||||
import au.com.dius.pact.model.matchingrules.NumberTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.RegexMatcher
|
||||
import au.com.dius.pact.model.matchingrules.RuleLogic
|
||||
import au.com.dius.pact.model.matchingrules.TimeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.TimestampMatcher
|
||||
import au.com.dius.pact.model.matchingrules.TypeMatcher
|
||||
import au.com.dius.pact.model.v3.messaging.Message
|
||||
import au.com.dius.pact.model.v3.messaging.MessagePact
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.verifier.util.JsonPaths
|
||||
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
|
||||
|
||||
/**
|
||||
* Creator of {@link Contract} instances
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@CompileStatic
|
||||
@PackageScope
|
||||
class MessagingSCContractCreator {
|
||||
|
||||
private static final String FULL_BODY = '$'
|
||||
|
||||
Collection<Contract> convertFrom(MessagePact pact) {
|
||||
return pact.messages.collect({ Message message ->
|
||||
Contract.make {
|
||||
label("$message.description")
|
||||
if (!message.providerStates.isEmpty()) {
|
||||
input {
|
||||
triggeredBy(getTriggeredBy(message))
|
||||
}
|
||||
}
|
||||
outputMessage {
|
||||
if (message.contents.present) {
|
||||
body(BodyConverter.toSCCBody(message))
|
||||
Category bodyRules = message.matchingRules.rulesForCategory('body')
|
||||
if (bodyRules && !bodyRules.matchingRules.isEmpty()) {
|
||||
bodyMatchers {
|
||||
bodyRules.matchingRules.each { String key, MatchingRuleGroup ruleGroup ->
|
||||
if (ruleGroup.ruleLogic != RuleLogic.AND) {
|
||||
throw new UnsupportedOperationException("Currently only the AND combination rule logic is supported")
|
||||
}
|
||||
|
||||
if (FULL_BODY.equals(key)) {
|
||||
JsonPaths jsonPaths = JsonToJsonPathsConverter.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(message.contents.value)
|
||||
jsonPaths.each {
|
||||
jsonPath(it.keyBeforeChecking(), byType())
|
||||
}
|
||||
} else {
|
||||
ruleGroup.rules.each { MatchingRule rule ->
|
||||
if (rule instanceof NullMatcher) {
|
||||
jsonPath(key, byNull())
|
||||
} else if (rule instanceof RegexMatcher) {
|
||||
jsonPath(key, byRegex(rule.regex))
|
||||
} else if (rule instanceof DateMatcher) {
|
||||
jsonPath(key, byDate())
|
||||
} else if (rule instanceof TimeMatcher) {
|
||||
jsonPath(key, byTime())
|
||||
} else if (rule instanceof TimestampMatcher) {
|
||||
jsonPath(key, byTimestamp())
|
||||
} else if (rule instanceof MinTypeMatcher) {
|
||||
jsonPath(key, byType() {
|
||||
minOccurrence((rule as MinTypeMatcher).min)
|
||||
})
|
||||
} else if (rule instanceof MinMaxTypeMatcher) {
|
||||
jsonPath(key, byType() {
|
||||
minOccurrence((rule as MinMaxTypeMatcher).min)
|
||||
maxOccurrence((rule as MinMaxTypeMatcher).max)
|
||||
})
|
||||
} else if (rule instanceof MaxTypeMatcher) {
|
||||
jsonPath(key, byType() {
|
||||
maxOccurrence((rule as MaxTypeMatcher).max)
|
||||
})
|
||||
} else if (rule instanceof TypeMatcher) {
|
||||
jsonPath(key, byType())
|
||||
} else if (rule instanceof NumberTypeMatcher) {
|
||||
switch (rule.numberType) {
|
||||
case NumberTypeMatcher.NumberType.NUMBER:
|
||||
jsonPath(key, byRegex(number()))
|
||||
break
|
||||
case NumberTypeMatcher.NumberType.INTEGER:
|
||||
jsonPath(key, byRegex(anInteger()))
|
||||
break
|
||||
case NumberTypeMatcher.NumberType.DECIMAL:
|
||||
jsonPath(key, byRegex(aDouble()))
|
||||
break
|
||||
default:
|
||||
throw new RuntimeException("Unsupported number type!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!message.metaData.isEmpty()) {
|
||||
headers {
|
||||
message.metaData.each { String k, String v ->
|
||||
if (k.equalsIgnoreCase("contentType")) {
|
||||
messagingContentType(v)
|
||||
} else {
|
||||
header(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private String getTriggeredBy(Message message) {
|
||||
return message.providerStates.first().name
|
||||
.replace(':', ' ')
|
||||
.replace(' ', '_')
|
||||
.replace('(', '')
|
||||
.replace(')', '')
|
||||
.uncapitalize() + "()"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +1,43 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.model.BasePact
|
||||
import au.com.dius.pact.model.Consumer
|
||||
import au.com.dius.pact.model.Interaction
|
||||
import au.com.dius.pact.model.OptionalBody
|
||||
import au.com.dius.pact.model.Pact
|
||||
import au.com.dius.pact.model.PactReader
|
||||
import au.com.dius.pact.model.Provider
|
||||
import au.com.dius.pact.model.Request
|
||||
import au.com.dius.pact.model.RequestResponseInteraction
|
||||
import au.com.dius.pact.model.RequestResponsePact
|
||||
import au.com.dius.pact.model.Response
|
||||
import groovy.json.JsonOutput
|
||||
import au.com.dius.pact.model.v3.messaging.MessagePact
|
||||
import groovy.transform.CompileStatic
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.ContractConverter
|
||||
import org.springframework.cloud.contract.spec.internal.BodyMatchers
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
|
||||
import org.springframework.cloud.contract.spec.internal.Headers
|
||||
import org.springframework.cloud.contract.spec.internal.MatchingType
|
||||
import org.springframework.cloud.contract.spec.internal.QueryParameters
|
||||
import org.springframework.cloud.contract.verifier.util.JsonPaths
|
||||
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
|
||||
import org.springframework.cloud.contract.verifier.util.MapConverter
|
||||
|
||||
/**
|
||||
* Converter of JSON PACT file
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@CompileStatic
|
||||
class PactContractConverter implements ContractConverter<Pact> {
|
||||
class PactContractConverter implements ContractConverter<Collection<Pact>> {
|
||||
|
||||
private static final String MATCH_KEY = "match"
|
||||
private static final String REGEX_KEY = "regex"
|
||||
private static final String MAX_KEY = "max"
|
||||
private static final String MIN_KEY = "min"
|
||||
private static final String FULL_BODY = '$.body'
|
||||
private RequestResponseSCContractCreator requestResponseSCContractCreator = new RequestResponseSCContractCreator()
|
||||
private MessagingSCContractCreator messagingSCContractCreator = new MessagingSCContractCreator()
|
||||
|
||||
private RequestResponsePactCreator requestResponsePactCreator = new RequestResponsePactCreator()
|
||||
private MessagePactCreator messagePactCreator = new MessagePactCreator()
|
||||
|
||||
@Override
|
||||
boolean isAccepted(File file) {
|
||||
@@ -52,284 +52,26 @@ class PactContractConverter implements ContractConverter<Pact> {
|
||||
@Override
|
||||
Collection<Contract> convertFrom(File file) {
|
||||
Pact pact = PactReader.loadPact(file)
|
||||
List<Interaction> interactions = pact.interactions
|
||||
return interactions.collect { Interaction interaction ->
|
||||
Contract.make {
|
||||
if (interaction instanceof RequestResponseInteraction) {
|
||||
RequestResponseInteraction requestResponseInteraction = (RequestResponseInteraction) interaction
|
||||
description("$requestResponseInteraction.description${providerState(interaction)}")
|
||||
request {
|
||||
method(requestResponseInteraction.request.method)
|
||||
if (requestResponseInteraction.request.query) {
|
||||
url(requestResponseInteraction.request.path) {
|
||||
queryParameters {
|
||||
requestResponseInteraction.request.query.each { String key, List<String> value ->
|
||||
value.each { String singleValue ->
|
||||
parameter(key, singleValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
url(requestResponseInteraction.request.path)
|
||||
}
|
||||
if (requestResponseInteraction.request.headers) {
|
||||
headers {
|
||||
requestResponseInteraction.request.headers.each { String key, String value ->
|
||||
header(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requestResponseInteraction.request.body.state == OptionalBody.State.PRESENT) {
|
||||
def parsedBody = BasePact.parseBody(requestResponseInteraction.request)
|
||||
if (parsedBody instanceof Map) {
|
||||
body(parsedBody as Map)
|
||||
} else if (parsedBody instanceof List) {
|
||||
body(parsedBody as List)
|
||||
} else {
|
||||
body(parsedBody.toString())
|
||||
}
|
||||
}
|
||||
if (requestResponseInteraction.request?.matchingRules) {
|
||||
stubMatchers {
|
||||
Map<String, Map<String, Object>> rules = requestResponseInteraction.request.matchingRules
|
||||
rules.each { String key, Map<String, Object> value ->
|
||||
String keyFromBody = toKeyStartingFromBody(key)
|
||||
if (value.containsKey(MATCH_KEY)) {
|
||||
MatchingType matchingType = MatchingType.valueOf((value.get(MATCH_KEY) as String).toUpperCase())
|
||||
switch (matchingType) {
|
||||
case MatchingType.EQUALITY:
|
||||
// equality is checked by default in the standard way
|
||||
break
|
||||
case MatchingType.DATE:
|
||||
jsonPath(keyFromBody, byDate())
|
||||
break
|
||||
case MatchingType.TIME:
|
||||
jsonPath(keyFromBody, byTime())
|
||||
break
|
||||
case MatchingType.TIMESTAMP:
|
||||
jsonPath(keyFromBody, byTimestamp())
|
||||
break
|
||||
case MatchingType.REGEX:
|
||||
jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
|
||||
break
|
||||
}
|
||||
} else if (value.containsKey(REGEX_KEY)) {
|
||||
jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response {
|
||||
status(requestResponseInteraction.response.status)
|
||||
if (requestResponseInteraction.response.body.state == OptionalBody.State.PRESENT) {
|
||||
def parsedBody = BasePact.parseBody(requestResponseInteraction.response)
|
||||
if (parsedBody instanceof Map) {
|
||||
body(parsedBody as Map)
|
||||
} else if (parsedBody instanceof List) {
|
||||
body(parsedBody as List)
|
||||
} else {
|
||||
body(parsedBody.toString())
|
||||
}
|
||||
}
|
||||
if (requestResponseInteraction.response?.matchingRules) {
|
||||
testMatchers {
|
||||
Map<String, Map<String, Object>> rules = requestResponseInteraction.response.matchingRules
|
||||
Map<String, Object> fullBodyCheck = rules.get(FULL_BODY)
|
||||
if (fullBodyCheck != null) {
|
||||
JsonPaths jsonPaths = JsonToJsonPathsConverter.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(requestResponseInteraction.response?.body?.value)
|
||||
jsonPaths.each {
|
||||
jsonPath(it.keyBeforeChecking(), byType())
|
||||
}
|
||||
}
|
||||
rules.each { String key, Map<String, Object> value ->
|
||||
String keyFromBody = toKeyStartingFromBody(key)
|
||||
if (!keyFromBody) {
|
||||
return
|
||||
}
|
||||
if (value.containsKey(MATCH_KEY)) {
|
||||
MatchingType matchingType = MatchingType.valueOf((value.get(MATCH_KEY) as String).toUpperCase())
|
||||
switch (matchingType) {
|
||||
case MatchingType.EQUALITY:
|
||||
// equality is checked by default in the standard way
|
||||
break
|
||||
case MatchingType.DATE:
|
||||
jsonPath(keyFromBody, byDate())
|
||||
break
|
||||
case MatchingType.TIME:
|
||||
jsonPath(keyFromBody, byTime())
|
||||
break
|
||||
case MatchingType.TIMESTAMP:
|
||||
jsonPath(keyFromBody, byTimestamp())
|
||||
break
|
||||
case MatchingType.REGEX:
|
||||
jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
|
||||
break
|
||||
case MatchingType.TYPE:
|
||||
jsonPath(keyFromBody, byType() {
|
||||
if (value.containsKey(MIN_KEY)) {
|
||||
minOccurrence(value.get(MIN_KEY) as Integer)
|
||||
}
|
||||
if (value.containsKey(MAX_KEY)) {
|
||||
maxOccurrence(value.get(MAX_KEY) as Integer)
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
} else if (value.containsKey(REGEX_KEY)) {
|
||||
jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
requestResponseInteraction.response.headers?.each { String key, String value ->
|
||||
headers {
|
||||
header(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pact instanceof RequestResponsePact) {
|
||||
return requestResponseSCContractCreator.convertFrom(pact as RequestResponsePact)
|
||||
}
|
||||
}
|
||||
|
||||
protected String providerState(Interaction interaction) {
|
||||
return interaction.providerState ? " ${interaction.providerState}" : ""
|
||||
}
|
||||
|
||||
protected String toKeyStartingFromBody(String key) {
|
||||
if (key == FULL_BODY) {
|
||||
return ""
|
||||
if (pact instanceof MessagePact) {
|
||||
return messagingSCContractCreator.convertFrom(pact as MessagePact)
|
||||
}
|
||||
return key.replace(FULL_BODY, '$')
|
||||
throw new UnsupportedOperationException("We currently don't support pact contracts of type" + pact.class.simpleName)
|
||||
}
|
||||
|
||||
@Override
|
||||
Pact convertTo(Collection<Contract> contract) {
|
||||
Provider provider = new Provider()
|
||||
provider.name = "Provider"
|
||||
Consumer consumer = new Consumer()
|
||||
consumer.name = "Consumer"
|
||||
List<RequestResponseInteraction> interactions = contract.find { it.request }.collect { Contract dsl ->
|
||||
RequestResponseInteraction interaction = new RequestResponseInteraction()
|
||||
interaction.description = dsl.description ?: ""
|
||||
Request request = new Request().with {
|
||||
method = dsl.request.method.serverValue.toString()
|
||||
path = url(dsl)
|
||||
QueryParameters params = queryParams(dsl)
|
||||
if (params) {
|
||||
query = params.parameters.collectEntries {
|
||||
String name = it.name
|
||||
String value = it.serverValue
|
||||
return [(name) : [value]]
|
||||
}
|
||||
}
|
||||
if (dsl.request.headers) {
|
||||
headers = headers(dsl.request.headers, { DslProperty property -> property.serverValue })
|
||||
}
|
||||
if (dsl.request.body) {
|
||||
assertInputContract(dsl.request.body.serverValue)
|
||||
def json = MapConverter.getTestSideValues(dsl.request.body.serverValue)
|
||||
String jsonBody = JsonOutput.toJson(json)
|
||||
body = new OptionalBody(OptionalBody.State.PRESENT, jsonBody)
|
||||
}
|
||||
if (dsl.request.matchers && dsl.request.matchers.hasMatchers()) {
|
||||
matchingRules = matchingRules(dsl.request.matchers)
|
||||
}
|
||||
return it
|
||||
Collection<Pact> convertTo(Collection<Contract> contracts) {
|
||||
List<Pact> pactContracts = new ArrayList<>()
|
||||
contracts.collect({ Contract contract ->
|
||||
if (contract.request) {
|
||||
pactContracts.add(requestResponsePactCreator.createFromContract(contract))
|
||||
}
|
||||
Response response = new Response().with {
|
||||
status = dsl.response.status.clientValue as Integer
|
||||
if (dsl.response.headers) {
|
||||
headers = headers(dsl.response.headers, { DslProperty property -> property.clientValue })
|
||||
}
|
||||
if (dsl.response.body) {
|
||||
assertInputContract(dsl.response.body.clientValue)
|
||||
def json = MapConverter.getStubSideValues(dsl.response.body.clientValue)
|
||||
String jsonBody = JsonOutput.toJson(json)
|
||||
body = new OptionalBody(OptionalBody.State.PRESENT, jsonBody)
|
||||
}
|
||||
if (dsl.response.matchers && dsl.response.matchers.hasMatchers()) {
|
||||
matchingRules = matchingRules(dsl.response.matchers)
|
||||
}
|
||||
return it
|
||||
if (contract.input) {
|
||||
pactContracts.add(messagePactCreator.createFromContract(contract))
|
||||
}
|
||||
interaction.request = request
|
||||
interaction.response = response
|
||||
return interaction
|
||||
}
|
||||
return new RequestResponsePact(provider, consumer, interactions)
|
||||
}
|
||||
|
||||
protected void assertInputContract(parsedJson) {
|
||||
boolean hasExecutionProp = false
|
||||
MapConverter.transformValues(parsedJson, {
|
||||
if (it instanceof ExecutionProperty) {
|
||||
hasExecutionProp = true
|
||||
}
|
||||
return it
|
||||
})
|
||||
if (hasExecutionProp) {
|
||||
throw new UnsupportedOperationException("We can't convert a contract that has execution property")
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, String> headers(Headers headers, Closure closure) {
|
||||
return headers.entries.collectEntries {
|
||||
String name = it.name
|
||||
String value = closure(it)
|
||||
return [(name) : value]
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, Map<String, Object>> matchingRules(BodyMatchers bodyMatchers) {
|
||||
return bodyMatchers.jsonPathMatchers().collectEntries {
|
||||
MatchingType matchingType = it.matchingType()
|
||||
String key = "\$.body${it.path().startsWith('$') ? it.path().substring(1) : it.path()}"
|
||||
Object value = it.value()
|
||||
Integer minTypeOccurrence = it.minTypeOccurrence()
|
||||
Integer maxTypeOccurrence = it.maxTypeOccurrence()
|
||||
Map<String, Object> matchingRule = [:]
|
||||
switch (matchingType) {
|
||||
case MatchingType.EQUALITY:
|
||||
matchingRule << [(MATCH_KEY) : MatchingType.EQUALITY.toString().toLowerCase() as Object]
|
||||
break
|
||||
case MatchingType.TYPE:
|
||||
Map<String, Object> map = [(MATCH_KEY) : MatchingType.TYPE.toString().toLowerCase() as Object]
|
||||
if (minTypeOccurrence) map.put(MIN_KEY, minTypeOccurrence)
|
||||
if (maxTypeOccurrence) map.put(MAX_KEY, maxTypeOccurrence)
|
||||
matchingRule << map
|
||||
break
|
||||
case MatchingType.DATE:
|
||||
case MatchingType.TIME:
|
||||
case MatchingType.TIMESTAMP:
|
||||
case MatchingType.REGEX:
|
||||
matchingRule << [
|
||||
(MATCH_KEY) : MatchingType.REGEX.toString().toLowerCase() as Object,
|
||||
(REGEX_KEY) : value
|
||||
]
|
||||
break
|
||||
}
|
||||
return [(key) : matchingRule]
|
||||
}
|
||||
}
|
||||
|
||||
protected String url(Contract dsl) {
|
||||
if (dsl.request.urlPath) {
|
||||
return dsl.request.urlPath.serverValue.toString()
|
||||
} else if (dsl.request.url) {
|
||||
return dsl.request.url.serverValue.toString()
|
||||
}
|
||||
throw new IllegalStateException("No url provided")
|
||||
}
|
||||
|
||||
protected QueryParameters queryParams(Contract dsl) {
|
||||
if (dsl.request.urlPath) {
|
||||
return dsl.request.urlPath.queryParameters
|
||||
} else if (dsl.request.url) {
|
||||
return dsl.request.url.queryParameters
|
||||
}
|
||||
throw new IllegalStateException("No url provided")
|
||||
return pactContracts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.consumer.ConsumerPactBuilder
|
||||
import au.com.dius.pact.consumer.dsl.DslPart
|
||||
import au.com.dius.pact.consumer.dsl.PactDslRequestWithPath
|
||||
import au.com.dius.pact.consumer.dsl.PactDslResponse
|
||||
import au.com.dius.pact.consumer.dsl.PactDslWithProvider
|
||||
import au.com.dius.pact.model.RequestResponsePact
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.internal.Body
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
|
||||
import org.springframework.cloud.contract.spec.internal.Header
|
||||
import org.springframework.cloud.contract.spec.internal.QueryParameters
|
||||
import org.springframework.cloud.contract.spec.internal.Request
|
||||
import org.springframework.cloud.contract.spec.internal.Response
|
||||
|
||||
/**
|
||||
* Creator of {@link RequestResponsePact} instances
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@CompileStatic
|
||||
@PackageScope
|
||||
class RequestResponsePactCreator {
|
||||
|
||||
RequestResponsePact createFromContract(Contract contract) {
|
||||
assertNoExecutionProperty(contract)
|
||||
PactDslWithProvider pactDslWithProvider = ConsumerPactBuilder.consumer("Consumer")
|
||||
.hasPactWith("Provider")
|
||||
PactDslRequestWithPath pactDslRequest = createPactDslRequestWithPath(contract, pactDslWithProvider)
|
||||
PactDslResponse pactDslResponse = createPactDslResponse(contract, pactDslRequest)
|
||||
return pactDslResponse.toPact()
|
||||
}
|
||||
|
||||
private void assertNoExecutionProperty(Contract contract) {
|
||||
assertNoExecutionPropertyInBody(contract.request.body, { DslProperty dslProperty -> dslProperty.serverValue })
|
||||
assertNoExecutionPropertyInBody(contract.response.body, { DslProperty dslProperty -> dslProperty.clientValue })
|
||||
}
|
||||
|
||||
private void assertNoExecutionPropertyInBody(Body body, Closure dslPropertyValueExtractor) {
|
||||
traverseValues(body, dslPropertyValueExtractor, {
|
||||
if (it instanceof ExecutionProperty) {
|
||||
throw new UnsupportedOperationException("We can't convert a contract that has execution property")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private void traverseValues(def value, Closure dslPropertyValueExtractor, Closure closure) {
|
||||
if (value instanceof DslProperty) {
|
||||
traverseValues(dslPropertyValueExtractor(value), dslPropertyValueExtractor, closure)
|
||||
} else if (value instanceof Map) {
|
||||
value.values().forEach({traverseValues(it, dslPropertyValueExtractor, closure)})
|
||||
} else if (value instanceof Collection) {
|
||||
value.forEach({traverseValues(it, dslPropertyValueExtractor, closure)})
|
||||
} else {
|
||||
closure(value)
|
||||
}
|
||||
}
|
||||
|
||||
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslWithProvider pactDslWithProvider) {
|
||||
Request request = contract.request
|
||||
PactDslRequestWithPath pactDslRequest = pactDslWithProvider
|
||||
.uponReceiving(contract.description ?: "")
|
||||
.path(url(request))
|
||||
.method(request.method.serverValue.toString())
|
||||
String query = query(request)
|
||||
if (query) {
|
||||
pactDslRequest = pactDslRequest.encodedQuery(query)
|
||||
}
|
||||
if (request.headers) {
|
||||
request.headers.entries.each { Header header ->
|
||||
pactDslRequest = processHeader(pactDslRequest, header)
|
||||
}
|
||||
}
|
||||
if (request.body) {
|
||||
DslPart pactRequestBody = BodyConverter.toPactBody(request.body, { DslProperty property -> property.serverValue })
|
||||
if (request.bodyMatchers) {
|
||||
pactRequestBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(request.bodyMatchers))
|
||||
}
|
||||
pactRequestBody.setGenerators(ValueGeneratorConverter.extract(request.body, { DslProperty dslProperty -> dslProperty.clientValue }))
|
||||
pactDslRequest = pactDslRequest.body(pactRequestBody)
|
||||
}
|
||||
return pactDslRequest
|
||||
}
|
||||
|
||||
private PactDslResponse createPactDslResponse(Contract contract, PactDslRequestWithPath pactDslRequest) {
|
||||
Response response = contract.response
|
||||
PactDslResponse pactDslResponse = pactDslRequest.willRespondWith()
|
||||
.status(response.status.clientValue as Integer)
|
||||
if (response.headers) {
|
||||
response.headers.entries.each { Header header ->
|
||||
pactDslResponse = processHeader(pactDslResponse, header)
|
||||
}
|
||||
}
|
||||
if (response.body) {
|
||||
DslPart pactResponseBody = BodyConverter.toPactBody(response.body, { DslProperty property -> property.clientValue })
|
||||
if (response.bodyMatchers) {
|
||||
pactResponseBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(response.bodyMatchers))
|
||||
}
|
||||
pactResponseBody.setGenerators(ValueGeneratorConverter.extract(response.body, { DslProperty dslProperty -> dslProperty.serverValue }))
|
||||
pactDslResponse = pactDslResponse.body(pactResponseBody)
|
||||
}
|
||||
return pactDslResponse
|
||||
}
|
||||
|
||||
private String url(Request request) {
|
||||
if (request.urlPath) {
|
||||
return request.urlPath.serverValue.toString()
|
||||
} else if (request.url) {
|
||||
return request.url.serverValue.toString()
|
||||
}
|
||||
throw new IllegalStateException("No url provided")
|
||||
}
|
||||
|
||||
private String query(Request request) {
|
||||
String query = null
|
||||
QueryParameters params = queryParams(request)
|
||||
if (params) {
|
||||
query = ""
|
||||
params.parameters.eachWithIndex { param, index ->
|
||||
query += param.name + '=' + param.serverValue
|
||||
if (index + 1 < params.parameters.size()) {
|
||||
query += '&'
|
||||
}
|
||||
}
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
private QueryParameters queryParams(Request request) {
|
||||
if (request.urlPath) {
|
||||
return request.urlPath.queryParameters
|
||||
} else if (request.url) {
|
||||
return request.url.queryParameters
|
||||
}
|
||||
throw new IllegalStateException("No url provided")
|
||||
|
||||
}
|
||||
|
||||
private PactDslRequestWithPath processHeader(PactDslRequestWithPath pactDslRequest, Header header) {
|
||||
if (header.isSingleValue()) {
|
||||
String value = getDslPropertyServerValue(header).toString()
|
||||
return pactDslRequest.headers(header.name, value)
|
||||
} else {
|
||||
String regex = getDslPropertyClientValue(header).toString()
|
||||
String example = getDslPropertyServerValue(header).toString()
|
||||
return pactDslRequest.matchHeader(header.name, regex, example)
|
||||
}
|
||||
}
|
||||
|
||||
private PactDslResponse processHeader(PactDslResponse pactDslResponse, Header header) {
|
||||
if (header.isSingleValue()) {
|
||||
String value = getDslPropertyClientValue(header).toString()
|
||||
return pactDslResponse.headers([(header.name) : value])
|
||||
} else {
|
||||
String regex = getDslPropertyServerValue(header).toString()
|
||||
String example = getDslPropertyClientValue(header).toString()
|
||||
return pactDslResponse.matchHeader(header.name, regex, example)
|
||||
}
|
||||
}
|
||||
|
||||
private Object getDslPropertyClientValue(Object o) {
|
||||
Object value = o
|
||||
if (value instanceof DslProperty) {
|
||||
value = getDslPropertyClientValue(value.getClientValue())
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private Object getDslPropertyServerValue(Object o) {
|
||||
Object value = o
|
||||
if (value instanceof DslProperty) {
|
||||
value = getDslPropertyServerValue(value.getServerValue())
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.model.OptionalBody
|
||||
import au.com.dius.pact.model.ProviderState
|
||||
import au.com.dius.pact.model.Request
|
||||
import au.com.dius.pact.model.RequestResponseInteraction
|
||||
import au.com.dius.pact.model.RequestResponsePact
|
||||
import au.com.dius.pact.model.Response
|
||||
import au.com.dius.pact.model.matchingrules.Category
|
||||
import au.com.dius.pact.model.matchingrules.DateMatcher
|
||||
import au.com.dius.pact.model.matchingrules.MatchingRule
|
||||
import au.com.dius.pact.model.matchingrules.MatchingRuleGroup
|
||||
import au.com.dius.pact.model.matchingrules.MaxTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.MinMaxTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.MinTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.NullMatcher
|
||||
import au.com.dius.pact.model.matchingrules.NumberTypeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.RegexMatcher
|
||||
import au.com.dius.pact.model.matchingrules.RuleLogic
|
||||
import au.com.dius.pact.model.matchingrules.TimeMatcher
|
||||
import au.com.dius.pact.model.matchingrules.TimestampMatcher
|
||||
import au.com.dius.pact.model.matchingrules.TypeMatcher
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.NotToEscapePattern
|
||||
import org.springframework.cloud.contract.spec.internal.RegexPatterns
|
||||
import org.springframework.cloud.contract.verifier.util.JsonPaths
|
||||
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Creator of {@link Contract} instances
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@CompileStatic
|
||||
@PackageScope
|
||||
class RequestResponseSCContractCreator {
|
||||
|
||||
private static final String FULL_BODY = '$'
|
||||
private static final RegexPatterns regexPatterns = new RegexPatterns()
|
||||
|
||||
Collection<Contract> convertFrom(RequestResponsePact pact) {
|
||||
return pact.interactions.collect { RequestResponseInteraction interaction ->
|
||||
Contract.make {
|
||||
description(buildDescription(interaction))
|
||||
request {
|
||||
Request request = interaction.request
|
||||
method(request.method)
|
||||
if (request.query) {
|
||||
url(request.path) {
|
||||
queryParameters {
|
||||
request.query.each { String key, List<String> value ->
|
||||
value.each { String singleValue ->
|
||||
parameter(key, singleValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
url(request.path)
|
||||
}
|
||||
if (request.headers) {
|
||||
Category headerRules = request.matchingRules.rulesForCategory('header')
|
||||
headers {
|
||||
request.headers.each { k, v ->
|
||||
if (headerRules.matchingRules.containsKey(k)) {
|
||||
MatchingRuleGroup ruleGroup = headerRules.matchingRules.get(k)
|
||||
if (ruleGroup.rules.size() > 1) {
|
||||
throw new UnsupportedOperationException("Currently only 1 rule at a time for a header is supported")
|
||||
}
|
||||
MatchingRule rule = ruleGroup.rules[0]
|
||||
if (rule instanceof RegexMatcher) {
|
||||
header(k, new DslProperty((Object)Pattern.compile(rule.getRegex()), (Object)v))
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Currently only the header matcher of type regex is supported")
|
||||
}
|
||||
} else {
|
||||
header(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (request.body.state == OptionalBody.State.PRESENT) {
|
||||
def parsedBody = BodyConverter.toSCCBody(request)
|
||||
if (parsedBody instanceof Map) {
|
||||
body(parsedBody as Map)
|
||||
} else if (parsedBody instanceof List) {
|
||||
body(parsedBody as List)
|
||||
} else {
|
||||
body(parsedBody.toString())
|
||||
}
|
||||
}
|
||||
Category bodyRules = request.matchingRules.rulesForCategory('body')
|
||||
if (bodyRules && !bodyRules.matchingRules.isEmpty()) {
|
||||
bodyMatchers {
|
||||
bodyRules.matchingRules.each { String key, MatchingRuleGroup ruleGroup ->
|
||||
if (ruleGroup.ruleLogic != RuleLogic.AND) {
|
||||
throw new UnsupportedOperationException("Currently only the AND combination rule logic is supported")
|
||||
}
|
||||
|
||||
ruleGroup.rules.each { MatchingRule rule ->
|
||||
if (rule instanceof RegexMatcher) {
|
||||
jsonPath(key, byRegex(rule.regex))
|
||||
} else if (rule instanceof DateMatcher) {
|
||||
jsonPath(key, byDate())
|
||||
} else if (rule instanceof TimeMatcher) {
|
||||
jsonPath(key, byTime())
|
||||
} else if (rule instanceof TimestampMatcher) {
|
||||
jsonPath(key, byTimestamp())
|
||||
} else if (rule instanceof NumberTypeMatcher) {
|
||||
switch (rule.numberType) {
|
||||
case NumberTypeMatcher.NumberType.NUMBER:
|
||||
jsonPath(key, byRegex(regexPatterns.number()))
|
||||
break
|
||||
case NumberTypeMatcher.NumberType.INTEGER:
|
||||
jsonPath(key, byRegex(regexPatterns.anInteger()))
|
||||
break
|
||||
case NumberTypeMatcher.NumberType.DECIMAL:
|
||||
jsonPath(key, byRegex(regexPatterns.aDouble()))
|
||||
break
|
||||
default:
|
||||
throw new RuntimeException("Unsupported number type!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response {
|
||||
Response response = interaction.response
|
||||
status(response.status)
|
||||
if (response.body.present) {
|
||||
def parsedBody = BodyConverter.toSCCBody(response)
|
||||
if (parsedBody instanceof Map) {
|
||||
body(parsedBody as Map)
|
||||
} else if (parsedBody instanceof List) {
|
||||
body(parsedBody as List)
|
||||
} else {
|
||||
body(parsedBody.toString())
|
||||
}
|
||||
}
|
||||
Category bodyRules = response.matchingRules.rulesForCategory('body')
|
||||
if (bodyRules && !bodyRules.matchingRules.isEmpty()) {
|
||||
bodyMatchers {
|
||||
bodyRules.matchingRules.each { String key, MatchingRuleGroup ruleGroup ->
|
||||
if (ruleGroup.ruleLogic != RuleLogic.AND) {
|
||||
throw new UnsupportedOperationException("Currently only the AND combination rule logic is supported")
|
||||
}
|
||||
|
||||
if (FULL_BODY.equals(key)) {
|
||||
JsonPaths jsonPaths = JsonToJsonPathsConverter.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(response.body.value)
|
||||
jsonPaths.each {
|
||||
jsonPath(it.keyBeforeChecking(), byType())
|
||||
}
|
||||
} else {
|
||||
ruleGroup.rules.each { MatchingRule rule ->
|
||||
if (rule instanceof NullMatcher) {
|
||||
jsonPath(key, byNull())
|
||||
} else if (rule instanceof RegexMatcher) {
|
||||
jsonPath(key, byRegex(rule.regex))
|
||||
} else if (rule instanceof DateMatcher) {
|
||||
jsonPath(key, byDate())
|
||||
} else if (rule instanceof TimeMatcher) {
|
||||
jsonPath(key, byTime())
|
||||
} else if (rule instanceof TimestampMatcher) {
|
||||
jsonPath(key, byTimestamp())
|
||||
} else if (rule instanceof MinTypeMatcher) {
|
||||
jsonPath(key, byType() {
|
||||
minOccurrence((rule as MinTypeMatcher).min)
|
||||
})
|
||||
} else if (rule instanceof MinMaxTypeMatcher) {
|
||||
jsonPath(key, byType() {
|
||||
minOccurrence((rule as MinMaxTypeMatcher).min)
|
||||
maxOccurrence((rule as MinMaxTypeMatcher).max)
|
||||
})
|
||||
} else if (rule instanceof MaxTypeMatcher) {
|
||||
jsonPath(key, byType() {
|
||||
maxOccurrence((rule as MaxTypeMatcher).max)
|
||||
})
|
||||
} else if (rule instanceof TypeMatcher) {
|
||||
jsonPath(key, byType())
|
||||
} else if (rule instanceof NumberTypeMatcher) {
|
||||
switch (rule.numberType) {
|
||||
case NumberTypeMatcher.NumberType.NUMBER:
|
||||
jsonPath(key, byRegex(regexPatterns.number()))
|
||||
break
|
||||
case NumberTypeMatcher.NumberType.INTEGER:
|
||||
jsonPath(key, byRegex(regexPatterns.anInteger()))
|
||||
break
|
||||
case NumberTypeMatcher.NumberType.DECIMAL:
|
||||
jsonPath(key, byRegex(regexPatterns.aDouble()))
|
||||
break
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported number type!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (response.headers) {
|
||||
Category headerRules = response.matchingRules.rulesForCategory('header')
|
||||
headers {
|
||||
response.headers.forEach({ String k, String v ->
|
||||
if (headerRules.matchingRules.containsKey(k)) {
|
||||
MatchingRuleGroup ruleGroup = headerRules.matchingRules.get(k)
|
||||
if (ruleGroup.rules.size() > 1) {
|
||||
throw new UnsupportedOperationException("Currently only 1 rule at a time for a header is supported")
|
||||
}
|
||||
MatchingRule rule = ruleGroup.rules[0]
|
||||
if (rule instanceof RegexMatcher) {
|
||||
header(k, new DslProperty(new DslProperty(v), new NotToEscapePattern(Pattern.compile(rule.getRegex()))))
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Currently only the header matcher of type regex is supported")
|
||||
}
|
||||
} else {
|
||||
header(k, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String buildDescription(RequestResponseInteraction interaction) {
|
||||
String description = "$interaction.description"
|
||||
interaction.providerStates.forEach({ ProviderState it ->
|
||||
description += " $it.name"
|
||||
if (!it.params.isEmpty()) {
|
||||
Map<String, Object> params = it.params
|
||||
description += "("
|
||||
params.forEach({ String k, Object v ->
|
||||
description += k + ": " + v.toString()
|
||||
if (params.keySet().last() != k) {
|
||||
description += ", "
|
||||
}
|
||||
})
|
||||
description += ")"
|
||||
}
|
||||
})
|
||||
return description
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.model.generators.Category
|
||||
import au.com.dius.pact.model.generators.DateGenerator
|
||||
import au.com.dius.pact.model.generators.DateTimeGenerator
|
||||
import au.com.dius.pact.model.generators.Generator
|
||||
import au.com.dius.pact.model.generators.Generators
|
||||
import au.com.dius.pact.model.generators.RandomBooleanGenerator
|
||||
import au.com.dius.pact.model.generators.RandomDecimalGenerator
|
||||
import au.com.dius.pact.model.generators.RandomHexadecimalGenerator
|
||||
import au.com.dius.pact.model.generators.RandomIntGenerator
|
||||
import au.com.dius.pact.model.generators.RandomStringGenerator
|
||||
import au.com.dius.pact.model.generators.RegexGenerator
|
||||
import au.com.dius.pact.model.generators.TimeGenerator
|
||||
import au.com.dius.pact.model.generators.UuidGenerator
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.PackageScope
|
||||
import org.springframework.cloud.contract.spec.internal.Body
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty
|
||||
import org.springframework.cloud.contract.spec.internal.OutputMessage
|
||||
import org.springframework.cloud.contract.verifier.util.ContentUtils
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
* @Since 2.0.0
|
||||
*/
|
||||
@CompileStatic
|
||||
@PackageScope
|
||||
class ValueGeneratorConverter {
|
||||
|
||||
private static final Pattern INTEGER = Pattern.compile(INTEGER_PATTERN)
|
||||
private static final String INTEGER_PATTERN = '-?(\\d+)'
|
||||
private static final Pattern DECIMAL = Pattern.compile(DECIMAL_PATTERN)
|
||||
private static final String DECIMAL_PATTERN = '-?(\\d*\\.\\d+)'
|
||||
private static final Pattern HEX = Pattern.compile(HEX_PATTERN)
|
||||
private static final String HEX_PATTERN = '[a-fA-F0-9]+'
|
||||
private static final Pattern ALPHA_NUMERIC = Pattern.compile(ALPHA_NUMERIC_PATTERN)
|
||||
private static final String ALPHA_NUMERIC_PATTERN = '[a-zA-Z0-9]+'
|
||||
private static final Pattern UUID = Pattern.compile(UUID_PATTERN)
|
||||
private static final String UUID_PATTERN = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
|
||||
private static final Pattern ANY_DATE = Pattern.compile(ANY_DATE_PATTERN)
|
||||
private static final String ANY_DATE_PATTERN = '(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])'
|
||||
private static final Pattern ANY_TIME = Pattern.compile(ANY_TIME_PATTERN)
|
||||
private static final String ANY_TIME_PATTERN = '(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])'
|
||||
private static final Pattern ANY_DATE_TIME = Pattern.compile(ANY_DATE_TIME_PATTERN)
|
||||
private static final String ANY_DATE_TIME_PATTERN = '([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])'
|
||||
private static final Pattern TRUE_OR_FALSE = Pattern.compile(TRUE_OR_FALSE_PATTERN)
|
||||
private static final String TRUE_OR_FALSE_PATTERN = /(true|false)/
|
||||
|
||||
static DslProperty convert(Generator generator, Closure<DslProperty> dslPropertyProvider) {
|
||||
Pattern pattern
|
||||
if (generator instanceof RandomIntGenerator) {
|
||||
pattern = INTEGER
|
||||
} else if (generator instanceof RandomDecimalGenerator) {
|
||||
pattern = DECIMAL
|
||||
} else if (generator instanceof RandomHexadecimalGenerator) {
|
||||
pattern = HEX
|
||||
} else if (generator instanceof RandomStringGenerator) {
|
||||
pattern = ALPHA_NUMERIC
|
||||
} else if (generator instanceof RegexGenerator) {
|
||||
pattern = Pattern.compile(generator.regex)
|
||||
} else if (generator instanceof UuidGenerator) {
|
||||
pattern = UUID
|
||||
} else if (generator instanceof DateGenerator) {
|
||||
pattern = getDateTimePattern(generator.format, ANY_DATE)
|
||||
} else if (generator instanceof TimeGenerator) {
|
||||
pattern = getDateTimePattern(generator.format, ANY_TIME)
|
||||
} else if (generator instanceof DateTimeGenerator) {
|
||||
pattern = getDateTimePattern(generator.format, ANY_DATE_TIME)
|
||||
} else if (generator instanceof RandomBooleanGenerator) {
|
||||
pattern = TRUE_OR_FALSE
|
||||
}
|
||||
if (pattern == null) {
|
||||
throw new UnsupportedOperationException("We currently don't support a generator of type " + generator.class.simpleName)
|
||||
} else {
|
||||
Object generatedValue = generator.generate(null)
|
||||
return dslPropertyProvider(pattern, generatedValue)
|
||||
}
|
||||
}
|
||||
|
||||
private static Pattern getDateTimePattern(String format, Pattern defaultPattern) {
|
||||
return format ? Pattern.compile(format) : defaultPattern
|
||||
}
|
||||
|
||||
static Generators extract(Body body, Closure dslPropertyValueProvider) {
|
||||
Generators generators = new Generators()
|
||||
traverse(body, dslPropertyValueProvider, '', generators, Category.BODY)
|
||||
return generators
|
||||
}
|
||||
|
||||
static Generators extract(OutputMessage message, Closure dslPropertyValueProvider) {
|
||||
Generators generators = new Generators()
|
||||
traverse(message.body, dslPropertyValueProvider, '', generators, Category.BODY)
|
||||
return generators
|
||||
}
|
||||
|
||||
private static void traverse(Object value, Closure dslPropertyValueProvider, String path, Generators generators, Category category) {
|
||||
Object v = value
|
||||
if (v instanceof DslProperty) {
|
||||
v = dslPropertyValueProvider(v)
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue(v, dslPropertyValueProvider)
|
||||
}
|
||||
if (v instanceof Map) {
|
||||
v.each { Map.Entry entry ->
|
||||
traverse(entry.value, dslPropertyValueProvider, path + "." + entry.key, generators, category)
|
||||
}
|
||||
} else if (v instanceof Collection) {
|
||||
v.eachWithIndex{ def entry, int index ->
|
||||
traverse(entry, dslPropertyValueProvider, path + "[" + index + "]", generators, category)
|
||||
}
|
||||
} else if (v instanceof DslProperty) {
|
||||
traverse(v, dslPropertyValueProvider, path, generators, category)
|
||||
} else if (v instanceof Pattern) {
|
||||
switch (v.pattern()) {
|
||||
case INTEGER_PATTERN:
|
||||
generators.addGenerator(category, path, new RandomIntGenerator(0, Integer.MAX_VALUE))
|
||||
break
|
||||
case DECIMAL_PATTERN:
|
||||
generators.addGenerator(category, path, new RandomDecimalGenerator(10))
|
||||
break
|
||||
case HEX_PATTERN:
|
||||
generators.addGenerator(category, path, new RandomHexadecimalGenerator(10))
|
||||
break
|
||||
case ALPHA_NUMERIC_PATTERN:
|
||||
generators.addGenerator(category, path, new RandomStringGenerator(10))
|
||||
break
|
||||
case UUID_PATTERN:
|
||||
generators.addGenerator(category, path, UuidGenerator.INSTANCE)
|
||||
break
|
||||
case ANY_DATE_PATTERN:
|
||||
generators.addGenerator(category, path, new DateGenerator())
|
||||
break
|
||||
case ANY_TIME_PATTERN:
|
||||
generators.addGenerator(category, path, new TimeGenerator())
|
||||
break
|
||||
case ANY_DATE_TIME_PATTERN:
|
||||
generators.addGenerator(category, path, new DateTimeGenerator())
|
||||
break
|
||||
case TRUE_OR_FALSE_PATTERN:
|
||||
generators.addGenerator(category, path, RandomBooleanGenerator.INSTANCE)
|
||||
break
|
||||
default:
|
||||
generators.addGenerator(category, path, new RegexGenerator(v.pattern()))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.contract.verifier.spec.pact
|
||||
|
||||
import au.com.dius.pact.model.Pact
|
||||
@@ -11,13 +26,19 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver
|
||||
import spock.lang.Issue
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Subject
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class PactContractConverterSpec extends Specification {
|
||||
|
||||
File pactJson = new File(PactContractConverterSpec.getResource("/pact/pact.json").toURI())
|
||||
File pact509Json = new File(PactContractConverterSpec.getResource("/pact/pact_509.json").toURI())
|
||||
File pactv2Json = new File(PactContractConverterSpec.getResource("/pact/pact_v2.json").toURI())
|
||||
File pactv3Json = new File(PactContractConverterSpec.getResource("/pact/pact_v3.json").toURI())
|
||||
File pactv3MessagingJson = new File(PactContractConverterSpec.getResource("/pact/pact_v3_messaging.json").toURI())
|
||||
File pactv3UnsupportedRuleLogicJson = new File(PactContractConverterSpec.getResource("/pact/pact_v3_unsupported_rule_logic.json").toURI())
|
||||
@Subject PactContractConverter converter = new PactContractConverter()
|
||||
|
||||
def "should accept json files that are pact files"() {
|
||||
@@ -48,7 +69,7 @@ class PactContractConverterSpec extends Specification {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body(id: "123", method: "create")
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.id', byRegex("[0-9]{3}"))
|
||||
}
|
||||
}
|
||||
@@ -62,7 +83,7 @@ class PactContractConverterSpec extends Specification {
|
||||
id: "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
|
||||
userName: "AJQrokEGPAVdOHprQpKP"]
|
||||
]])
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$[0][*].email', byType())
|
||||
jsonPath('$[0][*].id', byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
|
||||
jsonPath('$[0]', byType() {
|
||||
@@ -97,7 +118,7 @@ class PactContractConverterSpec extends Specification {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body(id: 42, firstName: "Arthur", lastName: "Dent")
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('''$.['id']''', byType())
|
||||
jsonPath('''$.['lastName']''', byType())
|
||||
jsonPath('''$.['firstName']''', byType())
|
||||
@@ -131,7 +152,7 @@ class PactContractConverterSpec extends Specification {
|
||||
method: $(stub(regex("[0][1][2]"))),
|
||||
something: "foo"
|
||||
)
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.id', byRegex("[0-9]{3}"))
|
||||
jsonPath('$.something', byEquality())
|
||||
}
|
||||
@@ -145,18 +166,29 @@ class PactContractConverterSpec extends Specification {
|
||||
[email: "rddtGwwWMEhnkAPEmsyE",
|
||||
id: "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
|
||||
number: $(producer(regex("[0-9]{3}")), consumer(923)),
|
||||
positiveInteger: 1234567890,
|
||||
negativeInteger: -1234567890,
|
||||
positiveDecimalNumber: 123.4567890,
|
||||
negativeDecimalNumber: -123.4567890,
|
||||
something: "foo",
|
||||
userName: "AJQrokEGPAVdOHprQpKP"]
|
||||
userName: "AJQrokEGPAVdOHprQpKP",
|
||||
nullValue: null]
|
||||
]])
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$[0][*].email', byType())
|
||||
jsonPath('$[0][*].id', byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
|
||||
jsonPath('$[0]', byType() {
|
||||
minOccurrence(1)
|
||||
maxOccurrence(5)
|
||||
})
|
||||
jsonPath('$[0][*].number', byRegex(number()))
|
||||
jsonPath('$[0][*].positiveInteger', byRegex(anInteger()))
|
||||
jsonPath('$[0][*].negativeInteger', byRegex(anInteger()))
|
||||
jsonPath('$[0][*].positiveDecimalNumber', byRegex(aDouble()))
|
||||
jsonPath('$[0][*].negativeDecimalNumber', byRegex(aDouble()))
|
||||
jsonPath('$[0][*].userName', byType())
|
||||
jsonPath('$[0][*].something', byEquality())
|
||||
jsonPath('$[0][*].nullValue', byNull())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +207,10 @@ class PactContractConverterSpec extends Specification {
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "\\/mallory",
|
||||
"query": "name=ron&status=good",
|
||||
"query": {
|
||||
"name": ["ron"],
|
||||
"status": ["good"]
|
||||
},
|
||||
"headers": {
|
||||
"Content-Type": "application\\/json"
|
||||
},
|
||||
@@ -185,12 +220,18 @@ class PactContractConverterSpec extends Specification {
|
||||
"something": "foo"
|
||||
},
|
||||
"matchingRules": {
|
||||
"$.body.id": {
|
||||
"match": "regex",
|
||||
"regex": "[0-9]{3}"
|
||||
},
|
||||
"$.body.something": {
|
||||
"match": "equality"
|
||||
"body": {
|
||||
"$.id": {
|
||||
"matchers": [{
|
||||
"match": "regex",
|
||||
"regex": "[0-9]{3}"
|
||||
}]
|
||||
},
|
||||
"$.something": {
|
||||
"matchers": [{
|
||||
"match": "equality"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -205,28 +246,76 @@ class PactContractConverterSpec extends Specification {
|
||||
"email": "rddtGwwWMEhnkAPEmsyE",
|
||||
"id": "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
|
||||
"number": 923,
|
||||
"userName": "AJQrokEGPAVdOHprQpKP"
|
||||
"positiveInteger": 1234567890,
|
||||
"negativeInteger": -1234567890,
|
||||
"positiveDecimalNumber": 123.4567890,
|
||||
"negativeDecimalNumber": -123.4567890,
|
||||
"userName": "AJQrokEGPAVdOHprQpKP",
|
||||
"something": "foo",
|
||||
"nullValue": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"matchingRules": {
|
||||
"$.body[0][*].email": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body[0][*].id": {
|
||||
"match": "regex",
|
||||
"regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
},
|
||||
"$.body[0]": {
|
||||
"match": "type",
|
||||
"min": 1,
|
||||
"max": 5
|
||||
},
|
||||
"$.body[0][*].userName": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body[0][*].something": {
|
||||
"match": "equality"
|
||||
"body": {
|
||||
"$[0][*].email": {
|
||||
"matchers": [{
|
||||
"match": "type"
|
||||
}]
|
||||
},
|
||||
"$[0][*].id": {
|
||||
"matchers": [{
|
||||
"match": "regex",
|
||||
"regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
}]
|
||||
},
|
||||
"$[0]": {
|
||||
"matchers": [{
|
||||
"match": "type",
|
||||
"min": 1,
|
||||
"max": 5
|
||||
}]
|
||||
},
|
||||
"$[0][*].number": {
|
||||
"matchers": [{
|
||||
"match": "number"
|
||||
}]
|
||||
},
|
||||
"$[0][*].positiveInteger": {
|
||||
"matchers": [{
|
||||
"match": "integer"
|
||||
}]
|
||||
},
|
||||
"$[0][*].negativeInteger": {
|
||||
"matchers": [{
|
||||
"match": "integer"
|
||||
}]
|
||||
},
|
||||
"$[0][*].positiveDecimalNumber": {
|
||||
"matchers": [{
|
||||
"match": "decimal"
|
||||
}]
|
||||
},
|
||||
"$[0][*].negativeDecimalNumber": {
|
||||
"matchers": [{
|
||||
"match": "decimal"
|
||||
}]
|
||||
},
|
||||
"$[0][*].userName": {
|
||||
"matchers": [{
|
||||
"match": "type"
|
||||
}]
|
||||
},
|
||||
"$[0][*].something": {
|
||||
"matchers": [{
|
||||
"match": "equality"
|
||||
}]
|
||||
},
|
||||
"$[0][*].nullValue": {
|
||||
"matchers": [{
|
||||
"match": "null"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,18 +323,18 @@ class PactContractConverterSpec extends Specification {
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
when:
|
||||
Pact pact = converter.convertTo(inputContracts)
|
||||
Pact pact = converter.convertTo(inputContracts).get(0)
|
||||
then:
|
||||
String actual = JsonOutput.toJson(pact.toMap(PactSpecVersion.V2))
|
||||
String actual = JsonOutput.toJson(pact.toMap(PactSpecVersion.V3))
|
||||
JSONAssert.assertEquals(expectedJson, actual, false)
|
||||
}
|
||||
|
||||
@@ -303,15 +392,267 @@ class PactContractConverterSpec extends Specification {
|
||||
Map<String, Collection<Contract>> contracts = contractResources.collectEntries { [(it.filename) : ContractVerifierDslConverter.convertAsCollection(new File("/"), it.file)] }
|
||||
Map<String, String> jsonPacts = pactResources.collectEntries { [(it.filename) : it.file.text] }
|
||||
when:
|
||||
Map<String, Pact> pacts = contracts.entrySet().collectEntries { [(it.key) : converter.convertTo(it.value)] }
|
||||
Map<String, Collection<Pact>> pacts = contracts.entrySet().collectEntries { [(it.key) : converter.convertTo(it.value)] }
|
||||
then:
|
||||
pacts.entrySet().each {
|
||||
String convertedPactAsText = JsonOutput.toJson(it.value.toMap(PactSpecVersion.V2))
|
||||
String convertedPactAsText = JsonOutput.toJson(it.value[0].toMap(PactSpecVersion.V3))
|
||||
String pactFileName = it.key.replace("groovy", "json")
|
||||
println "File name [${it.key}]"
|
||||
JSONAssert.assertEquals(jsonPacts.get(pactFileName), convertedPactAsText, false)
|
||||
JSONAssert.assertEquals(jsonPacts.get(pactFileName), convertedPactAsText, true)
|
||||
}
|
||||
}
|
||||
|
||||
def "should convert from pact v2 to two SC contracts"() {
|
||||
given:
|
||||
Collection<Contract> expectedContracts = [
|
||||
Contract.make {
|
||||
description("get all users for max a user with an id named 'user' exists")
|
||||
request {
|
||||
method(GET())
|
||||
url("/idm/user")
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body([[
|
||||
[email: "rddtGwwWMEhnkAPEmsyE",
|
||||
id: "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
|
||||
userName: "AJQrokEGPAVdOHprQpKP"]
|
||||
]])
|
||||
bodyMatchers {
|
||||
jsonPath('$[0][*].email', byType())
|
||||
jsonPath('$[0][*].id', byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
|
||||
jsonPath('$[0]', byType() {
|
||||
maxOccurrence(5)
|
||||
})
|
||||
jsonPath('$[0][*].userName', byType())
|
||||
}
|
||||
}
|
||||
},
|
||||
Contract.make {
|
||||
description("get all users for min a user with an id named 'user' exists")
|
||||
request {
|
||||
method(GET())
|
||||
url("/idm/user")
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body([[
|
||||
[email: "DPvAfkCZpOBZWzKYiDMC",
|
||||
id: "95d0371b-bf30-4943-90a8-8bb1967c4cb2",
|
||||
userName: "GIUlVKoiLdHLYNKGbcSy"]
|
||||
]])
|
||||
bodyMatchers {
|
||||
jsonPath('$[0][*].email', byType())
|
||||
jsonPath('$[0][*].id', byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
|
||||
jsonPath('$[0]', byType() {
|
||||
minOccurrence(5)
|
||||
})
|
||||
jsonPath('$[0][*].userName', byType())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
when:
|
||||
Collection<Contract> contracts = converter.convertFrom(pactv2Json)
|
||||
then:
|
||||
contracts == expectedContracts
|
||||
}
|
||||
|
||||
def "should convert from pact v3 to three SC contracts"() {
|
||||
given:
|
||||
Collection<Contract> expectedContracts = [
|
||||
Contract.make {
|
||||
description("java test interaction with a DSL array body")
|
||||
request {
|
||||
method(GET())
|
||||
url("/")
|
||||
headers {
|
||||
contentType(applicationJsonUtf8())
|
||||
header("Some-Header", $(c(regex("[a-zA-Z]{9}")), p("someValue")))
|
||||
header("someHeaderWithJsonContent", '{"issue":"#595"}')
|
||||
}
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
headers {
|
||||
contentType(applicationJsonUtf8())
|
||||
header("Some-Header", $(c("someValue"), p(regex("[a-zA-Z]{9}"))))
|
||||
header("someHeaderWithJsonContent", '{"issue":"#595"}')
|
||||
}
|
||||
body([
|
||||
[
|
||||
"dob": "07/19/2016",
|
||||
"id": 8958464620,
|
||||
"name": "Rogger the Dogger",
|
||||
"timestamp": "2016-07-19T12:14:39",
|
||||
"nullValue": null,
|
||||
"aNumber": 1234567890,
|
||||
"positiveInteger": 1234567890,
|
||||
"negativeInteger": -1234567890,
|
||||
"positiveDecimalNumber": 123.4567890,
|
||||
"negativeDecimalNumber": -123.4567890
|
||||
],
|
||||
[
|
||||
"dob": "07/19/2016",
|
||||
"id": 4143398442,
|
||||
"name": "Cat in the Hat",
|
||||
"timestamp": "2016-07-19T12:14:39",
|
||||
"nullValue": null,
|
||||
"aNumber": 1234567890,
|
||||
"positiveInteger": 1234567890,
|
||||
"negativeInteger": -1234567890,
|
||||
"positiveDecimalNumber": 123.4567890,
|
||||
"negativeDecimalNumber": -123.4567890
|
||||
]
|
||||
])
|
||||
bodyMatchers {
|
||||
jsonPath('$[0].id', byType())
|
||||
jsonPath('$[1].id', byType())
|
||||
jsonPath('$[*].nullValue', byNull())
|
||||
jsonPath('$[*].aNumber', byRegex(number()))
|
||||
jsonPath('$[*].positiveInteger', byRegex(anInteger()))
|
||||
jsonPath('$[*].negativeInteger', byRegex(anInteger()))
|
||||
jsonPath('$[*].positiveDecimalNumber', byRegex(aDouble()))
|
||||
jsonPath('$[*].negativeDecimalNumber', byRegex(aDouble()))
|
||||
}
|
||||
}
|
||||
},
|
||||
Contract.make {
|
||||
description("test interaction with a array body with templates")
|
||||
request {
|
||||
method(GET())
|
||||
url("/")
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
headers {
|
||||
contentType(applicationJsonUtf8())
|
||||
}
|
||||
body([
|
||||
[
|
||||
"dob": "2016-07-19",
|
||||
"id": 1943791933,
|
||||
"name": "ZSAICmTmiwgFFInuEuiK"
|
||||
],
|
||||
[
|
||||
"dob": "2016-07-19",
|
||||
"id": 1943791933,
|
||||
"name": "ZSAICmTmiwgFFInuEuiK"
|
||||
],
|
||||
[
|
||||
"dob": "2016-07-19",
|
||||
"id": 1943791933,
|
||||
"name": "ZSAICmTmiwgFFInuEuiK"
|
||||
]
|
||||
])
|
||||
bodyMatchers {
|
||||
jsonPath('$[2].name', byType())
|
||||
jsonPath('$[0].id', byType())
|
||||
jsonPath('$[1].id', byType())
|
||||
jsonPath('$[2].id', byType())
|
||||
jsonPath('$[1].name', byType())
|
||||
jsonPath('$[0].name', byType())
|
||||
jsonPath('$[0].dob', byDate())
|
||||
}
|
||||
}
|
||||
},
|
||||
Contract.make {
|
||||
description("test interaction with an array like matcher")
|
||||
request {
|
||||
method(GET())
|
||||
url("/")
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
headers {
|
||||
contentType(applicationJsonUtf8())
|
||||
}
|
||||
body([
|
||||
"data": [
|
||||
"array1": [[
|
||||
"dob": "2016-07-19",
|
||||
"id": 1600309982,
|
||||
"name": "FVsWAGZTFGPLhWjLuBOd"
|
||||
]],
|
||||
"array2": [[
|
||||
"address": "127.0.0.1",
|
||||
"name": "jvxrzduZnwwxpFYrQnpd"
|
||||
]],
|
||||
"array3": [[
|
||||
[
|
||||
"itemCount": 652571349
|
||||
]
|
||||
]]
|
||||
],
|
||||
"id": 7183997828
|
||||
])
|
||||
bodyMatchers {
|
||||
jsonPath('$.data.array3[0]', byType() {
|
||||
maxOccurrence(5)
|
||||
})
|
||||
jsonPath('$.data.array1', byType() {
|
||||
minOccurrence(0)
|
||||
})
|
||||
jsonPath('$.data.array2', byType() {
|
||||
minOccurrence(1)
|
||||
})
|
||||
jsonPath('$.id', byType())
|
||||
jsonPath('$.data.array2[*].name', byType())
|
||||
jsonPath('$.data.array2[*].address', byRegex("(\\d{1,3}\\.)+\\d{1,3}"))
|
||||
jsonPath('$.data.array1[*].name', byType())
|
||||
jsonPath('$.data.array1[*].id', byType())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
when:
|
||||
Collection<Contract> contracts = converter.convertFrom(pactv3Json)
|
||||
then:
|
||||
contracts == expectedContracts
|
||||
}
|
||||
|
||||
def "should convert from pact v3 messaging to one SC message contract"() {
|
||||
|
||||
given:
|
||||
Collection<Contract> expectedContracts = [
|
||||
Contract.make {
|
||||
label 'message sent to activemq:output'
|
||||
input {
|
||||
triggeredBy('bookReturnedTriggered()')
|
||||
}
|
||||
outputMessage {
|
||||
body([
|
||||
bookName: "foo"
|
||||
])
|
||||
headers {
|
||||
header('BOOK-NAME', 'foo')
|
||||
messagingContentType(applicationJson())
|
||||
}
|
||||
bodyMatchers {
|
||||
jsonPath('$.bookName', byType())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
when:
|
||||
Collection<Contract> contracts = converter.convertFrom(pactv3MessagingJson)
|
||||
then:
|
||||
contracts == expectedContracts
|
||||
}
|
||||
|
||||
def "should fail to convert a pact v3 contract with unsupported rule logic"() {
|
||||
when:
|
||||
converter.convertFrom(pactv3UnsupportedRuleLogicJson)
|
||||
then:
|
||||
def e = thrown(UnsupportedOperationException)
|
||||
e.message.contains("Currently only the AND combination rule logic is supported")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -319,6 +660,6 @@ class PactContractConverterSpec extends Specification {
|
||||
// file creator
|
||||
/*
|
||||
pacts.entrySet().each {
|
||||
new File("target/${it.key.replace("groovy", "json")}").text = JsonOutput.toJson(it.value.toMap(PactSpecVersion.V2))
|
||||
new File("target/${it.key.replace("groovy", "json")}").text = JsonOutput.toJson(it.value.toMap(PactSpecVersion.V3))
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package contracts
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract.make {
|
||||
request {
|
||||
method 'POST'
|
||||
url '/'
|
||||
body([
|
||||
someInteger: $(c(anyInteger()), p(1234567890)),
|
||||
someDecimal: $(c(anyDouble()), p(123.123)),
|
||||
someHex: $(c(anyHex()), p('DEADC0DE')),
|
||||
someAlphaNumeric: $(c(anyAlphaNumeric()), p('Some alpha numeric string with 1234567890')),
|
||||
someUUID: $(c(anyUuid()), p('00000000-0000-0000-0000-000000000000')),
|
||||
someDate: $(c(anyDate()), p('2018-03-26')),
|
||||
someTime: $(c(anyTime()), p('13:37:00')),
|
||||
someDateTime: $(c(anyDateTime()), p('2018-03-26 13:37:00')),
|
||||
someBoolean: $(c(anyBoolean()), p('true')),
|
||||
someRegex: $(c(regex('[0-9]{10}')), p(1234567890))
|
||||
])
|
||||
headers {
|
||||
contentType('application/json')
|
||||
}
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body([
|
||||
someInteger: $(c(1234567890), p(anyInteger())),
|
||||
someDecimal: $(c(123.123), p(anyDouble())),
|
||||
someHex: $(c('DEADC0DE'), p(anyHex())),
|
||||
someAlphaNumeric: $(c('Some alpha numeric string with 1234567890'), p(anyAlphaNumeric())),
|
||||
someUUID: $(c('00000000-0000-0000-0000-000000000000'), p(anyUuid())),
|
||||
someDate: $(c('2018-03-26'), p(anyDate())),
|
||||
someTime: $(c('13:37:00'), p(anyTime())),
|
||||
someDateTime: $(c('2018-03-26 13:37:00'), p(anyDateTime())),
|
||||
someBoolean: $(c('true'), p(anyBoolean())),
|
||||
someRegex: $(c(1234567890), p(regex('[0-9]{10}')))
|
||||
])
|
||||
headers {
|
||||
contentType('application/json')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"someInteger": 1234567890,
|
||||
"someDecimal": 123.123,
|
||||
"someHex": "DEADC0DE",
|
||||
"someAlphaNumeric": "Some alpha numeric string with 1234567890",
|
||||
"someUUID": "00000000-0000-0000-0000-000000000000",
|
||||
"someDate": "2018-03-26",
|
||||
"someTime": "13:37:00",
|
||||
"someDateTime": "2018-03-26 13:37:00",
|
||||
"someBoolean": "true",
|
||||
"someRegex": 1234567890
|
||||
},
|
||||
"generators": {
|
||||
"body": {
|
||||
"$.someInteger":{
|
||||
"type": "RandomInt",
|
||||
"min": 0,
|
||||
"max": 2147483647
|
||||
},
|
||||
"$.someDecimal":{
|
||||
"type": "RandomDecimal",
|
||||
"digits": 10
|
||||
},
|
||||
"$.someHex":{
|
||||
"type": "RandomHexadecimal",
|
||||
"digits": 10
|
||||
},
|
||||
"$.someAlphaNumeric":{
|
||||
"type": "RandomString",
|
||||
"size": 10
|
||||
},
|
||||
"$.someUUID":{
|
||||
"type": "Uuid"
|
||||
},
|
||||
"$.someDate":{
|
||||
"type": "Date"
|
||||
},
|
||||
"$.someTime":{
|
||||
"type": "Time"
|
||||
},
|
||||
"$.someDateTime":{
|
||||
"type": "DateTime"
|
||||
},
|
||||
"$.someBoolean":{
|
||||
"type": "RandomBoolean"
|
||||
},
|
||||
"$.someRegex":{
|
||||
"type": "Regex",
|
||||
"regex": "[0-9]{10}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"matchingRules":{
|
||||
"header": {
|
||||
"Content-Type":{
|
||||
"matchers":[
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
},
|
||||
"body":{
|
||||
"$.someHex":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someAlphaNumeric":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someUUID":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someDate":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someTime":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someDateTime":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someBoolean":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"someInteger": 1234567890,
|
||||
"someDecimal": 123.123,
|
||||
"someHex": "DEADC0DE",
|
||||
"someAlphaNumeric": "Some alpha numeric string with 1234567890",
|
||||
"someUUID": "00000000-0000-0000-0000-000000000000",
|
||||
"someDate": "2018-03-26",
|
||||
"someTime": "13:37:00",
|
||||
"someDateTime": "2018-03-26 13:37:00",
|
||||
"someBoolean": "true",
|
||||
"someRegex": 1234567890
|
||||
},
|
||||
"generators": {
|
||||
"body": {
|
||||
"$.someInteger":{
|
||||
"type": "RandomInt",
|
||||
"min": 0,
|
||||
"max": 2147483647
|
||||
},
|
||||
"$.someDecimal":{
|
||||
"type": "RandomDecimal",
|
||||
"digits": 10
|
||||
},
|
||||
"$.someHex":{
|
||||
"type": "RandomHexadecimal",
|
||||
"digits": 10
|
||||
},
|
||||
"$.someAlphaNumeric":{
|
||||
"type": "RandomString",
|
||||
"size": 10
|
||||
},
|
||||
"$.someUUID":{
|
||||
"type": "Uuid"
|
||||
},
|
||||
"$.someDate":{
|
||||
"type": "Date"
|
||||
},
|
||||
"$.someTime":{
|
||||
"type": "Time"
|
||||
},
|
||||
"$.someDateTime":{
|
||||
"type": "DateTime"
|
||||
},
|
||||
"$.someBoolean":{
|
||||
"type": "RandomBoolean"
|
||||
},
|
||||
"$.someRegex":{
|
||||
"type": "Regex",
|
||||
"regex": "[0-9]{10}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"matchingRules":{
|
||||
"header":{
|
||||
"Content-Type":{
|
||||
"matchers":[
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
},
|
||||
"body":{
|
||||
"$.someHex":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someAlphaNumeric":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someUUID":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someDate":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someTime":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someDateTime":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.someBoolean":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package contracts
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract.make {
|
||||
request {
|
||||
method 'POST'
|
||||
url '/'
|
||||
body([
|
||||
someInteger: 1234567890,
|
||||
someDecimal: 123.123,
|
||||
someHex: 'DEADC0DE',
|
||||
someAlphaNumeric: 'Some alpha numeric string with 1234567890',
|
||||
someUUID: '00000000-0000-0000-0000-000000000000',
|
||||
someDate: '2018-03-26',
|
||||
someTime: '13:37:00',
|
||||
someDateTime: '2018-03-26 13:37:00',
|
||||
someBoolean: 'true',
|
||||
someNullValue: null
|
||||
])
|
||||
headers {
|
||||
contentType('application/json')
|
||||
header("Some-Header", $(c(regex('[a-zA-Z]{9}')), p('someValue')))
|
||||
header("Header-Without-Matcher", 'someValue')
|
||||
}
|
||||
bodyMatchers {
|
||||
jsonPath('$.someInteger', byRegex(anInteger()))
|
||||
jsonPath('$.someDecimal', byRegex(aDouble()))
|
||||
jsonPath('$.someHex', byRegex('[a-fA-F0-9]+'))
|
||||
jsonPath('$.someAlphaNumeric', byRegex(alphaNumeric()))
|
||||
jsonPath('$.someUUID', byRegex(uuid()))
|
||||
jsonPath('$.someDate', byDate())
|
||||
jsonPath('$.someTime', byTime())
|
||||
jsonPath('$.someDateTime', byTimestamp())
|
||||
jsonPath('$.someBoolean', byRegex(anyBoolean()))
|
||||
}
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body([
|
||||
someInteger: 1234567890,
|
||||
someDecimal: 123.123,
|
||||
someHex: 'DEADC0DE',
|
||||
someAlphaNumeric: 'Some alpha numeric string with 1234567890',
|
||||
someUUID: '00000000-0000-0000-0000-000000000000',
|
||||
someDate: '2018-03-26',
|
||||
someTime: '13:37:00',
|
||||
someDateTime: '2018-03-26 13:37:00',
|
||||
someBoolean: 'true',
|
||||
someRegex: 1234567890
|
||||
])
|
||||
headers {
|
||||
contentType('application/json')
|
||||
header("Some-Header", $(c('someValue'), p(regex('[a-zA-Z]{9}'))))
|
||||
header("Header-Without-Matcher", 'someValue')
|
||||
}
|
||||
bodyMatchers {
|
||||
jsonPath('$.someInteger', byRegex(anInteger()))
|
||||
jsonPath('$.someDecimal', byRegex(aDouble()))
|
||||
jsonPath('$.someHex', byRegex('[a-fA-F0-9]+'))
|
||||
jsonPath('$.someAlphaNumeric', byRegex(alphaNumeric()))
|
||||
jsonPath('$.someUUID', byRegex(uuid()))
|
||||
jsonPath('$.someDate', byDate())
|
||||
jsonPath('$.someTime', byTime())
|
||||
jsonPath('$.someDateTime', byTimestamp())
|
||||
jsonPath('$.someBoolean', byRegex(anyBoolean()))
|
||||
jsonPath('$.someNullValue', byNull())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/",
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"Header-Without-Matcher": "someValue",
|
||||
"Some-Header": "someValue"
|
||||
},
|
||||
"body": {
|
||||
"someHex": "DEADC0DE",
|
||||
"someTime": "13:37:00",
|
||||
"someNullValue": null,
|
||||
"someInteger": 1234567890,
|
||||
"someBoolean": "true",
|
||||
"someDate": "2018-03-26",
|
||||
"someUUID": "00000000-0000-0000-0000-000000000000",
|
||||
"someDecimal": 123.123,
|
||||
"someAlphaNumeric": "Some alpha numeric string with 1234567890",
|
||||
"someDateTime": "2018-03-26 13:37:00"
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"Some-Header": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-zA-Z]{9}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.someInteger": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "integer"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDecimal": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "decimal"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someHex": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-fA-F0-9]+"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someAlphaNumeric": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-zA-Z0-9]+"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someUUID": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDate": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "date",
|
||||
"date": "yyyy-MM-dd"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someTime": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "time",
|
||||
"time": "HH:mm:ss"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDateTime": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "timestamp",
|
||||
"timestamp": "yyyy-MM-dd HH:mm:ssZZZ"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someBoolean": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "(true|false)"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"Header-Without-Matcher": "someValue",
|
||||
"Some-Header": "someValue"
|
||||
},
|
||||
"body": {
|
||||
"someHex": "DEADC0DE",
|
||||
"someTime": "13:37:00",
|
||||
"someInteger": 1234567890,
|
||||
"someBoolean": "true",
|
||||
"someRegex": 1234567890,
|
||||
"someDate": "2018-03-26",
|
||||
"someUUID": "00000000-0000-0000-0000-000000000000",
|
||||
"someDecimal": 123.123,
|
||||
"someAlphaNumeric": "Some alpha numeric string with 1234567890",
|
||||
"someDateTime": "2018-03-26 13:37:00"
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"Some-Header": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-zA-Z]{9}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.someInteger": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "integer"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDecimal": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "decimal"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someHex": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-fA-F0-9]+"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someAlphaNumeric": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-zA-Z0-9]+"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someUUID": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDate": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "date",
|
||||
"date": "yyyy-MM-dd"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someTime": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "time",
|
||||
"time": "HH:mm:ss"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDateTime": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "timestamp",
|
||||
"timestamp": "yyyy-MM-dd HH:mm:ssZZZ"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someBoolean": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "(true|false)"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someNullValue": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "null"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,37 @@
|
||||
"body": {
|
||||
"clientId": "8532032713",
|
||||
"loanAmount": 99999
|
||||
},
|
||||
"generators": {
|
||||
"body": {
|
||||
"$.clientId":{
|
||||
"type": "Regex",
|
||||
"regex": "[0-9]{10}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"matchingRules":{
|
||||
"header":{
|
||||
"Content-Type":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"regex",
|
||||
"regex":"application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
},
|
||||
"body":{
|
||||
"$.clientId":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
@@ -27,16 +58,47 @@
|
||||
"body": {
|
||||
"fraudCheckStatus": "FRAUD",
|
||||
"rejectionReason": "Amount too high"
|
||||
},
|
||||
"matchingRules":{
|
||||
"header":{
|
||||
"Content-Type":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"regex",
|
||||
"regex":"application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
},
|
||||
"body":{
|
||||
"$.fraudCheckStatus":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
},
|
||||
"$.rejectionReason":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,37 @@
|
||||
"body": {
|
||||
"clientId": "1234567890",
|
||||
"loanAmount": 123.123
|
||||
},
|
||||
"generators": {
|
||||
"body": {
|
||||
"$.clientId":{
|
||||
"type": "Regex",
|
||||
"regex": "[0-9]{10}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"matchingRules":{
|
||||
"header":{
|
||||
"Content-Type":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"regex",
|
||||
"regex":"application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
},
|
||||
"body":{
|
||||
"$.clientId":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
@@ -27,16 +58,39 @@
|
||||
"body": {
|
||||
"fraudCheckStatus": "OK",
|
||||
"rejectionReason": null
|
||||
},
|
||||
"matchingRules":{
|
||||
"header":{
|
||||
"Content-Type":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"regex",
|
||||
"regex":"application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
},
|
||||
"body":{
|
||||
"$.fraudCheckStatus":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,16 +19,30 @@
|
||||
},
|
||||
"body": {
|
||||
"count": 200
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package contracts
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
[
|
||||
Contract.make {
|
||||
label 'some_label'
|
||||
input {
|
||||
triggeredBy('bookReturnedTriggered()')
|
||||
}
|
||||
outputMessage {
|
||||
sentTo('activemq:output')
|
||||
body('''{ "bookName" : "foo" }''')
|
||||
headers {
|
||||
header('BOOK-NAME', 'foo')
|
||||
messagingContentType(applicationJson())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"consumer":{
|
||||
"name":"Consumer"
|
||||
},
|
||||
"provider":{
|
||||
"name":"Provider"
|
||||
},
|
||||
"messages":[
|
||||
{
|
||||
"description":"message sent to activemq:output",
|
||||
"metaData":{
|
||||
"BOOK-NAME":"foo",
|
||||
"contentType":"application/json"
|
||||
},
|
||||
"contents":{
|
||||
"bookName":"foo"
|
||||
},
|
||||
"providerStates":[
|
||||
{
|
||||
"name":"bookReturnedTriggered()"
|
||||
}
|
||||
],
|
||||
"matchingRules":{
|
||||
"body":{
|
||||
"$.bookName":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata":{
|
||||
"pact-specification":{
|
||||
"version":"3.0.0"
|
||||
},
|
||||
"pact-jvm":{
|
||||
"version":"3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package contracts
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
[
|
||||
Contract.make {
|
||||
label 'some_label'
|
||||
input {
|
||||
messageFrom('jms:input')
|
||||
messageBody([
|
||||
bookName: 'foo'
|
||||
])
|
||||
messageHeaders {
|
||||
header('sample', 'header')
|
||||
}
|
||||
}
|
||||
outputMessage {
|
||||
sentTo('jms:output')
|
||||
body([
|
||||
bookName: 'foo'
|
||||
])
|
||||
headers {
|
||||
header('BOOK-NAME', 'foo')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"consumer":{
|
||||
"name":"Consumer"
|
||||
},
|
||||
"provider":{
|
||||
"name":"Provider"
|
||||
},
|
||||
"messages":[
|
||||
{
|
||||
"description":"message sent to jms:output",
|
||||
"metaData":{
|
||||
"BOOK-NAME":"foo"
|
||||
},
|
||||
"contents":{
|
||||
"bookName":"foo"
|
||||
},
|
||||
"providerStates":[
|
||||
{
|
||||
"name":"received message from jms:input"
|
||||
}
|
||||
],
|
||||
"matchingRules":{
|
||||
"body":{
|
||||
"$.bookName":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata":{
|
||||
"pact-specification":{
|
||||
"version":"3.0.0"
|
||||
},
|
||||
"pact-jvm":{
|
||||
"version":"3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package contracts
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
[
|
||||
Contract.make {
|
||||
label 'some_label'
|
||||
input {
|
||||
messageFrom('jms:delete')
|
||||
messageBody([
|
||||
bookName: 'foo'
|
||||
])
|
||||
messageHeaders {
|
||||
header('sample', 'header')
|
||||
}
|
||||
assertThat('bookWasDeleted()')
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"consumer":{
|
||||
"name":"Consumer"
|
||||
},
|
||||
"provider":{
|
||||
"name":"Provider"
|
||||
},
|
||||
"messages":[
|
||||
{
|
||||
"description":"assert that bookWasDeleted()",
|
||||
"metaData":{
|
||||
|
||||
},
|
||||
"providerStates":[
|
||||
{
|
||||
"name":"received message from jms:delete"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata":{
|
||||
"pact-specification":{
|
||||
"version":"3.0.0"
|
||||
},
|
||||
"pact-jvm":{
|
||||
"version":"3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "266_provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "test_consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "get all users for max",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/idm/user"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
},
|
||||
"body": [
|
||||
[
|
||||
{
|
||||
"email": "rddtGwwWMEhnkAPEmsyE",
|
||||
"id": "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
|
||||
"userName": "AJQrokEGPAVdOHprQpKP"
|
||||
}
|
||||
]
|
||||
],
|
||||
"matchingRules": {
|
||||
"$.body[0][*].email": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body[0][*].id": {
|
||||
"regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
},
|
||||
"$.body[0]": {
|
||||
"max": 5,
|
||||
"match": "type"
|
||||
},
|
||||
"$.body[0][*].userName": {
|
||||
"match": "type"
|
||||
}
|
||||
}
|
||||
},
|
||||
"providerState": "a user with an id named 'user' exists"
|
||||
},
|
||||
{
|
||||
"description": "get all users for min",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/idm/user"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
},
|
||||
"body": [
|
||||
[
|
||||
{
|
||||
"email": "DPvAfkCZpOBZWzKYiDMC",
|
||||
"id": "95d0371b-bf30-4943-90a8-8bb1967c4cb2",
|
||||
"userName": "GIUlVKoiLdHLYNKGbcSy"
|
||||
}
|
||||
]
|
||||
],
|
||||
"matchingRules": {
|
||||
"$.body[0][*].email": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body[0][*].id": {
|
||||
"regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
},
|
||||
"$.body[0]": {
|
||||
"min": 5,
|
||||
"match": "type"
|
||||
},
|
||||
"$.body[0][*].userName": {
|
||||
"match": "type"
|
||||
}
|
||||
}
|
||||
},
|
||||
"providerState": "a user with an id named 'user' exists"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.2.11"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "test_provider_array"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "test_consumer_array"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "java test interaction with a DSL array body",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/",
|
||||
"headers": {
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
"Some-Header": "someValue",
|
||||
"someHeaderWithJsonContent": "{\"issue\":\"#595\"}"
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Some-Header": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-zA-Z]{9}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
"Some-Header": "someValue",
|
||||
"someHeaderWithJsonContent": "{\"issue\":\"#595\"}"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"dob": "07/19/2016",
|
||||
"id": 8958464620,
|
||||
"name": "Rogger the Dogger",
|
||||
"timestamp": "2016-07-19T12:14:39",
|
||||
"nullValue": null,
|
||||
"aNumber": 1234567890,
|
||||
"positiveInteger": 1234567890,
|
||||
"negativeInteger": -1234567890,
|
||||
"positiveDecimalNumber": 123.4567890,
|
||||
"negativeDecimalNumber": -123.4567890
|
||||
},
|
||||
{
|
||||
"dob": "07/19/2016",
|
||||
"id": 4143398442,
|
||||
"name": "Cat in the Hat",
|
||||
"timestamp": "2016-07-19T12:14:39",
|
||||
"nullValue": null,
|
||||
"aNumber": 1234567890,
|
||||
"positiveInteger": 1234567890,
|
||||
"negativeInteger": -1234567890,
|
||||
"positiveDecimalNumber": 123.4567890,
|
||||
"negativeDecimalNumber": -123.4567890
|
||||
}
|
||||
],
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Some-Header": {
|
||||
"matchers": [
|
||||
{ "match" : "regex", "regex" : "[a-zA-Z]{9}" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$[0].id": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$[1].id": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$[*].nullValue": {
|
||||
"matchers": [
|
||||
{ "match": "null" }
|
||||
]
|
||||
},
|
||||
"$[*].aNumber": {
|
||||
"matchers": [
|
||||
{ "match": "number" }
|
||||
]
|
||||
},
|
||||
"$[*].positiveInteger": {
|
||||
"matchers": [
|
||||
{ "match": "integer" }
|
||||
]
|
||||
},
|
||||
"$[*].negativeInteger": {
|
||||
"matchers": [
|
||||
{ "match": "integer" }
|
||||
]
|
||||
},
|
||||
"$[*].positiveDecimalNumber": {
|
||||
"matchers": [
|
||||
{ "match": "decimal" }
|
||||
]
|
||||
},
|
||||
"$[*].negativeDecimalNumber": {
|
||||
"matchers": [
|
||||
{ "match": "decimal" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "test interaction with a array body with templates",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"dob": "2016-07-19",
|
||||
"id": 1943791933,
|
||||
"name": "ZSAICmTmiwgFFInuEuiK"
|
||||
},
|
||||
{
|
||||
"dob": "2016-07-19",
|
||||
"id": 1943791933,
|
||||
"name": "ZSAICmTmiwgFFInuEuiK"
|
||||
},
|
||||
{
|
||||
"dob": "2016-07-19",
|
||||
"id": 1943791933,
|
||||
"name": "ZSAICmTmiwgFFInuEuiK"
|
||||
}
|
||||
],
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$[2].name": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$[0].id": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$[1].id": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$[2].id": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$[1].name": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$[0].name": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$[0].dob": {
|
||||
"matchers": [
|
||||
{ "date": "yyyy-MM-dd" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "test interaction with an array like matcher",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"data": {
|
||||
"array1": [
|
||||
{
|
||||
"dob": "2016-07-19",
|
||||
"id": 1600309982,
|
||||
"name": "FVsWAGZTFGPLhWjLuBOd"
|
||||
}
|
||||
],
|
||||
"array2": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"name": "jvxrzduZnwwxpFYrQnpd"
|
||||
}
|
||||
],
|
||||
"array3": [
|
||||
[
|
||||
{
|
||||
"itemCount": 652571349
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"id": 7183997828
|
||||
},
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$.data.array3[0]": {
|
||||
"matchers": [
|
||||
{ "max": 5, "match": "type" }
|
||||
]
|
||||
},
|
||||
"$.data.array1": {
|
||||
"matchers": [
|
||||
{ "min": 0, "match": "type" }
|
||||
]
|
||||
},
|
||||
"$.data.array2": {
|
||||
"matchers": [
|
||||
{ "min": 1, "match": "type" }
|
||||
]
|
||||
},
|
||||
"$.id": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$.data.array2[*].name": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$.data.array2[*].address": {
|
||||
"matchers": [
|
||||
{ "regex": "(\\d{1,3}\\.)+\\d{1,3}" }
|
||||
]
|
||||
},
|
||||
"$.data.array1[*].name": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
},
|
||||
"$.data.array1[*].id": {
|
||||
"matchers": [
|
||||
{ "match": "type" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.2.11"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"consumer":{
|
||||
"name":"Consumer"
|
||||
},
|
||||
"provider":{
|
||||
"name":"Provider"
|
||||
},
|
||||
"messages":[
|
||||
{
|
||||
"description":"message sent to activemq:output",
|
||||
"metaData":{
|
||||
"BOOK-NAME":"foo",
|
||||
"contentType":"application/json"
|
||||
},
|
||||
"contents":{
|
||||
"bookName":"foo"
|
||||
},
|
||||
"providerStates":[
|
||||
{
|
||||
"name":"bookReturnedTriggered()"
|
||||
}
|
||||
],
|
||||
"matchingRules":{
|
||||
"body":{
|
||||
"$.bookName":{
|
||||
"matchers":[
|
||||
{
|
||||
"match":"type"
|
||||
}
|
||||
],
|
||||
"combine":"AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata":{
|
||||
"pact-specification":{
|
||||
"version":"3.0.0"
|
||||
},
|
||||
"pact-jvm":{
|
||||
"version":"3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "test_unsupported_rule_logic"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "test_unsupported_rule_logic"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "test unsupported rule logic",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"optionalField": 1234567890
|
||||
}
|
||||
],
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$[*].optionalField": {
|
||||
"matchers": [
|
||||
{ "match": "integer" },
|
||||
{ "match": "null" }
|
||||
],
|
||||
"combine": "OR"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.2.11"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,6 +35,7 @@ import static org.springframework.cloud.contract.verifier.util.ContentUtils.reco
|
||||
* Do not change to {@code @CompileStatic} since it's using double dispatch.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma, codearte.io
|
||||
* @author Tim Ysewyn
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@@ -85,7 +86,7 @@ abstract class MessagingMethodBodyBuilder extends MethodBodyBuilder {
|
||||
} else {
|
||||
bb.startBlock()
|
||||
}
|
||||
validateResponseBodyBlock(bb, outputMessage.matchers, outputMessage.body.serverValue)
|
||||
validateResponseBodyBlock(bb, outputMessage.bodyMatchers, outputMessage.body.serverValue)
|
||||
}
|
||||
if (outputMessage.assertThat) {
|
||||
bb.addLine(outputMessage.assertThat.executionCommand)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -57,6 +57,7 @@ import static org.springframework.cloud.contract.verifier.util.ContentUtils.extr
|
||||
* Do not change to {@code @CompileStatic} since it's using double dispatch.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma, codearte.io
|
||||
* @author Tim Ysewyn
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@@ -373,7 +374,9 @@ abstract class MethodBodyBuilder {
|
||||
bb.startBlock()
|
||||
// for the rest we'll do JsonPath matching in brute force
|
||||
bodyMatchers.jsonPathMatchers().each {
|
||||
if (MatchingType.regexRelated(it.matchingType()) || it.matchingType() == MatchingType.EQUALITY) {
|
||||
if (it.matchingType() == MatchingType.NULL) {
|
||||
methodForNullCheck(it, bb)
|
||||
} else if (MatchingType.regexRelated(it.matchingType()) || it.matchingType() == MatchingType.EQUALITY) {
|
||||
methodForEqualityCheck(it, bb, copiedBody)
|
||||
} else if (it.matchingType() == MatchingType.COMMAND) {
|
||||
methodForCommandExecution(it, bb, copiedBody)
|
||||
@@ -453,6 +456,13 @@ abstract class MethodBodyBuilder {
|
||||
addColonIfRequired(bb)
|
||||
}
|
||||
|
||||
protected void methodForNullCheck(BodyMatcher bodyMatcher, BlockBuilder bb) {
|
||||
String quotedAndEscaptedPath = quotedAndEscaped(bodyMatcher.path())
|
||||
String method = "assertThat(parsedJson.read(${quotedAndEscaptedPath})).isNull()"
|
||||
bb.addLine(postProcessJsonPathCall(method))
|
||||
addColonIfRequired(bb)
|
||||
}
|
||||
|
||||
protected void methodForTypeCheck(BodyMatcher bodyMatcher, BlockBuilder bb, Object copiedBody) {
|
||||
Object elementFromBody = value(copiedBody, bodyMatcher)
|
||||
if (bodyMatcher.minTypeOccurrence() != null || bodyMatcher.maxTypeOccurrence() != null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -43,6 +43,7 @@ import static org.springframework.cloud.contract.verifier.util.ContentUtils.reco
|
||||
* Do not change to {@code @CompileStatic} since it's using double dispatch.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma, codearte.io
|
||||
* @author Tim Ysewyn
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@@ -171,7 +172,7 @@ abstract class RequestProcessingMethodBodyBuilder extends MethodBodyBuilder {
|
||||
if (response.body) {
|
||||
bb.endBlock()
|
||||
bb.addLine(addCommentSignIfRequired('and:')).startBlock()
|
||||
validateResponseBodyBlock(bb, response.matchers, response.body.serverValue)
|
||||
validateResponseBodyBlock(bb, response.bodyMatchers, response.body.serverValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import groovy.transform.CompileStatic
|
||||
*
|
||||
* @since 1.2.1
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
@CompileStatic
|
||||
class YamlContract {
|
||||
@@ -130,12 +131,12 @@ class YamlContract {
|
||||
|
||||
@CompileStatic
|
||||
static enum StubMatcherType {
|
||||
by_date, by_time, by_timestamp, by_regex, by_equality
|
||||
by_date, by_time, by_timestamp, by_regex, by_equality, by_null
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static enum TestMatcherType {
|
||||
by_date, by_time, by_timestamp, by_regex, by_equality, by_type, by_command
|
||||
by_date, by_time, by_timestamp, by_regex, by_equality, by_type, by_command, by_null
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -53,6 +53,7 @@ import org.springframework.cloud.contract.verifier.util.MapConverter
|
||||
*
|
||||
* @since 1.2.1
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
@CompileStatic
|
||||
class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
@@ -167,7 +168,7 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
}
|
||||
multipart(multipartMap)
|
||||
}
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
yamlContract.request.matchers?.body?.each { BodyStubMatcher matcher ->
|
||||
MatchingTypeValue value = null
|
||||
switch (matcher.type) {
|
||||
@@ -214,7 +215,7 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
if (yamlContract.response.body) body(yamlContract.response.body)
|
||||
if (yamlContract.response.bodyFromFile) body(file(yamlContract.response.bodyFromFile))
|
||||
if (yamlContract.response.async) async()
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
yamlContract.response?.matchers?.body?.each { BodyTestMatcher testMatcher ->
|
||||
MatchingTypeValue value = null
|
||||
switch (testMatcher.type) {
|
||||
@@ -246,6 +247,9 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
case TestMatcherType.by_command:
|
||||
value = byCommand(testMatcher.value)
|
||||
break
|
||||
case TestMatcherType.by_null:
|
||||
value = byNull()
|
||||
break
|
||||
}
|
||||
jsonPath(testMatcher.path, value)
|
||||
}
|
||||
@@ -266,7 +270,7 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
}
|
||||
if (yamlContract.input.messageBody) messageBody(yamlContract.input.messageBody)
|
||||
if (yamlContract.input.messageBodyFromFile) messageBody(file(yamlContract.input.messageBodyFromFile))
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
yamlContract.input.matchers.body?.each { BodyStubMatcher matcher ->
|
||||
MatchingTypeValue value = null
|
||||
switch (matcher.type) {
|
||||
@@ -306,7 +310,7 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
if (outputMsg.body) body(outputMsg.body)
|
||||
if (outputMsg.bodyFromFile) body(file(outputMsg.bodyFromFile))
|
||||
if (outputMsg.matchers) {
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
yamlContract.outputMessage?.matchers?.body?.each { BodyTestMatcher testMatcher ->
|
||||
MatchingTypeValue value = null
|
||||
switch (testMatcher.type) {
|
||||
@@ -334,6 +338,9 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
case TestMatcherType.by_command:
|
||||
value = byCommand(testMatcher.value)
|
||||
break
|
||||
case TestMatcherType.by_null:
|
||||
value = byNull()
|
||||
break
|
||||
}
|
||||
jsonPath(testMatcher.path, value)
|
||||
}
|
||||
@@ -456,7 +463,7 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
headers = (contract?.request?.headers as Headers)?.asTestSideMap()
|
||||
body = MapConverter.getTestSideValues(contract?.request?.body)
|
||||
matchers = new StubMatchers()
|
||||
contract?.request?.matchers?.jsonPathMatchers()?.each { BodyMatcher matcher ->
|
||||
contract?.request?.bodyMatchers?.jsonPathMatchers()?.each { BodyMatcher matcher ->
|
||||
matchers.body << new BodyStubMatcher(
|
||||
path: matcher.path(),
|
||||
type: stubMatcherType(matcher.matchingType()),
|
||||
@@ -469,7 +476,7 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
status = contract?.response?.status?.clientValue as Integer
|
||||
headers = (contract?.response?.headers as Headers)?.asStubSideMap()
|
||||
body = MapConverter.getStubSideValues(contract?.response?.body)
|
||||
contract?.response?.matchers?.jsonPathMatchers()?.each { BodyMatcher matcher ->
|
||||
contract?.response?.bodyMatchers?.jsonPathMatchers()?.each { BodyMatcher matcher ->
|
||||
matchers.body << new BodyTestMatcher(
|
||||
path: matcher.path(),
|
||||
type: testMatcherType(matcher.matchingType()),
|
||||
@@ -488,7 +495,7 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
yamlContract.input.messageBody = MapConverter.getTestSideValues(contract?.input?.messageBody)
|
||||
yamlContract.input.messageFrom = contract?.input?.messageFrom?.serverValue
|
||||
yamlContract.input.matchers.body.each {
|
||||
contract?.input?.matchers?.jsonPathMatchers()?.each { BodyMatcher matcher ->
|
||||
contract?.input?.bodyMatchers?.jsonPathMatchers()?.each { BodyMatcher matcher ->
|
||||
yamlContract.input.matchers.body << new BodyStubMatcher(
|
||||
path: matcher.path(),
|
||||
type: stubMatcherType(matcher.matchingType()),
|
||||
@@ -502,7 +509,7 @@ class YamlContractConverter implements ContractConverter<List<YamlContract>> {
|
||||
yamlContract.outputMessage.headers = (contract?.outputMessage?.headers as Headers)?.asStubSideMap()
|
||||
yamlContract.outputMessage.body = MapConverter.getStubSideValues(contract?.outputMessage?.body)
|
||||
yamlContract.outputMessage.matchers.body.each {
|
||||
contract?.input?.matchers?.jsonPathMatchers()?.each { BodyMatcher matcher ->
|
||||
contract?.input?.bodyMatchers?.jsonPathMatchers()?.each { BodyMatcher matcher ->
|
||||
yamlContract.outputMessage.matchers.body << new BodyTestMatcher(
|
||||
path: matcher.path(),
|
||||
type: testMatcherType(matcher.matchingType()),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -52,6 +52,8 @@ import static org.springframework.cloud.contract.verifier.util.RegexpBuilders.bu
|
||||
/**
|
||||
* Converts a {@link Request} into {@link RequestPattern}
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@TypeChecked
|
||||
@@ -95,17 +97,17 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
}
|
||||
if (contentType == ContentType.JSON) {
|
||||
def originalBody = getMatchingStrategyFromBody(request.body)?.clientValue
|
||||
def body = JsonToJsonPathsConverter.removeMatchingJsonPaths(originalBody, request.matchers)
|
||||
def body = JsonToJsonPathsConverter.removeMatchingJsonPaths(originalBody, request.bodyMatchers)
|
||||
JsonPaths values = JsonToJsonPathsConverter.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(body)
|
||||
if ((values.empty && !request.matchers?.hasMatchers()) || onlySizeAssertionsArePresent(values)) {
|
||||
if ((values.empty && !request.bodyMatchers?.hasMatchers()) || onlySizeAssertionsArePresent(values)) {
|
||||
requestPattern.withRequestBody(WireMock.equalToJson(JsonOutput.toJson(getMatchingStrategy(request.body.clientValue).clientValue), false, false))
|
||||
} else {
|
||||
values.findAll{ !it.assertsSize() }.each {
|
||||
requestPattern.withRequestBody(WireMock.matchingJsonPath(it.jsonPath().replace("\\\\", "\\")))
|
||||
}
|
||||
}
|
||||
if (request.matchers?.hasMatchers()) {
|
||||
request.matchers.jsonPathMatchers().each {
|
||||
if (request.bodyMatchers?.hasMatchers()) {
|
||||
request.bodyMatchers.jsonPathMatchers().each {
|
||||
String newPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(it, originalBody)
|
||||
requestPattern.withRequestBody(WireMock.matchingJsonPath(newPath.replace("\\\\", "\\")))
|
||||
}
|
||||
@@ -122,7 +124,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
|
||||
}
|
||||
|
||||
private boolean onlySizeAssertionsArePresent(JsonPaths values) {
|
||||
return !values.empty && !request.matchers?.hasMatchers() && values.every { it.assertsSize() }
|
||||
return !values.empty && !request.bodyMatchers?.hasMatchers() && values.every { it.assertsSize() }
|
||||
}
|
||||
|
||||
private void appendMultipart(RequestPatternBuilder requestPattern) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class MessagingMethodBodyBuilderSpec extends Specification {
|
||||
|
||||
@@ -544,7 +545,9 @@ Contract.make {
|
||||
body([
|
||||
alpha: $(anyAlphaUnicode()),
|
||||
number: $(anyNumber()),
|
||||
anInteger: $(anyInteger()),
|
||||
positiveInt: $(anyPositiveInt()),
|
||||
aDouble: $(anyDouble()),
|
||||
aBoolean: $(aBoolean()),
|
||||
ip: $(anyIpAddress()),
|
||||
hostname: $(anyHostname()),
|
||||
@@ -571,12 +574,14 @@ Contract.make {
|
||||
test.contains('assertThatJson(parsedJson).field("[\'aBoolean\']").matches("(true|false)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'alpha\']").matches("[\\\\p{L}]*")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'hostname\']").matches("((http[s]?|ftp):/)/?([^:/\\\\s]+)(:[0-9]{1,5})?")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'url\']").matches("^(?:(?:[A-Za-z][+-.\\\\w^_]*:/{2})?(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'httpsUrl\']").matches("^(?:https:/{2}(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'number\']").matches("-?(\\\\d*\\\\.\\\\d+|\\\\d+)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'anInteger\']").matches("-?(\\\\d+)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'positiveInt\']").matches("([1-9]\\\\d*)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'aDouble\']").matches("-?(\\\\d*\\\\.\\\\d+)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'email\']").matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,6}")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'ip\']").matches("([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'url\']").matches("^(?:(?:[A-Za-z][+-.\\\\w^_]*:/{2})?(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'httpsUrl\']").matches("^(?:https:/{2}(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'uuid\']").matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'date\']").matches("(\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'dateTime\']").matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])')
|
||||
@@ -589,7 +594,7 @@ Contract.make {
|
||||
!test.contains('REGEXP>>')
|
||||
and:
|
||||
String jsonSample = '''\
|
||||
String json = "{\\"shouldFail\\":123,\\"duck\\":\\"8\\",\\"allpha\\":\\"YAJEOWYGMFBEWPMEMAZI\\",\\"number\\":-2095030871,\\"positiveInt\\":345,\\"aBoolean\\":true,\\"ip\\":\\"129.168.99.100\\",\\"hostname\\":\\"http://foo389886219.com\\",\\"email\\":\\"foo@bar1367573183.com\\",\\"url\\":\\"http://foo-597104692.com\\",\\"httpsUrl\\":\\"https://baz-486093581.com\\",\\"uuid\\":\\"e436b817-b764-49a2-908e-967f2f99eb9f\\",\\"date\\":\\"2014-04-14\\",\\"dateTime\\":\\"2011-01-11T12:23:34\\",\\"time\\":\\"12:20:30\\",\\"iso8601WithOffset\\":\\"2015-05-15T12:23:34.123Z\\",\\"nonBlankString\\":\\"EPZWVIRHSUAPBJMMQSFO\\",\\"nonEmptyString\\":\\"RVMFDSEQFHRQFVUVQPIA\\",\\"anyOf\\":\\"foo\\"}";
|
||||
String json = "{\\"shouldFail\\":123,\\"duck\\":\\"8\\",\\"alpha\\":\\"YAJEOWYGMFBEWPMEMAZI\\",\\"number\\":-2095030871,\\"anInteger\\":1780305902,\\"positiveInt\\":345,\\"aDouble\\":42.345,\\"aBoolean\\":true,\\"ip\\":\\"129.168.99.100\\",\\"hostname\\":\\"http://foo389886219.com\\",\\"email\\":\\"foo@bar1367573183.com\\",\\"url\\":\\"http://foo-597104692.com\\",\\"httpsUrl\\":\\"https://baz-486093581.com\\",\\"uuid\\":\\"e436b817-b764-49a2-908e-967f2f99eb9f\\",\\"date\\":\\"2014-04-14\\",\\"dateTime\\":\\"2011-01-11T12:23:34\\",\\"time\\":\\"12:20:30\\",\\"iso8601WithOffset\\":\\"2015-05-15T12:23:34.123Z\\",\\"nonBlankString\\":\\"EPZWVIRHSUAPBJMMQSFO\\",\\"nonEmptyString\\":\\"RVMFDSEQFHRQFVUVQPIA\\",\\"anyOf\\":\\"foo\\"}";
|
||||
DocumentContext parsedJson = JsonPath.parse(json);
|
||||
'''
|
||||
and:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -684,4 +684,36 @@ DocumentContext parsedJson = JsonPath.parse(json);
|
||||
"MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
|
||||
}
|
||||
|
||||
def "should assert null values without matchers [#methodBuilderName]"() {
|
||||
given:
|
||||
Contract contractDsl = Contract.make {
|
||||
request {
|
||||
method GET()
|
||||
url "test"
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body([
|
||||
nullValue: null
|
||||
])
|
||||
}
|
||||
}
|
||||
MethodBodyBuilder builder = methodBuilder(contractDsl)
|
||||
BlockBuilder blockBuilder = new BlockBuilder(" ")
|
||||
when:
|
||||
builder.appendTo(blockBuilder)
|
||||
then:
|
||||
blockBuilder.toString().contains("""assertThatJson(parsedJson).field("['nullValue']").isNull()""")
|
||||
and:
|
||||
SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, blockBuilder.toString())
|
||||
and:
|
||||
stubMappingIsValidWireMockStub(contractDsl)
|
||||
where:
|
||||
methodBuilderName | methodBuilder
|
||||
"MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
|
||||
"MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) }
|
||||
"JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) }
|
||||
"JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,6 +32,7 @@ import org.springframework.cloud.contract.verifier.dsl.WireMockStubVerifier
|
||||
import org.springframework.cloud.contract.verifier.util.SyntaxChecker
|
||||
/**
|
||||
* @author Jakub Kubrynski, codearte.io
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStubVerifier {
|
||||
|
||||
@@ -2073,6 +2074,7 @@ World.'''"""
|
||||
duck: $(regex("[0-9]")),
|
||||
alpha: $(anyAlphaUnicode()),
|
||||
number: $(anyNumber()),
|
||||
anInteger: $(anyInteger()),
|
||||
positiveInt: $(positiveInt()),
|
||||
aDouble: $(anyDouble()),
|
||||
aBoolean: $(aBoolean()),
|
||||
@@ -2099,6 +2101,7 @@ World.'''"""
|
||||
body([
|
||||
alpha: $(anyAlphaUnicode()),
|
||||
number: $(anyNumber()),
|
||||
anInteger: $(anyInteger()),
|
||||
positiveInt: $(positiveInt()),
|
||||
aDouble: $(anyDouble()),
|
||||
aBoolean: $(aBoolean()),
|
||||
@@ -2130,13 +2133,14 @@ World.'''"""
|
||||
test.contains('assertThatJson(parsedJson).field("[\'aBoolean\']").matches("(true|false)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'alpha\']").matches("[\\\\p{L}]*")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'hostname\']").matches("((http[s]?|ftp):/)/?([^:/\\\\s]+)(:[0-9]{1,5})?")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'url\']").matches("^(?:(?:[A-Za-z][+-.\\\\w^_]*:/{2})?(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'httpsUrl\']").matches("^(?:https:/{2}(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'number\']").matches("-?(\\\\d*\\\\.\\\\d+|\\\\d+)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'anInteger\']").matches("-?(\\\\d+)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'positiveInt\']").matches("([1-9]\\\\d*)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'aDouble\']").matches("-?(\\\\d*\\\\.\\\\d+)")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'email\']").matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,6}")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'ip\']").matches("([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'url\']").matches("^(?:(?:[A-Za-z][+-.\\\\w^_]*:/{2})?(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'httpsUrl\']").matches("^(?:https:/{2}(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))(?::\\\\d{2,5})?(?:/\\\\S*)?)')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'uuid\']").matches("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}")')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'date\']").matches("(\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])')
|
||||
test.contains('assertThatJson(parsedJson).field("[\'dateTime\']").matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])')
|
||||
@@ -2151,7 +2155,7 @@ World.'''"""
|
||||
SyntaxChecker.tryToCompile(methodBuilderName, blockBuilder.toString())
|
||||
and:
|
||||
String jsonSample = '''\
|
||||
String json = "{\\"duck\\":\\"8\\",\\"alpha\\":\\"YAJEOWYGMFBEWPMEMAZI\\",\\"number\\":-2095030871,\\"positiveInt\\":41,\\"aDouble\\":42.345,\\"aBoolean\\":true,\\"ip\\":\\"129.168.99.100\\",\\"hostname\\":\\"http://foo389886219.com\\",\\"email\\":\\"foo@bar1367573183.com\\",\\"url\\":\\"http://foo-597104692.com\\",\\"httpsUrl\\":\\"https://baz-486093581.com\\",\\"uuid\\":\\"e436b817-b764-49a2-908e-967f2f99eb9f\\",\\"date\\":\\"2014-04-14\\",\\"dateTime\\":\\"2011-01-11T12:23:34\\",\\"time\\":\\"12:20:30\\",\\"iso8601WithOffset\\":\\"2015-05-15T12:23:34.123Z\\",\\"nonBlankString\\":\\"EPZWVIRHSUAPBJMMQSFO\\",\\"nonEmptyString\\":\\"RVMFDSEQFHRQFVUVQPIA\\",\\"anyOf\\":\\"foo\\"}";
|
||||
String json = "{\\"duck\\":\\"8\\",\\"alpha\\":\\"YAJEOWYGMFBEWPMEMAZI\\",\\"number\\":-2095030871,\\"anInteger\\":1780305902,\\"positiveInt\\":345,\\"aDouble\\":42.345,\\"aBoolean\\":true,\\"ip\\":\\"129.168.99.100\\",\\"hostname\\":\\"http://foo389886219.com\\",\\"email\\":\\"foo@bar1367573183.com\\",\\"url\\":\\"http://foo-597104692.com\\",\\"httpsUrl\\":\\"https://baz-486093581.com\\",\\"uuid\\":\\"e436b817-b764-49a2-908e-967f2f99eb9f\\",\\"date\\":\\"2014-04-14\\",\\"dateTime\\":\\"2011-01-11T12:23:34\\",\\"time\\":\\"12:20:30\\",\\"iso8601WithOffset\\":\\"2015-05-15T12:23:34.123Z\\",\\"nonBlankString\\":\\"EPZWVIRHSUAPBJMMQSFO\\",\\"nonEmptyString\\":\\"RVMFDSEQFHRQFVUVQPIA\\",\\"anyOf\\":\\"foo\\"}";
|
||||
DocumentContext parsedJson = JsonPath.parse(json);
|
||||
'''
|
||||
and:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -56,7 +56,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
'complex.key' : 'foo'
|
||||
]
|
||||
])
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.duck', byRegex("[0-9]{3}"))
|
||||
jsonPath('$.duck', byEquality())
|
||||
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
|
||||
@@ -78,6 +78,10 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
duck: 123,
|
||||
alpha: "abc",
|
||||
number: 123,
|
||||
positiveInteger: 1234567890,
|
||||
negativeInteger: -1234567890,
|
||||
positiveDecimalNumber: 123.4567890,
|
||||
negativeDecimalNumber: -123.4567890,
|
||||
aBoolean: true,
|
||||
date: "2017-01-01",
|
||||
dateTime: "2017-01-01T01:23:45",
|
||||
@@ -97,9 +101,10 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
valueWithMaxEmpty: [],
|
||||
key: [
|
||||
'complex.key' : 'foo'
|
||||
]
|
||||
],
|
||||
nullValue: null
|
||||
])
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
// asserts the jsonpath value against manual regex
|
||||
jsonPath('$.duck', byRegex("[0-9]{3}"))
|
||||
// asserts the jsonpath value against the provided value
|
||||
@@ -108,6 +113,10 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
|
||||
jsonPath('$.alpha', byEquality())
|
||||
jsonPath('$.number', byRegex(number()))
|
||||
jsonPath('$.positiveInteger', byRegex(anInteger()))
|
||||
jsonPath('$.negativeInteger', byRegex(anInteger()))
|
||||
jsonPath('$.positiveDecimalNumber', byRegex(aDouble()))
|
||||
jsonPath('$.negativeDecimalNumber', byRegex(aDouble()))
|
||||
jsonPath('$.aBoolean', byRegex(anyBoolean()))
|
||||
// asserts vs inbuilt time related regex
|
||||
jsonPath('$.date', byDate())
|
||||
@@ -139,9 +148,11 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
// will execute a method `assertThatValueIsANumber`
|
||||
jsonPath('$.duck', byCommand('assertThatValueIsANumber($it)'))
|
||||
jsonPath("\$.['key'].['complex.key']", byEquality())
|
||||
jsonPath('$.nullValue', byNull())
|
||||
}
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
header('Some-Header', $(c('someValue'), p(regex('[a-zA-Z]{9}'))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,6 +168,10 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.alpha", String.class)).matches("[\\\\p{L}]*")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.alpha", String.class)).isEqualTo("abc")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.number", String.class)).matches("-?(\\\\d*\\\\.\\\\d+|\\\\d+)")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.positiveInteger", String.class)).matches("-?(\\\\d+)")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.negativeInteger", String.class)).matches("-?(\\\\d+)")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.positiveDecimalNumber", String.class)).matches("-?(\\\\d*\\\\.\\\\d+)")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.negativeDecimalNumber", String.class)).matches("-?(\\\\d*\\\\.\\\\d+)")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.aBoolean", String.class)).matches("(true|false)")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.date", String.class)).matches("(\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])")')
|
||||
@@ -174,6 +189,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
test.contains('assertThat((java.lang.Iterable) parsedJson.read("' + rootElement + '.valueWithMaxEmpty", java.util.Collection.class)).as("' + rootElement + '.valueWithMaxEmpty").hasSizeLessThanOrEqualTo(0)')
|
||||
test.contains('assertThatValueIsANumber(parsedJson.read("' + rootElement + '.duck")')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '''.['key'].['complex.key']", String.class)).isEqualTo("foo")''')
|
||||
test.contains('assertThat(parsedJson.read("' + rootElement + '.nullValue")).isNull()')
|
||||
!test.contains('cursor')
|
||||
and:
|
||||
try {
|
||||
@@ -224,7 +240,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
]
|
||||
],
|
||||
])
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.phoneNumbers', byType {
|
||||
minOccurrence(0) // min occurrence of 1
|
||||
maxOccurrence(4) // max occurrence of 3
|
||||
@@ -276,7 +292,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
number: "foo"
|
||||
]
|
||||
])
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.phoneNumbers[*].number', byType {
|
||||
minOccurrence(0)
|
||||
maxOccurrence(4)
|
||||
@@ -329,7 +345,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
number: "foo"
|
||||
]
|
||||
])
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.phoneNumbers[*].number', byCommand('foo($it)'))
|
||||
}
|
||||
}
|
||||
@@ -364,7 +380,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
number: "foo"
|
||||
]
|
||||
])
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.nonExistingPhoneNumbers[*].number', byCommand('foo($it)'))
|
||||
}
|
||||
}
|
||||
@@ -401,7 +417,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('''$[0][0].access_token''', byEquality())
|
||||
}
|
||||
}
|
||||
@@ -445,7 +461,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
]
|
||||
}
|
||||
""")
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.items[*].id', byRegex(nonBlank()))
|
||||
jsonPath('$.items[*].title', byRegex(nonBlank()))
|
||||
jsonPath('$.items[*]', byType { minOccurrence(2); maxOccurrence(2) })
|
||||
@@ -488,7 +504,7 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
"title": "lorem ipsum"
|
||||
]
|
||||
])
|
||||
testMatchers {
|
||||
bodyMatchers {
|
||||
jsonPath('$.items[*].id', byRegex(nonBlank()))
|
||||
jsonPath('$.items[*].title', byRegex(nonBlank()))
|
||||
jsonPath('$.items[*]', byType { minOccurrence(2); maxOccurrence(2) })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ import org.springframework.cloud.contract.verifier.util.MapConverter
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class YamlContractConverterSpec extends Specification {
|
||||
|
||||
@@ -81,9 +82,9 @@ class YamlContractConverterSpec extends Specification {
|
||||
contract.request.headers.entries.find { it.name == "fooReq" &&
|
||||
it.serverValue == "baz" }
|
||||
contract.request.body.clientValue == [foo: "bar"]
|
||||
contract.request.matchers.jsonPathRegexMatchers[0].path() == '$.foo'
|
||||
contract.request.matchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.request.matchers.jsonPathRegexMatchers[0].value() == 'bar'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.foo'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar'
|
||||
and:
|
||||
contract.response.status.clientValue == 200
|
||||
contract.response.headers.entries.find { it.name == "foo2" &&
|
||||
@@ -92,13 +93,16 @@ class YamlContractConverterSpec extends Specification {
|
||||
((ExecutionProperty) it.serverValue).insertValue('foo') == "andMeToo(foo)" }
|
||||
contract.response.headers.entries.find { it.name == "fooRes" &&
|
||||
it.clientValue == "baz" }
|
||||
contract.response.body.clientValue == [foo2: "bar", foo3: "baz"]
|
||||
contract.response.matchers.jsonPathRegexMatchers[0].path() == '$.foo2'
|
||||
contract.response.matchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.response.matchers.jsonPathRegexMatchers[0].value() == 'bar'
|
||||
contract.response.matchers.jsonPathRegexMatchers[1].path() == '$.foo3'
|
||||
contract.response.matchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.COMMAND
|
||||
contract.response.matchers.jsonPathRegexMatchers[1].value() == new ExecutionProperty('executeMe($it)')
|
||||
contract.response.body.clientValue == [foo2: "bar", foo3: "baz", nullValue: null]
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.foo2'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.foo3'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.COMMAND
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[1].value() == new ExecutionProperty('executeMe($it)')
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.nullValue'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.NULL
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[2].value() == null
|
||||
where:
|
||||
yamlFile << [ymlWithRest, ymlWithRest2, ymlWithRest3]
|
||||
}
|
||||
@@ -151,81 +155,81 @@ class YamlContractConverterSpec extends Specification {
|
||||
RegexPatterns patterns = new RegexPatterns()
|
||||
contract.request.headers.entries.find { it.name == "Content-Type" &&
|
||||
((Pattern) it.clientValue).pattern == "application/json.*" && it.serverValue == "application/json" }
|
||||
contract.request.matchers.jsonPathRegexMatchers[0].path() == '$.duck'
|
||||
contract.request.matchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.request.matchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}'
|
||||
contract.request.matchers.jsonPathRegexMatchers[1].path() == '$.duck'
|
||||
contract.request.matchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY
|
||||
contract.request.matchers.jsonPathRegexMatchers[2].path() == '$.alpha'
|
||||
contract.request.matchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX
|
||||
contract.request.matchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern()
|
||||
contract.request.matchers.jsonPathRegexMatchers[3].path() == '$.alpha'
|
||||
contract.request.matchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY
|
||||
contract.request.matchers.jsonPathRegexMatchers[4].path() == '$.number'
|
||||
contract.request.matchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX
|
||||
contract.request.matchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern()
|
||||
contract.request.matchers.jsonPathRegexMatchers[5].path() == '$.aBoolean'
|
||||
contract.request.matchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX
|
||||
contract.request.matchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern()
|
||||
contract.request.matchers.jsonPathRegexMatchers[6].path() == '$.date'
|
||||
contract.request.matchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE
|
||||
contract.request.matchers.jsonPathRegexMatchers[6].value() == patterns.isoDate()
|
||||
contract.request.matchers.jsonPathRegexMatchers[7].path() == '$.dateTime'
|
||||
contract.request.matchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP
|
||||
contract.request.matchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime()
|
||||
contract.request.matchers.jsonPathRegexMatchers[8].path() == '$.time'
|
||||
contract.request.matchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME
|
||||
contract.request.matchers.jsonPathRegexMatchers[8].value() == patterns.isoTime()
|
||||
contract.request.matchers.jsonPathRegexMatchers[9].path() == "\$.['key'].['complex.key']"
|
||||
contract.request.matchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.EQUALITY
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern()
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern()
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern()
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate()
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime()
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time'
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime()
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[9].path() == "\$.['key'].['complex.key']"
|
||||
contract.request.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.EQUALITY
|
||||
and:
|
||||
contract.response.status.clientValue == 200
|
||||
contract.response.matchers.jsonPathRegexMatchers[0].path() == '$.duck'
|
||||
contract.response.matchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.response.matchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}'
|
||||
contract.response.matchers.jsonPathRegexMatchers[1].path() == '$.duck'
|
||||
contract.response.matchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY
|
||||
contract.response.matchers.jsonPathRegexMatchers[2].path() == '$.alpha'
|
||||
contract.response.matchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX
|
||||
contract.response.matchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern()
|
||||
contract.response.matchers.jsonPathRegexMatchers[3].path() == '$.alpha'
|
||||
contract.response.matchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY
|
||||
contract.response.matchers.jsonPathRegexMatchers[4].path() == '$.number'
|
||||
contract.response.matchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX
|
||||
contract.response.matchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern()
|
||||
contract.response.matchers.jsonPathRegexMatchers[5].path() == '$.aBoolean'
|
||||
contract.response.matchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX
|
||||
contract.response.matchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern()
|
||||
contract.response.matchers.jsonPathRegexMatchers[6].path() == '$.date'
|
||||
contract.response.matchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE
|
||||
contract.response.matchers.jsonPathRegexMatchers[6].value() == patterns.isoDate()
|
||||
contract.response.matchers.jsonPathRegexMatchers[7].path() == '$.dateTime'
|
||||
contract.response.matchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP
|
||||
contract.response.matchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime()
|
||||
contract.response.matchers.jsonPathRegexMatchers[8].path() == '$.time'
|
||||
contract.response.matchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME
|
||||
contract.response.matchers.jsonPathRegexMatchers[8].value() == patterns.isoTime()
|
||||
contract.response.matchers.jsonPathRegexMatchers[9].path() == '$.valueWithTypeMatch'
|
||||
contract.response.matchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.TYPE
|
||||
contract.response.matchers.jsonPathRegexMatchers[10].path() == '$.valueWithMin'
|
||||
contract.response.matchers.jsonPathRegexMatchers[10].matchingType() == MatchingType.TYPE
|
||||
contract.response.matchers.jsonPathRegexMatchers[10].minTypeOccurrence() == 1
|
||||
contract.response.matchers.jsonPathRegexMatchers[11].path() == '$.valueWithMax'
|
||||
contract.response.matchers.jsonPathRegexMatchers[11].matchingType() == MatchingType.TYPE
|
||||
contract.response.matchers.jsonPathRegexMatchers[11].maxTypeOccurrence() == 3
|
||||
contract.response.matchers.jsonPathRegexMatchers[12].path() == '$.valueWithMinMax'
|
||||
contract.response.matchers.jsonPathRegexMatchers[12].matchingType() == MatchingType.TYPE
|
||||
contract.response.matchers.jsonPathRegexMatchers[12].minTypeOccurrence() == 1
|
||||
contract.response.matchers.jsonPathRegexMatchers[12].maxTypeOccurrence() == 3
|
||||
contract.response.matchers.jsonPathRegexMatchers[13].path() == '$.valueWithMinEmpty'
|
||||
contract.response.matchers.jsonPathRegexMatchers[13].matchingType() == MatchingType.TYPE
|
||||
contract.response.matchers.jsonPathRegexMatchers[13].minTypeOccurrence() == 0
|
||||
contract.response.matchers.jsonPathRegexMatchers[14].path() == '$.valueWithMaxEmpty'
|
||||
contract.response.matchers.jsonPathRegexMatchers[14].matchingType() == MatchingType.TYPE
|
||||
contract.response.matchers.jsonPathRegexMatchers[14].maxTypeOccurrence() == 0
|
||||
contract.response.matchers.jsonPathRegexMatchers[15].path() == '$.duck'
|
||||
contract.response.matchers.jsonPathRegexMatchers[15].matchingType() == MatchingType.COMMAND
|
||||
contract.response.matchers.jsonPathRegexMatchers[15].value() == new ExecutionProperty('assertThatValueIsANumber($it)')
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.duck'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[0].value() == '[0-9]{3}'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.duck'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.EQUALITY
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[2].path() == '$.alpha'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[2].matchingType() == MatchingType.REGEX
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[2].value() == patterns.onlyAlphaUnicode().pattern()
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[3].path() == '$.alpha'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[3].matchingType() == MatchingType.EQUALITY
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[4].path() == '$.number'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[4].matchingType() == MatchingType.REGEX
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[4].value() == patterns.number().pattern()
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[5].path() == '$.aBoolean'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[5].matchingType() == MatchingType.REGEX
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[5].value() == patterns.anyBoolean().pattern()
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[6].path() == '$.date'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[6].matchingType() == MatchingType.DATE
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[6].value() == patterns.isoDate()
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[7].path() == '$.dateTime'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[7].matchingType() == MatchingType.TIMESTAMP
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[7].value() == patterns.isoDateTime()
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[8].path() == '$.time'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[8].matchingType() == MatchingType.TIME
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[8].value() == patterns.isoTime()
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[9].path() == '$.valueWithTypeMatch'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[9].matchingType() == MatchingType.TYPE
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[10].path() == '$.valueWithMin'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[10].matchingType() == MatchingType.TYPE
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[10].minTypeOccurrence() == 1
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[11].path() == '$.valueWithMax'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[11].matchingType() == MatchingType.TYPE
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[11].maxTypeOccurrence() == 3
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[12].path() == '$.valueWithMinMax'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[12].matchingType() == MatchingType.TYPE
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[12].minTypeOccurrence() == 1
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[12].maxTypeOccurrence() == 3
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[13].path() == '$.valueWithMinEmpty'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[13].matchingType() == MatchingType.TYPE
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[13].minTypeOccurrence() == 0
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[14].path() == '$.valueWithMaxEmpty'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[14].matchingType() == MatchingType.TYPE
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[14].maxTypeOccurrence() == 0
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[15].path() == '$.duck'
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[15].matchingType() == MatchingType.COMMAND
|
||||
contract.response.bodyMatchers.jsonPathRegexMatchers[15].value() == new ExecutionProperty('assertThatValueIsANumber($it)')
|
||||
}
|
||||
|
||||
def "should convert YAML with REST with response from request"() {
|
||||
@@ -285,9 +289,9 @@ class YamlContractConverterSpec extends Specification {
|
||||
contract.input.messageHeaders.entries.find { it.name == "foo" &&
|
||||
((Pattern) it.clientValue).pattern == "bar" && it.serverValue == "bar" }
|
||||
contract.input.messageBody.clientValue == [foo: "bar"]
|
||||
contract.input.matchers.jsonPathRegexMatchers[0].path() == '$.bar'
|
||||
contract.input.matchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.input.matchers.jsonPathRegexMatchers[0].value() == 'bar'
|
||||
contract.input.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.bar'
|
||||
contract.input.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.input.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar'
|
||||
and:
|
||||
contract.outputMessage.assertThat.toString() == "baz()"
|
||||
contract.outputMessage.headers.entries.find { it.name == "foo2" &&
|
||||
@@ -297,12 +301,12 @@ class YamlContractConverterSpec extends Specification {
|
||||
contract.outputMessage.headers.entries.find { it.name == "fooRes" &&
|
||||
it.clientValue == "baz" }
|
||||
contract.outputMessage.body.clientValue == [foo2: "bar", foo3: "baz"]
|
||||
contract.outputMessage.matchers.jsonPathRegexMatchers[0].path() == '$.foo2'
|
||||
contract.outputMessage.matchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.outputMessage.matchers.jsonPathRegexMatchers[0].value() == 'bar'
|
||||
contract.outputMessage.matchers.jsonPathRegexMatchers[1].path() == '$.foo3'
|
||||
contract.outputMessage.matchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.COMMAND
|
||||
contract.outputMessage.matchers.jsonPathRegexMatchers[1].value() == new ExecutionProperty('executeMe($it)')
|
||||
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].path() == '$.foo2'
|
||||
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].matchingType() == MatchingType.REGEX
|
||||
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[0].value() == 'bar'
|
||||
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].path() == '$.foo3'
|
||||
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].matchingType() == MatchingType.COMMAND
|
||||
contract.outputMessage.bodyMatchers.jsonPathRegexMatchers[1].value() == new ExecutionProperty('executeMe($it)')
|
||||
}
|
||||
|
||||
def "should convert YAML with messaging triggered by a method to DSL"() {
|
||||
|
||||
@@ -54,6 +54,7 @@ response:
|
||||
body:
|
||||
foo2: bar
|
||||
foo3: baz
|
||||
nullValue: null
|
||||
matchers:
|
||||
body:
|
||||
- path: $.foo2
|
||||
@@ -62,6 +63,9 @@ response:
|
||||
- path: $.foo3
|
||||
type: by_command
|
||||
value: executeMe($it)
|
||||
- path: $.nullValue
|
||||
type: by_null
|
||||
value: null
|
||||
headers:
|
||||
- key: foo2
|
||||
regex: bar
|
||||
|
||||
@@ -13,6 +13,7 @@ request:
|
||||
valueWithTypeMatch: "string"
|
||||
key:
|
||||
"complex.key": 'foo'
|
||||
nullValue: null
|
||||
matchers:
|
||||
headers:
|
||||
- key: Content-Type
|
||||
@@ -42,6 +43,8 @@ request:
|
||||
type: by_time
|
||||
- path: "$.['key'].['complex.key']"
|
||||
type: by_equality
|
||||
- path: $.nullvalue
|
||||
type: by_null
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
response:
|
||||
@@ -72,6 +75,7 @@ response:
|
||||
valueWithMaxEmpty: []
|
||||
key:
|
||||
'complex.key' : 'foo'
|
||||
nulValue: null
|
||||
matchers:
|
||||
headers:
|
||||
- key: Content-Type
|
||||
@@ -120,5 +124,8 @@ response:
|
||||
- path: $.duck
|
||||
type: by_command
|
||||
value: assertThatValueIsANumber($it)
|
||||
- path: $.nullValue
|
||||
type: by_null
|
||||
value: null
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
@@ -30,6 +30,7 @@ response:
|
||||
body:
|
||||
foo2: bar
|
||||
foo3: baz
|
||||
nullValue: null
|
||||
matchers:
|
||||
body:
|
||||
- path: $.foo2
|
||||
@@ -38,6 +39,9 @@ response:
|
||||
- path: $.foo3
|
||||
type: by_command
|
||||
value: executeMe($it)
|
||||
- path: $.nullValue
|
||||
type: by_null
|
||||
value: null
|
||||
headers:
|
||||
- key: foo2
|
||||
regex: bar
|
||||
|
||||
@@ -32,6 +32,7 @@ response:
|
||||
body:
|
||||
foo2: bar
|
||||
foo3: baz
|
||||
nullValue: null
|
||||
matchers:
|
||||
body:
|
||||
- path: $.foo2
|
||||
@@ -40,6 +41,9 @@ response:
|
||||
- path: $.foo3
|
||||
type: by_command
|
||||
value: executeMe($it)
|
||||
- path: $.nullValue
|
||||
type: by_null
|
||||
value: null
|
||||
headers:
|
||||
- key: foo2
|
||||
regex: bar
|
||||
|
||||
@@ -17,11 +17,11 @@ Contract.make {
|
||||
}
|
||||
{{/request_headers_present}}
|
||||
{{#request_json_paths_present}}
|
||||
stubMatchers {
|
||||
bodyMatchers {
|
||||
{{#request_json_paths}}
|
||||
jsonPath('''{{jsonPath}}''', byEquality())
|
||||
{{/request_json_paths}}
|
||||
}
|
||||
}
|
||||
{{/request_json_paths_present}}
|
||||
}
|
||||
response {
|
||||
|
||||
@@ -91,7 +91,7 @@ public class ContractDslSnippetTests {
|
||||
then(parsedContract.getRequest().getUrl().getClientValue()).isNotNull();
|
||||
then(parsedContract.getRequest().getUrl().getClientValue().toString()).startsWith("/");
|
||||
then(parsedContract.getRequest().getBody().getClientValue()).isNotNull();
|
||||
then(parsedContract.getRequest().getMatchers().hasMatchers()).isTrue();
|
||||
then(parsedContract.getRequest().getBodyMatchers().hasMatchers()).isTrue();
|
||||
then(parsedContract.getResponse().getStatus().getClientValue()).isNotNull();
|
||||
then(parsedContract.getResponse().getHeaders().getEntries()).isNotEmpty();
|
||||
then(parsedContract.getResponse().getBody().getClientValue()).isNotNull();
|
||||
@@ -128,7 +128,7 @@ public class ContractDslSnippetTests {
|
||||
then(parsedContract.getRequest().getUrl().getClientValue()).isNotNull();
|
||||
then(parsedContract.getRequest().getUrl().getClientValue().toString()).startsWith("/");
|
||||
then(parsedContract.getRequest().getBody().getClientValue()).isNotNull();
|
||||
then(parsedContract.getRequest().getMatchers().hasMatchers()).isTrue();
|
||||
then(parsedContract.getRequest().getBodyMatchers().hasMatchers()).isTrue();
|
||||
then(parsedContract.getResponse().getStatus().getClientValue()).isNotNull();
|
||||
then(parsedContract.getResponse().getHeaders().getEntries()).isNotEmpty();
|
||||
then(parsedContract.getResponse().getBody().getClientValue()).isNotNull();
|
||||
@@ -152,7 +152,7 @@ public class ContractDslSnippetTests {
|
||||
then(parsedContract.getResponse().getStatus().getClientValue()).isNotNull();
|
||||
then(parsedContract.getResponse().getHeaders()).isNull();
|
||||
then(parsedContract.getResponse().getBody()).isNull();
|
||||
then(parsedContract.getResponse().getMatchers()).isNull();
|
||||
then(parsedContract.getResponse().getBodyMatchers()).isNull();
|
||||
}
|
||||
|
||||
private Set<String> headerNames(Set<Header> headers) {
|
||||
|
||||
Reference in New Issue
Block a user