Added support for Stub Runner and Pact Broker
fixes gh-191 commit 533f1d3d0e8f6dcd37677bfef555fc2ea86bc1b1 Author: Tim Ysewyn <Tim.Ysewyn@me.com> Date: Wed Mar 7 15:56:48 2018 +0100 Upgraded pact-jvm-model to 3.5.13
This commit is contained in:
@@ -1382,6 +1382,7 @@ org.springframework.cloud.contract.spec.ContractConverter=\
|
||||
org.springframework.cloud.contract.verifier.converter.YamlContractConverter
|
||||
----
|
||||
|
||||
[[pact-converter]]
|
||||
==== Pact Converter
|
||||
|
||||
Spring Cloud Contract includes support for https://docs.pact.io/[Pact] representation of
|
||||
@@ -1675,3 +1676,76 @@ properties
|
||||
|1000
|
||||
|Number of millis to wait between attempts to push the commits to `origin`
|
||||
|===
|
||||
|
||||
[[pact-stub-downloader]]
|
||||
=== Using the Pact Stub Downloader
|
||||
|
||||
Whenever the `repositoryRoot` starts with a Pact protocol
|
||||
(starts with `pact://`), the stub downloader will try
|
||||
to fetch the Pact contract definitions from the Pact Broker.
|
||||
Whatever is set after `pact://` will be parsed as the Pact Broker URL.
|
||||
|
||||
Either via environment variables, system properties, properties set
|
||||
inside the plugin or contracts repository configuration you can
|
||||
tweak the downloader's behaviour. Below you can find the list of
|
||||
properties
|
||||
|
||||
.SCM Stub Downloader properties
|
||||
|===
|
||||
|Type of a property |Name of the property | Description
|
||||
|
|
||||
* `pactbroker.host` (plugin prop)
|
||||
* `stubrunner.properties.pactbroker.host` (system prop)
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_HOST` (env prop)
|
||||
|Host from URL passed to `repositoryRoot`
|
||||
|What is the URL of Pact Broker
|
||||
|
||||
|
|
||||
* `pactbroker.port` (plugin prop)
|
||||
* `stubrunner.properties.pactbroker.port` (system prop)
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_PORT` (env prop)
|
||||
|Port from URL passed to `repositoryRoot`
|
||||
|What is 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`
|
||||
|What is 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 `+`
|
||||
|What tags 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`
|
||||
|What kind of authentication 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)
|
||||
|
|
||||
|Username used to connect to the Pact Broker
|
||||
|
||||
|
|
||||
* `pactbroker.auth.password` (plugin prop)
|
||||
* `stubrunner.properties.pactbroker.auth.password` (system prop)
|
||||
* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD` (env prop)
|
||||
|
|
||||
|Password used to connect 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 will be a combination of `groupId:artifactId`. If `false`, just `artifactId` is used
|
||||
|===
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
:introduction_url: https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/{branch}
|
||||
:samples_branch: 2.0.x
|
||||
:samples_branch: 2.0.x
|
||||
|
||||
== Spring Cloud Contract FAQ
|
||||
|
||||
@@ -707,6 +706,195 @@ to find stub definitions and contracts. E.g. for `com.example:foo:1.0.0` the pat
|
||||
* Stub servers will be started and fed with mappings
|
||||
* Messaging definitions will be read and used in the messaging tests
|
||||
|
||||
=== Can I use the Pact Broker?
|
||||
|
||||
When using http://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 one can fetch Pact files from the Pact Broker to generate
|
||||
tests and stubs.
|
||||
|
||||
As a prerequisite the Pact Converter and Pact Stub Downloader
|
||||
are required. You have to add it via the `spring-cloud-contract-pact` dependency.
|
||||
You can read more about it in the <<pact-converter>> section.
|
||||
|
||||
IMPORTANT: Pact follows the Consumer Contract convention. That means
|
||||
that the Consumer creates the Pact definitions first, then
|
||||
shares the files with the Producer. Those expectations are generated
|
||||
from the Consumer's code and can break the Producer if the expectation
|
||||
is not met.
|
||||
|
||||
==== Pact Consumer
|
||||
|
||||
The consumer uses Pact framework to generate Pact files. The
|
||||
Pact files are sent to the Pact Broker. An example of such
|
||||
setup can be found https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/{samples_branch}/consumer_pact[here].
|
||||
|
||||
==== Producer
|
||||
|
||||
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 via the URL that contains
|
||||
the `pact://` protocol. It's enough to pass the URL to the
|
||||
Pact Broker.
|
||||
|
||||
.Maven
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
<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>
|
||||
----
|
||||
|
||||
.Gradle
|
||||
[source,gradle,indent=0]
|
||||
----
|
||||
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 will be downloaded from the Pact Broker
|
||||
* Spring Cloud Contract will convert the Pact files into tests and stubs
|
||||
* The JAR with the stubs gets automatically created as usual
|
||||
|
||||
==== Pact Consumer (Producer Contract approach)
|
||||
|
||||
In the scenario where you don't want to do Consumer Contract approach
|
||||
(for every single consumer define the expectations) but you'd prefer
|
||||
to do Producer Contracts (the producer provides the contracts and
|
||||
publishes stubs), it's enough to use Spring Cloud Contract with
|
||||
Stub Runner option.
|
||||
|
||||
First, remember to add Stub Runner and Spring Cloud Contract Pact module
|
||||
as test dependencies.
|
||||
|
||||
.Maven
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
<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>
|
||||
----
|
||||
|
||||
.Gradle
|
||||
[source,gradle,indent=0]
|
||||
----
|
||||
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, just pass the URL of the Pact Broker to `repositoryRoot`, prefixed
|
||||
with `pact://` protocol. E.g. `pact://http://localhost:8085`
|
||||
|
||||
[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 will be downloaded from the Pact Broker
|
||||
* Spring Cloud Contract will convert the Pact files into stub definitions
|
||||
* The stub servers will be started and fed with stubs
|
||||
|
||||
For more information about Pact support you can go to
|
||||
the <<pact-stub-downloader>> section.
|
||||
|
||||
=== How can I debug the request/response being sent by the generated tests client?
|
||||
|
||||
The generated tests all boil down to RestAssured in some form or fashion which relies on https://hc.apache.org/httpcomponents-client-ga/[Apache HttpClient]. HttpClient has a facility called https://hc.apache.org/httpcomponents-client-ga/logging.html#Wire_Logging[wire logging] which logs the entire request and response to HttpClient. Spring Boot has a logging https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html[common application property] for doing this sort of thing, just add this to your application properties
|
||||
|
||||
11
pom.xml
11
pom.xml
@@ -123,6 +123,17 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-provider-junit_2.12</artifactId>
|
||||
<version>${pact.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-model</artifactId>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
server:
|
||||
port: 6565
|
||||
logging.level.org.springframework.cloud.contract: DEBUG
|
||||
@@ -10,7 +10,7 @@ buildscript {
|
||||
// end::repos[]
|
||||
dependencies {
|
||||
classpath 'org.codehaus.groovy:groovy-all:2.5.0-beta-1'
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${findProperty('verifierVersion') ?: verifierVersion}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${findProperty('verifierVersion') ?: verifierVersion}"
|
||||
classpath "com.jayway.restassured:rest-assured:2.9.0"
|
||||
classpath "com.jayway.restassured:spring-mock-mvc:2.9.0"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ dependencies {
|
||||
|
||||
testCompile "org.springframework.cloud:spring-cloud-starter-contract-stub-runner"
|
||||
//tag::pact_dependency[]
|
||||
testCompile "org.springframework.cloud:spring-cloud-contract-spec-pact"
|
||||
testCompile "org.springframework.cloud:spring-cloud-contract-pact"
|
||||
testCompile 'au.com.dius:pact-jvm-model:3.5.13'
|
||||
//end::pact_dependency[]
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<!-- tag::pact_dependency[] -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-spec-pact</artifactId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -7,10 +7,10 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${findProperty('verifierVersion') ?: verifierVersion}"
|
||||
//tag::pact_dependency[]
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-spec-pact:${findProperty('verifierVersion') ?: verifierVersion}"
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-pact:${findProperty('verifierVersion') ?: verifierVersion}"
|
||||
classpath 'au.com.dius:pact-jvm-model:3.5.13'
|
||||
//end::pact_dependency[]
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-spec-pact</artifactId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<version>${spring-cloud-contract.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/plugins-staging-local/" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/plugins-staging-local/" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
maven { url "http://repo.spring.io/release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ buildscript {
|
||||
}
|
||||
// end::repos[]
|
||||
dependencies {
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT"
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE"
|
||||
classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${findProperty('verifierVersion') ?: verifierVersion}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-spec-pact</artifactId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -255,7 +255,7 @@ public class AetherStubDownloader implements StubDownloader {
|
||||
}
|
||||
|
||||
private static File unpackStubJarToATemporaryFolder(URI stubJarUri) {
|
||||
File tmpDirWhereStubsWillBeUnzipped = TemporaryFileStorage.unpackStubJarToATemporaryFolder(TEMP_DIR_PREFIX);
|
||||
File tmpDirWhereStubsWillBeUnzipped = TemporaryFileStorage.createTempDir(TEMP_DIR_PREFIX);
|
||||
log.info("Unpacking stub from JAR [URI: " + stubJarUri + "]");
|
||||
unzipTo(new File(stubJarUri), tmpDirWhereStubsWillBeUnzipped);
|
||||
TemporaryFileStorage.add(tmpDirWhereStubsWillBeUnzipped);
|
||||
|
||||
@@ -81,11 +81,17 @@ public class ContractDownloader {
|
||||
} else {
|
||||
log.info("Will pick a pattern from group id and artifact id");
|
||||
if (hasGavInPath(contractsDirectory)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Group & artifact in path");
|
||||
}
|
||||
contractsDirectory = contractsSubDirIfPresent(contractsDirectory);
|
||||
// we're already under proper folder (for the given version)
|
||||
// we're already under proper folder (for the given group and artifact)
|
||||
pattern = fileToPattern(contractsDirectory);
|
||||
includedAntPattern = "**/";
|
||||
} else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("No group & artifact in path");
|
||||
}
|
||||
pattern = groupArtifactToPattern(contractsDirectory);
|
||||
includedAntPattern = wrapWithAntPattern(slashSeparatedGroupId() + "/" + this.projectArtifactId);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ class GitContractsRepo {
|
||||
File file = CACHED_LOCATIONS.get(repo);
|
||||
GitStubDownloaderProperties properties = new GitStubDownloaderProperties(repo, this.options);
|
||||
if (file == null) {
|
||||
File tmpDirWhereStubsWillBeUnzipped = TemporaryFileStorage.unpackStubJarToATemporaryFolder(TEMP_DIR_PREFIX);
|
||||
File tmpDirWhereStubsWillBeUnzipped = TemporaryFileStorage.createTempDir(TEMP_DIR_PREFIX);
|
||||
GitRepo gitRepo = new GitRepo(tmpDirWhereStubsWillBeUnzipped, properties);
|
||||
file = gitRepo.cloneProject(properties.url);
|
||||
gitRepo.checkout(file, properties.branch);
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class StubConfiguration {
|
||||
private static final String STUB_COLON_DELIMITER = ":";
|
||||
private static final String DEFAULT_VERSION = "+";
|
||||
static final String DEFAULT_VERSION = "+";
|
||||
public static final String DEFAULT_CLASSIFIER = "stubs";
|
||||
|
||||
final String groupId;
|
||||
|
||||
@@ -46,6 +46,15 @@ class StubRunnerPropertyUtils {
|
||||
return StringUtils.hasText(value) && Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* For options, system props and env vars returns {@code true}
|
||||
* when property is set
|
||||
*/
|
||||
static boolean hasProperty(Map<String, String> options, String propName) {
|
||||
String value = getProperty(options, propName);
|
||||
return StringUtils.hasText(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to pick a value from options, for Env vars takes the prop name, converts
|
||||
* dots to underscores and applies upper case
|
||||
|
||||
@@ -97,7 +97,7 @@ class TemporaryFileStorage {
|
||||
}
|
||||
}
|
||||
|
||||
static File unpackStubJarToATemporaryFolder(String tempDirPrefix) {
|
||||
static File createTempDir(String tempDirPrefix) {
|
||||
try {
|
||||
return createTempDirectory(tempDirPrefix)
|
||||
.toFile();
|
||||
|
||||
@@ -82,6 +82,34 @@ class StubRunnerPropertyUtilsSpec extends Specification {
|
||||
"foo.bar-baz" | null | "" | "bc" | "bc" | "stubrunner.properties.foo.bar-baz" | "STUBRUNNER_PROPERTIES_FOO_BAR_BAZ"
|
||||
}
|
||||
|
||||
@RestoreSystemProperties
|
||||
def "should return [#expectedResult] when prop is set for [#queriedProp] and system is [#systemProperty] and env [#envVariable]"() {
|
||||
given:
|
||||
def sysProp = systemProperty
|
||||
def envVar = envVariable
|
||||
PropertyFetcher fetcher = new PropertyFetcher() {
|
||||
@Override
|
||||
String systemProp(String prop) {
|
||||
return sysProp
|
||||
}
|
||||
|
||||
@Override
|
||||
String envVar(String prop) {
|
||||
return envVar
|
||||
}
|
||||
}
|
||||
StubRunnerPropertyUtils.FETCHER = fetcher
|
||||
expect:
|
||||
expectedResult == StubRunnerPropertyUtils.hasProperty(map, queriedProp)
|
||||
where:
|
||||
queriedProp | map | systemProperty | envVariable | expectedResult
|
||||
"foo.bar-baz" | ["foo.bar-baz": "faz"] | "ab" | "bc" | true
|
||||
"foo.bar-baz" | [:] | "ab" | "bc" | true
|
||||
"foo.bar-baz" | [:] | "" | "bc" | true
|
||||
"foo.bar-baz" | null | "" | "bc" | true
|
||||
"foo.bar-baz" | null | null | null | false
|
||||
}
|
||||
|
||||
def cleanupSpec() {
|
||||
StubRunnerPropertyUtils.FETCHER = new PropertyFetcher()
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-contract-converters</module>
|
||||
<module>spring-cloud-contract-spec-pact</module>
|
||||
<module>spring-cloud-contract-pact</module>
|
||||
<module>spring-cloud-contract-maven-plugin</module>
|
||||
<module>spring-cloud-contract-gradle-plugin</module>
|
||||
</modules>
|
||||
|
||||
@@ -415,7 +415,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-spec-pact</artifactId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -90,7 +90,7 @@ class MavenContractsDownloader {
|
||||
contractDownloader().updatePropertiesWithInclusion(downloadedContractsDir, config);
|
||||
return downloadedContractsDir;
|
||||
} else if (shouldDownloadContracts()) {
|
||||
this.log.info("Download dependency is provided - will download contract jars");
|
||||
this.log.info("Download dependency is provided - will retrieve contracts from a remote location");
|
||||
File downloadedContracts = contractDownloader().unpackedDownloadedContracts(config);
|
||||
this.project.getProperties().setProperty(CONTRACTS_DIRECTORY_PROP, downloadedContracts.getAbsolutePath());
|
||||
return downloadedContracts;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-spec-pact</artifactId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<version>${spring.cloud.contract.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-contract-spec-pact</artifactId>
|
||||
<artifactId>spring-cloud-contract-pact</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Contract Spec Pact</name>
|
||||
<description>Spring Cloud Contract Spec Pact</description>
|
||||
<name>Spring Cloud Contract Pact</name>
|
||||
<description>Spring Cloud Contract Pact</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
@@ -19,7 +19,11 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-verifier</artifactId>
|
||||
<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>
|
||||
@@ -37,6 +41,10 @@
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-consumer-java8_2.12</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>au.com.dius</groupId>
|
||||
<artifactId>pact-jvm-provider-junit_2.12</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spockframework</groupId>
|
||||
<artifactId>spock-core</artifactId>
|
||||
@@ -52,6 +60,11 @@
|
||||
<artifactId>spock-global-unroll</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-wiremock</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
@@ -62,8 +75,13 @@
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>addSources</goal>
|
||||
<goal>addTestSources</goal>
|
||||
<goal>generateStubs</goal>
|
||||
<goal>compile</goal>
|
||||
<goal>testGenerateStubs</goal>
|
||||
<goal>testCompile</goal>
|
||||
<goal>removeStubs</goal>
|
||||
<goal>removeTestStubs</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.contract.stubrunner;
|
||||
|
||||
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.model.Pact;
|
||||
import au.com.dius.pact.model.PactSpecVersion;
|
||||
import au.com.dius.pact.provider.junit.loader.PactBroker;
|
||||
import au.com.dius.pact.provider.junit.loader.PactBrokerAuth;
|
||||
import au.com.dius.pact.provider.junit.loader.PactBrokerLoader;
|
||||
import au.com.dius.pact.provider.junit.loader.PactLoader;
|
||||
import au.com.dius.pact.provider.junit.sysprops.SystemPropertyResolver;
|
||||
import au.com.dius.pact.provider.junit.sysprops.ValueResolver;
|
||||
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.jetbrains.annotations.NotNull;
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
|
||||
import org.springframework.cloud.contract.verifier.converter.StubGenerator;
|
||||
import org.springframework.cloud.contract.verifier.converter.StubGeneratorProvider;
|
||||
import org.springframework.cloud.contract.verifier.file.ContractMetadata;
|
||||
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.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Allows downloading of Pact files from the Pact Broker.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @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
|
||||
*/
|
||||
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.isEmpty(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 final StubRunnerOptions stubRunnerOptions;
|
||||
private final boolean deleteStubsAfterTest;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private static final String PROVIDER_NAME_WITH_GROUP_ID = "pactbroker.provider-name-with-group-id";
|
||||
|
||||
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()) {
|
||||
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");
|
||||
}
|
||||
StubGeneratorProvider provider = new StubGeneratorProvider();
|
||||
Collection<StubGenerator> stubGenerators = provider
|
||||
.converterForName(ARTIFICIAL_NAME_ENDING_WITH_GROOVY);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found following matching stub generators " + stubGenerators);
|
||||
}
|
||||
for (StubGenerator stubGenerator : stubGenerators) {
|
||||
Map<Contract, String> map = stubGenerator
|
||||
.convertContents(file.getName(),
|
||||
new ContractMetadata(file.toPath(), false,
|
||||
contracts.size(), null, contracts));
|
||||
for (Map.Entry<Contract, String> entry : map.entrySet()) {
|
||||
String value = entry.getValue();
|
||||
File mapping = new File(mappingsFolder,
|
||||
StringUtils.stripFilenameExtension(file.getName()) + "_" +
|
||||
Math.abs(entry.getKey().hashCode()) + ".json");
|
||||
storeFile(mapping.toPath(), value.getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void storeFile(Path path, byte[] contents) {
|
||||
try {
|
||||
Files.write(path, contents);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Stored file [" + path.toString() + "]");
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
@NotNull PactLoader pactBrokerLoader(ValueResolver resolver,
|
||||
List<String> tags) throws IOException {
|
||||
Resource repo = this.stubRunnerOptions.getStubRepositoryRoot();
|
||||
String schemeSpecificPart = schemeSpecificPart(repo.getURI());
|
||||
URI pactBrokerUrl = URI.create(schemeSpecificPart);
|
||||
return new PactBrokerLoader(new PactBroker() {
|
||||
|
||||
@Override public Class<? extends Annotation> annotationType() {
|
||||
return PactBroker.class;
|
||||
}
|
||||
|
||||
@Override public String host() {
|
||||
return resolver.resolveValue("pactbroker.host:" + pactBrokerUrl.getHost());
|
||||
}
|
||||
|
||||
@Override public String port() {
|
||||
return resolver.resolveValue("pactbroker.port:" + pactBrokerUrl.getPort());
|
||||
}
|
||||
|
||||
@Override public String protocol() {
|
||||
return resolver.resolveValue("pactbroker.protocol:" + pactBrokerUrl.getScheme());
|
||||
}
|
||||
|
||||
@Override public String[] tags() {
|
||||
return tags.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override public boolean failIfNoPactsFound() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public PactBrokerAuth authentication() {
|
||||
return new PactBrokerAuth() {
|
||||
@Override public Class<? extends Annotation> annotationType() {
|
||||
return PactBrokerAuth.class;
|
||||
}
|
||||
|
||||
@Override public String scheme() {
|
||||
return resolver.resolveValue("pactbroker.auth.scheme:basic");
|
||||
}
|
||||
|
||||
@Override public String username() {
|
||||
return resolver.resolveValue("pactbroker.auth.username:");
|
||||
}
|
||||
|
||||
@Override public String password() {
|
||||
return resolver.resolveValue("pactbroker.auth.password:");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override public Class<? extends ValueResolver> valueResolver() {
|
||||
return SystemPropertyResolver.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String schemeSpecificPart(URI uri) {
|
||||
String part = uri.getSchemeSpecificPart();
|
||||
if (StringUtils.isEmpty(part)) {
|
||||
return part;
|
||||
}
|
||||
return part.startsWith("//") ? part.substring(2) : part;
|
||||
}
|
||||
|
||||
@NotNull 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 = new SystemPropertyResolver();
|
||||
StubRunnerOptions options;
|
||||
|
||||
FromPropsThenFromSysEnv(StubRunnerOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override public String resolveValue(String expression) {
|
||||
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);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
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
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.springframework.cloud.contract.stubrunner
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
import au.com.dius.pact.model.Pact
|
||||
import au.com.dius.pact.model.PactSource
|
||||
import au.com.dius.pact.provider.junit.loader.PactLoader
|
||||
import au.com.dius.pact.provider.junit.sysprops.ValueResolver
|
||||
import com.github.tomakehurst.wiremock.stubbing.StubMapping
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import spock.lang.Specification
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
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) {
|
||||
@NotNull @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)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
org.springframework.cloud.contract.spec.ContractConverter=\
|
||||
org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
|
||||
@@ -1,68 +0,0 @@
|
||||
package contracts
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract.make {
|
||||
request {
|
||||
method 'POST'
|
||||
url '/'
|
||||
body([
|
||||
someInteger: 1234567890,
|
||||
someDecimal: 123.123,
|
||||
someHex: 'DEADC0DE',
|
||||
someAlphaNumeric: 'Some alpha numeric string with 1234567890',
|
||||
someUUID: '00000000-0000-0000-0000-000000000000',
|
||||
someDate: '2018-03-26',
|
||||
someTime: '13:37:00',
|
||||
someDateTime: '2018-03-26 13:37:00',
|
||||
someBoolean: 'true',
|
||||
someNullValue: null
|
||||
])
|
||||
headers {
|
||||
contentType('application/json')
|
||||
header("Some-Header", $(c(regex('[a-zA-Z]{9}')), p('someValue')))
|
||||
header("Header-Without-Matcher", 'someValue')
|
||||
}
|
||||
bodyMatchers {
|
||||
jsonPath('$.someInteger', byRegex(anInteger()))
|
||||
jsonPath('$.someDecimal', byRegex(aDouble()))
|
||||
jsonPath('$.someHex', byRegex('[a-fA-F0-9]+'))
|
||||
jsonPath('$.someAlphaNumeric', byRegex(alphaNumeric()))
|
||||
jsonPath('$.someUUID', byRegex(uuid()))
|
||||
jsonPath('$.someDate', byDate())
|
||||
jsonPath('$.someTime', byTime())
|
||||
jsonPath('$.someDateTime', byTimestamp())
|
||||
jsonPath('$.someBoolean', byRegex(anyBoolean()))
|
||||
}
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body([
|
||||
someInteger: 1234567890,
|
||||
someDecimal: 123.123,
|
||||
someHex: 'DEADC0DE',
|
||||
someAlphaNumeric: 'Some alpha numeric string with 1234567890',
|
||||
someUUID: '00000000-0000-0000-0000-000000000000',
|
||||
someDate: '2018-03-26',
|
||||
someTime: '13:37:00',
|
||||
someDateTime: '2018-03-26 13:37:00',
|
||||
someBoolean: 'true',
|
||||
someRegex: 1234567890
|
||||
])
|
||||
headers {
|
||||
contentType('application/json')
|
||||
header("Some-Header", $(c('someValue'), p(regex('[a-zA-Z]{9}'))))
|
||||
header("Header-Without-Matcher", 'someValue')
|
||||
}
|
||||
bodyMatchers {
|
||||
jsonPath('$.someInteger', byRegex(anInteger()))
|
||||
jsonPath('$.someDecimal', byRegex(aDouble()))
|
||||
jsonPath('$.someHex', byRegex('[a-fA-F0-9]+'))
|
||||
jsonPath('$.someAlphaNumeric', byRegex(alphaNumeric()))
|
||||
jsonPath('$.someUUID', byRegex(uuid()))
|
||||
jsonPath('$.someDate', byDate())
|
||||
jsonPath('$.someTime', byTime())
|
||||
jsonPath('$.someDateTime', byTimestamp())
|
||||
jsonPath('$.someBoolean', byRegex(anyBoolean()))
|
||||
jsonPath('$.someNullValue', byNull())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "Provider"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "Consumer"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/",
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"Header-Without-Matcher": "someValue",
|
||||
"Some-Header": "someValue"
|
||||
},
|
||||
"body": {
|
||||
"someHex": "DEADC0DE",
|
||||
"someTime": "13:37:00",
|
||||
"someNullValue": null,
|
||||
"someInteger": 1234567890,
|
||||
"someBoolean": "true",
|
||||
"someDate": "2018-03-26",
|
||||
"someUUID": "00000000-0000-0000-0000-000000000000",
|
||||
"someDecimal": 123.123,
|
||||
"someAlphaNumeric": "Some alpha numeric string with 1234567890",
|
||||
"someDateTime": "2018-03-26 13:37:00"
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"Some-Header": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-zA-Z]{9}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.someInteger": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "integer"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDecimal": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "decimal"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someHex": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-fA-F0-9]+"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someAlphaNumeric": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-zA-Z0-9]+"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someUUID": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDate": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "date",
|
||||
"date": "yyyy-MM-dd"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someTime": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "time",
|
||||
"time": "HH:mm:ss"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDateTime": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "timestamp",
|
||||
"timestamp": "yyyy-MM-dd HH:mm:ssZZZ"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someBoolean": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "(true|false)"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"Header-Without-Matcher": "someValue",
|
||||
"Some-Header": "someValue"
|
||||
},
|
||||
"body": {
|
||||
"someHex": "DEADC0DE",
|
||||
"someTime": "13:37:00",
|
||||
"someInteger": 1234567890,
|
||||
"someBoolean": "true",
|
||||
"someRegex": 1234567890,
|
||||
"someDate": "2018-03-26",
|
||||
"someUUID": "00000000-0000-0000-0000-000000000000",
|
||||
"someDecimal": 123.123,
|
||||
"someAlphaNumeric": "Some alpha numeric string with 1234567890",
|
||||
"someDateTime": "2018-03-26 13:37:00"
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json.*"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"Some-Header": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-zA-Z]{9}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.someInteger": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "integer"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDecimal": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "decimal"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someHex": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-fA-F0-9]+"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someAlphaNumeric": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-zA-Z0-9]+"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someUUID": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDate": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "date",
|
||||
"date": "yyyy-MM-dd"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someTime": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "time",
|
||||
"time": "HH:mm:ss"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someDateTime": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "timestamp",
|
||||
"timestamp": "yyyy-MM-dd HH:mm:ssZZZ"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someBoolean": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "(true|false)"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.someNullValue": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "null"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-specification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import java.nio.file.Path
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import groovy.transform.PackageScope
|
||||
import org.apache.commons.logging.Log
|
||||
import org.apache.commons.logging.LogFactory
|
||||
import wiremock.com.google.common.collect.ListMultimap
|
||||
|
||||
import org.springframework.cloud.contract.spec.ContractVerifierException
|
||||
@@ -42,6 +44,7 @@ import static org.springframework.cloud.contract.verifier.util.NamesUtil.toLastD
|
||||
*/
|
||||
class TestGenerator {
|
||||
|
||||
private static final Log log = LogFactory.getLog(TestGenerator)
|
||||
private static final String DEFAULT_CLASS_PREFIX = "ContractVerifier"
|
||||
private static final String DEFAULT_TEST_PACKAGE = "org.springframework.cloud.contract.verifier.tests"
|
||||
|
||||
@@ -111,6 +114,9 @@ class TestGenerator {
|
||||
|
||||
private void processIncludedDirectory(
|
||||
final String includedDirectoryRelativePath, Collection<ContractMetadata> contracts, final String basePackageNameForClass) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Collected contracts with metadata ${contracts}")
|
||||
}
|
||||
if (contracts.size()) {
|
||||
def className = afterLast(includedDirectoryRelativePath.toString(), File.separator) + resolveNameSuffix()
|
||||
def convertedClassName = convertIllegalPackageChars(className)
|
||||
|
||||
@@ -87,7 +87,7 @@ class ContractFileScanner {
|
||||
* and try to convert via pluggable Contract Converters any possible contracts
|
||||
*/
|
||||
private void appendRecursively(File baseDir, ListMultimap<Path, ContractMetadata> result) {
|
||||
List<ContractConverter> converters = SpringFactoriesLoader.loadFactories(ContractConverter, null)
|
||||
List<ContractConverter> converters = converters()
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Found the following contract converters ${converters}")
|
||||
}
|
||||
@@ -122,13 +122,18 @@ class ContractFileScanner {
|
||||
}
|
||||
}
|
||||
|
||||
protected List<ContractConverter> converters() {
|
||||
return SpringFactoriesLoader.loadFactories(ContractConverter, null)
|
||||
}
|
||||
|
||||
private void addContractToTestGeneration(List<ContractConverter> converters, ListMultimap<Path, ContractMetadata> result,
|
||||
File[] files, File file, int index) {
|
||||
boolean converted = false
|
||||
if (!file.isDirectory()) {
|
||||
for (ContractConverter converter : converters) {
|
||||
if (converter.isAccepted(file)) {
|
||||
addContractToTestGeneration(result, files, file, index, converter.convertFrom(file))
|
||||
Collection<Contract> contracts = tryConvert(converter, file)
|
||||
if (contracts) {
|
||||
addContractToTestGeneration(result, files, file, index, contracts)
|
||||
converted = true
|
||||
break
|
||||
}
|
||||
@@ -142,6 +147,21 @@ class ContractFileScanner {
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Contract> tryConvert(ContractConverter converter, File file) {
|
||||
boolean accepted = converter.isAccepted(file)
|
||||
if (!accepted) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
return converter.convertFrom(file)
|
||||
} catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Exception occurred while trying to convert the file", e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private void addContractToTestGeneration(ListMultimap<Path, ContractMetadata> result, File[] files, File file,
|
||||
int index, Collection<Contract> convertedContract) {
|
||||
Path path = file.toPath()
|
||||
@@ -154,6 +174,9 @@ class ContractFileScanner {
|
||||
files.size(), order, convertedContract)
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Creating a contract entry for path [" + path + "] and metadata [" + metadata + "]")
|
||||
}
|
||||
if (convertedContract) {
|
||||
|
||||
}
|
||||
result.put(parent, metadata)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ import spock.lang.Specification
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.ContractConverter
|
||||
|
||||
/**
|
||||
* @author Jakub Kubrynski, codearte.io
|
||||
*/
|
||||
@@ -77,7 +80,27 @@ class ContractFileScannerSpec extends Specification {
|
||||
def "should find contract files with converters"() {
|
||||
given:
|
||||
File baseDir = new File(this.getClass().getResource("/directory/with/mixed").toURI())
|
||||
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null)
|
||||
ContractFileScanner scanner = new ContractFileScanner(baseDir, null, null) {
|
||||
@Override
|
||||
protected List<ContractConverter> converters() {
|
||||
return [new ContractConverter() {
|
||||
@Override
|
||||
boolean isAccepted(File file) {
|
||||
return file.name.endsWith(".json")
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Contract> convertFrom(File file) {
|
||||
throw new RuntimeException("boom")
|
||||
}
|
||||
|
||||
@Override
|
||||
Object convertTo(Collection contract) {
|
||||
throw new RuntimeException("boom")
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
when:
|
||||
ListMultimap<Path, ContractMetadata> result = scanner.findContracts()
|
||||
then:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "/loanApplication",
|
||||
"headers": {
|
||||
"Content-Type": {
|
||||
"equalTo": "application/vnd.loanapplicationservice.v1+json"
|
||||
}
|
||||
},
|
||||
"bodyPatterns": [
|
||||
{
|
||||
"matches": "\\{\"clientPesel\":\"1234567890\",\"loanAmount\":123.123\\}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"body": "{\"loanApplicationStatus\":\"LOAN_APPLIED\",\"loanApplicationId\":\"${123123123:$anyInt($it)}\",\"rejectionReason\":null}",
|
||||
"headers": {
|
||||
"Content-Type": "application/vnd.loanapplicationservice.v1+json"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,8 +66,7 @@ public abstract class WireMockSpring {
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
WireMockConfiguration config = new WireMockConfiguration();
|
||||
return config;
|
||||
return new WireMockConfiguration();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user