Removing the Pact integration; fixes gh-1845
This commit is contained in:
@@ -4,8 +4,6 @@ IMPORTANT: The following properties can be passed as a system property (for exam
|
||||
|===
|
||||
|Name | Default | Description
|
||||
|
||||
|`stubrunner.properties.pactbroker.provider-name-with-group-id` | `false` | When using the Pact Broker-based approach, you can automatically group ID to the provider name.
|
||||
|
||||
|`stubrunner.properties.git.branch` | | When using the SCM-based approach, you can customize the branch name to check out.
|
||||
|`stubrunner.properties.git.commit-message` | Updating project [$project] with stubs | When using the SCM based approach, you can customize the commit message for created stubs. The `$project` text is replaced with the project name.
|
||||
|`stubrunner.properties.git.no-of-attempts` | `10` | When using the-SCM based approach, you can customize the number of retries to push the stubs to Git.
|
||||
|
||||
@@ -36,4 +36,3 @@
|
||||
:doc_samples_url: {samples_url}/wiremock-for-contract-docs
|
||||
:wiremock_tests: {core_path}/spring-cloud-contract-wiremock
|
||||
:introduction_url: {core_path}
|
||||
:standalone_pact_path: {samples_url}/
|
||||
|
||||
@@ -14,5 +14,5 @@ The reference documentation consists of the following sections:
|
||||
<<using.adoc#using,Using {project-full-name}>> :: {project-full-name} usage examples and workflows.
|
||||
<<project-features.adoc#features,{project-full-name} Features>> :: Contract DSL, Messaging, Spring Cloud Contract Stub Runner, and Spring Cloud Contract WireMock.
|
||||
<<project-features.adoc#features-build-tools,Build Tools>> :: Maven Plugin, Gradle Plugin, and Docker.
|
||||
<<howto.adoc#howto,"`How-to`" Guides>> :: Stubs versioning, Pact integration, Debugging, and more.
|
||||
<<howto.adoc#howto,"`How-to`" Guides>> :: Stubs versioning, Debugging, and more.
|
||||
<<appendix.adoc#appendix,Appendices>> :: Properties, Metadata, Configuration, Dependencies, and more.
|
||||
|
||||
@@ -96,7 +96,6 @@ Need more details about {project-full-name}'s core features?
|
||||
<<project-features.adoc#features-jax-rs, JAX-RS>> |
|
||||
<<project-features.adoc#features-context-paths, Context Paths>> |
|
||||
<<project-features.adoc#features-rest-docs, RESTDocs>>
|
||||
<<howto.adoc#how-to-generate-pact-from-scc, Pact>>
|
||||
* *Modules:*
|
||||
<<project-features.adoc#features-stub-runner, Stub Runner>> |
|
||||
<<project-features.adoc#features-wiremock, WireMock>>
|
||||
|
||||
@@ -112,7 +112,7 @@ buildscript {
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}"
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${verifier_version}"
|
||||
// here you can also pass additional dependencies such as Pact or Kotlin spec e.g.:
|
||||
// here you can also pass additional dependencies such as Kotlin spec e.g.:
|
||||
// classpath "org.springframework.cloud:spring-cloud-contract-spec-kotlin:${verifier_version}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ Spring Cloud Contract Verifier stand out on the market of Consumer Driven Contra
|
||||
- Automatic generation of tests from the defined contract.
|
||||
- Stub Runner functionality: The stubs are automatically downloaded at runtime from Nexus or Artifactory.
|
||||
- Spring Cloud integration: No discovery service is needed for integration tests.
|
||||
- Spring Cloud Contract integrates with Pact and provides easy hooks to extend its functionality.
|
||||
- Ability to add support for any language & framework through Docker.
|
||||
|
||||
[[how-to-not-write-contracts-in-groovy]]
|
||||
@@ -831,427 +830,6 @@ to find stub definitions and contracts. For example, for `com.example:foo:1.0.0`
|
||||
* Stub servers are started and fed with mappings.
|
||||
* Messaging definitions are read and used in the messaging tests.
|
||||
|
||||
[[how-to-use-pact-broker]]
|
||||
== How Can I Use the Pact Broker?
|
||||
|
||||
When using https://pact.io/[Pact], you can use the https://github.com/pact-foundation/pact_broker[Pact Broker]
|
||||
to store and share Pact definitions. Starting from Spring Cloud Contract
|
||||
2.0.0, you can fetch Pact files from the Pact Broker to generate
|
||||
tests and stubs.
|
||||
|
||||
IMPORTANT: Pact follows the consumer contract convention. That means
|
||||
that the consumer creates the Pact definitions first and then
|
||||
shares the files with the Producer. Those expectations are generated
|
||||
from the Consumer's code and can break the Producer if the expectations
|
||||
are not met.
|
||||
|
||||
[[how-to-use-pact-broker-pact]]
|
||||
=== How to Work with Pact
|
||||
|
||||
Spring Cloud Contract includes support for the https://docs.pact.io/[Pact] representation of
|
||||
contracts up until version 4. Instead of using the DSL, you can use Pact files. In this section, we
|
||||
show how to add Pact support for your project. Note, however, that not all functionality is supported.
|
||||
Starting with version 3, you can combine multiple matchers 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 by 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 is skipped and the ISO format is used.
|
||||
|
||||
[[how-to-use-pact-broker-pact-converter]]
|
||||
=== Pact Converter
|
||||
|
||||
In order to properly support the Spring Cloud Contract way of doing messaging
|
||||
with Pact, you have to provide some additional metadata entries.
|
||||
|
||||
To define the destination to which a message gets sent, you have to
|
||||
set a `metaData` entry in the Pact file with the `sentTo` key equal to the destination to
|
||||
which a message is to be sent (for example, `"metaData": { "sentTo": "activemq:output" }`).
|
||||
|
||||
[[how-to-use-pact-broker-pact-contract]]
|
||||
=== Pact Contract
|
||||
|
||||
Spring Cloud Contract can read the Pact JSON definition. You can place the file in the
|
||||
`src/test/resources/contracts` folder. Remember to put the `spring-cloud-contract-pact` dependency to your classpath.
|
||||
|
||||
[[how-to-use-pact-broker-pact-for-producers]]
|
||||
=== Pact for Producers
|
||||
|
||||
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. The following listing shows how to do so for both
|
||||
Maven and Gradle:
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
include::{standalone_pact_path}/producer_pact/pom.xml[tags=pact_dependency,indent=0]
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
include::{standalone_pact_path}/producer_pact/build.gradle[tags=pact_dependency,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
When you build your application, a test and stub is generated. The following
|
||||
example shows a test and stub that came from this process:
|
||||
|
||||
====
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.test
|
||||
----
|
||||
@Test
|
||||
public void validate_shouldMarkClientAsFraud() throws Exception {
|
||||
// given:
|
||||
MockMvcRequestSpecification request = given()
|
||||
.header("Content-Type", "application/vnd.fraud.v1+json")
|
||||
.body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}");
|
||||
|
||||
// when:
|
||||
ResponseOptions response = given().spec(request)
|
||||
.put("/fraudcheck");
|
||||
|
||||
// then:
|
||||
assertThat(response.statusCode()).isEqualTo(200);
|
||||
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");
|
||||
// and:
|
||||
assertThat(parsedJson.read("$.fraudCheckStatus", String.class)).matches("FRAUD");
|
||||
}
|
||||
----
|
||||
|
||||
[source,json,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.stub
|
||||
----
|
||||
{
|
||||
"id" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
|
||||
"uuid" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
|
||||
"request" : {
|
||||
"url" : "/fraudcheck",
|
||||
"method" : "PUT",
|
||||
"headers" : {
|
||||
"Content-Type" : {
|
||||
"matches" : "application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
},
|
||||
"bodyPatterns" : [ {
|
||||
"matchesJsonPath" : "$[?(@.['loanAmount'] = 99999)]"
|
||||
}, {
|
||||
"matchesJsonPath" : "$[?(@.clientId =~ /([0-9]{10})/)]"
|
||||
} ]
|
||||
},
|
||||
"response" : {
|
||||
"status" : 200,
|
||||
"body" : "{\"fraudCheckStatus\":\"FRAUD\",\"rejectionReason\":\"Amount too high\"}",
|
||||
"headers" : {
|
||||
"Content-Type" : "application/vnd.fraud.v1+json;charset=UTF-8"
|
||||
},
|
||||
"transformers" : [ "response-template" ]
|
||||
},
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[how-to-use-pact-broker-pact-consumers]]
|
||||
=== Pact for Consumers
|
||||
|
||||
On the consumer side, you must add two additional dependencies to your project
|
||||
dependencies. One is the Spring Cloud Contract Pact support, and the other represents the
|
||||
current Pact version that you use. The following listing shows how to do so for both
|
||||
Maven and Gradle:
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
include::{standalone_pact_path}/consumer_pact_stubrunner/pom.xml[tags=pact_dependency,indent=0]
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
include::{standalone_pact_path}/consumer_pact_stubrunner/build.gradle[tags=pact_dependency,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
[[pact-stub-downloader]]
|
||||
=== Communicating with the Pact Broker
|
||||
|
||||
Whenever the `repositoryRoot` property starts with a Pact protocol
|
||||
(that is, starts with `pact://`), the stub downloader tries
|
||||
to fetch the Pact contract definitions from the Pact Broker.
|
||||
Whatever is set after `pact://` is parsed as the Pact Broker URL.
|
||||
|
||||
By setting environment variables, system properties, or properties set
|
||||
inside the plugin or contracts repository configuration, you can
|
||||
tweak the downloader's behavior. The following table describes the
|
||||
properties:
|
||||
|
||||
.Pact Stub Downloader properties
|
||||
|====
|
||||
|Name of a property |Default | Description
|
||||
|
|
||||
* `pactbroker.host` (plugin prop)
|
||||
|
||||
* `stubrunner.properties.pactbroker.host` (system prop)
|
||||
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_HOST` (env prop)
|
||||
|Host from URL passed to `repositoryRoot`
|
||||
|The URL of the Pact Broker.
|
||||
|
||||
|
|
||||
* `pactbroker.port` (plugin prop)
|
||||
|
||||
* `stubrunner.properties.pactbroker.port` (system prop)
|
||||
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_PORT` (env prop)
|
||||
|Port from URL passed to `repositoryRoot`
|
||||
|The port of Pact Broker.
|
||||
|
||||
|
|
||||
* `pactbroker.protocol` (plugin prop)
|
||||
|
||||
* `stubrunner.properties.pactbroker.protocol` (system prop)
|
||||
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL` (env prop)
|
||||
|Protocol from URL passed to `repositoryRoot`
|
||||
|The protocol of Pact Broker.
|
||||
|
||||
|
|
||||
* `pactbroker.tags` (plugin prop)
|
||||
|
||||
* `stubrunner.properties.pactbroker.tags` (system prop)
|
||||
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_TAGS` (env prop)
|
||||
|Version of the stub, or `latest` if version is `+`
|
||||
|The tags that should be used to fetch the stub.
|
||||
|
||||
|
|
||||
* `pactbroker.auth.scheme` (plugin prop)
|
||||
|
||||
* `stubrunner.properties.pactbroker.auth.scheme` (system prop)
|
||||
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME` (env prop)
|
||||
|`Basic`
|
||||
|The kind of authentication that should be used to connect to the Pact Broker.
|
||||
|
||||
|
|
||||
* `pactbroker.auth.username` (plugin prop)
|
||||
|
||||
* `stubrunner.properties.pactbroker.auth.username` (system prop)
|
||||
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME` (env prop)
|
||||
|The username passed to `contractsRepositoryUsername` (Maven) or `contractRepository.username` (Gradle)
|
||||
|The username to use when connecting to the Pact Broker.
|
||||
|
||||
|
|
||||
* `pactbroker.auth.password` (plugin prop)
|
||||
|
||||
* `stubrunner.properties.pactbroker.auth.password` (system prop)
|
||||
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD` (env prop)
|
||||
|The password passed to `contractsRepositoryPassword` (Maven) or `contractRepository.password` (Gradle)
|
||||
|The password to use when connecting to the Pact Broker.
|
||||
|
||||
|
|
||||
* `pactbroker.provider-name-with-group-id` (plugin prop)
|
||||
|
||||
* `stubrunner.properties.pactbroker.provider-name-with-group-id` (system prop)
|
||||
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID` (env prop)
|
||||
|false
|
||||
|When `true`, the provider name is a combination of `groupId:artifactId`. If `false`, only `artifactId` is used.
|
||||
|====
|
||||
|
||||
[[how-to-pact-consumer]]
|
||||
=== Flow: Consumer Contract Approach with Pact Broker on the Consumer Side
|
||||
|
||||
The consumer uses the Pact framework to generate Pact files. The
|
||||
Pact files are sent to the Pact Broker. You can find an example of such a setup
|
||||
https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/{samples_branch}/consumer_pact[here].
|
||||
|
||||
[[how-to-pact-producer]]
|
||||
=== Flow: Consumer Contract Approach with Pact Broker on the Producer Side
|
||||
|
||||
For the producer to use the Pact files from the Pact Broker, we can reuse the
|
||||
same mechanism we use for external contracts. We route Spring Cloud Contract
|
||||
to use the Pact implementation with the URL that contains
|
||||
the `pact://` protocol. You can pass the URL to the
|
||||
Pact Broker. You can find an example of such a setup
|
||||
https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/{samples_branch}/producer_pact[here].
|
||||
The following listing shows the configuration details for both Maven and Gradle:
|
||||
|
||||
====
|
||||
[source,xml,indent=0,role="primary"]
|
||||
.Maven
|
||||
----
|
||||
<plugin>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
|
||||
<version>${spring-cloud-contract.version}</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<!-- Base class mappings etc. -->
|
||||
|
||||
<!-- We want to pick contracts from a Git repository -->
|
||||
<contractsRepositoryUrl>pact://http://localhost:8085</contractsRepositoryUrl>
|
||||
|
||||
<!-- We reuse the contract dependency section to set up the path
|
||||
to the folder that contains the contract definitions. In our case the
|
||||
path will be /groupId/artifactId/version/contracts -->
|
||||
<contractDependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>${project.artifactId}</artifactId>
|
||||
<!-- When + is passed, a latest tag will be applied when fetching pacts -->
|
||||
<version>+</version>
|
||||
</contractDependency>
|
||||
|
||||
<!-- The contracts mode can't be classpath -->
|
||||
<contractsMode>REMOTE</contractsMode>
|
||||
</configuration>
|
||||
<!-- Don't forget to add spring-cloud-contract-pact to the classpath! -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<version>${spring-cloud-contract.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
buildscript {
|
||||
repositories {
|
||||
//...
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// ...
|
||||
// Don't forget to add spring-cloud-contract-pact to the classpath!
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-pact:${contractVersion}"
|
||||
}
|
||||
}
|
||||
|
||||
contracts {
|
||||
// When + is passed, a latest tag will be applied when fetching pacts
|
||||
contractDependency {
|
||||
stringNotation = "${project.group}:${project.name}:+"
|
||||
}
|
||||
contractRepository {
|
||||
repositoryUrl = "pact://http://localhost:8085"
|
||||
}
|
||||
// The mode can't be classpath
|
||||
contractsMode = "REMOTE"
|
||||
// Base class mappings etc.
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
With such a setup:
|
||||
|
||||
* Pact files are downloaded from the Pact Broker.
|
||||
* Spring Cloud Contract converts the Pact files into tests and stubs.
|
||||
* The JAR with the stubs gets automatically created, as usual.
|
||||
|
||||
[[how-to-pact-consumer-producer-contract]]
|
||||
=== Flow: Producer Contract Approach with Pact on the Consumer Side
|
||||
|
||||
In the scenario where you do not want to do the consumer contract approach
|
||||
(for every single consumer, define the expectations) but you prefer
|
||||
to do producer contracts (the producer provides the contracts and
|
||||
publishes stubs), you can use Spring Cloud Contract with the
|
||||
Stub Runner option. You can find an example of such a setup
|
||||
https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/{samples_branch}/consumer_pact_stubrunner[here].
|
||||
|
||||
Remember to add the Stub Runner and Spring Cloud Contract Pact modules
|
||||
as test dependencies.
|
||||
|
||||
The following listing shows the configuration details for both Maven and Gradle:
|
||||
|
||||
====
|
||||
[source,xml,indent=0,role="primary"]
|
||||
.Maven
|
||||
----
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<!-- Don't forget to add spring-cloud-contract-pact to the classpath! -->
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//...
|
||||
testCompile("org.springframework.cloud:spring-cloud-starter-contract-stub-runner")
|
||||
// Don't forget to add spring-cloud-contract-pact to the classpath!
|
||||
testCompile("org.springframework.cloud:spring-cloud-contract-pact")
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Next, you can pass the URL of the Pact Broker to `repositoryRoot`, prefixed
|
||||
with the `pact://` protocol (for example, `pact://http://localhost:8085`), as the following
|
||||
example shows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureStubRunner(stubsMode = StubRunnerProperties.StubsMode.REMOTE,
|
||||
ids = "com.example:beer-api-producer-pact",
|
||||
repositoryRoot = "pact://http://localhost:8085")
|
||||
public class BeerControllerTest {
|
||||
//Inject the port of the running stub
|
||||
@StubRunnerPort("beer-api-producer-pact") int producerPort;
|
||||
//...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
With such a setup:
|
||||
|
||||
* Pact files are downloaded from the Pact Broker.
|
||||
* Spring Cloud Contract converts the Pact files into stub definitions.
|
||||
* The stub servers are started and fed with stubs.
|
||||
|
||||
[[how-to-debug]]
|
||||
== How Can I Debug the Request/Response Being Sent by the Generated Tests Client?
|
||||
|
||||
|
||||
@@ -14,5 +14,5 @@ The reference documentation consists of the following sections:
|
||||
<<using.adoc#using,Using {project-full-name}>> :: {project-full-name} usage examples and workflows.
|
||||
<<project-features.adoc#features,{project-full-name} Features>> :: Contract DSL, Messaging, Spring Cloud Contract Stub Runner, and Spring Cloud Contract WireMock.
|
||||
<<project-features.adoc#features-build-tools,Build Tools>> :: Maven Plugin, Gradle Plugin, and Docker.
|
||||
<<howto.adoc#howto,"`How-to`" Guides>> :: Stubs versioning, Pact integration, Debugging, and more.
|
||||
<<howto.adoc#howto,"`How-to`" Guides>> :: Stubs versioning, Debugging, and more.
|
||||
<<appendix.adoc#appendix,Appendices>> :: Properties, Metadata, Configuration, Dependencies, and more.
|
||||
|
||||
31
pom.xml
31
pom.xml
@@ -25,7 +25,6 @@
|
||||
<properties>
|
||||
<!-- Align it also in Docker image under [docker/spring-cloud-contract-docker/project/gradle.properties] -->
|
||||
<camel.version>3.19.0</camel.version>
|
||||
<pact.version>4.3.15</pact.version>
|
||||
<jsch-agent.version>0.0.9</jsch-agent.version>
|
||||
<spring-cloud-build.version>4.0.0-SNAPSHOT</spring-cloud-build.version>
|
||||
<spring-cloud-zookeeper.version>4.0.0-SNAPSHOT</spring-cloud-zookeeper.version>
|
||||
@@ -226,36 +225,6 @@
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>${commons-text.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius.pact.consumer</groupId>
|
||||
<artifactId>junit5</artifactId>
|
||||
<version>${pact.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius.pact.provider</groupId>
|
||||
<artifactId>junit5</artifactId>
|
||||
<version>${pact.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.jknack</groupId>
|
||||
<artifactId>handlebars</artifactId>
|
||||
|
||||
@@ -96,14 +96,6 @@
|
||||
<packageWithBaseClasses>com.example.fraud</packageWithBaseClasses>
|
||||
<!-- <convertToYaml>true</convertToYaml>-->
|
||||
</configuration>
|
||||
<!-- if additional dependencies are needed e.g. for Pact -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<version>${spring-cloud-contract.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<!-- end::contract_maven_plugin[] -->
|
||||
<plugin>
|
||||
|
||||
@@ -51,11 +51,6 @@ public class FraudDetectionController {
|
||||
// end::initial_impl[]
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/pactfraudcheck", method = PUT)
|
||||
public FraudCheckResult pactFraudCheck(@RequestBody FraudCheck fraudCheck) {
|
||||
return fraudCheck(fraudCheck);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/yamlfraudcheck", method = PUT)
|
||||
public FraudCheckResult yamlFraudCheck(@RequestBody FraudCheck fraudCheck) {
|
||||
return fraudCheck(fraudCheck);
|
||||
|
||||
@@ -46,11 +46,6 @@ public class FraudStatsController {
|
||||
return new Response(this.statsProvider.count(FraudType.ALL));
|
||||
}
|
||||
|
||||
@GetMapping(value = "/pactfrauds")
|
||||
public Response countAllPactFrauds() {
|
||||
return countAllFrauds();
|
||||
}
|
||||
|
||||
@GetMapping(value = "/yamlfrauds")
|
||||
public Response countAllYamlFrauds() {
|
||||
return countAllFrauds();
|
||||
@@ -61,11 +56,6 @@ public class FraudStatsController {
|
||||
return new Response(this.statsProvider.count(FraudType.DRUNKS));
|
||||
}
|
||||
|
||||
@GetMapping(value = "/pactdrunks")
|
||||
public Response countAllPactDrunks() {
|
||||
return countAllDrunks();
|
||||
}
|
||||
|
||||
@GetMapping(value = "/yamldrunks")
|
||||
public Response countAllYamlDrunks() {
|
||||
return countAllDrunks();
|
||||
@@ -101,4 +91,4 @@ class DefaultStatsProvider implements StatsProvider {
|
||||
public int count(FraudType fraudType) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 com.example.fraud;
|
||||
|
||||
import io.restassured.module.mockmvc.RestAssuredMockMvc;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
public class PactBase {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
RestAssuredMockMvc.standaloneSetup(new FraudDetectionController(),
|
||||
new FraudStatsController(stubbedStatsProvider()));
|
||||
}
|
||||
|
||||
private StatsProvider stubbedStatsProvider() {
|
||||
return fraudType -> {
|
||||
switch (fraudType) {
|
||||
case DRUNKS:
|
||||
return 100;
|
||||
case ALL:
|
||||
return 200;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
public void assertThatRejectionReasonIsNull(Object rejectionReason) {
|
||||
assert rejectionReason == null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"path": "/pactfraudcheck",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"clientId": "1234567890",
|
||||
"loanAmount": 99999
|
||||
},
|
||||
"generators": {
|
||||
"body": {
|
||||
"$.clientId": {
|
||||
"type": "Regex",
|
||||
"regex": "[0-9]{10}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.clientId": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[0-9]{10}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"fraudCheckStatus": "FRAUD",
|
||||
"rejection.reason": "Amount too high"
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.fraudCheckStatus": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "FRAUD"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"path": "/pactfraudcheck",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"clientId": "1234567890",
|
||||
"loanAmount": 123.123
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"fraudCheckStatus": "OK",
|
||||
"rejection.reason": null
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/pactdrunks"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"count": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/pactfrauds"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"count": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,6 @@ public class Contract {
|
||||
throw new IllegalStateException("Status is missing for HTTP contract");
|
||||
}
|
||||
}
|
||||
// Can't assert messaging part cause Pact doesn't require destinations it seems
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,11 +55,6 @@
|
||||
<artifactId>spring-cloud-contract-converters</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-stub-runner</artifactId>
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-contract-converters</module>
|
||||
<module>spring-cloud-contract-pact</module>
|
||||
<module>spring-cloud-contract-maven-plugin</module>
|
||||
<module>spring-cloud-contract-gradle-plugin</module>
|
||||
<module>spring-cloud-contract-gradle-portal-plugin</module>
|
||||
|
||||
@@ -378,11 +378,6 @@
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugin-testing</groupId>
|
||||
<artifactId>maven-plugin-testing-harness</artifactId>
|
||||
|
||||
@@ -22,8 +22,6 @@ import org.codehaus.plexus.util.xml.Xpp3Dom;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static java.nio.charset.Charset.defaultCharset;
|
||||
import static org.apache.commons.io.FileUtils.readFileToString;
|
||||
import static org.apache.maven.plugin.testing.MojoParameters.newParameter;
|
||||
@@ -309,21 +307,6 @@ public class PluginUnitTest extends AbstractMojoTest {
|
||||
"target/stubs/META-INF/org.springframework.cloud.verifier.sample/common-repo/0.1/contracts/pom.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGenerateContractTestsForPactAndMaintainIndents() throws Exception {
|
||||
File basedir = getBasedir("pact");
|
||||
|
||||
executeMojo(basedir, "generateTests", defaultPackageForTests());
|
||||
|
||||
assertFilesPresent(basedir,
|
||||
"target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/ContractVerifierTest.java");
|
||||
File test = new File(basedir,
|
||||
"target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/ContractVerifierTest.java");
|
||||
String testContents = readFileToString(test, defaultCharset());
|
||||
int countOccurrencesOf = StringUtils.countOccurrencesOf(testContents, "\t\tMockMvcRequestSpecification");
|
||||
then(countOccurrencesOf).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRunPushStubsToScm() throws Exception {
|
||||
File basedir = getBasedir("git-basic-remote-contracts");
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
|
||||
Copyright 2013-2020 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
|
||||
|
||||
https://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.
|
||||
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework.cloud.verifier.sample</groupId>
|
||||
<artifactId>sample-pact-project</artifactId>
|
||||
<version>0.1</version>
|
||||
|
||||
<properties>
|
||||
<spring.cloud.contract.version>2.0.0.BUILD-SNAPSHOT
|
||||
</spring.cloud.contract.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<baseClassForTests>com.example.FooBase</baseClassForTests>
|
||||
<baseClassMappings>
|
||||
<baseClassMapping>
|
||||
<contractPackageRegex>.*com.*</contractPackageRegex>
|
||||
<baseClassFQN>com.example.TestBase</baseClassFQN>
|
||||
</baseClassMapping>
|
||||
</baseClassMappings>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<version>${spring.cloud.contract.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"path": "/fraudcheck",
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json"
|
||||
},
|
||||
"body": {
|
||||
"clientId": "1234567890",
|
||||
"loanAmount": 99999
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"fraudCheckStatus": "FRAUD",
|
||||
"rejectionReason": "Amount too high"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"path": "/fraudcheck",
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json"
|
||||
},
|
||||
"body": {
|
||||
"clientId": "1234567890",
|
||||
"loanAmount": 123.123
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"fraudCheckStatus": "OK",
|
||||
"rejectionReason": null
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/drunks"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"count": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/frauds"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"count": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "2.4.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-tools</artifactId>
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Contract Pact</name>
|
||||
<description>Spring Cloud Contract Pact</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-converters</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-stub-runner</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.groovy</groupId>
|
||||
<artifactId>groovy-nio</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius.pact.consumer</groupId>
|
||||
<artifactId>junit5</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius.pact.provider</groupId>
|
||||
<artifactId>junit5</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-wiremock</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.gmavenplus</groupId>
|
||||
<artifactId>gmavenplus-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>addSources</goal>
|
||||
<goal>addTestSources</goal>
|
||||
<goal>generateStubs</goal>
|
||||
<goal>compile</goal>
|
||||
<goal>generateTestStubs</goal>
|
||||
<goal>compileTests</goal>
|
||||
<goal>removeStubs</goal>
|
||||
<goal>removeTestStubs</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,467 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import au.com.dius.pact.core.model.Pact;
|
||||
import au.com.dius.pact.core.model.PactSpecVersion;
|
||||
import au.com.dius.pact.core.support.expressions.SystemPropertyResolver;
|
||||
import au.com.dius.pact.core.support.expressions.ValueResolver;
|
||||
import au.com.dius.pact.provider.junitsupport.loader.PactBroker;
|
||||
import au.com.dius.pact.provider.junitsupport.loader.PactBrokerAuth;
|
||||
import au.com.dius.pact.provider.junitsupport.loader.PactBrokerLoader;
|
||||
import au.com.dius.pact.provider.junitsupport.loader.PactLoader;
|
||||
import au.com.dius.pact.provider.junitsupport.loader.VersionSelector;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
|
||||
import org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter;
|
||||
import org.springframework.core.io.AbstractResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Allows downloading of Pact files from the Pact Broker.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class PactStubDownloaderBuilder implements StubDownloaderBuilder {
|
||||
|
||||
private static final List<String> ACCEPTABLE_PROTOCOLS = Collections.singletonList("pact");
|
||||
|
||||
/**
|
||||
* Does any of the accepted protocols matches the URL of the repository.
|
||||
* @param url - of the repository
|
||||
* @return {@code true} if the protocol is accepted
|
||||
*/
|
||||
private static boolean isProtocolAccepted(String url) {
|
||||
return ACCEPTABLE_PROTOCOLS.stream().anyMatch(url::startsWith);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StubDownloader build(StubRunnerOptions stubRunnerOptions) {
|
||||
if (stubRunnerOptions.getStubsMode() == StubRunnerProperties.StubsMode.CLASSPATH
|
||||
|| stubRunnerOptions.getStubRepositoryRoot() == null) {
|
||||
return null;
|
||||
}
|
||||
Resource resource = stubRunnerOptions.getStubRepositoryRoot();
|
||||
if (!(resource instanceof PactResource)) {
|
||||
return null;
|
||||
}
|
||||
return new PactStubDownloader(stubRunnerOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource resolve(String location, ResourceLoader resourceLoader) {
|
||||
if (!StringUtils.hasText(location) || !isProtocolAccepted(location)) {
|
||||
return null;
|
||||
}
|
||||
return new PactResource(location);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PactResource extends AbstractResource {
|
||||
|
||||
private final String rawLocation;
|
||||
|
||||
PactResource(String location) {
|
||||
this.rawLocation = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.rawLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return URI.create(this.rawLocation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PactStubDownloader implements StubDownloader {
|
||||
|
||||
private static final String TEMP_DIR_PREFIX = "pact";
|
||||
|
||||
private static final Log log = LogFactory.getLog(PactStubDownloader.class);
|
||||
|
||||
// Preloading class for the shutdown hook not to throw ClassNotFound
|
||||
private static final Class CLAZZ = TemporaryFileStorage.class;
|
||||
|
||||
private static final String ARTIFICIAL_NAME_ENDING_WITH_GROOVY = "name.groovy";
|
||||
|
||||
private static final String PROVIDER_NAME_WITH_GROUP_ID = "pactbroker.provider-name-with-group-id";
|
||||
|
||||
private final StubRunnerOptions stubRunnerOptions;
|
||||
|
||||
private final boolean deleteStubsAfterTest;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
PactStubDownloader(StubRunnerOptions stubRunnerOptions) {
|
||||
this.stubRunnerOptions = stubRunnerOptions;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.deleteStubsAfterTest = stubRunnerOptions.isDeleteStubsAfterTest();
|
||||
registerShutdownHook();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(StubConfiguration stubConfiguration) {
|
||||
String version = stubConfiguration.version;
|
||||
final FromPropsThenFromSysEnv resolver = new FromPropsThenFromSysEnv(this.stubRunnerOptions);
|
||||
List<String> tags = tags(version, resolver);
|
||||
try {
|
||||
PactLoader loader = pactBrokerLoader(resolver, tags);
|
||||
String providerName = providerName(stubConfiguration);
|
||||
List<Pact> pacts = loader.load(providerName);
|
||||
if (pacts.isEmpty()) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("No pact definitions found for provider [" + providerName + "]");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
File tmpDirWhereStubsWillBeUnzipped = TemporaryFileStorage.createTempDir(TEMP_DIR_PREFIX);
|
||||
// make the groupid / artifactid folders
|
||||
String coordinatesFolderName = stubConfiguration.getGroupId().replace(".", File.separator) + File.separator
|
||||
+ stubConfiguration.getArtifactId();
|
||||
File contractsFolder = new File(tmpDirWhereStubsWillBeUnzipped,
|
||||
coordinatesFolderName + File.separator + "contracts");
|
||||
File mappingsFolder = new File(tmpDirWhereStubsWillBeUnzipped,
|
||||
coordinatesFolderName + File.separator + "mappings");
|
||||
boolean createdContractsDirs = contractsFolder.mkdirs();
|
||||
boolean createdMappingsDirs = mappingsFolder.mkdirs();
|
||||
if (!createdContractsDirs || !createdMappingsDirs) {
|
||||
throw new IllegalStateException("Failed to create mandatory [contracts] or [mappings] folders under ["
|
||||
+ coordinatesFolderName + "]");
|
||||
}
|
||||
storePacts(providerName, pacts, contractsFolder, mappingsFolder);
|
||||
return new AbstractMap.SimpleEntry<>(stubConfiguration, tmpDirWhereStubsWillBeUnzipped);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void storePacts(String providerName, List<Pact> pacts, File contractsFolder, File mappingsFolder) {
|
||||
for (int i = 0; i < pacts.size(); i++) {
|
||||
String json = toJson(pacts.get(i).toMap(PactSpecVersion.V3));
|
||||
File file = new File(contractsFolder, i + "_" + providerName.replace(":", "_") + "_pact.json");
|
||||
storeFile(file.toPath(), json.getBytes());
|
||||
try {
|
||||
storeMapping(mappingsFolder, file);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Exception occurred while trying to store the mapping", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void storeMapping(File mappingsFolder, File file) {
|
||||
Collection<Contract> contracts = new PactContractConverter().convertFrom(file);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Converted pact file [" + file + "] to [" + contracts.size() + "] contracts");
|
||||
}
|
||||
MappingGenerator.toMappings(file, contracts, mappingsFolder);
|
||||
}
|
||||
|
||||
private Path storeFile(Path path, byte[] contents) {
|
||||
try {
|
||||
Path storedPath = Files.write(path, contents);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Stored file [" + path + "]");
|
||||
}
|
||||
return storedPath;
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String providerName(StubConfiguration stubConfiguration) {
|
||||
boolean providerNameWithGroupId = Boolean.parseBoolean(StubRunnerPropertyUtils
|
||||
.getProperty(this.stubRunnerOptions.getProperties(), PROVIDER_NAME_WITH_GROUP_ID));
|
||||
if (providerNameWithGroupId) {
|
||||
return stubConfiguration.getGroupId() + ":" + stubConfiguration.getArtifactId();
|
||||
}
|
||||
return stubConfiguration.getArtifactId();
|
||||
}
|
||||
|
||||
PactLoader pactBrokerLoader(ValueResolver resolver, List<String> tags) throws IOException {
|
||||
Resource repo = this.stubRunnerOptions.getStubRepositoryRoot();
|
||||
String schemeSpecificPart = schemeSpecificPart(repo.getURI());
|
||||
URI pactBrokerUrl = URI.create(schemeSpecificPart);
|
||||
String stubRunnerUsername = this.stubRunnerOptions.getUsername() != null ? this.stubRunnerOptions.getUsername()
|
||||
: "";
|
||||
String stubRunnerPassword = this.stubRunnerOptions.getPassword() != null ? this.stubRunnerOptions.getPassword()
|
||||
: "";
|
||||
return new PactBrokerLoader(new PactBroker() {
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return PactBroker.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
return resolver.resolveValue("pactbroker.url",
|
||||
pactBrokerUrl.getScheme() + "://" + pactBrokerUrl.getHost() + ":" + pactBrokerUrl.getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public String host() {
|
||||
return resolver.resolveValue("pactbroker.host", pactBrokerUrl.getHost());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public String port() {
|
||||
return resolver.resolveValue("pactbroker.port", String.valueOf(pactBrokerUrl.getPort()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public String scheme() {
|
||||
return resolver.resolveValue("pactbroker.protocol", pactBrokerUrl.getScheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public String[] tags() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionSelector[] consumerVersionSelectors() {
|
||||
return new VersionSelector[] { new VersionSelector() {
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return VersionSelector.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String tag() {
|
||||
return resolver.resolveValue("pactbroker.consumerversionselectors.tags", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String latest() {
|
||||
return resolver.resolveValue("pactbroker.consumerversionselectors.latest", "true");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String consumer() {
|
||||
return resolver.resolveValue("pactbroker.consumers", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fallbackTag() {
|
||||
return resolver.resolveValue("pactbroker.fallbacktag", "");
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] consumers() {
|
||||
return new String[] { resolver.resolveValue("pactbroker.consumers", "") };
|
||||
}
|
||||
|
||||
@Override
|
||||
public PactBrokerAuth authentication() {
|
||||
return new PactBrokerAuth() {
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return PactBrokerAuth.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String username() {
|
||||
return resolver.resolveValue("pactbroker.auth.username", stubRunnerUsername);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String password() {
|
||||
return resolver.resolveValue("pactbroker.auth.password", stubRunnerPassword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String token() {
|
||||
return resolver.resolveValue("pactbroker.auth.token", "");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ValueResolver> valueResolver() {
|
||||
return SystemPropertyResolver.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String enablePendingPacts() {
|
||||
return resolver.resolveValue("pactbroker.enablePending", "false");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] providerTags() {
|
||||
return resolver.resolveValue("pactbroker.providerTags", "").split(",");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String providerBranch() {
|
||||
return resolver.resolveValue("pactbroker.providerBranch", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String includeWipPactsSince() {
|
||||
return resolver.resolveValue("pactbroker.includeWipPactsSince", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String enableInsecureTls() {
|
||||
return resolver.resolveValue("pactbroker.enableInsecureTls", "false");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String schemeSpecificPart(URI uri) {
|
||||
String part = uri.getSchemeSpecificPart();
|
||||
if (!StringUtils.hasText(part)) {
|
||||
return part;
|
||||
}
|
||||
return part.startsWith("//") ? part.substring(2) : part;
|
||||
}
|
||||
|
||||
private List<String> tags(String version, ValueResolver resolver) {
|
||||
String defaultTag = StubConfiguration.DEFAULT_VERSION.equals(version) ? "latest" : version;
|
||||
return new ArrayList<>(Arrays.asList(
|
||||
StringUtils.commaDelimitedListToStringArray(resolver.resolveValue("pactbroker.tags", defaultTag))));
|
||||
}
|
||||
|
||||
private String toJson(Map map) {
|
||||
try {
|
||||
return this.objectMapper.writeValueAsString(map);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerShutdownHook() {
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
new Thread(() -> TemporaryFileStorage.cleanup(PactStubDownloader.this.deleteStubsAfterTest)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FromPropsThenFromSysEnv implements ValueResolver {
|
||||
|
||||
final SystemPropertyResolver resolver = SystemPropertyResolver.INSTANCE;
|
||||
|
||||
StubRunnerOptions options;
|
||||
|
||||
FromPropsThenFromSysEnv(StubRunnerOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveValue(String expression) {
|
||||
return doResolve(expression, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String doResolve(String expression, @Nullable String defaultValue) {
|
||||
PropertyValueTuple tuple = new PropertyValueTuple(expression).invoke();
|
||||
String propertyName = tuple.getPropertyName();
|
||||
String property = StubRunnerPropertyUtils.getProperty(this.options.getProperties(), propertyName);
|
||||
if (StringUtils.hasText(property)) {
|
||||
return property;
|
||||
}
|
||||
return this.resolver.resolveValue(expression, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean propertyDefined(String property) {
|
||||
PropertyValueTuple tuple = new PropertyValueTuple(property).invoke();
|
||||
String propertyName = tuple.getPropertyName();
|
||||
boolean hasProperty = StubRunnerPropertyUtils.hasProperty(this.options.getProperties(), propertyName);
|
||||
if (hasProperty) {
|
||||
return true;
|
||||
}
|
||||
return this.resolver.propertyDefined(property);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String resolveValue(String expression, String defaultValue) {
|
||||
return doResolve(expression, defaultValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// taken from pact -
|
||||
// au.com.dius.pact.provider.junit.sysprops.SystemPropertyResolver.PropertyValueTuple
|
||||
class PropertyValueTuple {
|
||||
|
||||
private String propertyName;
|
||||
|
||||
PropertyValueTuple(String property) {
|
||||
this.propertyName = property;
|
||||
}
|
||||
|
||||
String getPropertyName() {
|
||||
return this.propertyName;
|
||||
}
|
||||
|
||||
PropertyValueTuple invoke() {
|
||||
if (this.propertyName.contains(":")) {
|
||||
String[] kv = org.apache.commons.lang3.StringUtils.splitPreserveAllTokens(this.propertyName, ':');
|
||||
this.propertyName = kv[0];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
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.core.model.OptionalBody;
|
||||
import au.com.dius.pact.core.model.Request;
|
||||
import au.com.dius.pact.core.model.Response;
|
||||
import au.com.dius.pact.core.model.generators.Generator;
|
||||
import au.com.dius.pact.core.model.messaging.Message;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.internal.EvaluationContext;
|
||||
import com.jayway.jsonpath.internal.Path;
|
||||
import com.jayway.jsonpath.internal.path.PathCompiler;
|
||||
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
|
||||
import groovy.lang.GString;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
* @author Stessy Delcroix
|
||||
* @since 2.0.0
|
||||
*/
|
||||
final class BodyConverter {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.INSTANCE.getMapper();
|
||||
|
||||
private BodyConverter() {
|
||||
|
||||
}
|
||||
|
||||
static DslPart toPactBody(DslProperty<?> dslProperty, Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
|
||||
return traverse(dslProperty, null, dslPropertyValueExtractor);
|
||||
}
|
||||
|
||||
private static DslPart traverse(Object value, DslPart parent,
|
||||
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
|
||||
boolean isRoot = parent == null;
|
||||
Object v = value;
|
||||
if (v instanceof DslProperty) {
|
||||
v = dslPropertyValueExtractor.apply((DslProperty<?>) v);
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue((GString) v, dslPropertyValueExtractor);
|
||||
}
|
||||
if (v instanceof String) {
|
||||
String stringValue = ((String) v).trim();
|
||||
if (StringUtils.startsWith(stringValue, "{") && StringUtils.endsWith(stringValue, "}")) {
|
||||
try {
|
||||
v = OBJECT_MAPPER.readValue(stringValue, Object.class);
|
||||
}
|
||||
catch (JsonProcessingException ex) { /*
|
||||
* it wasn't a JSON string after
|
||||
* all...
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
DslPart p = isRoot ? createRootDslPart(v) : parent;
|
||||
if (v instanceof Map) {
|
||||
processMap((Map) v, (PactDslJsonBody) p, dslPropertyValueExtractor);
|
||||
}
|
||||
else if (v instanceof Collection) {
|
||||
processCollection((Collection) v, (PactDslJsonArray) p, dslPropertyValueExtractor);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private static DslPart createRootDslPart(Object value) {
|
||||
return value instanceof Collection ? new PactDslJsonArray() : new PactDslJsonBody();
|
||||
}
|
||||
|
||||
private static void processCollection(Collection values, PactDslJsonArray jsonArray,
|
||||
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
|
||||
values.forEach(v -> {
|
||||
|
||||
if (v instanceof DslProperty) {
|
||||
v = dslPropertyValueExtractor.apply((DslProperty<?>) v);
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue((GString) v, dslPropertyValueExtractor);
|
||||
}
|
||||
if (v == null) {
|
||||
jsonArray.nullValue();
|
||||
}
|
||||
else if (v instanceof String) {
|
||||
jsonArray.string((String) v);
|
||||
}
|
||||
else if (v instanceof Number) {
|
||||
jsonArray.number((Number) v);
|
||||
}
|
||||
else if (v instanceof Map) {
|
||||
PactDslJsonBody current = jsonArray.object();
|
||||
traverse(v, current, dslPropertyValueExtractor);
|
||||
current.closeObject();
|
||||
}
|
||||
else if (v instanceof Collection) {
|
||||
PactDslJsonArray current = jsonArray.array();
|
||||
traverse(v, current, dslPropertyValueExtractor);
|
||||
current.closeArray();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void processMap(Map<String, Object> values, PactDslJsonBody jsonObject,
|
||||
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
|
||||
values.forEach((k, v) -> {
|
||||
if (v instanceof DslProperty) {
|
||||
v = dslPropertyValueExtractor.apply((DslProperty<?>) v);
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue((GString) v, dslPropertyValueExtractor);
|
||||
}
|
||||
if (v == null) {
|
||||
jsonObject.nullValue(k);
|
||||
}
|
||||
else if (v instanceof Boolean) {
|
||||
jsonObject.booleanValue(k, (Boolean) v);
|
||||
}
|
||||
else if (v instanceof String) {
|
||||
jsonObject.stringType(k, (String) v);
|
||||
}
|
||||
else if (v instanceof Number) {
|
||||
jsonObject.numberValue(k, (Number) v);
|
||||
}
|
||||
else if (v instanceof Map) {
|
||||
PactDslJsonBody current = jsonObject.object(k);
|
||||
traverse(v, current, dslPropertyValueExtractor);
|
||||
current.closeObject();
|
||||
}
|
||||
else if (v instanceof Collection) {
|
||||
PactDslJsonArray current = jsonObject.array(k);
|
||||
traverse(v, current, dslPropertyValueExtractor);
|
||||
current.closeArray();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Object toSCCBody(Request request) {
|
||||
Object body = parseBody(request.getBody());
|
||||
if (request.getGenerators().isNotEmpty() && request.getGenerators().getCategories()
|
||||
.containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
|
||||
applyGenerators(body,
|
||||
request.getGenerators().getCategories().get(au.com.dius.pact.core.model.generators.Category.BODY),
|
||||
currentValue -> pattern -> generatedValue -> new DslProperty<>(
|
||||
new ClientDslProperty(pattern, generatedValue), currentValue));
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
static Object toSCCBody(Response response) {
|
||||
Object body = parseBody(response.getBody());
|
||||
if (response.getGenerators().isNotEmpty() && response.getGenerators().getCategories()
|
||||
.containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
|
||||
applyGenerators(body,
|
||||
response.getGenerators().getCategories().get(au.com.dius.pact.core.model.generators.Category.BODY),
|
||||
currentValue -> pattern -> generatedValue -> new DslProperty<>(currentValue,
|
||||
new ServerDslProperty(pattern, generatedValue)));
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
static Object toSCCBody(Message message) {
|
||||
Object body = parseBody(message.getContents());
|
||||
if (message.getGenerators().isNotEmpty() && message.getGenerators().getCategories()
|
||||
.containsKey(au.com.dius.pact.core.model.generators.Category.BODY)) {
|
||||
applyGenerators(body,
|
||||
message.getGenerators().getCategories().get(au.com.dius.pact.core.model.generators.Category.BODY),
|
||||
currentValue -> pattern -> generatedValue -> new DslProperty<>(
|
||||
new ClientDslProperty(pattern, generatedValue), currentValue));
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
private static Object parseBody(OptionalBody optionalBody) {
|
||||
if (optionalBody.isPresent()) {
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(optionalBody.getValue(), Object.class);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("Body could not be read", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return optionalBody.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyGenerators(Object body, Map<String, Generator> generatorsPerPath,
|
||||
Function<Pattern, Function<Object, Function<Object, DslProperty<Object>>>> dslPropertyProvider) {
|
||||
Configuration configuration = Configuration.builder().jsonProvider(new JacksonJsonProvider(OBJECT_MAPPER))
|
||||
.build();
|
||||
generatorsPerPath.forEach((path, generator) -> {
|
||||
Path compiledPath = PathCompiler.compile(path);
|
||||
EvaluationContext evaluationContext = compiledPath.evaluate(body, body, configuration, true);
|
||||
evaluationContext.updateOperations().forEach(pathRef -> {
|
||||
pathRef.convert(((currentValue, config) -> ValueGeneratorConverter.convert(generator,
|
||||
(pattern, generatedValue) -> dslPropertyProvider.apply(pattern).apply(generatedValue)
|
||||
.apply(currentValue))),
|
||||
configuration);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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.core.model.matchingrules.DateMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.EqualsMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory;
|
||||
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.NullMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.RegexMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.TimeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.TypeMatcher;
|
||||
|
||||
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
|
||||
* @author Stessy Delcroix
|
||||
* @since 2.0.0
|
||||
*/
|
||||
final class MatchingRulesConverter {
|
||||
|
||||
private MatchingRulesConverter() {
|
||||
}
|
||||
|
||||
static MatchingRuleCategory matchingRulesForBody(BodyMatchers bodyMatchers) {
|
||||
return matchingRulesFor("body", bodyMatchers);
|
||||
}
|
||||
|
||||
private static MatchingRuleCategory matchingRulesFor(String categoryName, BodyMatchers bodyMatchers) {
|
||||
MatchingRuleCategory category = new MatchingRuleCategory(categoryName);
|
||||
bodyMatchers.matchers().forEach((b) -> {
|
||||
String key = getMatcherKey(b.path());
|
||||
MatchingType matchingType = b.matchingType();
|
||||
switch (matchingType) {
|
||||
case NULL:
|
||||
category.addRule(key, NullMatcher.INSTANCE);
|
||||
break;
|
||||
case EQUALITY:
|
||||
category.addRule(key, EqualsMatcher.INSTANCE);
|
||||
break;
|
||||
case TYPE:
|
||||
if (b.minTypeOccurrence() != null && b.maxTypeOccurrence() != null) {
|
||||
category.addRule(key, new MinMaxTypeMatcher(b.minTypeOccurrence(), b.maxTypeOccurrence()));
|
||||
}
|
||||
else if (b.minTypeOccurrence() != null) {
|
||||
category.addRule(key, new MinTypeMatcher(b.minTypeOccurrence()));
|
||||
}
|
||||
else if (b.maxTypeOccurrence() != null) {
|
||||
category.addRule(key, new MaxTypeMatcher(b.maxTypeOccurrence()));
|
||||
}
|
||||
else {
|
||||
category.addRule(key, TypeMatcher.INSTANCE);
|
||||
}
|
||||
break;
|
||||
case DATE:
|
||||
category.addRule(key, new DateMatcher());
|
||||
break;
|
||||
case TIME:
|
||||
category.addRule(key, new TimeMatcher());
|
||||
break;
|
||||
case TIMESTAMP:
|
||||
category.addRule(key, new TimestampMatcher());
|
||||
break;
|
||||
case REGEX:
|
||||
String pattern = b.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import au.com.dius.pact.consumer.MessagePactBuilder;
|
||||
import au.com.dius.pact.consumer.dsl.DslPart;
|
||||
import au.com.dius.pact.core.model.messaging.MessagePact;
|
||||
import groovy.lang.GString;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
||||
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
|
||||
* @author Stessy Delcroix
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class MessagePactCreator {
|
||||
|
||||
private static final Function<DslProperty<?>, Object> clientValueExtractor = DslProperty::getClientValue;
|
||||
|
||||
MessagePact createFromContract(List<Contract> contracts) {
|
||||
if (CollectionUtils.isEmpty(contracts)) {
|
||||
return null;
|
||||
}
|
||||
Names names = NamingUtil.name(contracts.get(0));
|
||||
MessagePactBuilder pactBuilder = new MessagePactBuilder().consumer(names.getConsumer())
|
||||
.hasPactWith(names.getProducer());
|
||||
|
||||
for (Contract contract : contracts) {
|
||||
pactBuilder = pactBuilder.given(getGiven(contract.getInput())).expectsToReceive(getOutcome(contract));
|
||||
if (contract.getOutputMessage() != null) {
|
||||
OutputMessage message = contract.getOutputMessage();
|
||||
if (message.getBody() != null) {
|
||||
DslPart pactResponseBody = BodyConverter.toPactBody(message.getBody(), clientValueExtractor);
|
||||
if (message.getBodyMatchers() != null) {
|
||||
pactResponseBody
|
||||
.setMatchers(MatchingRulesConverter.matchingRulesForBody(message.getBodyMatchers()));
|
||||
}
|
||||
pactResponseBody
|
||||
.setGenerators(ValueGeneratorConverter.extract(message, DslProperty::getServerValue));
|
||||
pactBuilder = pactBuilder.withContent(pactResponseBody);
|
||||
}
|
||||
if (message.getHeaders() != null) {
|
||||
pactBuilder = pactBuilder.withMetadata(getMetadata(message.getHeaders()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return pactBuilder.toPact();
|
||||
}
|
||||
|
||||
private String getGiven(Input input) {
|
||||
if (input.getTriggeredBy() != null) {
|
||||
return input.getTriggeredBy().getExecutionCommand();
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getOutcome(Contract contract) {
|
||||
if (contract.getOutputMessage() != null) {
|
||||
OutputMessage message = contract.getOutputMessage();
|
||||
return "message sent to " + clientValueExtractor.apply(message.getSentTo());
|
||||
}
|
||||
else {
|
||||
return "assert that " + contract.getInput().getAssertThat().getExecutionCommand();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> getMetadata(Headers headers) {
|
||||
return headers.getEntries().stream().collect(Collectors.toMap(Header::getName, this::extractValue));
|
||||
}
|
||||
|
||||
private String extractValue(Object value) {
|
||||
Object v = value;
|
||||
if (v instanceof DslProperty) {
|
||||
v = clientValueExtractor.apply((DslProperty) v);
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue((GString) v, clientValueExtractor);
|
||||
}
|
||||
if (v instanceof String) {
|
||||
return (String) v;
|
||||
}
|
||||
else {
|
||||
return v.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import au.com.dius.pact.core.model.matchingrules.DateMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.MatchingRule;
|
||||
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory;
|
||||
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.NullMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.RegexMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.RuleLogic;
|
||||
import au.com.dius.pact.core.model.matchingrules.TimeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.TypeMatcher;
|
||||
import au.com.dius.pact.core.model.messaging.Message;
|
||||
import au.com.dius.pact.core.model.messaging.MessagePact;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.spec.internal.Headers;
|
||||
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
|
||||
import org.springframework.cloud.contract.spec.internal.ResponseBodyMatchers;
|
||||
import org.springframework.cloud.contract.verifier.util.JsonPaths;
|
||||
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
|
||||
|
||||
/**
|
||||
* Creator of {@link Contract} instances.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @author Stessy Delcroix
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class MessagingSCContractCreator {
|
||||
|
||||
private static final String FULL_BODY = "$";
|
||||
|
||||
private static final String DESTINATION_KEY = "sentTo";
|
||||
|
||||
private static final List<String> NON_HEADER_META_DATA = Collections.singletonList(DESTINATION_KEY);
|
||||
|
||||
Collection<Contract> convertFrom(MessagePact pact) {
|
||||
return pact.getMessages().stream().map(message -> Contract.make(contract -> {
|
||||
contract.label(message.getDescription());
|
||||
if (!message.getProviderStates().isEmpty()) {
|
||||
contract.input(i -> i.triggeredBy(this.getTriggeredBy(message)));
|
||||
}
|
||||
|
||||
contract.outputMessage((outputMessage) -> {
|
||||
if (message.getContents().isPresent()) {
|
||||
outputMessage.body(BodyConverter.toSCCBody(message));
|
||||
MatchingRuleCategory bodyRules = message.getMatchingRules().rulesForCategory("body");
|
||||
if (bodyRules != null && !bodyRules.getMatchingRules().isEmpty()) {
|
||||
outputMessage.bodyMatchers((responseBodyMatchers) -> outputMessageBodyMatchers(message,
|
||||
bodyRules, responseBodyMatchers));
|
||||
}
|
||||
}
|
||||
if (!message.getMetadata().isEmpty()) {
|
||||
outputMessage.headers((headers) -> outputMessageHeaders(message, headers));
|
||||
}
|
||||
String dest = findDestination(message);
|
||||
if (StringUtils.isNotBlank(dest)) {
|
||||
outputMessage.sentTo(dest);
|
||||
}
|
||||
});
|
||||
})).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void outputMessageHeaders(Message message, Headers headers) {
|
||||
message.getMetadata().forEach((key, value) -> {
|
||||
String matchingRuleGroup = value.toString();
|
||||
if (key.equalsIgnoreCase("contentType")) {
|
||||
headers.messagingContentType(matchingRuleGroup);
|
||||
}
|
||||
else if (!NON_HEADER_META_DATA.contains(key)) {
|
||||
headers.header(key, matchingRuleGroup);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void outputMessageBodyMatchers(Message message, MatchingRuleCategory bodyRules,
|
||||
ResponseBodyMatchers responseBodyMatchers) {
|
||||
bodyRules.getMatchingRules().forEach((matchingRuleKey, matchingRuleGroup) -> {
|
||||
if (matchingRuleGroup.getRuleLogic() != RuleLogic.AND) {
|
||||
throw new UnsupportedOperationException("Currently only the AND combination rule logic is supported");
|
||||
}
|
||||
if (FULL_BODY.equals(matchingRuleKey)) {
|
||||
JsonPaths jsonPaths = JsonToJsonPathsConverter
|
||||
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(message.getContents().getValue());
|
||||
jsonPaths.forEach((j) -> {
|
||||
responseBodyMatchers.jsonPath(j.keyBeforeChecking(), responseBodyMatchers.byType());
|
||||
});
|
||||
}
|
||||
else {
|
||||
matchingRuleGroup.getRules().forEach((rule) -> {
|
||||
applyJsonPathToResponseBodyMatchers(rule, matchingRuleKey, responseBodyMatchers);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getTriggeredBy(Message message) {
|
||||
String triggeredBy = message.getProviderStates().get(0).getName().replaceAll(":", " ").replaceAll(" ", "_")
|
||||
.replaceAll("\\(", "").replaceAll("\\)", "");
|
||||
return StringUtils.uncapitalize(triggeredBy) + "()";
|
||||
}
|
||||
|
||||
private String findDestination(Message message) {
|
||||
return message.getMetadata().get(DESTINATION_KEY) != null
|
||||
? message.getMetadata().get(DESTINATION_KEY).toString() : "";
|
||||
}
|
||||
|
||||
void applyJsonPathToResponseBodyMatchers(MatchingRule matchingRule, String key,
|
||||
ResponseBodyMatchers responseBodyMatchers) {
|
||||
if (matchingRule instanceof NullMatcher) {
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byNull());
|
||||
}
|
||||
else if (matchingRule instanceof RegexMatcher) {
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(((RegexMatcher) matchingRule).getRegex()));
|
||||
}
|
||||
else if (matchingRule instanceof DateMatcher) {
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byDate());
|
||||
}
|
||||
else if (matchingRule instanceof TimeMatcher) {
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byTime());
|
||||
}
|
||||
else if (matchingRule instanceof TimestampMatcher) {
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byTimestamp());
|
||||
}
|
||||
else if (matchingRule instanceof MinTypeMatcher) {
|
||||
responseBodyMatchers.jsonPath(key,
|
||||
responseBodyMatchers.byType((b) -> b.minOccurrence(((MinTypeMatcher) matchingRule).getMin())));
|
||||
}
|
||||
else if (matchingRule instanceof MinMaxTypeMatcher) {
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byType((c) -> {
|
||||
c.minOccurrence(((MinMaxTypeMatcher) matchingRule).getMin());
|
||||
c.maxOccurrence(((MinMaxTypeMatcher) matchingRule).getMax());
|
||||
}));
|
||||
}
|
||||
else if (matchingRule instanceof MaxTypeMatcher) {
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byType((c) -> {
|
||||
c.maxOccurrence(((MaxTypeMatcher) matchingRule).getMax());
|
||||
}));
|
||||
}
|
||||
else if (matchingRule instanceof TypeMatcher) {
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byType());
|
||||
}
|
||||
else if (matchingRule instanceof NumberTypeMatcher) {
|
||||
switch (((NumberTypeMatcher) matchingRule).getNumberType()) {
|
||||
case NUMBER:
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(RegexPatterns.number()));
|
||||
break;
|
||||
case INTEGER:
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(RegexPatterns.anInteger()));
|
||||
break;
|
||||
case DECIMAL:
|
||||
responseBodyMatchers.jsonPath(key, responseBodyMatchers.byRegex(RegexPatterns.aDouble()));
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unsupported number type!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Stessy Delcroix
|
||||
* @since
|
||||
*/
|
||||
class Names {
|
||||
|
||||
private final String consumer;
|
||||
|
||||
private final String producer;
|
||||
|
||||
private final String test;
|
||||
|
||||
Names(String[] strings) {
|
||||
this.consumer = strings[0];
|
||||
this.producer = strings[1];
|
||||
this.test = strings.length >= 2 ? strings[2] : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.consumer + "_" + this.producer;
|
||||
}
|
||||
|
||||
String getConsumer() {
|
||||
return consumer;
|
||||
}
|
||||
|
||||
String getProducer() {
|
||||
return producer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Stessy Delcroix
|
||||
* @since
|
||||
*/
|
||||
final class NamingUtil {
|
||||
|
||||
// consumer___producer___testname
|
||||
private static final String SEPARATOR = "___";
|
||||
|
||||
private NamingUtil() {
|
||||
}
|
||||
|
||||
static Names name(Contract contract) {
|
||||
String contractName = contract.getName();
|
||||
if (contractName == null || !contractName.contains(SEPARATOR)) {
|
||||
return new Names(new String[] { "Consumer", "Provider", "" });
|
||||
}
|
||||
return new Names(contractName.split(SEPARATOR));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* {@link ObjectMapper} singleton instance creation.
|
||||
* @author Stessy Delcroix
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public enum ObjectMapperFactory {
|
||||
|
||||
/**
|
||||
* Singleton instance constant.
|
||||
*/
|
||||
INSTANCE;
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
static {
|
||||
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
|
||||
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
|
||||
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
|
||||
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
|
||||
OBJECT_MAPPER.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
|
||||
}
|
||||
|
||||
public ObjectMapper getMapper() {
|
||||
return OBJECT_MAPPER;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import au.com.dius.pact.core.model.DefaultPactReader;
|
||||
import au.com.dius.pact.core.model.Pact;
|
||||
import au.com.dius.pact.core.model.PactSpecVersion;
|
||||
import au.com.dius.pact.core.model.RequestResponsePact;
|
||||
import au.com.dius.pact.core.model.messaging.MessagePact;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.util.DefaultIndenter;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.core.util.Separators;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.spec.ContractConverter;
|
||||
|
||||
/**
|
||||
* Converter of JSON PACT file.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
* @author Stessy Delcroix
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class PactContractConverter implements ContractConverter<Collection<Pact>> {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.INSTANCE.getMapper();
|
||||
|
||||
private final RequestResponseSCContractCreator requestResponseSCContractCreator = new RequestResponseSCContractCreator();
|
||||
|
||||
private final MessagingSCContractCreator messagingSCContractCreator = new MessagingSCContractCreator();
|
||||
|
||||
private final RequestResponsePactCreator requestResponsePactCreator = new RequestResponsePactCreator();
|
||||
|
||||
private final MessagePactCreator messagePactCreator = new MessagePactCreator();
|
||||
|
||||
@Override
|
||||
public boolean isAccepted(File file) {
|
||||
try {
|
||||
Pact pact = DefaultPactReader.INSTANCE.loadPact(file);
|
||||
return !pact.getInteractions().isEmpty();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contract> convertFrom(File file) {
|
||||
Pact pact = DefaultPactReader.INSTANCE.loadPact(file);
|
||||
if (pact instanceof RequestResponsePact) {
|
||||
return requestResponseSCContractCreator.convertFrom((RequestResponsePact) pact);
|
||||
}
|
||||
if (pact instanceof MessagePact) {
|
||||
return messagingSCContractCreator.convertFrom((MessagePact) pact);
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"We currently don't support pact contracts of type" + pact.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Pact> convertTo(Collection<Contract> contracts) {
|
||||
List<Pact> pactContracts = new ArrayList<>();
|
||||
Map<String, List<Contract>> groupedContracts = contracts.stream()
|
||||
.collect(Collectors.groupingBy(c -> NamingUtil.name(c).toString()));
|
||||
for (List<Contract> list : groupedContracts.values()) {
|
||||
List<Contract> httpOnly = list.stream().filter(c -> c.getRequest() != null).collect(Collectors.toList());
|
||||
List<Contract> messagingOnly = list.stream().filter(c -> c.getInput() != null).collect(Collectors.toList());
|
||||
RequestResponsePact responsePact = requestResponsePactCreator.createFromContract(httpOnly);
|
||||
if (responsePact != null) {
|
||||
pactContracts.add(responsePact);
|
||||
}
|
||||
MessagePact messagePact = messagePactCreator.createFromContract(messagingOnly);
|
||||
if (messagePact != null) {
|
||||
pactContracts.add(messagePact);
|
||||
}
|
||||
}
|
||||
return pactContracts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, byte[]> store(Collection<Pact> contracts) {
|
||||
return contracts.stream().collect(Collectors.toMap(this::name, c -> {
|
||||
try {
|
||||
return this.buildPrettyPrint(OBJECT_MAPPER.writeValueAsString(c.toMap(PactSpecVersion.V3))).getBytes();
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("The pact contract is not a valid map", e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
protected String name(Pact contract) {
|
||||
return contract.getConsumer().getName() + "_" + contract.getProvider().getName() + "_"
|
||||
+ Math.abs(contract.hashCode()) + ".json";
|
||||
}
|
||||
|
||||
private String buildPrettyPrint(String contract) {
|
||||
try {
|
||||
Object intermediateObjectForPrettyPrinting = OBJECT_MAPPER.reader().readValue(contract, Object.class);
|
||||
DefaultIndenter customIndenter = new DefaultIndenter(" ", "\n");
|
||||
return OBJECT_MAPPER
|
||||
.writer(new CustomPrettyPrinter().withArrayIndenter(customIndenter)
|
||||
.withObjectIndenter(customIndenter))
|
||||
.writeValueAsString(intermediateObjectForPrettyPrinting);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("WireMock response body could not be pretty printed");
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomPrettyPrinter extends DefaultPrettyPrinter {
|
||||
|
||||
@Override
|
||||
public CustomPrettyPrinter createInstance() {
|
||||
return new CustomPrettyPrinter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultPrettyPrinter withSeparators(Separators separators) {
|
||||
_separators = separators;
|
||||
_objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import au.com.dius.pact.core.model.ProviderState;
|
||||
|
||||
import org.springframework.cloud.contract.verifier.util.MetadataUtil;
|
||||
import org.springframework.cloud.contract.verifier.util.SpringCloudContractMetadata;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
public class PactMetaData implements SpringCloudContractMetadata {
|
||||
|
||||
/**
|
||||
* Key under which this metadata entry can be found in contract's metadata.
|
||||
*/
|
||||
public static final String METADATA_KEY = "pact";
|
||||
|
||||
/**
|
||||
* Metadata for adding {@link ProviderState} in Pact contract.
|
||||
*/
|
||||
private List<ProviderStateMetadata> providerStates = new ArrayList<>();
|
||||
|
||||
public List<ProviderStateMetadata> getProviderStates() {
|
||||
return this.providerStates;
|
||||
}
|
||||
|
||||
public void setProviderStates(List<ProviderStateMetadata> providerStates) {
|
||||
this.providerStates = providerStates;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static PactMetaData fromMetadata(Map<String, Object> metadata) {
|
||||
return MetadataUtil.fromMetadata(metadata, PactMetaData.METADATA_KEY, new PactMetaData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String key() {
|
||||
return METADATA_KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Metadata for converting Contract to Pact";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ProviderState} metadata.
|
||||
*/
|
||||
public static class ProviderStateMetadata {
|
||||
|
||||
/**
|
||||
* If set, will be added to PACT provider states.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
private Map<String, Object> params;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Map<String, Object> getParams() {
|
||||
return this.params;
|
||||
}
|
||||
|
||||
public void setParams(Map<String, Object> params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.consumer.dsl.PactDslWithState;
|
||||
import au.com.dius.pact.core.model.RequestResponsePact;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.spec.internal.Body;
|
||||
import org.springframework.cloud.contract.spec.internal.Cookies;
|
||||
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.RegexProperty;
|
||||
import org.springframework.cloud.contract.spec.internal.Request;
|
||||
import org.springframework.cloud.contract.spec.internal.Response;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
/**
|
||||
* Creator of {@link RequestResponsePact} instances.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @author Stessy Delcroix
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class RequestResponsePactCreator {
|
||||
|
||||
RequestResponsePact createFromContract(List<Contract> contracts) {
|
||||
if (CollectionUtils.isEmpty(contracts)) {
|
||||
return null;
|
||||
}
|
||||
Names names = NamingUtil.name(contracts.get(0));
|
||||
PactDslWithProvider pactDslWithProvider = ConsumerPactBuilder.consumer(names.getConsumer())
|
||||
.hasPactWith(names.getProducer());
|
||||
PactDslResponse pactDslResponse = null;
|
||||
for (Contract contract : contracts) {
|
||||
assertNoExecutionProperty(contract);
|
||||
PactDslRequestWithPath pactDslRequest = pactDslResponse != null
|
||||
? createPactDslRequestWithPath(contract, pactDslResponse)
|
||||
: createPactDslRequestWithPath(contract, pactDslWithProvider);
|
||||
pactDslResponse = createPactDslResponse(contract, pactDslRequest);
|
||||
}
|
||||
return pactDslResponse.toPact();
|
||||
}
|
||||
|
||||
private void assertNoExecutionProperty(Contract contract) {
|
||||
assertNoExecutionPropertyInBody(contract.getRequest().getBody(), DslProperty::getServerValue);
|
||||
assertNoExecutionPropertyInBody(contract.getResponse().getBody(), DslProperty::getClientValue);
|
||||
}
|
||||
|
||||
private void assertNoExecutionPropertyInBody(Body body,
|
||||
Function<DslProperty<?>, Object> dslPropertyValueExtractor) {
|
||||
traverseValues(body, dslPropertyValueExtractor, (Object object) -> {
|
||||
if (object instanceof ExecutionProperty) {
|
||||
throw new UnsupportedOperationException("We can't convert a contract that has execution property");
|
||||
}
|
||||
return object;
|
||||
});
|
||||
}
|
||||
|
||||
private void traverseValues(Object value, Function<DslProperty<?>, Object> dslPropertyValueExtractor,
|
||||
Function<Object, Object> function) {
|
||||
if (value instanceof DslProperty) {
|
||||
traverseValues(dslPropertyValueExtractor.apply((DslProperty<?>) value), dslPropertyValueExtractor,
|
||||
function);
|
||||
}
|
||||
else if (value instanceof Map) {
|
||||
((Map) value).values().forEach(v -> traverseValues(v, dslPropertyValueExtractor, function));
|
||||
}
|
||||
else if (value instanceof Collection) {
|
||||
((Collection<?>) value).forEach(v -> traverseValues(v, dslPropertyValueExtractor, function));
|
||||
}
|
||||
else {
|
||||
function.apply(value);
|
||||
}
|
||||
}
|
||||
|
||||
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract, PactDslResponse pactDslResponse) {
|
||||
PactDslRequestWithPath pactDslRequest = getPactDslRequest(contract,
|
||||
getPactDslWithStateFunction(pactDslResponse), getPactDslRequestWithPathBiFunction(pactDslResponse));
|
||||
Request request = contract.getRequest();
|
||||
final PactDslRequestWithPath finalPactDslRequest = pactDslRequest;
|
||||
if (request.getHeaders() != null) {
|
||||
request.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslRequest, h));
|
||||
}
|
||||
if (request.getCookies() != null) {
|
||||
pactDslRequest = processCookies(finalPactDslRequest, request.getCookies());
|
||||
}
|
||||
if (request.getBody() != null) {
|
||||
DslPart pactRequestBody = BodyConverter.toPactBody(request.getBody(), DslProperty::getClientValue);
|
||||
if (request.getBodyMatchers() != null) {
|
||||
pactRequestBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(request.getBodyMatchers()));
|
||||
}
|
||||
pactRequestBody
|
||||
.setGenerators(ValueGeneratorConverter.extract(request.getBody(), DslProperty::getClientValue));
|
||||
pactDslRequest = pactDslRequest.body(pactRequestBody);
|
||||
}
|
||||
return pactDslRequest;
|
||||
}
|
||||
|
||||
private Function<PactMetaData.ProviderStateMetadata, PactDslWithState> getPactDslWithStateFunction(
|
||||
PactDslResponse pactDslResponse) {
|
||||
return stateMetadata -> pactDslResponse.given(stateMetadata.getName(), stateMetadata.getParams());
|
||||
}
|
||||
|
||||
private BiFunction<String, Request, PactDslRequestWithPath> getPactDslRequestWithPathBiFunction(
|
||||
PactDslResponse pactDslResponse) {
|
||||
return (description, request) -> pactDslResponse.uponReceiving(description).path(url(request))
|
||||
.method(request.getMethod().getServerValue().toString());
|
||||
}
|
||||
|
||||
private PactDslRequestWithPath createPactDslRequestWithPath(Contract contract,
|
||||
PactDslWithProvider pactDslWithProvider) {
|
||||
PactDslRequestWithPath pactDslRequest = getPactDslRequest(contract,
|
||||
getPactDslWithStateFunction(pactDslWithProvider),
|
||||
getPactDslRequestWithPathBiFunction(pactDslWithProvider));
|
||||
Request request = contract.getRequest();
|
||||
final PactDslRequestWithPath finalPactDslRequest = pactDslRequest;
|
||||
if (request.getHeaders() != null) {
|
||||
request.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslRequest, h));
|
||||
}
|
||||
if (request.getBody() != null) {
|
||||
DslPart pactRequestBody = BodyConverter.toPactBody(request.getBody(), DslProperty::getServerValue);
|
||||
if (request.getBodyMatchers() != null) {
|
||||
pactRequestBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(request.getBodyMatchers()));
|
||||
}
|
||||
pactRequestBody
|
||||
.setGenerators(ValueGeneratorConverter.extract(request.getBody(), DslProperty::getClientValue));
|
||||
pactDslRequest = pactDslRequest.body(pactRequestBody);
|
||||
}
|
||||
return pactDslRequest;
|
||||
}
|
||||
|
||||
private Function<PactMetaData.ProviderStateMetadata, PactDslWithState> getPactDslWithStateFunction(
|
||||
PactDslWithProvider pactDslWithProvider) {
|
||||
return stateMetadata -> pactDslWithProvider.given(stateMetadata.getName(), stateMetadata.getParams());
|
||||
}
|
||||
|
||||
private BiFunction<String, Request, PactDslRequestWithPath> getPactDslRequestWithPathBiFunction(
|
||||
PactDslWithProvider pactDslWithProvider) {
|
||||
return (description, request) -> pactDslWithProvider.uponReceiving(description).path(url(request))
|
||||
.method(request.getMethod().getServerValue().toString());
|
||||
}
|
||||
|
||||
private PactDslRequestWithPath getPactDslRequest(Contract contract,
|
||||
Function<PactMetaData.ProviderStateMetadata, PactDslWithState> pactDslWithStateFunction,
|
||||
BiFunction<String, Request, PactDslRequestWithPath> pactDslRequestWithPathBiFunction) {
|
||||
PactDslWithState pactDslWithState = getPactDslWithState(contract, pactDslWithStateFunction);
|
||||
String description = StringUtils.isNotBlank(contract.getDescription()) ? contract.getDescription() : "";
|
||||
Request request = contract.getRequest();
|
||||
PactDslRequestWithPath pactDslRequest;
|
||||
if (pactDslWithState != null) {
|
||||
pactDslRequest = pactDslWithState.uponReceiving(description).path(url(request))
|
||||
.method(request.getMethod().getServerValue().toString());
|
||||
}
|
||||
else {
|
||||
pactDslRequest = pactDslRequestWithPathBiFunction.apply(description, request);
|
||||
}
|
||||
String query = query(request);
|
||||
if (StringUtils.isNotBlank(query)) {
|
||||
pactDslRequest = pactDslRequest.encodedQuery(query);
|
||||
}
|
||||
return pactDslRequest;
|
||||
}
|
||||
|
||||
private PactDslWithState getPactDslWithState(Contract contract,
|
||||
Function<PactMetaData.ProviderStateMetadata, PactDslWithState> pactDslWithStateFunction) {
|
||||
PactDslWithState pactDslWithState = null;
|
||||
if (contract.getMetadata().containsKey(PactMetaData.METADATA_KEY)) {
|
||||
PactMetaData metadata = PactMetaData.fromMetadata(contract.getMetadata());
|
||||
if (!metadata.getProviderStates().isEmpty()) {
|
||||
for (PactMetaData.ProviderStateMetadata stateMetadata : metadata.getProviderStates()) {
|
||||
if (pactDslWithState == null) {
|
||||
pactDslWithState = pactDslWithStateFunction.apply(stateMetadata);
|
||||
}
|
||||
else {
|
||||
pactDslWithState = pactDslWithState.given(stateMetadata.getName(), stateMetadata.getParams());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pactDslWithState;
|
||||
}
|
||||
|
||||
private String url(Request request) {
|
||||
if (request.getUrlPath() != null) {
|
||||
return request.getUrlPath().getServerValue().toString();
|
||||
}
|
||||
else if (request.getUrl() != null) {
|
||||
return request.getUrl().getServerValue().toString();
|
||||
}
|
||||
throw new IllegalStateException("No url provided");
|
||||
}
|
||||
|
||||
private String query(Request request) {
|
||||
final StringBuilder query = new StringBuilder();
|
||||
QueryParameters params = queryParams(request);
|
||||
if (params != null) {
|
||||
AtomicInteger index = new AtomicInteger();
|
||||
params.getParameters().forEach(p -> {
|
||||
query.append(p.getName()).append('=').append(p.getServerValue());
|
||||
if (index.incrementAndGet() < params.getParameters().size()) {
|
||||
query.append('&');
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
private QueryParameters queryParams(Request request) {
|
||||
if (request.getUrlPath() != null) {
|
||||
return request.getUrlPath().getQueryParameters();
|
||||
}
|
||||
else if (request.getUrl() != null) {
|
||||
return request.getUrl().getQueryParameters();
|
||||
}
|
||||
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.getName(), value);
|
||||
}
|
||||
else {
|
||||
String regex = getDslPropertyClientValue(header).toString();
|
||||
String example = getDslPropertyServerValue(header).toString();
|
||||
return pactDslRequest.matchHeader(header.getName(), regex, example);
|
||||
}
|
||||
}
|
||||
|
||||
private String stubSideCookieExample(Cookies cookies) {
|
||||
return cookies.asStubSideMap().entrySet().stream().map(Object::toString).collect(joining(";"));
|
||||
}
|
||||
|
||||
private Object getDslPropertyClientValue(Object o) {
|
||||
Object value = o;
|
||||
if (value instanceof DslProperty) {
|
||||
value = getDslPropertyClientValue(((DslProperty) value).getClientValue());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Object getDslPropertyServerValue(Object o) {
|
||||
Object value = o;
|
||||
if (value instanceof DslProperty) {
|
||||
value = getDslPropertyServerValue(((DslProperty) value).getServerValue());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private PactDslRequestWithPath processCookies(PactDslRequestWithPath pactDslRequest, Cookies cookies) {
|
||||
Map<String, Object> stubSideCookies = cookies.asStubSideMap();
|
||||
Collection<RegexProperty> regexProperties = stubSideCookies.values().stream()
|
||||
.filter(r -> r instanceof Pattern || r instanceof RegexProperty).map(RegexProperty::new)
|
||||
.collect(Collectors.toList());
|
||||
if (!regexProperties.isEmpty()) {
|
||||
String regex = regexProperties.stream().map(RegexProperty::pattern).collect(joining("|"));
|
||||
return pactDslRequest.matchHeader("Cookie", regex, testSideCookieExample(cookies));
|
||||
}
|
||||
else {
|
||||
return pactDslRequest.headers("Cookie", testSideCookieExample(cookies));
|
||||
}
|
||||
}
|
||||
|
||||
private String testSideCookieExample(Cookies cookies) {
|
||||
return cookies.asTestSideMap().entrySet().stream().map(Object::toString).collect(joining(";"));
|
||||
}
|
||||
|
||||
private PactDslResponse createPactDslResponse(Contract contract, PactDslRequestWithPath pactDslRequest) {
|
||||
Response response = contract.getResponse();
|
||||
PactDslResponse pactDslResponse = pactDslRequest.willRespondWith()
|
||||
.status((Integer) response.getStatus().getClientValue());
|
||||
|
||||
PactDslResponse finalPactDslResponse = pactDslResponse;
|
||||
if (response.getHeaders() != null) {
|
||||
response.getHeaders().getEntries().forEach(h -> processHeader(finalPactDslResponse, h));
|
||||
}
|
||||
|
||||
if (response.getCookies() != null) {
|
||||
pactDslResponse = processCookies(pactDslResponse, response.getCookies());
|
||||
}
|
||||
if (response.getBody() != null) {
|
||||
DslPart pactResponseBody = BodyConverter.toPactBody(response.getBody(), DslProperty::getClientValue);
|
||||
if (response.getBodyMatchers() != null) {
|
||||
pactResponseBody.setMatchers(MatchingRulesConverter.matchingRulesForBody(response.getBodyMatchers()));
|
||||
}
|
||||
pactResponseBody
|
||||
.setGenerators(ValueGeneratorConverter.extract(response.getBody(), DslProperty::getServerValue));
|
||||
pactDslResponse = pactDslResponse.body(pactResponseBody);
|
||||
}
|
||||
return pactDslResponse;
|
||||
}
|
||||
|
||||
private PactDslResponse processHeader(PactDslResponse pactDslResponse, Header header) {
|
||||
if (header.isSingleValue()) {
|
||||
String value = getDslPropertyClientValue(header).toString();
|
||||
return pactDslResponse.headers(Collections.singletonMap(header.getName(), value));
|
||||
}
|
||||
else {
|
||||
String regex = getDslPropertyServerValue(header).toString();
|
||||
String example = getDslPropertyClientValue(header).toString();
|
||||
return pactDslResponse.matchHeader(header.getName(), regex, example);
|
||||
}
|
||||
}
|
||||
|
||||
private PactDslResponse processCookies(PactDslResponse pactDslResponse, Cookies cookies) {
|
||||
Map<String, Object> testSideCookies = cookies.asTestSideMap();
|
||||
Collection<RegexProperty> regexProperties = testSideCookies.values().stream()
|
||||
.filter(p -> p instanceof Pattern || p instanceof RegexProperty).map(RegexProperty::new)
|
||||
.collect(Collectors.toList());
|
||||
if (!regexProperties.isEmpty()) {
|
||||
String regex = regexProperties.stream().map(RegexProperty::pattern).collect(joining("|"));
|
||||
return pactDslResponse.matchHeader("Cookie", regex, stubSideCookieExample(cookies));
|
||||
}
|
||||
else {
|
||||
return pactDslResponse.headers(Collections.singletonMap("Cookie", stubSideCookieExample(cookies)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import au.com.dius.pact.core.model.Interaction;
|
||||
import au.com.dius.pact.core.model.OptionalBody;
|
||||
import au.com.dius.pact.core.model.ProviderState;
|
||||
import au.com.dius.pact.core.model.Request;
|
||||
import au.com.dius.pact.core.model.RequestResponseInteraction;
|
||||
import au.com.dius.pact.core.model.RequestResponsePact;
|
||||
import au.com.dius.pact.core.model.Response;
|
||||
import au.com.dius.pact.core.model.matchingrules.DateMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.MatchingRule;
|
||||
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory;
|
||||
import au.com.dius.pact.core.model.matchingrules.MatchingRuleGroup;
|
||||
import au.com.dius.pact.core.model.matchingrules.MaxTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.MinMaxTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.MinTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.NullMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.NumberTypeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.RegexMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.RuleLogic;
|
||||
import au.com.dius.pact.core.model.matchingrules.TimeMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.TimestampMatcher;
|
||||
import au.com.dius.pact.core.model.matchingrules.TypeMatcher;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.spec.internal.RegexPatterns;
|
||||
import org.springframework.cloud.contract.verifier.util.JsonPaths;
|
||||
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
|
||||
|
||||
/**
|
||||
* Creator of {@link Contract} instances.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @author Stessy Delcroix
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class RequestResponseSCContractCreator {
|
||||
|
||||
private static final String FULL_BODY = "$";
|
||||
|
||||
Collection<Contract> convertFrom(RequestResponsePact pact) {
|
||||
return pact.getInteractions().stream().map(interaction -> Contract.make(contract -> {
|
||||
mapContractDescription(interaction, contract);
|
||||
mapContractRequest(interaction, contract);
|
||||
mapContractResponse(interaction, contract);
|
||||
})).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void mapContractDescription(Interaction interaction, Contract contract) {
|
||||
contract.description(buildDescription(interaction));
|
||||
}
|
||||
|
||||
private void mapContractRequest(Interaction inter, Contract contract) {
|
||||
contract.request((contractRequest) -> {
|
||||
if (!(inter instanceof RequestResponseInteraction)) {
|
||||
return;
|
||||
}
|
||||
RequestResponseInteraction interaction = (RequestResponseInteraction) inter;
|
||||
Request pactRequest = interaction.getRequest();
|
||||
contractRequest.method(pactRequest.getMethod());
|
||||
mapRequestUrl(contractRequest, pactRequest);
|
||||
|
||||
if (!pactRequest.getHeaders().isEmpty()) {
|
||||
mapRequestHeaders(contractRequest, pactRequest);
|
||||
}
|
||||
if (pactRequest.getHeaders().containsKey("Cookie")) {
|
||||
mapRequestCookies(contractRequest, pactRequest);
|
||||
}
|
||||
if (pactRequest.getBody().getState() == OptionalBody.State.PRESENT) {
|
||||
mapRequestBody(contractRequest, pactRequest);
|
||||
}
|
||||
MatchingRuleCategory bodyRules = pactRequest.getMatchingRules().rulesForCategory("body");
|
||||
if (!bodyRules.getMatchingRules().isEmpty()) {
|
||||
mapRequestBodyRules(contractRequest, bodyRules);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void mapContractResponse(Interaction inter, Contract contract) {
|
||||
contract.response((contractResponse) -> {
|
||||
if (!(inter instanceof RequestResponseInteraction)) {
|
||||
return;
|
||||
}
|
||||
RequestResponseInteraction interaction = (RequestResponseInteraction) inter;
|
||||
Response pactResponse = interaction.getResponse();
|
||||
contractResponse.status(pactResponse.getStatus());
|
||||
if (pactResponse.getBody().isPresent()) {
|
||||
mapResponseBody(contractResponse, pactResponse);
|
||||
}
|
||||
|
||||
MatchingRuleCategory bodyRules = pactResponse.getMatchingRules().rulesForCategory("body");
|
||||
if (!bodyRules.getMatchingRules().isEmpty()) {
|
||||
mapResponseBodyRules(contractResponse, pactResponse, bodyRules);
|
||||
}
|
||||
if (!pactResponse.getHeaders().isEmpty()) {
|
||||
mapResponseHeaders(contractResponse, pactResponse);
|
||||
}
|
||||
|
||||
if (pactResponse.getHeaders().containsKey("Cookie")) {
|
||||
mapResponseCookies(contractResponse, pactResponse);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void mapResponseBodyRules(org.springframework.cloud.contract.spec.internal.Response contractResponse,
|
||||
Response pactResponse, MatchingRuleCategory bodyRules) {
|
||||
contractResponse.bodyMatchers((bodyMatchers) -> {
|
||||
bodyRules.getMatchingRules().forEach((key, matchindRuleGroup) -> {
|
||||
if (matchindRuleGroup.getRuleLogic() != RuleLogic.AND) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only the AND combination rule logic is supported");
|
||||
}
|
||||
if (FULL_BODY.equals(key)) {
|
||||
JsonPaths jsonPaths = JsonToJsonPathsConverter
|
||||
.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(
|
||||
new String(pactResponse.getBody().getValue()));
|
||||
jsonPaths.forEach(
|
||||
(jsonPath) -> bodyMatchers.jsonPath(jsonPath.keyBeforeChecking(), bodyMatchers.byType()));
|
||||
}
|
||||
else {
|
||||
matchindRuleGroup.getRules().forEach((matchingRule) -> {
|
||||
if (matchingRule instanceof NullMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byNull());
|
||||
}
|
||||
else if (matchingRule instanceof RegexMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(((RegexMatcher) matchingRule).getRegex()));
|
||||
}
|
||||
else if (matchingRule instanceof DateMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byDate());
|
||||
}
|
||||
else if (matchingRule instanceof TimeMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byTime());
|
||||
}
|
||||
else if (matchingRule instanceof TimestampMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byTimestamp());
|
||||
}
|
||||
else if (matchingRule instanceof MinTypeMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byType((valueHolder) -> valueHolder
|
||||
.minOccurrence((((MinTypeMatcher) matchingRule).getMin()))));
|
||||
}
|
||||
else if (matchingRule instanceof MinMaxTypeMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byType((valueHolder) -> {
|
||||
valueHolder.minOccurrence((((MinMaxTypeMatcher) matchingRule).getMin()));
|
||||
valueHolder.maxOccurrence((((MinMaxTypeMatcher) matchingRule).getMax()));
|
||||
}));
|
||||
}
|
||||
else if (matchingRule instanceof MaxTypeMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byType((valueHolder) -> valueHolder
|
||||
.maxOccurrence((((MaxTypeMatcher) matchingRule).getMax()))));
|
||||
}
|
||||
else if (matchingRule instanceof TypeMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byType());
|
||||
}
|
||||
else if (matchingRule instanceof NumberTypeMatcher) {
|
||||
switch (((NumberTypeMatcher) matchingRule).getNumberType()) {
|
||||
case NUMBER:
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.number()));
|
||||
break;
|
||||
case INTEGER:
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.anInteger()));
|
||||
break;
|
||||
case DECIMAL:
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.aDouble()));
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported number type!");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void mapResponseBody(org.springframework.cloud.contract.spec.internal.Response contractResponse,
|
||||
Response pactResponse) {
|
||||
Object parsedBody = BodyConverter.toSCCBody(pactResponse);
|
||||
if (parsedBody instanceof Map) {
|
||||
contractResponse.body((Map) parsedBody);
|
||||
}
|
||||
else if (parsedBody instanceof List) {
|
||||
contractResponse.body((List) parsedBody);
|
||||
}
|
||||
else {
|
||||
contractResponse.body(parsedBody.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void mapRequestBodyRules(org.springframework.cloud.contract.spec.internal.Request contractRequest,
|
||||
MatchingRuleCategory bodyRules) {
|
||||
contractRequest.bodyMatchers((bodyMatchers) -> {
|
||||
bodyRules.getMatchingRules().forEach((key, matchingRuleGroup) -> {
|
||||
if (matchingRuleGroup.getRuleLogic() != RuleLogic.AND) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only the AND combination rule logic is supported");
|
||||
}
|
||||
|
||||
matchingRuleGroup.getRules().forEach((matchingRule) -> {
|
||||
if (matchingRule instanceof RegexMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(((RegexMatcher) matchingRule).getRegex()));
|
||||
}
|
||||
else if (matchingRule instanceof DateMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byDate());
|
||||
}
|
||||
else if (matchingRule instanceof TimeMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byTime());
|
||||
}
|
||||
else if (matchingRule instanceof TimestampMatcher) {
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byTimestamp());
|
||||
}
|
||||
else if (matchingRule instanceof NumberTypeMatcher) {
|
||||
switch (((NumberTypeMatcher) matchingRule).getNumberType()) {
|
||||
case NUMBER:
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.number()));
|
||||
break;
|
||||
case INTEGER:
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.anInteger()));
|
||||
break;
|
||||
case DECIMAL:
|
||||
bodyMatchers.jsonPath(key, bodyMatchers.byRegex(RegexPatterns.aDouble()));
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unsupported number type!");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void mapRequestBody(org.springframework.cloud.contract.spec.internal.Request contractRequest,
|
||||
Request pactRequest) {
|
||||
Object parsedBody = BodyConverter.toSCCBody(pactRequest);
|
||||
if (parsedBody instanceof Map) {
|
||||
contractRequest.body((Map) parsedBody);
|
||||
}
|
||||
else if (parsedBody instanceof List) {
|
||||
contractRequest.body((List) parsedBody);
|
||||
}
|
||||
else {
|
||||
contractRequest.body(parsedBody.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void mapRequestCookies(org.springframework.cloud.contract.spec.internal.Request contractRequest,
|
||||
Request pactRequest) {
|
||||
MatchingRuleCategory headerRules = pactRequest.getMatchingRules().rulesForCategory("header");
|
||||
String[] splitCookiesHeader = pactRequest.getHeaders().get("Cookie").get(0).split(";");
|
||||
Map<String, String> foundCookies = Stream.of(splitCookiesHeader).map((cookieHeader) -> cookieHeader.split("="))
|
||||
.collect(Collectors.toMap(splittedCookieHeader -> splittedCookieHeader[0],
|
||||
splittedCookieHeader -> splittedCookieHeader[1]));
|
||||
|
||||
contractRequest.cookies((cookies) -> foundCookies.forEach((key, value) -> {
|
||||
if (headerRules.getMatchingRules().containsKey("Cookie")) {
|
||||
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get("Cookie");
|
||||
if (matchingRuleGroup.getRules().size() > 1) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only 1 rule at a time for a header is supported");
|
||||
}
|
||||
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
|
||||
if (matchingRule instanceof RegexMatcher) {
|
||||
cookies.cookie(key,
|
||||
contractRequest.$(
|
||||
contractRequest.c(contractRequest.regex(((RegexMatcher) matchingRule).getRegex())),
|
||||
contractRequest.p(value)));
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only the header matcher of type regex is supported");
|
||||
}
|
||||
}
|
||||
else {
|
||||
cookies.cookie(key, value);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void mapResponseCookies(org.springframework.cloud.contract.spec.internal.Response contractResponse,
|
||||
Response pactResponse) {
|
||||
MatchingRuleCategory headerRules = pactResponse.getMatchingRules().rulesForCategory("header");
|
||||
String[] splitCookiesHeader = pactResponse.getHeaders().get("Cookie").get(0).split(";");
|
||||
Map<String, String> foundCookies = Stream.of(splitCookiesHeader).map((cookieHeader) -> cookieHeader.split("="))
|
||||
.collect(Collectors.toMap(splittedCookieHeader -> splittedCookieHeader[0],
|
||||
splittedCookieHeader -> splittedCookieHeader[1]));
|
||||
|
||||
contractResponse.cookies((cookies) -> foundCookies.forEach((key, value) -> {
|
||||
if (headerRules.getMatchingRules().containsKey("Cookie")) {
|
||||
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get("Cookie");
|
||||
if (matchingRuleGroup.getRules().size() > 1) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only 1 rule at a time for a header is supported");
|
||||
}
|
||||
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
|
||||
if (matchingRule instanceof RegexMatcher) {
|
||||
cookies.cookie(key, contractResponse.$(
|
||||
contractResponse.p(
|
||||
contractResponse.regex(Pattern.compile(((RegexMatcher) matchingRule).getRegex()))),
|
||||
contractResponse.c(value)));
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only the header matcher of type regex is supported");
|
||||
}
|
||||
}
|
||||
else {
|
||||
cookies.cookie(key, value);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void mapRequestUrl(org.springframework.cloud.contract.spec.internal.Request contractRequest,
|
||||
Request pactRequest) {
|
||||
if (!pactRequest.getQuery().isEmpty()) {
|
||||
contractRequest.url(pactRequest.getPath(),
|
||||
(url) -> url.queryParameters((queryParameters) -> pactRequest.getQuery().forEach((key,
|
||||
values) -> values.forEach((singleValue) -> queryParameters.parameter(key, singleValue)))));
|
||||
}
|
||||
else {
|
||||
contractRequest.url(pactRequest.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
private void mapRequestHeaders(org.springframework.cloud.contract.spec.internal.Request contractRequest,
|
||||
Request pactRequest) {
|
||||
MatchingRuleCategory headerRules = pactRequest.getMatchingRules().rulesForCategory("header");
|
||||
contractRequest.headers((headers) -> pactRequest.getHeaders().forEach((key, values) -> {
|
||||
if (key.equalsIgnoreCase("Cookie")) {
|
||||
return;
|
||||
}
|
||||
if (headerRules.getMatchingRules().containsKey(key)) {
|
||||
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get(key);
|
||||
if (matchingRuleGroup.getRules().size() > 1) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only 1 rule at a time for a header is supported");
|
||||
}
|
||||
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
|
||||
if (matchingRule instanceof RegexMatcher) {
|
||||
values.forEach((value) -> {
|
||||
headers.header(key,
|
||||
contractRequest.$(
|
||||
contractRequest
|
||||
.c(contractRequest.regex(((RegexMatcher) matchingRule).getRegex())),
|
||||
contractRequest.p(value)));
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only the header matcher of type regex is supported");
|
||||
}
|
||||
}
|
||||
else {
|
||||
values.forEach((value) -> headers.header(key, value));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void mapResponseHeaders(org.springframework.cloud.contract.spec.internal.Response contractResponse,
|
||||
Response pactResponse) {
|
||||
MatchingRuleCategory headerRules = pactResponse.getMatchingRules().rulesForCategory("header");
|
||||
contractResponse.headers((headers) -> pactResponse.getHeaders().forEach((key, values) -> {
|
||||
if (key.equalsIgnoreCase("Cookie")) {
|
||||
return;
|
||||
}
|
||||
if (headerRules.getMatchingRules().containsKey(key)) {
|
||||
MatchingRuleGroup matchingRuleGroup = headerRules.getMatchingRules().get(key);
|
||||
if (matchingRuleGroup.getRules().size() > 1) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only 1 rule at a time for a header is supported");
|
||||
}
|
||||
MatchingRule matchingRule = matchingRuleGroup.getRules().get(0);
|
||||
if (matchingRule instanceof RegexMatcher) {
|
||||
values.forEach((value) -> {
|
||||
headers.header(key,
|
||||
contractResponse.$(
|
||||
contractResponse.p(contractResponse
|
||||
.regex(Pattern.compile(((RegexMatcher) matchingRule).getRegex()))),
|
||||
contractResponse.c(value)));
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException(
|
||||
"Currently only the header matcher of type regex is supported");
|
||||
}
|
||||
}
|
||||
else {
|
||||
values.forEach((value) -> headers.header(key, value));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private String buildDescription(Interaction interaction) {
|
||||
StringBuilder description = new StringBuilder(interaction.getDescription());
|
||||
List<ProviderState> providerStates = interaction.getProviderStates();
|
||||
for (ProviderState providerState : providerStates) {
|
||||
description.append(" ").append(providerState.getName());
|
||||
Map<String, Object> params = providerState.getParams();
|
||||
if (!params.isEmpty()) {
|
||||
description.append("(");
|
||||
params.forEach((k, v) -> {
|
||||
description.append(k).append(": ").append(v.toString()).append(", ");
|
||||
});
|
||||
description.delete(description.length() - 2, description.length());
|
||||
description.append(")");
|
||||
}
|
||||
}
|
||||
return description.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import au.com.dius.pact.core.model.generators.Category;
|
||||
import au.com.dius.pact.core.model.generators.DateGenerator;
|
||||
import au.com.dius.pact.core.model.generators.DateTimeGenerator;
|
||||
import au.com.dius.pact.core.model.generators.Generator;
|
||||
import au.com.dius.pact.core.model.generators.Generators;
|
||||
import au.com.dius.pact.core.model.generators.RandomBooleanGenerator;
|
||||
import au.com.dius.pact.core.model.generators.RandomDecimalGenerator;
|
||||
import au.com.dius.pact.core.model.generators.RandomHexadecimalGenerator;
|
||||
import au.com.dius.pact.core.model.generators.RandomIntGenerator;
|
||||
import au.com.dius.pact.core.model.generators.RandomStringGenerator;
|
||||
import au.com.dius.pact.core.model.generators.RegexGenerator;
|
||||
import au.com.dius.pact.core.model.generators.TimeGenerator;
|
||||
import au.com.dius.pact.core.model.generators.UuidGenerator;
|
||||
import groovy.lang.GString;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
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.spec.internal.RegexProperty;
|
||||
import org.springframework.cloud.contract.verifier.util.ContentUtils;
|
||||
|
||||
/**
|
||||
* Convert a value for a given {@link Generator}.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @author Stessy Delcroix
|
||||
* @since 2.0.0
|
||||
*/
|
||||
final class ValueGeneratorConverter {
|
||||
|
||||
private static final String INTEGER_PATTERN = "-?(\\d+)";
|
||||
|
||||
private static final Pattern INTEGER = Pattern.compile(INTEGER_PATTERN);
|
||||
|
||||
private static final String DECIMAL_PATTERN = "-?(\\d*\\.\\d+)";
|
||||
|
||||
private static final Pattern DECIMAL = Pattern.compile(DECIMAL_PATTERN);
|
||||
|
||||
private static final String HEX_PATTERN = "[a-fA-F0-9]+";
|
||||
|
||||
private static final Pattern HEX = Pattern.compile(HEX_PATTERN);
|
||||
|
||||
private static final String ALPHA_NUMERIC_PATTERN = "[a-zA-Z0-9]+";
|
||||
|
||||
private static final Pattern ALPHA_NUMERIC = Pattern.compile(ALPHA_NUMERIC_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 UUID = Pattern.compile(UUID_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_DATE = Pattern.compile(ANY_DATE_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_TIME = Pattern.compile(ANY_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 ANY_DATE_TIME = Pattern.compile(ANY_DATE_TIME_PATTERN);
|
||||
|
||||
private static final String TRUE_OR_FALSE_PATTERN = "(true|false)";
|
||||
|
||||
private static final Pattern TRUE_OR_FALSE = Pattern.compile(TRUE_OR_FALSE_PATTERN);
|
||||
|
||||
private ValueGeneratorConverter() {
|
||||
|
||||
}
|
||||
|
||||
static DslProperty<Object> convert(Generator generator,
|
||||
BiFunction<Pattern, Object, DslProperty<Object>> dslPropertyProvider) {
|
||||
Pattern pattern = null;
|
||||
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(((RegexGenerator) generator).getRegex());
|
||||
}
|
||||
else if (generator instanceof UuidGenerator) {
|
||||
pattern = UUID;
|
||||
}
|
||||
else if (generator instanceof DateGenerator) {
|
||||
pattern = getDateTimePattern(((DateGenerator) generator).getFormat(), ANY_DATE);
|
||||
}
|
||||
else if (generator instanceof TimeGenerator) {
|
||||
pattern = getDateTimePattern(((TimeGenerator) generator).getFormat(), ANY_TIME);
|
||||
}
|
||||
else if (generator instanceof DateTimeGenerator) {
|
||||
pattern = getDateTimePattern(((DateTimeGenerator) generator).getFormat(), 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.getClass().getSimpleName());
|
||||
}
|
||||
else {
|
||||
Object generatedValue = generator.generate(new HashMap<>(), null);
|
||||
return dslPropertyProvider.apply(pattern, generatedValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static Pattern getDateTimePattern(String format, Pattern defaultPattern) {
|
||||
return StringUtils.isNotBlank(format) ? Pattern.compile(format) : defaultPattern;
|
||||
}
|
||||
|
||||
static Generators extract(Body body, Function<DslProperty<?>, Object> dslPropertyValueProvider) {
|
||||
Generators generators = new Generators();
|
||||
traverse(body, dslPropertyValueProvider, "", generators, Category.BODY);
|
||||
return generators;
|
||||
}
|
||||
|
||||
static Generators extract(OutputMessage message, Function<DslProperty<?>, Object> dslPropertyValueProvider) {
|
||||
Generators generators = new Generators();
|
||||
traverse(message.getBody(), dslPropertyValueProvider, "", generators, Category.BODY);
|
||||
return generators;
|
||||
}
|
||||
|
||||
private static void traverse(Object value, Function<DslProperty<?>, Object> dslPropertyValueProvider, String path,
|
||||
Generators generators, Category category) {
|
||||
Object v = value;
|
||||
if (v instanceof DslProperty) {
|
||||
v = dslPropertyValueProvider.apply((DslProperty<?>) v);
|
||||
}
|
||||
if (v instanceof GString) {
|
||||
v = ContentUtils.extractValue((GString) v, dslPropertyValueProvider);
|
||||
}
|
||||
if (v instanceof Map) {
|
||||
((Map) v).forEach(
|
||||
(key, val) -> traverse(val, dslPropertyValueProvider, path + "." + key, generators, category));
|
||||
}
|
||||
else if (v instanceof Collection) {
|
||||
AtomicInteger index = new AtomicInteger();
|
||||
((Collection<?>) v).forEach(val -> traverse(val, dslPropertyValueProvider,
|
||||
path + "[" + index.getAndIncrement() + "]", generators, category));
|
||||
}
|
||||
else if (v instanceof DslProperty) {
|
||||
traverse(v, dslPropertyValueProvider, path, generators, category);
|
||||
}
|
||||
else if (v instanceof RegexProperty || v instanceof Pattern) {
|
||||
RegexProperty regexProperty = new RegexProperty(v);
|
||||
switch (regexProperty.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, new UuidGenerator());
|
||||
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(regexProperty.pattern()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
org.springframework.cloud.contract.spec.ContractConverter=\
|
||||
org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
|
||||
org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\
|
||||
org.springframework.cloud.contract.stubrunner.PactStubDownloaderBuilder
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
import au.com.dius.pact.core.model.Pact
|
||||
import au.com.dius.pact.core.model.PactSource
|
||||
import au.com.dius.pact.provider.junitsupport.loader.PactLoader
|
||||
import au.com.dius.pact.core.support.expressions.ValueResolver
|
||||
import com.github.tomakehurst.wiremock.stubbing.StubMapping
|
||||
import spock.lang.Ignore
|
||||
import spock.lang.Specification
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
@Ignore("Flakey")
|
||||
class PactStubDownloaderBuilderSpec extends Specification {
|
||||
|
||||
def "should retrieve pacts from broker"() throws IOException {
|
||||
given:
|
||||
Collection<Pact> pacts = new PactContractConverter().
|
||||
convertTo([Contract.make {
|
||||
request {
|
||||
url "/foo"
|
||||
method GET()
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
}
|
||||
}, Contract.make {
|
||||
request {
|
||||
url "/bar"
|
||||
method GET()
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
}
|
||||
}])
|
||||
StubRunnerOptions options = new StubRunnerOptionsBuilder()
|
||||
.withProperties(props())
|
||||
.build()
|
||||
PactStubDownloader downloader = new PactStubDownloader(options) {
|
||||
@Override
|
||||
PactLoader pactBrokerLoader(ValueResolver resolver,
|
||||
List<String> tags) {
|
||||
return new PactLoader() {
|
||||
@Override
|
||||
List<Pact> load(String providerName) {
|
||||
return pacts
|
||||
}
|
||||
|
||||
@Override
|
||||
PactSource getPactSource() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
when:
|
||||
Map.Entry<StubConfiguration, File> entry = downloader
|
||||
.
|
||||
downloadAndUnpackStubJar(new StubConfiguration("com.example:bobby:+:classifier"))
|
||||
then:
|
||||
entry != null
|
||||
entry.getValue().exists()
|
||||
File contracts = new File(entry.getValue(), "com/example/bobby/contracts")
|
||||
contracts.exists()
|
||||
contracts.list() != null
|
||||
File mappings = new File(entry.getValue(), "com/example/bobby/mappings")
|
||||
mappings.exists()
|
||||
mappings.list() != null
|
||||
mappings.list().size() == 2
|
||||
StubMapping.buildFrom(new String(Files.
|
||||
readAllBytes(mappings.listFiles()[0].toPath())))
|
||||
StubMapping.buildFrom(new String(Files.
|
||||
readAllBytes(mappings.listFiles()[1].toPath())))
|
||||
}
|
||||
|
||||
Map<String, String> props() {
|
||||
Map<String, String> map = new HashMap<>()
|
||||
// map.put("pactbroker.host", "localhost")
|
||||
// map.put("pactbroker.port", String.valueOf(this.port))
|
||||
// map.put("pactbroker.host", "test.pact.dius.com.au")
|
||||
// map.put("pactbroker.port", "443")
|
||||
// map.put("pactbroker.protocol", "https")
|
||||
// map.put("pactbroker.auth.scheme", "Basic")
|
||||
// map.put("pactbroker.auth.username", "dXfltyFMgNOFZAxr8io9wJ37iUpY42M")
|
||||
// map.put("pactbroker.auth.password", "O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1")
|
||||
return map
|
||||
}
|
||||
|
||||
// @After
|
||||
// void tearDown() {
|
||||
// SnapshotRecordResult recording = WireMock.stopRecording()
|
||||
// List<StubMapping> mappings = recording.getStubMappings()
|
||||
// storeMappings(mappings)
|
||||
// }
|
||||
|
||||
// private void recordFromBroker() {
|
||||
// WireMock.startRecording(WireMock.recordSpec()
|
||||
// .forTarget("https://test.pact.dius.com.au")
|
||||
// .extractTextBodiesOver(9999999L)
|
||||
// .extractBinaryBodiesOver(9999999L)
|
||||
// .makeStubsPersistent(false))
|
||||
// }
|
||||
|
||||
// private void storeMappings(List<StubMapping> mappings) {
|
||||
// try {
|
||||
// File proxiedStubs = new File("target/stubs")
|
||||
// proxiedStubs.mkdirs()
|
||||
// for (StubMapping mapping : mappings) {
|
||||
// File stub = new File(proxiedStubs, "foo" + ".json")
|
||||
// stub.createNewFile()
|
||||
// Files.write(stub.toPath(), mapping.toString().getBytes())
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// throw new RuntimeException(e)
|
||||
// }
|
||||
// }
|
||||
|
||||
def "should retrieve pacts from broker using stubrunner options"() throws IOException {
|
||||
given:
|
||||
StubRunnerOptions options = new StubRunnerOptionsBuilder()
|
||||
.withStubRepositoryRoot("pact://https://test.pact.dius.com.au:443")
|
||||
.withUsername("dXfltyFMgNOFZAxr8io9wJ37iUpY42M")
|
||||
.withPassword("O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1")
|
||||
.build()
|
||||
PactStubDownloader downloader = new PactStubDownloader(options)
|
||||
when:
|
||||
Map.Entry<StubConfiguration, File> entry = downloader
|
||||
.
|
||||
downloadAndUnpackStubJar(new StubConfiguration("com.example:bobby:+:classifier"))
|
||||
then:
|
||||
entry != null
|
||||
entry.getValue().exists()
|
||||
File contracts = new File(entry.getValue(), "com/example/bobby/contracts")
|
||||
contracts.exists()
|
||||
contracts.list() != null
|
||||
contracts.list().size() > 0
|
||||
File mappings = new File(entry.getValue(), "com/example/bobby/mappings")
|
||||
mappings.exists()
|
||||
mappings.list() != null
|
||||
mappings.list().size() > 0
|
||||
}
|
||||
}
|
||||
@@ -1,847 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 java.nio.file.Files
|
||||
|
||||
import au.com.dius.pact.core.model.Pact
|
||||
import au.com.dius.pact.core.model.PactSpecVersion
|
||||
import groovy.json.JsonOutput
|
||||
import org.skyscreamer.jsonassert.JSONAssert
|
||||
import spock.lang.Issue
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Subject
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.verifier.TestGenerator
|
||||
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
|
||||
|
||||
/**
|
||||
* @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 pactv3Issue889Json = new File(PactContractConverterSpec.
|
||||
getResource("/pact/pact_v3_issue_889.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())
|
||||
File pact1043Json = new File(PactContractConverterSpec.
|
||||
getResource("/pact/for-test-generation/pact_1043.json").toURI())
|
||||
File pact1134Json = new File(PactContractConverterSpec.
|
||||
getResource("/pact/1134/pact_1134.json").toURI())
|
||||
@Subject
|
||||
PactContractConverter converter = new PactContractConverter()
|
||||
|
||||
def "should accept json files that are pact files"() {
|
||||
expect:
|
||||
converter.isAccepted(pactJson)
|
||||
}
|
||||
|
||||
def "should reject json files that are invalid pact files"() {
|
||||
given:
|
||||
File invalidPact = new File(PactContractConverterSpec.
|
||||
getResource("/pact/invalid_pact.json").toURI())
|
||||
expect:
|
||||
!converter.isAccepted(invalidPact)
|
||||
}
|
||||
|
||||
def "should convert from pact to contract"() {
|
||||
given:
|
||||
Contract expectedContract = Contract.make {
|
||||
description("a retrieve Mallory request a user with username 'username' and password 'password' exists")
|
||||
request {
|
||||
method(GET())
|
||||
url("/mallory") {
|
||||
queryParameters {
|
||||
parameter("name", "ron")
|
||||
parameter("status", "good")
|
||||
}
|
||||
}
|
||||
headers {
|
||||
header(contentType(), applicationJson())
|
||||
}
|
||||
body(id: "123", method: "create")
|
||||
bodyMatchers {
|
||||
jsonPath('$.id', byRegex("[0-9]{3}"))
|
||||
}
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
headers {
|
||||
header(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())
|
||||
}
|
||||
}
|
||||
}
|
||||
when:
|
||||
Collection<Contract> contracts = converter.convertFrom(pactJson)
|
||||
then:
|
||||
contracts == [expectedContract]
|
||||
}
|
||||
|
||||
@Issue("#509")
|
||||
def "should convert from pact with matching rules to whole body to contract"() {
|
||||
given:
|
||||
Contract expectedContract = Contract.make {
|
||||
description("a request to POST a person provider accepts a new person")
|
||||
request {
|
||||
method(POST())
|
||||
url("/user-service/users")
|
||||
headers {
|
||||
header(contentType(), applicationJson())
|
||||
}
|
||||
body(firstName: "Arthur", lastName: "Dent")
|
||||
}
|
||||
response {
|
||||
status(201)
|
||||
headers {
|
||||
header(contentType(), applicationJsonUtf8())
|
||||
}
|
||||
body(firstName: "Arthur", id: 42, lastName: "Dent")
|
||||
bodyMatchers {
|
||||
jsonPath('''$.['firstName']''', byType())
|
||||
jsonPath('''$.['id']''', byType())
|
||||
jsonPath('''$.['lastName']''', byType())
|
||||
}
|
||||
}
|
||||
}
|
||||
when:
|
||||
Collection<Contract> contracts = converter.convertFrom(pact509Json)
|
||||
then:
|
||||
contracts == [expectedContract]
|
||||
}
|
||||
|
||||
def "should convert from contract to pact"() {
|
||||
given:
|
||||
Collection<Contract> inputContracts = [
|
||||
Contract.make {
|
||||
name("my_consumer___my_producer___testname")
|
||||
description("a retrieve Mallory request")
|
||||
request {
|
||||
method(GET())
|
||||
url("/mallory") {
|
||||
queryParameters {
|
||||
parameter("name", "ron")
|
||||
parameter("status", "good")
|
||||
}
|
||||
}
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body(
|
||||
id: 123,
|
||||
method: $(stub(regex("[0][1][2]"))),
|
||||
something: "foo"
|
||||
)
|
||||
bodyMatchers {
|
||||
jsonPath('$.id', byRegex("[0-9]{3}"))
|
||||
jsonPath('$.something', byEquality())
|
||||
}
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body([[
|
||||
[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",
|
||||
nullValue : null]
|
||||
]])
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
String expectedJson = '''
|
||||
{
|
||||
"provider": {
|
||||
"name": "my_producer"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "my_consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "a retrieve Mallory request",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "\\/mallory",
|
||||
"query": {
|
||||
"name": ["ron"],
|
||||
"status": ["good"]
|
||||
},
|
||||
"headers": {
|
||||
"Content-Type": "application\\/json"
|
||||
},
|
||||
"body": {
|
||||
"id": 123,
|
||||
"method": "012",
|
||||
"something": "foo"
|
||||
},
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$.id": {
|
||||
"matchers": [{
|
||||
"match": "regex",
|
||||
"regex": "[0-9]{3}"
|
||||
}]
|
||||
},
|
||||
"$.something": {
|
||||
"matchers": [{
|
||||
"match": "equality"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application\\/json"
|
||||
},
|
||||
"body": [
|
||||
[
|
||||
{
|
||||
"email": "rddtGwwWMEhnkAPEmsyE",
|
||||
"id": "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
|
||||
"number": 923,
|
||||
"positiveInteger": 1234567890,
|
||||
"negativeInteger": -1234567890,
|
||||
"positiveDecimalNumber": 123.4567890,
|
||||
"negativeDecimalNumber": -123.4567890,
|
||||
"userName": "AJQrokEGPAVdOHprQpKP",
|
||||
"something": "foo",
|
||||
"nullValue": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"matchingRules": {
|
||||
"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"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-jvm": {
|
||||
"version": "4.3.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
when:
|
||||
Pact pact = converter.convertTo(inputContracts).get(0)
|
||||
then:
|
||||
String actual = JsonOutput.toJson(pact.toMap(PactSpecVersion.V3))
|
||||
JSONAssert.assertEquals(expectedJson, actual, false)
|
||||
}
|
||||
|
||||
def "should fail to convert from contract to pact when contract has execution property in request"() {
|
||||
given:
|
||||
Collection<Contract> inputContracts = [
|
||||
Contract.make {
|
||||
request {
|
||||
method(GET())
|
||||
url("/mallory")
|
||||
body(
|
||||
id: $(c("foo"), p(execute("foo")))
|
||||
)
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
when:
|
||||
converter.convertTo(inputContracts)
|
||||
then:
|
||||
def e = thrown(UnsupportedOperationException)
|
||||
e.message.contains("execution property")
|
||||
}
|
||||
|
||||
def "should fail to convert from contract to pact when contract has execution property in response"() {
|
||||
given:
|
||||
Collection<Contract> inputContracts = [
|
||||
Contract.make {
|
||||
request {
|
||||
method(GET())
|
||||
url("/mallory")
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
body(
|
||||
id: $(c(execute("foo")), p("foo"))
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
when:
|
||||
converter.convertTo(inputContracts)
|
||||
then:
|
||||
def e = thrown(UnsupportedOperationException)
|
||||
e.message.contains("execution property")
|
||||
}
|
||||
|
||||
def "should convert contracts from samples to pacts"() {
|
||||
given:
|
||||
Resource[] contractResources = new PathMatchingResourcePatternResolver().
|
||||
getResources("contracts/*.groovy")
|
||||
Resource[] pactResources = new PathMatchingResourcePatternResolver().
|
||||
getResources("contracts/*.json")
|
||||
Map<String, Collection<Contract>> contracts = contractResources.
|
||||
collectEntries {
|
||||
[(it.filename): ContractVerifierDslConverter.
|
||||
convertAsCollection(new File("/"), it.getFile())]
|
||||
}
|
||||
Map<String, String> jsonPacts = pactResources.
|
||||
collectEntries { [(it.filename): it.getFile().text] }
|
||||
when:
|
||||
Map<String, Collection<Pact>> pacts = contracts.entrySet().
|
||||
collectEntries { [(it.key): converter.convertTo(it.value)] }
|
||||
then:
|
||||
pacts.entrySet().each {
|
||||
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, true)
|
||||
}
|
||||
}
|
||||
|
||||
def "should convert contracts from grouped contracts to pacts"() {
|
||||
given:
|
||||
List<Contract> contracts = ContractVerifierDslConverter.
|
||||
convertAsCollection(new File("/"),
|
||||
new File("src/test/resources/contracts/grouped/shouldWorkWithBeer.groovy"))
|
||||
when:
|
||||
Collection<Pact> pacts = converter.convertTo(contracts)
|
||||
then:
|
||||
pacts.size() == 1
|
||||
String convertedPactAsText = JsonOutput.
|
||||
toJson(pacts.first().toMap(PactSpecVersion.V3))
|
||||
JSONAssert.assertEquals(
|
||||
new File("src/test/resources/contracts/grouped/shouldWorkWithBeer.json").text,
|
||||
convertedPactAsText, false)
|
||||
}
|
||||
|
||||
def "should convert pacts to strings"() {
|
||||
given:
|
||||
List<Contract> contracts = ContractVerifierDslConverter.
|
||||
convertAsCollection(new File("/"),
|
||||
new File("src/test/resources/contracts/grouped/shouldWorkWithBeer.groovy"))
|
||||
and:
|
||||
Collection<Pact> pacts = converter.convertTo(contracts)
|
||||
when:
|
||||
Map<String, byte[]> strings = converter.store(pacts)
|
||||
then:
|
||||
strings.size() == 1
|
||||
strings.keySet().first().
|
||||
startsWith("10-04-pact-consumer_10-05-pact-producer_")
|
||||
strings.keySet().first().endsWith(".json")
|
||||
JSONAssert.assertEquals(
|
||||
new File("src/test/resources/contracts/grouped/shouldWorkWithBeer.json").text,
|
||||
new String(strings.values().first()), false)
|
||||
}
|
||||
|
||||
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 {
|
||||
header(contentType(), applicationJsonUtf8())
|
||||
}
|
||||
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 {
|
||||
header(contentType(), applicationJsonUtf8())
|
||||
}
|
||||
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 {
|
||||
header(contentType(), applicationJsonUtf8())
|
||||
header("Some-Header",
|
||||
$(c(regex("[a-zA-Z]{9}")), p("someValue")))
|
||||
header("someHeaderWithJsonContent", '{"issue":"#595"}')
|
||||
}
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
headers {
|
||||
header(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 {
|
||||
header(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 {
|
||||
header(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
|
||||
}
|
||||
|
||||
@Issue("#889")
|
||||
def "should not throw an exception when parsing Pact file with different matchers"() {
|
||||
when:
|
||||
converter.convertFrom(pactv3Issue889Json)
|
||||
then:
|
||||
noExceptionThrown()
|
||||
}
|
||||
|
||||
def "should convert from pact v3 messaging to one SC message contract"() {
|
||||
given:
|
||||
Collection<Contract> expectedContracts = [
|
||||
Contract.make {
|
||||
description()
|
||||
label 'message sent to activemq:output'
|
||||
input {
|
||||
triggeredBy('bookReturnedTriggered()')
|
||||
}
|
||||
outputMessage {
|
||||
sentTo 'activemq:output'
|
||||
body([
|
||||
bookName: "foo"
|
||||
])
|
||||
headers {
|
||||
header('BOOK-NAME', 'foo')
|
||||
header(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")
|
||||
}
|
||||
|
||||
@Issue("1043")
|
||||
def "should generate a test from pact and not contain a check for an empty array"() {
|
||||
given:
|
||||
File output = Files.createTempDirectory("pact").toFile()
|
||||
output.mkdirs()
|
||||
when:
|
||||
new TestGenerator(new ContractVerifierConfigProperties(contractsDslDir: pact1043Json.parentFile, generatedTestSourcesDir: output, generatedTestResourcesDir: output, basePackageForTests: "example")).generate()
|
||||
then:
|
||||
File generatedTest = new File(output, "example/ContractVerifierTest.java")
|
||||
String generatedTestText = generatedTest.text
|
||||
!generatedTestText.contains('''assertThatJson(parsedJson).array().array("['authors']").isEmpty()''')
|
||||
}
|
||||
|
||||
@Issue("1134")
|
||||
def "should work properly with cookies when reading a pact file as contract"() {
|
||||
given:
|
||||
File output = Files.createTempDirectory("pact").toFile()
|
||||
output.mkdirs()
|
||||
when:
|
||||
new TestGenerator(new ContractVerifierConfigProperties(contractsDslDir: pact1134Json.parentFile, generatedTestSourcesDir: output, generatedTestResourcesDir: output, basePackageForTests: "example")).generate()
|
||||
then:
|
||||
File generatedTest = new File(output, "example/ContractVerifierTest.java")
|
||||
String generatedTestText = generatedTest.text
|
||||
generatedTestText.contains('''cookie("a", "1")''')
|
||||
generatedTestText.contains('''cookie("b", "2")''')
|
||||
generatedTestText.contains('''assertThat(response.cookie("c")).isNotNull()''')
|
||||
generatedTestText.contains('''assertThat(response.cookie("c")).isEqualTo("1")''')
|
||||
generatedTestText.contains('''assertThat(response.cookie("d")).isNotNull()''')
|
||||
generatedTestText.contains('''assertThat(response.cookie("d")).isEqualTo("2")''')
|
||||
}
|
||||
|
||||
@Issue("1134")
|
||||
def "should work properly with cookies when converting contract to pact"() {
|
||||
given:
|
||||
Collection<Contract> expectedContracts = [
|
||||
Contract.make {
|
||||
description("A successful Api GET call get")
|
||||
request {
|
||||
method(GET())
|
||||
url("/books")
|
||||
// cause Pact stores cookies in headers
|
||||
headers {
|
||||
}
|
||||
cookies {
|
||||
cookie("a", "1")
|
||||
cookie("b", "2")
|
||||
}
|
||||
}
|
||||
response {
|
||||
status(200)
|
||||
// cause Pact stores cookies in headers
|
||||
headers {
|
||||
}
|
||||
cookies {
|
||||
cookie("c", "1")
|
||||
cookie("d", "2")
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
when:
|
||||
Collection<Contract> contracts = converter.convertFrom(pact1134Json)
|
||||
then:
|
||||
contracts == expectedContracts
|
||||
}
|
||||
|
||||
@Issue("1277")
|
||||
def "should work properly with json body"() {
|
||||
when:
|
||||
converter.convertTo([
|
||||
Contract.make {
|
||||
request {
|
||||
method 'PUT'
|
||||
url '/api/admins/1'
|
||||
body('''{
|
||||
"username" : "username",
|
||||
"password" : "password",
|
||||
"roles" : [ "ADMIN" ]
|
||||
}''')
|
||||
headers {
|
||||
header('''Content-Type''', '''application/json;charset=UTF-8''')
|
||||
}
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
body('''{
|
||||
"admin" : {
|
||||
"adminId" : 1,
|
||||
"username" : "username",
|
||||
"roles" : [ "ADMIN" ]
|
||||
}
|
||||
}''')
|
||||
headers {
|
||||
header('''Content-Type''', '''application/json;charset=UTF-8''')
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
then:
|
||||
noExceptionThrown()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// file creator
|
||||
/*
|
||||
pacts.entrySet().each {
|
||||
new File("target/${it.key.replace("groovy", "json")}").text = JsonOutput.toJson(it.value.toMap(PactSpecVersion.V3))
|
||||
}
|
||||
*/
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2020 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
|
||||
*
|
||||
* https://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 com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
|
||||
import org.assertj.core.api.BDDAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PactMetaDataTests {
|
||||
|
||||
YAMLMapper mapper = new YAMLMapper();
|
||||
|
||||
@Test
|
||||
void should_parse_the_metadata_entry() throws JsonProcessingException {
|
||||
// @formatter:off
|
||||
String yamlEntry = ""
|
||||
+ "pact:\n"
|
||||
+ " providerStates:\n"
|
||||
+ " - name: state1\n"
|
||||
+ " params:\n"
|
||||
+ " id: 1\n"
|
||||
+ " value: value1\n"
|
||||
+ " - name: state2\n"
|
||||
+ " params:\n"
|
||||
+ " id: 2\n"
|
||||
+ " value: value2";
|
||||
// @formatter:on
|
||||
|
||||
PactMetaData metadata = PactMetaData
|
||||
.fromMetadata(this.mapper.readerForMapOf(Object.class).readValue(yamlEntry));
|
||||
|
||||
String serialized = this.mapper.writer().forType(PactMetaData.class).writeValueAsString(metadata);
|
||||
BDDAssertions.then(serialized).isEqualToNormalizingPunctuationAndWhitespace(yamlEntry.replace("pact:\n", ""));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 contracts.grouped
|
||||
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
[
|
||||
Contract.make {
|
||||
description("""
|
||||
Represents a successful scenario of getting a beer
|
||||
|
||||
```
|
||||
given:
|
||||
client is old enough
|
||||
when:
|
||||
he applies for a beer
|
||||
then:
|
||||
we'll grant him the beer
|
||||
```
|
||||
|
||||
""")
|
||||
request {
|
||||
method POST()
|
||||
name("10-04-pact-consumer___10-05-pact-producer___too young")
|
||||
url '/check'
|
||||
body(
|
||||
age: 60
|
||||
)
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
bodyMatchers {
|
||||
jsonPath('$.age', byRegex(regex("[2-9][0-9]")))
|
||||
}
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
body("""
|
||||
{
|
||||
"status": "OK"
|
||||
}
|
||||
""")
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
}
|
||||
},
|
||||
Contract.make {
|
||||
description("""
|
||||
Represents an unsuccessful scenario of getting a beer
|
||||
|
||||
```
|
||||
given:
|
||||
client is not old enough
|
||||
when:
|
||||
he applies for a beer
|
||||
then:
|
||||
we'll NOT grant him the beer
|
||||
```
|
||||
|
||||
""")
|
||||
request {
|
||||
method POST()
|
||||
name("10-04-pact-consumer___10-05-pact-producer___old enough")
|
||||
url '/check'
|
||||
body(
|
||||
age: 10
|
||||
)
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
bodyMatchers {
|
||||
jsonPath('$.age', byRegex(regex("[0-1][0-9]")))
|
||||
}
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
body("""
|
||||
{
|
||||
"status": "NOT_OK"
|
||||
}
|
||||
""")
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
bodyMatchers {
|
||||
jsonPath('$.status', byType())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,153 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "10-05-pact-producer"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "10-04-pact-consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "\nRepresents an unsuccessful scenario of getting a beer\n\n```\ngiven:\n\tclient is not old enough\nwhen:\n\the applies for a beer\nthen:\n\twe'll NOT grant him the beer\n```\n\n",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/check",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"age": 10
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.age": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[0-1][0-9]"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"status": "NOT_OK"
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.status": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "\nRepresents a successful scenario of getting a beer\n\n```\ngiven:\n\tclient is old enough\nwhen:\n\the applies for a beer\nthen:\n\twe'll grant him the beer\n```\n\n",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/check",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"age": 60
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.age": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[2-9][0-9]"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"status": "OK"
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.status": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-jvm": {
|
||||
"version": "4.3.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 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')
|
||||
}
|
||||
}
|
||||
metadata([
|
||||
pact: [
|
||||
providerStates:
|
||||
[
|
||||
[
|
||||
name: "someState1",
|
||||
params: [
|
||||
id: 1,
|
||||
value: "someValue1"
|
||||
]
|
||||
],
|
||||
[
|
||||
name: "someState2",
|
||||
params: [
|
||||
id: 2,
|
||||
value: "someValue2"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
])
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"providerStates": [
|
||||
{
|
||||
"name": "someState1",
|
||||
"params": {
|
||||
"id": 1,
|
||||
"value": "someValue1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "someState2",
|
||||
"params": {
|
||||
"id": 2,
|
||||
"value": "someValue2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "4.3.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 contracts
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract.make {
|
||||
request { // (1)
|
||||
method 'PUT' // (2)
|
||||
url '/fraudcheck' // (3)
|
||||
body([ // (4)
|
||||
clientId : $(c(regex('[0-9]{10}')), p("8532032713")),
|
||||
loanAmount: 99999
|
||||
])
|
||||
headers { // (5)
|
||||
contentType('application/vnd.fraud.v1+json')
|
||||
}
|
||||
}
|
||||
response { // (6)
|
||||
status OK() // (7)
|
||||
body([ // (8)
|
||||
fraudCheckStatus: "FRAUD",
|
||||
rejectionReason : "Amount too high"
|
||||
])
|
||||
headers { // (9)
|
||||
contentType('application/vnd.fraud.v1+json')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Since we don't want to force on the user to hardcode values of fields that are dynamic
|
||||
(timestamps, database ids etc.), one can parametrize those entries. If you wrap your field's
|
||||
value in a `$(...)` or `value(...)` and provide a dynamic value of a field then
|
||||
the concrete value will be generated for you. If you want to be really explicit about
|
||||
which side gets which value you can do that by using the `value(consumer(...), producer(...))` notation.
|
||||
That way what's present in the `consumer` section will end up in the produced stub. What's
|
||||
there in the `producer` will end up in the autogenerated test. If you provide only the
|
||||
regular expression side without the concrete value then Spring Cloud Contract will generate one for you.
|
||||
|
||||
From the Consumer perspective, when shooting a request in the integration test:
|
||||
|
||||
(1) - If the consumer sends a request
|
||||
(2) - With the "PUT" method
|
||||
(3) - to the URL "/fraudcheck"
|
||||
(4) - with the JSON body that
|
||||
* has a field `clientId` that matches a regular expression `[0-9]{10}`
|
||||
* has a field `loanAmount` that is equal to `99999`
|
||||
(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
|
||||
(6) - then the response will be sent with
|
||||
(7) - status equal `200`
|
||||
(8) - and JSON body equal to
|
||||
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
||||
(9) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
|
||||
|
||||
From the Producer perspective, in the autogenerated producer-side test:
|
||||
|
||||
(1) - A request will be sent to the producer
|
||||
(2) - With the "PUT" method
|
||||
(3) - to the URL "/fraudcheck"
|
||||
(4) - with the JSON body that
|
||||
* has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}`
|
||||
* has a field `loanAmount` that is equal to `99999`
|
||||
(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
|
||||
(6) - then the test will assert if the response has been sent with
|
||||
(7) - status equal `200`
|
||||
(8) - and JSON body equal to
|
||||
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
||||
(9) - with header `Content-Type` matching `application/vnd.fraud.v1+json.*`
|
||||
*/
|
||||
@@ -1,104 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"path": "/fraudcheck",
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json"
|
||||
},
|
||||
"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": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json"
|
||||
},
|
||||
"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": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "4.3.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 contracts
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract.make {
|
||||
request {
|
||||
method 'PUT'
|
||||
url '/fraudcheck'
|
||||
body("""
|
||||
{
|
||||
"clientId":"${
|
||||
value(consumer(regex('[0-9]{10}')), producer('1234567890'))
|
||||
}",
|
||||
"loanAmount":123.123
|
||||
}
|
||||
"""
|
||||
)
|
||||
headers {
|
||||
contentType("application/vnd.fraud.v1+json")
|
||||
}
|
||||
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body(
|
||||
fraudCheckStatus: "OK",
|
||||
rejectionReason: $(consumer(null),
|
||||
producer(execute('assertThatRejectionReasonIsNull($it)')))
|
||||
)
|
||||
headers {
|
||||
contentType("application/vnd.fraud.v1+json")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"path": "/fraudcheck",
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json"
|
||||
},
|
||||
"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": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json"
|
||||
},
|
||||
"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": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "4.3.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 contracts
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
|
||||
[
|
||||
Contract.make {
|
||||
request {
|
||||
name "should count all frauds"
|
||||
method GET()
|
||||
url '/frauds'
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body([
|
||||
count: 200
|
||||
])
|
||||
headers {
|
||||
contentType("application/vnd.fraud.v1+json")
|
||||
}
|
||||
}
|
||||
},
|
||||
Contract.make {
|
||||
request {
|
||||
method GET()
|
||||
url '/drunks'
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body([
|
||||
count: 100
|
||||
])
|
||||
headers {
|
||||
contentType("application/vnd.fraud.v1+json")
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,76 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/frauds"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json"
|
||||
},
|
||||
"body": {
|
||||
"count": 200
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/drunks"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.fraud.v1+json"
|
||||
},
|
||||
"body": {
|
||||
"count": 100
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/vnd\\.fraud\\.v1\\+json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "4.3.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"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": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "4.3.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "cookie-service"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "cookie-consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "A successful Api GET call",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/books",
|
||||
"headers": {
|
||||
"Cookie": "a=1;b=2"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Cookie": "c=1;d=2"
|
||||
}
|
||||
},
|
||||
"providerStates": [
|
||||
{
|
||||
"name": "get"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "book-catalog-service"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "books-client-catalog-rest-consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "A successful Api GET call",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/books",
|
||||
"headers": {
|
||||
"Content-Type": "application/json;charset=UTF-8"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json;charset=UTF-8"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"isbn": "978-3-86680-192-9",
|
||||
"id": 100,
|
||||
"title": "A book",
|
||||
"authors": [
|
||||
{
|
||||
"firstName": "string",
|
||||
"lastName": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$[0].id": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "integer"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$[0].title": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": ".*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$[0].authors[0].firstName": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$[0].authors[0].lastName": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$[0].isbn": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[0-9]{3}-[0-9]{1}-[0-9]{5}-[0-9]{3}-[0-9]{1}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
},
|
||||
"generators": {
|
||||
"body": {
|
||||
"$[0].id": {
|
||||
"type": "RandomInt",
|
||||
"min": 0,
|
||||
"max": 2147483647
|
||||
},
|
||||
"$[0].authors[0].firstName": {
|
||||
"type": "RandomString",
|
||||
"size": 20
|
||||
},
|
||||
"$[0].authors[0].lastName": {
|
||||
"type": "RandomString",
|
||||
"size": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"providerStates": [
|
||||
{
|
||||
"name": "get"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"some": "json"
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Alice Service"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "a retrieve Mallory request",
|
||||
"provider_state": "a user with username 'username' and password 'password' exists",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/mallory",
|
||||
"query": "name=ron&status=good",
|
||||
"body": {
|
||||
"id": "123",
|
||||
"method": "create"
|
||||
},
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"matchingRules": {
|
||||
"$.body.id": {
|
||||
"match": "regex",
|
||||
"regex": "[0-9]{3}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"consumer": {
|
||||
"name": "ui"
|
||||
},
|
||||
"provider": {
|
||||
"name": "userservice"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "a request to POST a person",
|
||||
"providerState": "provider accepts a new person",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/user-service/users",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"firstName": "Arthur",
|
||||
"lastName": "Dent"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 201,
|
||||
"headers": {
|
||||
"Content-Type": "application/json;charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"id": 42,
|
||||
"firstName": "Arthur",
|
||||
"lastName": "Dent"
|
||||
},
|
||||
"matchingRules": {
|
||||
"$.body": {
|
||||
"match": "type"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pactSpecification": {
|
||||
"version": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"consumer": {
|
||||
"name": "order-manager"
|
||||
},
|
||||
"provider": {
|
||||
"name": "lineitem-worker"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"description": "A message sent to order-exchange",
|
||||
"metaData": {
|
||||
"sentTo": "order-exchange"
|
||||
},
|
||||
"contents": {
|
||||
"lineItemId": "string",
|
||||
"orderId": "XXX-XXXXXX-000000-0000C",
|
||||
"timestamp": 1550500138678,
|
||||
"marketId": "MRKTDE",
|
||||
"status": "PROCESSED"
|
||||
},
|
||||
"providerStates": [
|
||||
{
|
||||
"name": "statusMessageCreated()"
|
||||
}
|
||||
],
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$.timestamp": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "number"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.lineItemId": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.orderId": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[A-Z]{3}-[A-Z]{6}-[0-9]{6}-[0-9A-F]{5}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.marketId": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[A-Z]{6}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.status": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[A-Z,_]*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"description": "message sent to activemq:output",
|
||||
"metaData": {
|
||||
"sentTo": "activemq:output",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user