diff --git a/README.adoc b/README.adoc index 59a9673b21..4d492e96b2 100644 --- a/README.adoc +++ b/README.adoc @@ -414,6 +414,9 @@ going through the process. CDC is all about communication. The https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/dsl/http-server[server side code is available here] and https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/dsl/http-client[the client side code here]. +TIP: In this case the ownership of the contracts lays on the producer side. It means that physically +all the contract are present in the producer's repository + ===== Technical note If using the *SNAPSHOT* / *Milestone* / *Release Candidate* versions please add the following section to your @@ -1231,6 +1234,98 @@ Example of tests using production version of stubs You can pass those values also via properties from your deployment pipeline. +===== Common repo with contracts + +Another way of storing contracts other than having them with the producer is keeping them in a common place. +It can be related to security issues where the consumers can't clone the producer's code. Also if you keep +contracts in a single place then you, as a producer, will know how many consumers you have and which +consumer will you break with your local changes. + +* Repo structure * + +Let's assume that we have a producer with coordinates `com.example:server` and 3 consumers: `client1`, +`client2`, `client3`. Then in the repository with common contracts you would have the following setup +(which you can checkout https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/contracts[here]: + +[source,bash,indent=0] +---- +├── com +│   └── example +│   └── server +│   ├── client1 +│   │   └── expectation.groovy +│   ├── client2 +│   │   └── expectation.groovy +│   ├── client3 +│   │   └── expectation.groovy +│   └── pom.xml +├── mvnw +├── mvnw.cmd +├── pom.xml +└── src + └── assembly + └── contracts.xml +---- + +As you can see the under the slash-delimited groupid `/` artifact id folder (`com/example/server`) you have +expectations of the 3 consumers (`client1`, `client2` and `client3`). Expectations are the standard Groovy DSL +contract files as described throughout this documentation. This repository has to produce a JAR file that maps +one to one to the contents of the repo. + +Example of a `pom.xml` inside the `server` folder. + +[source,xml,indent=0] +---- +Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/contracts/com/example/server/pom.xml[indent=0] + +As you can see there are no dependencies other than the Spring Cloud Contract Verifier Maven plugin. +Those poms are necessary for the consumer side to run `mvn clean install -DskipTests` to locally install + stubs of the producer project. + +The `pom.xml` in the root folder can look like this: + +[source,xml,indent=0] +---- +Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/contracts/pom.xml[indent=0] + +It's using the assembly plugin in order to build the JAR with all the contracts. Example of such setup is here: + +[source,xml,indent=0] +---- +Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/contracts/src/assembly/contracts.xml[indent=0] + +*Workflow* + +The workflow would look similar to the one presented in the `Step by step guide to CDC`. The only difference + is that the producer doesn't own the contracts anymore. So the consumer and the producer have to work on + common contracts in a common repository. + +_Consumer_ + +When the *consumer* wants to work on the contracts offline, instead of cloning the producer code, the +consumer team clones the common repository, goes to the required producer's folder (e.g. `com/example/server`) +and runs `mvn clean install -DskipTests` to install locally the stubs converted from the contracts. + +TIP: You need to have http://maven.apache.org/download.cgi[Maven installed locally] + +_Producer_ + +As a *producer* it's enough to alter the Spring Cloud Contract Verifier to provide the URL and the dependency +of the JAR containing the contracts: + +[source,xml,indent=0] +---- +Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/basic-remote-contracts/pom-with-repo.xml[tags=remote_config,indent=0] +---- + +With this setup the JAR with groupid `com.example.standalone` and artifactid `contracts` will be downloaded +from `http://link/to/your/nexus/or/artifactory/or/sth`. It will be then unpacked in a local temporary folder +and contracts present under the `com/example/server` will be picked as the ones used to generate the +tests and the stubs. Due to this convention the producer team will know which consumer teams will be broken +when some incompatible changes are done. + +The rest of the flow looks the same. + === Links Here you can find interesting links related to Spring Cloud Contract Verifier: diff --git a/docs/src/main/asciidoc/verifier/introduction.adoc b/docs/src/main/asciidoc/verifier/introduction.adoc index 4900d9d157..7a299bbeaf 100644 --- a/docs/src/main/asciidoc/verifier/introduction.adoc +++ b/docs/src/main/asciidoc/verifier/introduction.adoc @@ -120,6 +120,9 @@ going through the process. CDC is all about communication. The https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/dsl/http-server[server side code is available here] and https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/dsl/http-client[the client side code here]. +TIP: In this case the ownership of the contracts lays on the producer side. It means that physically +all the contract are present in the producer's repository + ===== Technical note If using the *SNAPSHOT* / *Milestone* / *Release Candidate* versions please add the following section to your @@ -720,4 +723,99 @@ Example of tests using production version of stubs @AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"}) ---- -You can pass those values also via properties from your deployment pipeline. \ No newline at end of file +You can pass those values also via properties from your deployment pipeline. + +===== Common repo with contracts + +Another way of storing contracts other than having them with the producer is keeping them in a common place. +It can be related to security issues where the consumers can't clone the producer's code. Also if you keep +contracts in a single place then you, as a producer, will know how many consumers you have and which +consumer will you break with your local changes. + +* Repo structure * + +Let's assume that we have a producer with coordinates `com.example:server` and 3 consumers: `client1`, +`client2`, `client3`. Then in the repository with common contracts you would have the following setup +(which you can checkout https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/contracts[here]: + +[source,bash,indent=0] +---- +├── com +│   └── example +│   └── server +│   ├── client1 +│   │   └── expectation.groovy +│   ├── client2 +│   │   └── expectation.groovy +│   ├── client3 +│   │   └── expectation.groovy +│   └── pom.xml +├── mvnw +├── mvnw.cmd +├── pom.xml +└── src + └── assembly + └── contracts.xml +---- + +As you can see the under the slash-delimited groupid `/` artifact id folder (`com/example/server`) you have +expectations of the 3 consumers (`client1`, `client2` and `client3`). Expectations are the standard Groovy DSL +contract files as described throughout this documentation. This repository has to produce a JAR file that maps +one to one to the contents of the repo. + +Example of a `pom.xml` inside the `server` folder. + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/contracts/com/example/server/pom.xml[indent=0] +---- + +As you can see there are no dependencies other than the Spring Cloud Contract Verifier Maven plugin. +Those poms are necessary for the consumer side to run `mvn clean install -DskipTests` to locally install + stubs of the producer project. + +The `pom.xml` in the root folder can look like this: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/contracts/pom.xml[indent=0] +---- + +It's using the assembly plugin in order to build the JAR with all the contracts. Example of such setup is here: + +[source,xml,indent=0] +---- +include::{introduction_url}/samples/standalone/contracts/src/assembly/contracts.xml[indent=0] +---- + +*Workflow* + +The workflow would look similar to the one presented in the `Step by step guide to CDC`. The only difference + is that the producer doesn't own the contracts anymore. So the consumer and the producer have to work on + common contracts in a common repository. + +_Consumer_ + +When the *consumer* wants to work on the contracts offline, instead of cloning the producer code, the +consumer team clones the common repository, goes to the required producer's folder (e.g. `com/example/server`) +and runs `mvn clean install -DskipTests` to install locally the stubs converted from the contracts. + +TIP: You need to have http://maven.apache.org/download.cgi[Maven installed locally] + +_Producer_ + +As a *producer* it's enough to alter the Spring Cloud Contract Verifier to provide the URL and the dependency +of the JAR containing the contracts: + +[source,xml,indent=0] +---- +include::{introduction_url}/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/basic-remote-contracts/pom-with-repo.xml[tags=remote_config,indent=0] +---- + +With this setup the JAR with groupid `com.example.standalone` and artifactid `contracts` will be downloaded +from `http://link/to/your/nexus/or/artifactory/or/sth`. It will be then unpacked in a local temporary folder +and contracts present under the `com/example/server` will be picked as the ones used to generate the +tests and the stubs. Due to this convention the producer team will know which consumer teams will be broken +when some incompatible changes are done. + +The rest of the flow looks the same. \ No newline at end of file diff --git a/docs/src/main/asciidoc/verifier/rest.adoc b/docs/src/main/asciidoc/verifier/rest.adoc index ad6f94cffc..84c8692553 100644 --- a/docs/src/main/asciidoc/verifier/rest.adoc +++ b/docs/src/main/asciidoc/verifier/rest.adoc @@ -129,6 +129,11 @@ contracts { contractsDslDir = "${project.rootDir}/src/test/resources/contracts" basePackageForTests = 'org.springframework.cloud.verifier.tests' stubsOutputDir = project.file("${project.buildDir}/stubs") + + // the following properties are used when you want to provide where the JAR with contract lays + contractDependency = new org.springframework.cloud.contract.verifier.plugin.ContractVerifierExtension.Dependency() + contractsPath = '' + contractsWorkOffline = false } tasks.create(type: Jar, name: 'verifierStubsJar', dependsOn: 'generateWireMockClientStubs') { @@ -185,6 +190,12 @@ contracts { - **stubsOutputDir** - dir where the generated WireMock stubs from Groovy DSL should be placed - **targetFramework** - the target test framework to be used; currently Spock and JUnit are supported with JUnit being the default framework +The following properties are used when you want to provide where the JAR with contract lays + + - **contractDependency** - the Dependency that provides `groupid:artifactid:version:classifier` coordinates. You can use the `contractDependency` closure to set it up + - **contractsPath** - if contract deps are downloaded will default to `groupid/artifactid` where `groupid` will be slash separated. Otherwise will scan contracts under provided directory + - **contractsWorkOffline** - in order not to download the dependencies each time you can download them once and work offline afterwards (reuse local Maven repo) + ====== Base class for tests When using Spring Cloud Contract Verifier in default MockMvc you need to create a base specification for all generated acceptance tests. In this class you need to point to endpoint which should be verified. @@ -331,6 +342,13 @@ To change default configuration just add `configuration` section to plugin defin - **contractsDir** - directory containing contracts written using the GroovyDSL. By default `/src/test/resources/contracts`. - **testFramework** - the target test framework to be used; currently Spock and JUnit are supported with JUnit being the default framework +If you want to download your contract definitions from a Maven repository you can use + + - **contractsRepositoryUrl** - URL to a repo with the artifacts with contracts, if not provided should use the current Maven ones + - **contractDependency** - the contract dependency that contains all the packaged contracts + - **contractsPath** - path to concrete contracts in the JAR with packaged contracts. Defaults to `groupid/artifactid` where `gropuid` is slash separated. + - **contractsWorkOffline** - if the dependencies should be downloaded or local Maven only should be reused + For complete information take a look at https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract-maven-plugin/plugin-info.html[Plugin Documentation] ====== Base class for tests diff --git a/pom.xml b/pom.xml index 883984b6ae..2f5c65b4c2 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,6 @@ Brooklyn.BUILD-SNAPSHOT 1.2.0.BUILD-SNAPSHOT 1.1.0.BUILD-SNAPSHOT - 1.1.3.BUILD-SNAPSHOT @@ -147,13 +146,6 @@ pom import - - org.springframework.cloud - spring-cloud-commons-dependencies - ${spring-cloud-commons.version} - pom - import - diff --git a/samples/standalone/contracts/.mvn/wrapper/maven-wrapper.jar b/samples/standalone/contracts/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000..c6feb8bb6f Binary files /dev/null and b/samples/standalone/contracts/.mvn/wrapper/maven-wrapper.jar differ diff --git a/samples/standalone/contracts/.mvn/wrapper/maven-wrapper.properties b/samples/standalone/contracts/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..6637cedb28 --- /dev/null +++ b/samples/standalone/contracts/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip \ No newline at end of file diff --git a/samples/standalone/contracts/com/example/server/client1/expectation.groovy b/samples/standalone/contracts/com/example/server/client1/expectation.groovy new file mode 100644 index 0000000000..47b9ba6662 --- /dev/null +++ b/samples/standalone/contracts/com/example/server/client1/expectation.groovy @@ -0,0 +1,67 @@ +package contracts + +org.springframework.cloud.contract.spec.Contract.make { + request { // (1) + method 'PUT' // (2) + url '/fraudcheck1' // (3) + body([ // (4) + clientId: value(consumer(regex('[0-9]{10}'))), + loanAmount: 99999 + ]) + headers { // (5) + header('Content-Type', 'application/vnd.fraud.v1+json') + } + } + response { // (6) + status 200 // (7) + body([ // (8) + fraudCheckStatus: "FRAUD", + rejectionReason: "Amount too high" + ]) + headers { // (9) + header('Content-Type': value( + producer(regex('application/vnd.fraud.v1.json.*')), + consumer('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 provide parametrize those entries by using the +`value(consumer(...), producer(...))` method. 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.*` + */ \ No newline at end of file diff --git a/samples/standalone/contracts/com/example/server/client2/expectation.groovy b/samples/standalone/contracts/com/example/server/client2/expectation.groovy new file mode 100644 index 0000000000..c8eb2adff4 --- /dev/null +++ b/samples/standalone/contracts/com/example/server/client2/expectation.groovy @@ -0,0 +1,67 @@ +package contracts + +org.springframework.cloud.contract.spec.Contract.make { + request { // (1) + method 'PUT' // (2) + url '/fraudcheck2' // (3) + body([ // (4) + clientId: value(consumer(regex('[0-9]{10}'))), + loanAmount: 99999 + ]) + headers { // (5) + header('Content-Type', 'application/vnd.fraud.v1+json') + } + } + response { // (6) + status 200 // (7) + body([ // (8) + fraudCheckStatus: "FRAUD", + rejectionReason: "Amount too high" + ]) + headers { // (9) + header('Content-Type': value( + producer(regex('application/vnd.fraud.v1.json.*')), + consumer('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 provide parametrize those entries by using the +`value(consumer(...), producer(...))` method. 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.*` + */ \ No newline at end of file diff --git a/samples/standalone/contracts/com/example/server/client3/expectation.groovy b/samples/standalone/contracts/com/example/server/client3/expectation.groovy new file mode 100644 index 0000000000..47958d8ae9 --- /dev/null +++ b/samples/standalone/contracts/com/example/server/client3/expectation.groovy @@ -0,0 +1,67 @@ +package contracts + +org.springframework.cloud.contract.spec.Contract.make { + request { // (1) + method 'PUT' // (2) + url '/fraudcheck3' // (3) + body([ // (4) + clientId: value(consumer(regex('[0-9]{10}'))), + loanAmount: 99999 + ]) + headers { // (5) + header('Content-Type', 'application/vnd.fraud.v1+json') + } + } + response { // (6) + status 200 // (7) + body([ // (8) + fraudCheckStatus: "FRAUD", + rejectionReason: "Amount too high" + ]) + headers { // (9) + header('Content-Type': value( + producer(regex('application/vnd.fraud.v1.json.*')), + consumer('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 provide parametrize those entries by using the +`value(consumer(...), producer(...))` method. 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.*` + */ \ No newline at end of file diff --git a/samples/standalone/contracts/com/example/server/pom.xml b/samples/standalone/contracts/com/example/server/pom.xml new file mode 100644 index 0000000000..ad18558a2d --- /dev/null +++ b/samples/standalone/contracts/com/example/server/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + com.example + server + 0.0.1-SNAPSHOT + + Server Stubs + POM used to install locally stubs for consumer side + + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.BUILD-SNAPSHOT + + + + + UTF-8 + 1.8 + 1.0.0.BUILD-SNAPSHOT + Camden.BUILD-SNAPSHOT + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud-dependencies.version} + pom + import + + + + + + + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + + + ${project.basedir} + + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-releases + Spring Releases + https://repo.spring.io/release + + false + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-releases + Spring Releases + https://repo.spring.io/release + + false + + + + + diff --git a/samples/standalone/contracts/mvnw b/samples/standalone/contracts/mvnw new file mode 100755 index 0000000000..fc7efd17d0 --- /dev/null +++ b/samples/standalone/contracts/mvnw @@ -0,0 +1,234 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CMD_LINE_ARGS + diff --git a/samples/standalone/contracts/mvnw.cmd b/samples/standalone/contracts/mvnw.cmd new file mode 100644 index 0000000000..001048081d --- /dev/null +++ b/samples/standalone/contracts/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/samples/standalone/contracts/pom.xml b/samples/standalone/contracts/pom.xml new file mode 100644 index 0000000000..33fcad34dc --- /dev/null +++ b/samples/standalone/contracts/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + com.example.standalone + contracts + 0.0.1-SNAPSHOT + + Spring Cloud Contract Verifier Http Server Sample + Spring Cloud Contract Verifier Http Server Sample + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + contracts + prepare-package + + single + + + true + ${basedir}/src/assembly/contracts.xml + + false + + + + + + + + diff --git a/samples/standalone/contracts/src/assembly/contracts.xml b/samples/standalone/contracts/src/assembly/contracts.xml new file mode 100644 index 0000000000..89fb589f6c --- /dev/null +++ b/samples/standalone/contracts/src/assembly/contracts.xml @@ -0,0 +1,23 @@ + + project + + jar + + false + + + ${project.basedir} + / + true + + **/${project.build.directory}/** + mvnw + mvnw.cmd + .mvn/** + src/** + + + + \ No newline at end of file diff --git a/spring-cloud-contract-dependencies/pom.xml b/spring-cloud-contract-dependencies/pom.xml index 5552ffb5c1..6a848d3c49 100644 --- a/spring-cloud-contract-dependencies/pom.xml +++ b/spring-cloud-contract-dependencies/pom.xml @@ -17,6 +17,7 @@ 2.1.7 0.4.7 1.0.2.v20150114 + 1.1.3.BUILD-SNAPSHOT @@ -60,6 +61,13 @@ spring-cloud-starter-contract-stub-runner-jetty ${project.version} + + org.springframework.cloud + spring-cloud-commons-dependencies + ${spring-cloud-commons.version} + pom + import + com.github.tomakehurst wiremock diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java new file mode 100644 index 0000000000..e37b8c2ba4 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java @@ -0,0 +1,91 @@ +package org.springframework.cloud.contract.stubrunner; + +import java.io.File; +import java.lang.invoke.MethodHandles; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties; +import org.springframework.util.StringUtils; + +/** + * Downloads a JAR with contracts and sets up the plugin configuration with proper + * inclusion patterns + * + * @author Marcin Grzejszczak + * + * @since 1.0.0 + */ +public class ContractDownloader { + + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + + private final StubDownloader stubDownloader; + private final StubConfiguration contractsJarStubConfiguration; + private final String contractsPath; + private final String projectGroupId; + private final String projectArtifactId; + + public ContractDownloader(StubDownloader stubDownloader, + StubConfiguration contractsJarStubConfiguration, + String contractsPath, String projectGroupId, String projectArtifactId) { + this.stubDownloader = stubDownloader; + this.contractsJarStubConfiguration = contractsJarStubConfiguration; + this.contractsPath = contractsPath; + this.projectGroupId = projectGroupId; + this.projectArtifactId = projectArtifactId; + } + + /** + * Downloads JAR containing all the contracts. Plugin configuration gets updated with + * the inclusion pattern for the downloaded contracts. The JAR with the contracts contains all + * the contracts for all the projects. We're interested only in its subset. + * + * @param config - Plugin configuration that will get updated with the inclusion pattern + * @return location of the unpacked downloaded stubs + */ + public File unpackedDownloadedContracts(ContractVerifierConfigProperties config) { + File contractsDirectory = unpackAndDownloadContracts(); + updatePropertiesWithInclusion(contractsDirectory, config); + return contractsDirectory; + } + + public ContractVerifierConfigProperties updatePropertiesWithInclusion(File contractsDirectory, + ContractVerifierConfigProperties config) { + String pattern = StringUtils.hasText(this.contractsPath) ? patternFromProperty(contractsDirectory) : + groupArtifactToPattern(contractsDirectory); + log.info("Pattern to pick contracts equals [" + pattern + "]"); + config.setIncludedContracts(pattern); + return config; + } + + private String patternFromProperty(File contractsDirectory) { + return "^" + contractsDirectory.getAbsolutePath() + contractsPath() + ".*$"; + } + + private String contractsPath() { + return this.contractsPath.startsWith(File.separator) ? this.contractsPath : File.separator + this.contractsPath; + } + + private File unpackAndDownloadContracts() { + log.info("Will download contracts for [" + this.contractsJarStubConfiguration + "]"); + Map.Entry unpackedContractStubs = this.stubDownloader + .downloadAndUnpackStubJar(null, this.contractsJarStubConfiguration); + if (unpackedContractStubs == null) { + throw new IllegalStateException("The contracts failed to be downloaded!"); + } + return unpackedContractStubs.getValue(); + } + + private String groupArtifactToPattern(File contractsDirectory) { + return "^" + + contractsDirectory.getAbsolutePath() + + File.separator + + this.projectGroupId.replace(".", File.separator) + + File.separator + + this.projectArtifactId + + File.separator + + ".*$"; + } +} diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubConfiguration.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubConfiguration.java index 51b2417c8c..23a6caccfd 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubConfiguration.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubConfiguration.java @@ -91,10 +91,17 @@ public class StubConfiguration { return ""; } return StringUtils.arrayToDelimitedString( - new String[] { this.groupId, this.artifactId, this.version, this.classifier }, + new String[] { nullCheck(this.groupId), + nullCheck(this.artifactId), + nullCheck(this.version), + nullCheck(this.classifier) }, STUB_COLON_DELIMITER); } + private String nullCheck(String value) { + return StringUtils.hasText(value) ? value : ""; + } + public boolean groupIdAndArtifactMatches(String ivyNotationAsString) { String[] parts = ivyNotationFrom(ivyNotationAsString); String groupId = parts[0]; @@ -117,6 +124,10 @@ public class StubConfiguration { return this.classifier; } + public String getVersion() { + return this.version; + } + @Override public int hashCode() { final int prime = 31; diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/ContractDownloaderSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/ContractDownloaderSpec.groovy new file mode 100644 index 0000000000..89ee9e369a --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/ContractDownloaderSpec.groovy @@ -0,0 +1,41 @@ +package org.springframework.cloud.contract.stubrunner + +import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties +import spock.lang.Specification +/** + * @author Marcin Grzejszczak + */ +class ContractDownloaderSpec extends Specification { + + StubDownloader stubDownloader = Stub() + StubConfiguration stubConfiguration = new StubConfiguration('') + File file = new File('/some/path/to/somewhere') + + def 'should set inclusion pattern on config when path pattern was explicitly provided with a separator at the beginning'() { + given: + String contractPath = '/a/b/c/d' + ContractDownloader contractDownloader = new ContractDownloader(stubDownloader, + stubConfiguration, contractPath, '', '') + ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties() + and: + stubDownloader.downloadAndUnpackStubJar(_, _) >> new AbstractMap.SimpleEntry(stubConfiguration, file) + when: + contractDownloader.unpackedDownloadedContracts(properties) + then: + properties.includedContracts == '^/some/path/to/somewhere/a/b/c/d.*$' + } + + def 'should set inclusion pattern on config when path pattern was explicitly provided without a separator at the beginning'() { + given: + String contractPath = 'a/b/c/d' + ContractDownloader contractDownloader = new ContractDownloader(stubDownloader, + stubConfiguration, contractPath, '', '') + ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties() + and: + stubDownloader.downloadAndUnpackStubJar(_, _) >> new AbstractMap.SimpleEntry(stubConfiguration, file) + when: + contractDownloader.unpackedDownloadedContracts(properties) + then: + properties.includedContracts == '^/some/path/to/somewhere/a/b/c/d.*$' + } +} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-converters/pom.xml index 3c2f456076..0569637047 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/pom.xml +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/pom.xml @@ -14,6 +14,10 @@ Spring Cloud Contract Converters 1.8 + + org.springframework + spring-context + org.springframework.cloud spring-cloud-contract-verifier diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverter.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverter.groovy index 5be7dec7ab..8c8d5d6ddc 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverter.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverter.groovy @@ -54,7 +54,8 @@ class RecursiveFilesConverter { } void processFiles() { - ContractFileScanner scanner = new ContractFileScanner(properties.contractsDslDir, properties.excludedFiles as Set, [] as Set) + ContractFileScanner scanner = new ContractFileScanner(properties.contractsDslDir, + properties.excludedFiles as Set, [] as Set, properties.includedContracts) ListMultimap contracts = scanner.findContracts() if (log.isDebugEnabled()) { log.debug("Found the following contracts $contracts") diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy new file mode 100644 index 0000000000..01afc18dc5 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy @@ -0,0 +1,127 @@ +package org.springframework.cloud.contract.verifier.plugin + +import groovy.transform.ToString +import org.springframework.cloud.contract.verifier.config.TestFramework +import org.springframework.cloud.contract.verifier.config.TestMode + +/** + * @author Marcin Grzejszczak + */ +@ToString +class ContractVerifierExtension { + /** + * For which unit test library tests should be generated + */ + TestFramework targetFramework = TestFramework.JUNIT + + /** + * Which mechanism should be used to invoke REST calls during tests + */ + TestMode testMode = TestMode.MOCKMVC + + /** + * Base package for generated tests + */ + String basePackageForTests + + /** + * Class which all generated tests should extend + */ + String baseClassForTests + + /** + * Suffix for generated test classes, like Spec or Test + */ + String nameSuffixForTests + + /** + * Rule class that should be added to generated tests + */ + String ruleClassForTests + + /** + * Patterns that should not be taken into account for processing + */ + List excludedFiles = [] + + /** + * Patterns for which generated tests should be @Ignored + */ + List ignoredFiles = [] + + /** + * Imports that should be added to generated tests + */ + String[] imports = [] + + /** + * Static imports that should be added to generated tests + */ + String[] staticImports = [] + + /** + * Directory containing contracts written using the GroovyDSL + */ + File contractsDslDir + + /** + * Test source directory where tests generated from Groovy DSL should be placed + */ + File generatedTestSourcesDir + + /** + * Dir where the generated WireMock stubs from Groovy DSL should be placed. + * You can then mention them in your packaging task to create jar with stubs + */ + File stubsOutputDir + + /** + * Suffix for the generated Stubs Jar task + */ + String stubsSuffix = 'stubs' + + /** + * Incubating feature. You can check the size of JSON arrays. If not turned on + * explicitly will be disabled. + */ + Boolean assertJsonSize = false + + /** + * The URL from which a JAR containing the contracts should get downloaded. If not provided + * but artifactid / coordinates notation was provided then the current Maven's build repositories will be + * taken into consideration + */ + String contractsRepositoryUrl + + /** + * Dependency that contains packaged contracts + */ + Dependency contractDependency = new Dependency() + + /** + * The path in the JAR with all the contracts where contracts for this particular service lay. + * If not provided will be resolved to {@code groupid/artifactid}. Example: + *

+ * If {@code groupid} is {@code com.example} and {@code artifactid} is {@code service} then the resolved path will be + * {@code /com/example/artifactid} + */ + String contractsPath + + /** + * If {@code true} then JAR with contracts will be taken from local maven repository + */ + boolean contractsWorkOffline + + void contractDependency(@DelegatesTo(Dependency) Closure closure) { + closure.delegate = contractDependency + closure.call() + } + + static class Dependency { + String groupId + String artifactId + String classifier + String version + String stringNotation + } +} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy new file mode 100644 index 0000000000..6e0262ebfe --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy @@ -0,0 +1,31 @@ +package org.springframework.cloud.contract.verifier.plugin + +import groovy.transform.PackageScope +import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties + +/** + * @author Marcin Grzejszczak + */ +@PackageScope +class ExtensionToProperties { + + protected static ContractVerifierConfigProperties fromExtension(ContractVerifierExtension extension) { + return new ContractVerifierConfigProperties( + targetFramework: extension.targetFramework, + testMode: extension.testMode, + basePackageForTests: extension.basePackageForTests, + baseClassForTests: extension.baseClassForTests, + nameSuffixForTests: extension.nameSuffixForTests, + ruleClassForTests: extension.ruleClassForTests, + excludedFiles: extension.excludedFiles, + ignoredFiles: extension.ignoredFiles, + imports: extension.imports, + staticImports: extension.staticImports, + contractsDslDir: extension.contractsDslDir, + generatedTestSourcesDir: extension.generatedTestSourcesDir, + stubsOutputDir: extension.stubsOutputDir, + stubsSuffix: extension.stubsSuffix, + assertJsonSize: extension.assertJsonSize + ) + } +} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy index ce5301b3fd..055e58ba33 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy @@ -38,7 +38,7 @@ class GenerateServerTestsTask extends ConventionTask { File generatedTestSourcesDir //TODO: How to deal with @Input*, @Output* and that domain object? - ContractVerifierConfigProperties configProperties + ContractVerifierExtension configProperties @TaskAction void generate() { @@ -51,7 +51,9 @@ class GenerateServerTestsTask extends ConventionTask { try { //TODO: What with that? How to pass? - TestGenerator generator = new TestGenerator(getConfigProperties()) + ContractVerifierConfigProperties props = ExtensionToProperties.fromExtension(getConfigProperties()) + props.contractsDslDir = getContractsDslDir() + TestGenerator generator = new TestGenerator(props) int generatedClasses = generator.generate() project.logger.info("Generated {} test classes", generatedClasses) } catch (ContractVerifierException e) { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateWireMockClientStubsFromDslTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateWireMockClientStubsFromDslTask.groovy index a3693e28c8..eed1d46cc6 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateWireMockClientStubsFromDslTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateWireMockClientStubsFromDslTask.groovy @@ -38,18 +38,19 @@ class GenerateWireMockClientStubsFromDslTask extends ConventionTask { @OutputDirectory File stubsOutputDir - ContractVerifierConfigProperties configProperties + ContractVerifierExtension configProperties @TaskAction void generate() { logger.info("Spring Cloud Contract Verifier Plugin: Invoking DSL to WireMock client stubs conversion") logger.debug("From '${getContractsDslDir()}' to '${getStubsOutputDir()}'") - ContractVerifierConfigProperties props = getConfigProperties() + ContractVerifierConfigProperties props = ExtensionToProperties.fromExtension(getConfigProperties()) + props.contractsDslDir = getContractsDslDir() File outMappingsDir = props.stubsOutputDir != null ? new File(props.stubsOutputDir, DEFAULT_MAPPINGS_FOLDER) : new File(project.buildDir, "stubs/$DEFAULT_MAPPINGS_FOLDER") RecursiveFilesConverter converter = new RecursiveFilesConverter( new DslToWireMockClientConverter(), - getConfigProperties(), outMappingsDir) + props, outMappingsDir) converter.processFiles() } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloader.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloader.groovy new file mode 100644 index 0000000000..8ee920a0e3 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloader.groovy @@ -0,0 +1,82 @@ +package org.springframework.cloud.contract.verifier.plugin + +import groovy.transform.PackageScope +import org.gradle.api.Project +import org.gradle.api.logging.Logger +import org.springframework.cloud.contract.stubrunner.AetherStubDownloader +import org.springframework.cloud.contract.stubrunner.ContractDownloader +import org.springframework.cloud.contract.stubrunner.StubConfiguration +import org.springframework.cloud.contract.stubrunner.StubRunnerOptionsBuilder +import org.springframework.util.StringUtils + +import java.util.concurrent.ConcurrentHashMap +/** + * @author Marcin Grzejszczak + */ +@PackageScope +class GradleContractsDownloader { + + private static final String LATEST_VERSION = '+' + + private final Project project + private final Logger log + private static final Map downloadedContract = new ConcurrentHashMap<>() + + GradleContractsDownloader(Project project, Logger log) { + this.project = project + this.log = log + } + + File downloadAndUnpackContractsIfRequired(ContractVerifierExtension extension) { + File defaultContractsDir = extension.contractsDslDir + // download contracts, unzip them and pass as output directory + if (shouldDownloadContracts(extension)) { + this.log.info("For project [${this.project.name}] Download dependency is provided - will download contract jars") + StubConfiguration configuration = stubConfiguration(extension.contractDependency) + if (downloadedContract.get(configuration)) { + this.log.info("For project [${this.project.name}] Returning the cached location of the contracts") + return downloadedContract.get(configuration) + } + File downloadedContracts = contractDownloader(extension, configuration).unpackedDownloadedContracts( + ExtensionToProperties.fromExtension(extension)) + downloadedContract.put(configuration, downloadedContracts) + return downloadedContracts + } + this.log.info("For project [${this.project.name}] will use contracts provided in the folder [" + defaultContractsDir + "]") + return defaultContractsDir + } + + private boolean shouldDownloadContracts(ContractVerifierExtension extension) { + return StringUtils.hasText(extension.contractsRepositoryUrl) && + (StringUtils.hasText(extension.contractDependency.artifactId) || + StringUtils.hasText(extension.contractDependency.stringNotation)) + } + + private ContractDownloader contractDownloader(ContractVerifierExtension extension, StubConfiguration configuration) { + return new ContractDownloader(stubDownloader(extension), configuration, + extension.contractsPath, this.project.group as String, this.project.name) + } + + private AetherStubDownloader stubDownloader(ContractVerifierExtension extension) { + return new AetherStubDownloader( + new StubRunnerOptionsBuilder() + .withStubRepositoryRoot(extension.contractsRepositoryUrl) + .withWorkOffline(extension.contractsWorkOffline) + .build()) + } + + private StubConfiguration stubConfiguration(ContractVerifierExtension.Dependency contractDependency) { + String groupId = contractDependency.groupId + String artifactId = contractDependency.artifactId + String version = StringUtils.hasText(contractDependency.version) ? + contractDependency.version : LATEST_VERSION + String classifier = contractDependency.classifier + String stringNotation = contractDependency.stringNotation + if (StringUtils.hasText(stringNotation)) { + StubConfiguration stubConfiguration = new StubConfiguration(stringNotation) + return new StubConfiguration(stubConfiguration.groupId, stubConfiguration.artifactId, + stubConfiguration.version, contractDependency.classifier) + } + return new StubConfiguration(groupId, artifactId, version, classifier) + } +} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy index d8edc0944a..d228a6374c 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy @@ -24,7 +24,6 @@ import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.plugins.MavenPublishPlugin import org.gradle.api.tasks.Copy import org.gradle.jvm.tasks.Jar -import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties /** * Gradle plugin for Spring Cloud Contract Verifier that from the DSL contract can *
    @@ -62,14 +61,15 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { void apply(Project project) { this.project = project project.plugins.apply(GroovyPlugin) - ContractVerifierConfigProperties extension = project.extensions.create(EXTENSION_NAME, ContractVerifierConfigProperties) + ContractVerifierExtension extension = project.extensions.create(EXTENSION_NAME, ContractVerifierExtension) + GradleContractsDownloader downloader = new GradleContractsDownloader(this.project, this.project.logger) project.check.dependsOn(GENERATE_SERVER_TESTS_TASK_NAME) setConfigurationDefaults(extension) - createGenerateTestsTask(extension) - createAndConfigureGenerateWireMockClientStubsFromDslTask(extension) + createGenerateTestsTask(downloader, extension) + createAndConfigureGenerateWireMockClientStubsFromDslTask(downloader, extension) Task stubsJar = createAndConfigureStubsJarTasks(extension) - createAndConfigureCopyContractsTask(stubsJar, extension) - createAndConfigureMavenPublishPlugin(stubsJar, extension) + createAndConfigureCopyContractsTask(stubsJar, downloader, extension) + createAndConfigureMavenPublishPlugin(stubsJar) addProjectDependencies(project) addIdeaTestSources(project, extension) } @@ -94,11 +94,11 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { project.dependencies.add("testCompile", "org.assertj:assertj-core:2.3.0") } - private void setConfigurationDefaults(ContractVerifierConfigProperties extension) { + private void setConfigurationDefaults(ContractVerifierExtension extension) { extension.with { - generatedTestSourcesDir = project.file("${project.buildDir}/generated-test-sources/contracts") - contractsDslDir = defaultContractsDir() //TODO: Use sourceset - basePackageForTests = 'org.springframework.cloud.contract.verifier.tests' + generatedTestSourcesDir = generatedTestSourcesDir ?: project.file("${project.buildDir}/generated-test-sources/contracts") + contractsDslDir = contractsDslDir ?: defaultContractsDir() //TODO: Use sourceset + basePackageForTests = basePackageForTests ?: 'org.springframework.cloud.contract.verifier.tests' stubsOutputDir = stubsOutputDir ?: project.file("${project.buildDir}/stubs") } } @@ -107,29 +107,31 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { return project.file("${project.rootDir}/src/test/resources/contracts") } - private void createGenerateTestsTask(ContractVerifierConfigProperties extension) { + private void createGenerateTestsTask(GradleContractsDownloader downloader, + ContractVerifierExtension extension) { Task task = project.tasks.create(GENERATE_SERVER_TESTS_TASK_NAME, GenerateServerTestsTask) task.description = "Generate server tests from the contracts" task.group = GROUP_NAME task.conventionMapping.with { - contractsDslDir = { extension.contractsDslDir } + contractsDslDir = { downloader.downloadAndUnpackContractsIfRequired(extension) } generatedTestSourcesDir = { extension.generatedTestSourcesDir } configProperties = { extension } } } - private void createAndConfigureGenerateWireMockClientStubsFromDslTask(ContractVerifierConfigProperties extension) { + private void createAndConfigureGenerateWireMockClientStubsFromDslTask( + GradleContractsDownloader downloader, ContractVerifierExtension extension) { Task task = project.tasks.create(DSL_TO_WIREMOCK_CLIENT_TASK_NAME, GenerateWireMockClientStubsFromDslTask) task.description = "Generate WireMock client stubs from the contracts" task.group = GROUP_NAME task.conventionMapping.with { - contractsDslDir = { extension.contractsDslDir } + contractsDslDir = { downloader.downloadAndUnpackContractsIfRequired(extension) } stubsOutputDir = { extension.stubsOutputDir } configProperties = { extension } } } - private Task createAndConfigureStubsJarTasks(ContractVerifierConfigProperties extension) { + private Task createAndConfigureStubsJarTasks(ContractVerifierExtension extension) { Task task = stubsTask() if (task) { project.logger.info("Spring Cloud Contract Verifier Plugin: Stubs jar task was present - won't create one. Remember about adding it to artifacts as an archive!") @@ -158,9 +160,11 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { } } - private Task createAndConfigureCopyContractsTask(Task stubs, ContractVerifierConfigProperties extension) { + private Task createAndConfigureCopyContractsTask(Task stubs, + GradleContractsDownloader downloader, + ContractVerifierExtension extension) { Task task = project.tasks.create(type: Copy, name: COPY_CONTRACTS_TASK_NAME) { - from { extension.contractsDslDir } + from { downloader.downloadAndUnpackContractsIfRequired(extension) } into { extension.stubsOutputDir != null ? project.file("${extension.stubsOutputDir}/contracts") : project.file("${project.buildDir}/stubs/contracts") } } @@ -170,7 +174,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { return task } - private void createAndConfigureMavenPublishPlugin(Task stubsTask, ContractVerifierConfigProperties extension) { + private void createAndConfigureMavenPublishPlugin(Task stubsTask) { if (!classIsOnClasspath("org.gradle.api.publish.maven.plugins.MavenPublishPlugin")) { project.logger.debug("Maven Publish Plugin is not present - won't add default publication") return diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierIntegrationSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierIntegrationSpec.groovy index 722be177a3..ced185b132 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierIntegrationSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierIntegrationSpec.groovy @@ -91,7 +91,7 @@ abstract class ContractVerifierIntegrationSpec extends Specification { } protected String[] checkAndPublishToMavenLocal() { - String[] args = ["check", "publishToMavenLocal", "--info"] as String[] + String[] args = ["check", "publishToMavenLocal", "--info", "--stacktrace"] as String[] if (WORK_OFFLINE) args << "--offline" return args } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy index 53af60f5b2..e9f6a3f0ad 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy @@ -6,7 +6,6 @@ import org.gradle.api.plugins.GroovyPlugin import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.plugins.MavenPublishPlugin import org.gradle.testfixtures.ProjectBuilder -import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import spock.lang.Specification class ContractVerifierSpec extends Specification { @@ -32,7 +31,7 @@ class ContractVerifierSpec extends Specification { project.plugins.apply(SpringCloudContractVerifierGradlePlugin) expect: - project.extensions.findByType(ContractVerifierConfigProperties) != null + project.extensions.findByType(ContractVerifierExtension) != null } def "should create generateContractTests task"() { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/build.gradle b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/build.gradle index 099e9c5da0..7269ee6a6a 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/build.gradle +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/build.gradle @@ -52,24 +52,10 @@ subprojects { configure([project(':fraudDetectionService'), project(':loanApplicationService')]) { apply plugin: 'spring-boot' - apply plugin: 'spring-cloud-contract' apply plugin: 'maven-publish' - ext { - contractsDir = file("mappings") - stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/") - } ext['jetty.version'] = '9.2.17.v20160517' - contracts { - targetFramework = 'Spock' - testMode = 'JaxRsClient' - baseClassForTests = 'org.springframework.cloud.MvcSpec' - contractsDslDir = file("${project.projectDir.absolutePath}/mappings/") - generatedTestSourcesDir = file("${project.buildDir}/generated-test-sources/") - stubsOutputDir = stubsOutputDirRoot - } - jar { version = '0.0.1' } @@ -114,16 +100,38 @@ configure([project(':fraudDetectionService'), project(':loanApplicationService') configure(project(':fraudDetectionService')) { test.dependsOn('generateWireMockClientStubs') + + apply plugin: 'spring-cloud-contract' + + ext { + contractsDir = file("mappings") + stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/") + } + ext['jetty.version'] = '9.2.17.v20160517' + + contracts { + targetFramework = 'Spock' + testMode = 'JaxRsClient' + baseClassForTests = 'org.springframework.cloud.MvcSpec' + contractsRepositoryUrl = "file://" + file("${project.rootDir.absolutePath}/m2repo/repository").absolutePath + contractDependency { + stringNotation = "com.example:jersey-contracts" + } + generatedTestSourcesDir = file("${project.buildDir}/generated-test-sources/") + stubsOutputDir = stubsOutputDirRoot + } } configure(project(':loanApplicationService')) { task copyCollaboratorStubs(type: Copy) { File fraudBuildDir = project(':fraudDetectionService').buildDir - from(new File(fraudBuildDir, "/production/${project(':fraudDetectionService').name}-stubs/")) - into "src/test/resources/" + from(new File(fraudBuildDir, "/production/${project(':fraudDetectionService').name}-stubs/")) { + include '**/*.json' + } + into "src/test/resources/mappings" } - generateContractTests.dependsOn('copyCollaboratorStubs') + test.dependsOn('copyCollaboratorStubs') } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/mappings/fraudDetectionService/shouldMarkClientAsFraud.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/mappings/fraudDetectionService/shouldMarkClientAsFraud.groovy deleted file mode 100644 index 6c3106b3f0..0000000000 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/mappings/fraudDetectionService/shouldMarkClientAsFraud.groovy +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013-2016 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. - */ - -import org.springframework.cloud.contract.spec.Contract - -Contract.make { - request { - method """PUT""" - url """/fraudcheck""" - body(""" - { - "clientPesel":"${value(consumer(regex('[0-9]{10}')), producer('1234567890'))}", - "loanAmount":99999} - """ - ) - headers { - header("""Content-Type""", """application/vnd.fraud.v1+json""") - } - - } - response { - status 200 - body( """{ - "fraudCheckStatus": "${value(consumer('FRAUD'), producer(regex('[A-Z]{5}')))}", - "rejectionReason": "Amount too high" -}""") - headers { - header('Content-Type': value( - producer(regex('application/vnd.fraud.v1.json.*')), - consumer('application/vnd.fraud.v1+json')) - ) - } - } - -} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/mappings/fraudDetectionService/shouldMarkClientAsNotFraud.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/mappings/fraudDetectionService/shouldMarkClientAsNotFraud.groovy deleted file mode 100644 index 00d5236743..0000000000 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/mappings/fraudDetectionService/shouldMarkClientAsNotFraud.groovy +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2013-2016 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. - */ - -import org.springframework.cloud.contract.spec.Contract - -Contract.make { - request { - method 'PUT' - url '/fraudcheck' - body(""" - { - "clientPesel":"${value(consumer(regex('[0-9]{10}')), producer('1234567890'))}", - "loanAmount":123.123 - } - """ - ) - headers { - header('Content-Type', 'application/vnd.fraud.v1+json') - } - - } - response { - status 200 - body( - fraudCheckStatus: "OK", - rejectionReason: $(consumer(null), producer(execute('assertThatRejectionReasonIsNull($it)'))) - ) - headers { - header('Content-Type': value( - producer(regex('application/vnd.fraud.v1.json.*')), - consumer('application/vnd.fraud.v1+json')) - ) - } - } - -} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/0.0.1-SNAPSHOT/jersey-contracts-0.0.1-SNAPSHOT.jar b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/0.0.1-SNAPSHOT/jersey-contracts-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000000..93a77ae409 Binary files /dev/null and b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/0.0.1-SNAPSHOT/jersey-contracts-0.0.1-SNAPSHOT.jar differ diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/0.0.1-SNAPSHOT/jersey-contracts-0.0.1-SNAPSHOT.pom b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/0.0.1-SNAPSHOT/jersey-contracts-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..79bb66d076 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/0.0.1-SNAPSHOT/jersey-contracts-0.0.1-SNAPSHOT.pom @@ -0,0 +1,25 @@ + + + + + 4.0.0 + com.example + jersey-contracts + 0.0.1-SNAPSHOT + pom + diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/0.0.1-SNAPSHOT/maven-metadata-local.xml b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/0.0.1-SNAPSHOT/maven-metadata-local.xml new file mode 100644 index 0000000000..05d9ce3299 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/0.0.1-SNAPSHOT/maven-metadata-local.xml @@ -0,0 +1,24 @@ + + + com.example + jersey-contracts + 0.0.1-SNAPSHOT + + + true + + 20160916125313 + + + jar + 0.0.1-SNAPSHOT + 20160916125313 + + + pom + 0.0.1-SNAPSHOT + 20160916125313 + + + + diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/maven-metadata.xml b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/maven-metadata.xml new file mode 100644 index 0000000000..b3827fc5c5 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/m2repo/repository/com/example/jersey-contracts/maven-metadata.xml @@ -0,0 +1,28 @@ + + + + + com.example + jersey-contracts + 0.0.1-SNAPSHOT + + + 0.0.1-SNAPSHOT + + 20160409062112 + + diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/ConvertMojo.java b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/ConvertMojo.java index bbe5b76027..006e59128c 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/ConvertMojo.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/ConvertMojo.java @@ -16,8 +16,10 @@ package org.springframework.cloud.contract.maven.verifier; import java.io.File; +import javax.inject.Inject; import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Dependency; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -27,6 +29,8 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.filtering.MavenResourcesFiltering; +import org.eclipse.aether.RepositorySystemSession; +import org.springframework.cloud.contract.maven.verifier.stubrunner.AetherStubDownloaderFactory; import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties; import org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter; import org.springframework.cloud.contract.verifier.wiremock.RecursiveFilesConverter; @@ -40,6 +44,9 @@ import org.springframework.cloud.contract.verifier.wiremock.RecursiveFilesConver defaultPhase = LifecyclePhase.PROCESS_TEST_RESOURCES) public class ConvertMojo extends AbstractMojo { + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) + private RepositorySystemSession repoSession; + /** * Directory containing Spring Cloud Contract Verifier contracts written using the GroovyDSL */ @@ -72,9 +79,43 @@ public class ConvertMojo extends AbstractMojo { @Parameter(defaultValue = "${project}", readonly = true) private MavenProject project; + /** + * The URL from which a JAR containing the contracts should get downloaded. If not provided + * but artifactid / coordinates notation was provided then the current Maven's build repositories will be + * taken into consideration + */ + @Parameter(property = "contractsRepositoryUrl") + private String contractsRepositoryUrl; + + @Parameter(property = "contractDependency") + private Dependency contractDependency; + + /** + * The path in the JAR with all the contracts where contracts for this particular service lay. + * If not provided will be resolved to {@code groupid/artifactid}. Example: + *

    + * If {@code groupid} is {@code com.example} and {@code artifactid} is {@code service} then the resolved path will be + * {@code /com/example/artifactid} + */ + @Parameter(property = "contractsPath") + private String contractsPath; + + /** + * If {@code true} then JAR with contracts will be taken from local maven repository + */ + @Parameter(property = "contractsWorkOffline", defaultValue = "false") + private boolean contractsWorkOffline; + @Component(role = MavenResourcesFiltering.class, hint = "default") private MavenResourcesFiltering mavenResourcesFiltering; + private final AetherStubDownloaderFactory aetherStubDownloaderFactory; + + @Inject + public ConvertMojo(AetherStubDownloaderFactory aetherStubDownloaderFactory) { + this.aetherStubDownloaderFactory = aetherStubDownloaderFactory; + } + public void execute() throws MojoExecutionException, MojoFailureException { if (this.skip) { @@ -83,12 +124,18 @@ public class ConvertMojo extends AbstractMojo { this.skip)); return; } + // download contracts, unzip them and pass as output directory + ContractVerifierConfigProperties config = new ContractVerifierConfigProperties(); + File contractsDirectory = new MavenContractsDownloader(this.project, this.contractDependency, + this.contractsPath, this.contractsRepositoryUrl, this.contractsWorkOffline, getLog(), + this.aetherStubDownloaderFactory, this.repoSession).downloadAndUnpackContractsIfRequired(config, this.contractsDirectory); + getLog().info("Directory with contract is present at [" + contractsDirectory + "]"); new CopyContracts(this.project, this.mavenSession, this.mavenResourcesFiltering) - .copy(this.contractsDirectory, this.outputDirectory); + .copy(contractsDirectory, this.outputDirectory); - final ContractVerifierConfigProperties config = new ContractVerifierConfigProperties(); - config.setContractsDslDir(isInsideProject() ? this.contractsDirectory : this.source); + + config.setContractsDslDir(isInsideProject() ? contractsDirectory : this.source); config.setStubsOutputDir( isInsideProject() ? new File(this.outputDirectory, "mappings") : this.destination); @@ -100,6 +147,7 @@ public class ConvertMojo extends AbstractMojo { getLog().info(String.format("WireMock stubs mappings directory: %s", config.getStubsOutputDir())); + RecursiveFilesConverter converter = new RecursiveFilesConverter( new DslToWireMockClientConverter(), config); converter.processFiles(); diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateStubsMojo.java b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateStubsMojo.java index 98a14ca83f..88de449039 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateStubsMojo.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateStubsMojo.java @@ -29,6 +29,9 @@ import org.apache.maven.project.MavenProjectHelper; import org.codehaus.plexus.archiver.Archiver; import org.codehaus.plexus.archiver.jar.JarArchiver; +/** + * Picks the converted .json files and creates a jar. Requires convert to be executed first + */ @Mojo(name = "generateStubs", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true) public class GenerateStubsMojo extends AbstractMojo { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateTestsMojo.java b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateTestsMojo.java index e43ab91d6b..b041994013 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateTestsMojo.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateTestsMojo.java @@ -18,6 +18,9 @@ package org.springframework.cloud.contract.maven.verifier; import java.io.File; import java.util.List; +import javax.inject.Inject; + +import org.apache.maven.model.Dependency; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -26,6 +29,8 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystemSession; +import org.springframework.cloud.contract.maven.verifier.stubrunner.AetherStubDownloaderFactory; import org.springframework.cloud.contract.spec.ContractVerifierException; import org.springframework.cloud.contract.verifier.TestGenerator; import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties; @@ -36,6 +41,9 @@ import org.springframework.cloud.contract.verifier.config.TestMode; requiresDependencyResolution = ResolutionScope.TEST) public class GenerateTestsMojo extends AbstractMojo { + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) + private RepositorySystemSession repoSession; + @Parameter(property = "spring.cloud.contract.verifier.contractsDirectory", defaultValue = "${project.basedir}/src/test/resources/contracts") private File contractsDirectory; @@ -105,6 +113,40 @@ public class GenerateTestsMojo extends AbstractMojo { @Parameter(property = "skipTests", defaultValue = "false") private boolean skipTests; + /** + * The URL from which a JAR containing the contracts should get downloaded. If not provided + * but artifactid / coordinates notation was provided then the current Maven's build repositories will be + * taken into consideration + */ + @Parameter(property = "contractsRepositoryUrl") + private String contractsRepositoryUrl; + + @Parameter(property = "contractDependency") + private Dependency contractDependency; + + /** + * The path in the JAR with all the contracts where contracts for this particular service lay. + * If not provided will be resolved to {@code groupid/artifactid}. Example: + *

    + * If {@code groupid} is {@code com.example} and {@code artifactid} is {@code service} then the resolved path will be + * {@code /com/example/artifactid} + */ + @Parameter(property = "contractsPath") + private String contractsPath; + + /** + * If {@code true} then JAR with contracts will be taken from local maven repository + */ + @Parameter(property = "contractsWorkOffline", defaultValue = "false") + private boolean contractsWorkOffline; + + private final AetherStubDownloaderFactory aetherStubDownloaderFactory; + + @Inject + public GenerateTestsMojo(AetherStubDownloaderFactory aetherStubDownloaderFactory) { + this.aetherStubDownloaderFactory = aetherStubDownloaderFactory; + } + public void execute() throws MojoExecutionException, MojoFailureException { if (this.skip || this.mavenTestSkip || this.skipTests) { if (this.skip) getLog().info("Skipping Spring Cloud Contract Verifier execution: spring.cloud.contract.verifier.skip=" + this.skip); @@ -115,7 +157,12 @@ public class GenerateTestsMojo extends AbstractMojo { getLog().info( "Generating server tests source code for Spring Cloud Contract Verifier contract verification"); final ContractVerifierConfigProperties config = new ContractVerifierConfigProperties(); - config.setContractsDslDir(this.contractsDirectory); + // download contracts, unzip them and pass as output directory + File contractsDirectory = new MavenContractsDownloader(this.project, this.contractDependency, + this.contractsPath, this.contractsRepositoryUrl, this.contractsWorkOffline, getLog(), + this.aetherStubDownloaderFactory, this.repoSession).downloadAndUnpackContractsIfRequired(config, this.contractsDirectory); + getLog().info("Directory with contract is present at [" + contractsDirectory + "]"); + config.setContractsDslDir(contractsDirectory); config.setGeneratedTestSourcesDir(this.generatedTestSourcesDir); config.setTargetFramework(this.testFramework); config.setTestMode(this.testMode); diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/MavenContractsDownloader.java b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/MavenContractsDownloader.java new file mode 100644 index 0000000000..26e0ed88da --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/MavenContractsDownloader.java @@ -0,0 +1,102 @@ +package org.springframework.cloud.contract.maven.verifier; + +import java.io.File; + +import org.apache.maven.model.Dependency; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystemSession; +import org.springframework.cloud.contract.maven.verifier.stubrunner.AetherStubDownloaderFactory; +import org.springframework.cloud.contract.stubrunner.AetherStubDownloader; +import org.springframework.cloud.contract.stubrunner.ContractDownloader; +import org.springframework.cloud.contract.stubrunner.StubConfiguration; +import org.springframework.cloud.contract.stubrunner.StubRunnerOptionsBuilder; +import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties; +import org.springframework.util.StringUtils; + +/** + * Downloads JAR with contracts + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class MavenContractsDownloader { + + private static final String LATEST_VERSION = "+"; + private static final String CONTRACTS_DIRECTORY_PROP = "CONTRACTS_DIRECTORY"; + + private final MavenProject project; + private final Dependency contractDependency; + private final String contractsPath; + private final String contractsRepositoryUrl; + private final boolean contractsWorkOffline; + private final Log log; + private final AetherStubDownloaderFactory aetherStubDownloaderFactory; + private final RepositorySystemSession repoSession; + + MavenContractsDownloader(MavenProject project, Dependency contractDependency, + String contractsPath, String contractsRepositoryUrl, + boolean contractsWorkOffline, Log log, + AetherStubDownloaderFactory aetherStubDownloaderFactory, + RepositorySystemSession repoSession) { + this.project = project; + this.contractDependency = contractDependency; + this.contractsPath = contractsPath; + this.contractsRepositoryUrl = contractsRepositoryUrl; + this.contractsWorkOffline = contractsWorkOffline; + this.log = log; + this.aetherStubDownloaderFactory = aetherStubDownloaderFactory; + this.repoSession = repoSession; + } + + File downloadAndUnpackContractsIfRequired(ContractVerifierConfigProperties config, File defaultContractsDir) { + String contractsDirFromProp = this.project.getProperties().getProperty(CONTRACTS_DIRECTORY_PROP); + File downloadedContractsDir = StringUtils.hasText(contractsDirFromProp) ? + new File(contractsDirFromProp) : null; + // reuse downloaded contracts from another mojo + if (downloadedContractsDir != null && downloadedContractsDir.exists()) { + this.log.info("Another mojo has downloaded the contracts - will reuse them from [" + downloadedContractsDir + "]"); + contractDownloader().updatePropertiesWithInclusion(downloadedContractsDir, config); + return downloadedContractsDir; + } else if (shouldDownloadContracts()) { + this.log.info("Download dependency is provided - will download contract jars"); + File downloadedContracts = contractDownloader().unpackedDownloadedContracts(config); + this.project.getProperties().setProperty(CONTRACTS_DIRECTORY_PROP, downloadedContracts.getAbsolutePath()); + return downloadedContracts; + } + this.log.info("Will use contracts provided in the folder [" + defaultContractsDir + "]"); + return defaultContractsDir; + } + + private boolean shouldDownloadContracts() { + return this.contractDependency != null && StringUtils.hasText(this.contractDependency.getArtifactId()); + } + + private ContractDownloader contractDownloader() { + return new ContractDownloader(stubDownloader(), stubConfiguration(), + this.contractsPath, this.project.getGroupId(), this.project.getArtifactId()); + } + + private AetherStubDownloader stubDownloader() { + if (StringUtils.hasText(this.contractsRepositoryUrl) || this.contractsWorkOffline) { + this.log.info("Will download contracts from [" + this.contractsRepositoryUrl + "]. " + + "Work offline switch equals to [" + this.contractsWorkOffline + "]"); + return new AetherStubDownloader( + new StubRunnerOptionsBuilder() + .withStubRepositoryRoot(this.contractsRepositoryUrl) + .withWorkOffline(this.contractsWorkOffline) + .build()); + } + this.log.info("Will download contracts using current build's Maven repository setup"); + return this.aetherStubDownloaderFactory.build(this.repoSession); + } + + private StubConfiguration stubConfiguration() { + String groupId = this.contractDependency.getGroupId(); + String artifactId = this.contractDependency.getArtifactId(); + String version = StringUtils.hasText(this.contractDependency.getVersion()) ? + this.contractDependency.getVersion() : LATEST_VERSION; + String classifier = this.contractDependency.getClassifier(); + return new StubConfiguration(groupId, artifactId, version, classifier); + } +} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/java/org/springframework/cloud/contract/maven/verifier/PluginUnitTest.java b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/java/org/springframework/cloud/contract/maven/verifier/PluginUnitTest.java index 24a5f96b9e..4ecceb79b4 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/java/org/springframework/cloud/contract/maven/verifier/PluginUnitTest.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/java/org/springframework/cloud/contract/maven/verifier/PluginUnitTest.java @@ -134,5 +134,32 @@ public class PluginUnitTest { assertFilesPresent(basedir, "target/sample-project-0.1-foo.jar"); } + @Test + public void shouldGenerateStubsByDownloadingContractsFromARepo() throws Exception { + File basedir = this.resources.getBasedir("basic-remote-contracts"); + this.maven.executeMojo(basedir, "convert", newParameter("contractsRepositoryUrl", "file://" + PluginUnitTest.class.getClassLoader().getResource("m2repo/repository").getFile())); + assertFilesPresent(basedir, "target/stubs/mappings/com/example/server/client1/contracts/shouldMarkClientAsFraud.json"); + } + + @Test + public void shouldGenerateStubsByDownloadingContractsFromARepoWhenCustomPathIsProvided() throws Exception { + File basedir = this.resources.getBasedir("complex-remote-contracts"); + this.maven.executeMojo(basedir, "convert", newParameter("contractsRepositoryUrl", "file://" + PluginUnitTest.class.getClassLoader().getResource("m2repo/repository").getFile())); + assertFilesPresent(basedir, "target/stubs/mappings/com/example/server/client1/contracts/shouldMarkClientAsFraud.json"); + } + + @Test + public void shouldGenerateTestsByDownloadingContractsFromARepo() throws Exception { + File basedir = this.resources.getBasedir("basic-remote-contracts"); + this.maven.executeMojo(basedir, "generateTests", newParameter("contractsRepositoryUrl", "file://" + PluginUnitTest.class.getClassLoader().getResource("m2repo/repository").getFile())); + assertFilesPresent(basedir, "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/com/example/server/client1/ContractsTest.java"); + } + + @Test + public void shouldGenerateTestsByDownloadingContractsFromARepoWhenCustomPathIsProvided() throws Exception { + File basedir = this.resources.getBasedir("complex-remote-contracts"); + this.maven.executeMojo(basedir, "generateTests", newParameter("contractsRepositoryUrl", "file://" + PluginUnitTest.class.getClassLoader().getResource("m2repo/repository").getFile())); + assertFilesPresent(basedir, "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/com/example/server/client1/ContractsTest.java"); + } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/basic-remote-contracts/pom-with-repo.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/basic-remote-contracts/pom-with-repo.xml new file mode 100644 index 0000000000..9efa5fd96e --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/basic-remote-contracts/pom-with-repo.xml @@ -0,0 +1,46 @@ + + + + 4.0.0 + + com.example + server + 0.1.BUILD-SNAPSHOT + + + + + + org.springframework.cloud + spring-cloud-contract-maven-plugin + + http://link/to/your/nexus/or/artifactory/or/sth + + com.example.standalone + contracts + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/basic-remote-contracts/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/basic-remote-contracts/pom.xml new file mode 100644 index 0000000000..02a87e7a01 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/basic-remote-contracts/pom.xml @@ -0,0 +1,43 @@ + + + + 4.0.0 + + com.example + server + 0.1.BUILD-SNAPSHOT + + + + + org.springframework.cloud + spring-cloud-contract-maven-plugin + + + com.example + contracts + + + + + + + \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/complex-remote-contracts/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/complex-remote-contracts/pom.xml new file mode 100644 index 0000000000..19b4b2ef8c --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/complex-remote-contracts/pom.xml @@ -0,0 +1,46 @@ + + + + 4.0.0 + + com.example + someartifact + 0.1.BUILD-SNAPSHOT + + + + + org.springframework.cloud + spring-cloud-contract-maven-plugin + 1.0.0.BUILD-SNAPSHOT + + com/example/server + + com.example + contracts + + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/logback.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/logback.xml new file mode 100644 index 0000000000..df6a2daa72 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/logback.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/0.0.1-SNAPSHOT/contracts-0.0.1-SNAPSHOT.jar b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/0.0.1-SNAPSHOT/contracts-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000000..4e3d44862b Binary files /dev/null and b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/0.0.1-SNAPSHOT/contracts-0.0.1-SNAPSHOT.jar differ diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/0.0.1-SNAPSHOT/contracts-0.0.1-SNAPSHOT.pom b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/0.0.1-SNAPSHOT/contracts-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..3101d07b79 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/0.0.1-SNAPSHOT/contracts-0.0.1-SNAPSHOT.pom @@ -0,0 +1,25 @@ + + + + + 4.0.0 + com.example + contracts + 0.0.1-SNAPSHOT + pom + diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/0.0.1-SNAPSHOT/maven-metadata-local.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/0.0.1-SNAPSHOT/maven-metadata-local.xml new file mode 100644 index 0000000000..3b4511d3ba --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/0.0.1-SNAPSHOT/maven-metadata-local.xml @@ -0,0 +1,24 @@ + + + com.example + contracts + 0.0.1-SNAPSHOT + + + true + + 20160916125313 + + + jar + 0.0.1-SNAPSHOT + 20160916125313 + + + pom + 0.0.1-SNAPSHOT + 20160916125313 + + + + diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/maven-metadata.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/maven-metadata.xml new file mode 100644 index 0000000000..3d1d3547d5 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/resources/m2repo/repository/com/example/contracts/maven-metadata.xml @@ -0,0 +1,28 @@ + + + + + com.example + contracts + 0.0.1-SNAPSHOT + + + 0.0.1-SNAPSHOT + + 20160409062112 + + diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/config/ContractVerifierConfigProperties.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/config/ContractVerifierConfigProperties.groovy index 6e34af897e..9529f50910 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/config/ContractVerifierConfigProperties.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/config/ContractVerifierConfigProperties.groovy @@ -102,4 +102,11 @@ class ContractVerifierConfigProperties { */ Boolean assertJsonSize = false + /** + * A regular expression that matches contracts. Especially useful when using a single JAR containing + * all the contracts in the system. In this case you'd like to take into consideration only some of them. + * Defaults to picking all files. + */ + String includedContracts = ".*" + } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractFileScanner.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractFileScanner.groovy index e1af75de6a..f7d2b420d1 100755 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractFileScanner.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractFileScanner.groovy @@ -16,19 +16,17 @@ package org.springframework.cloud.contract.verifier.file +import com.google.common.collect.ArrayListMultimap +import com.google.common.collect.ListMultimap import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import org.apache.commons.lang3.SystemUtils import java.nio.file.FileSystem import java.nio.file.FileSystems import java.nio.file.Path import java.nio.file.PathMatcher import java.util.regex.Pattern - -import org.apache.commons.lang3.SystemUtils - -import com.google.common.collect.ArrayListMultimap -import com.google.common.collect.ListMultimap - /** * Scans the provided file path for the DSLs. There's a possibility to provide * inclusion and exclusion filters. @@ -38,6 +36,7 @@ import com.google.common.collect.ListMultimap * @since 1.0.0 */ @CompileStatic +@Slf4j class ContractFileScanner { private static final String MATCH_PREFIX = "glob:" @@ -45,11 +44,13 @@ class ContractFileScanner { private final File baseDir private final Set excludeMatchers private final Set ignoreMatchers + private final String includeMatcher - ContractFileScanner(File baseDir, Set excluded, Set ignored) { + ContractFileScanner(File baseDir, Set excluded, Set ignored, String includeMatcher = "") { this.baseDir = baseDir - excludeMatchers = processPatterns(excluded ?: [] as Set) - ignoreMatchers = processPatterns(ignored ?: [] as Set) + this.excludeMatchers = processPatterns(excluded ?: [] as Set) + this.ignoreMatchers = processPatterns(ignored ?: [] as Set) + this.includeMatcher = includeMatcher } private Set processPatterns(Set patterns) { @@ -79,8 +80,11 @@ class ContractFileScanner { return; } files.sort().eachWithIndex { File file, int index -> - if (!matchesPattern(file, excludeMatchers)) { - if (isContractFile(file)) { + boolean excluded = matchesPattern(file, excludeMatchers) + if (!excluded) { + boolean contractFile = isContractFile(file); + boolean included = includeMatcher ? file.absolutePath.matches(includeMatcher) : true + if (contractFile && included) { Path path = file.toPath() Integer order = null if (hasScenarioFilenamePattern(path)) { @@ -89,6 +93,13 @@ class ContractFileScanner { result.put(file.parentFile.toPath(), new ContractMetadata(path, matchesPattern(file, ignoreMatchers), files.size(), order)) } else { appendRecursively(file, result) + if (log.isDebugEnabled()) { + log.debug("File [$file] is ignored. Is a contract file? [$contractFile]. Should be included by pattern? [$included]") + } + } + } else { + if (log.isDebugEnabled()) { + log.debug("File [$file] is ignored. Should be excluded? [$excluded]") } } } @@ -98,8 +109,8 @@ class ContractFileScanner { return SCENARIO_STEP_FILENAME_PATTERN.matcher(path.fileName.toString()).matches() } - private boolean matchesPattern(File file, Set excludeMatchers) { - for (PathMatcher matcher : excludeMatchers) { + private boolean matchesPattern(File file, Set matchers) { + for (PathMatcher matcher : matchers) { if (matcher.matches(file.toPath())) { return true; }