diff --git a/docs/src/main/asciidoc/verifier/contract.adoc b/docs/src/main/asciidoc/verifier/contract.adoc
index 3537d59cf0..24d60699c9 100644
--- a/docs/src/main/asciidoc/verifier/contract.adoc
+++ b/docs/src/main/asciidoc/verifier/contract.adoc
@@ -705,6 +705,114 @@ and the YAML implementation
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/converter/YamlContractConverter.groovy[indent=0,lines=16..-1]
----
+===== Pact converter
+
+Spring Cloud Contract comes with an out of the box support for https://docs.pact.io/[Pact] representation of contracts.
+In other words instead of using the Groovy DSL you can use Pact files. In this section
+we will present how to add such a support for your project.
+
+====== Pact contract
+
+We will be working on the following example of a Pact contract. We've placed this file under
+the `src/test/resources/contracts` folder.
+
+[source,javascript,indent=0]
+----
+include::{standalone_pact_path}/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.json[indent=0]
+----
+
+====== Pact for producers
+
+On the producer side you have add to your plugin configuration two additional dependencies.
+One is the Spring Cloud Contract Pact support and the other represents the current
+Pact version that you're using.
+
+[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
+.Maven
+----
+include::{standalone_pact_path}/pact-http-server/pom.xml[tags=pact_dependency,indent=0]
+----
+
+[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
+.Gradle
+----
+include::{standalone_pact_path}/pact-http-server/build.gradle[tags=pact_dependency,indent=0]
+----
+
+When you execute the build of your application a test, looking more or less like this, will be generated
+
+[source,java,indent=0]
+----
+ @Test
+ public void validate_shouldMarkClientAsFraud() throws Exception {
+ // given:
+ MockMvcRequestSpecification request = given()
+ .header("Content-Type", "application/vnd.fraud.v1+json")
+ .body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}");
+
+ // when:
+ ResponseOptions response = given().spec(request)
+ .put("/fraudcheck");
+
+ // then:
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.header("Content-Type")).isEqualTo("application/vnd.fraud.v1+json;charset=UTF-8");
+ // and:
+ DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
+ assertThatJson(parsedJson).field("rejectionReason").isEqualTo("Amount too high");
+ // and:
+ assertThat(parsedJson.read("$.fraudCheckStatus", String.class)).matches("FRAUD");
+ }
+----
+
+and the stub looking like this
+
+[source,javascript,indent=0]
+----
+{
+ "uuid" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
+ "request" : {
+ "url" : "/fraudcheck",
+ "method" : "PUT",
+ "headers" : {
+ "Content-Type" : {
+ "equalTo" : "application/vnd.fraud.v1+json"
+ }
+ },
+ "bodyPatterns" : [ {
+ "matchesJsonPath" : "$[?(@.loanAmount == 99999)]"
+ }, {
+ "matchesJsonPath" : "$[?(@.clientId =~ /([0-9]{10})/)]"
+ } ]
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "{\"fraudCheckStatus\":\"FRAUD\",\"rejectionReason\":\"Amount too high\"}",
+ "headers" : {
+ "Content-Type" : "application/vnd.fraud.v1+json;charset=UTF-8"
+ }
+ }
+}
+----
+
+====== Pact for consumers
+
+On the producer side you have add to your project dependencies two additional dependencies.
+One is the Spring Cloud Contract Pact support and the other represents the current
+Pact version that you're using.
+
+[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
+.Maven
+----
+include::{standalone_pact_path}/pact-http-client/pom.xml[tags=pact_dependency,indent=0]
+----
+
+[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
+.Gradle
+----
+include::{standalone_pact_path}/pact-http-client/build.gradle[tags=pact_dependency,indent=0]
+----
+
==== Custom test generator
If you want to generate tests for different languages than Java or you're
diff --git a/docs/src/main/asciidoc/verifier/spring-cloud-contract-verifier.adoc b/docs/src/main/asciidoc/verifier/spring-cloud-contract-verifier.adoc
index 1c921cbfbb..2d1c9fe47a 100644
--- a/docs/src/main/asciidoc/verifier/spring-cloud-contract-verifier.adoc
+++ b/docs/src/main/asciidoc/verifier/spring-cloud-contract-verifier.adoc
@@ -8,6 +8,7 @@
:stubrunner_core_path: {core_path}/spring-cloud-contract-stub-runner
:standalone_samples_path: {samples_path}/standalone/dsl
:standalone_messaging_samples_path: {samples_path}/standalone/messaging
+:standalone_pact_path: {samples_path}/standalone/pact
:tests_path: {core_path}/tests
:samples_url: https://raw.githubusercontent.com/spring-cloud-samples/spring-cloud-contract-samples/master
diff --git a/pom.xml b/pom.xml
index abec0751fd..a397f70e9c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -127,6 +127,16 @@
hoverfly-junit
0.1.8
+
+ org.apache.commons
+ commons-lang3
+ 3.4
+
+
+ au.com.dius
+ pact-jvm-model
+ 2.4.18
+
org.springframework.cloud
spring-cloud-contract-dependencies
diff --git a/samples/standalone/pact/pact-http-client/.gitignore b/samples/standalone/pact/pact-http-client/.gitignore
new file mode 100644
index 0000000000..b05ba7bd6d
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/.gitignore
@@ -0,0 +1,6 @@
+
+target/
+
+.gradle
+build/
+
diff --git a/samples/standalone/pact/pact-http-client/.mvn/jvm.config b/samples/standalone/pact/pact-http-client/.mvn/jvm.config
new file mode 100644
index 0000000000..894bef17a5
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/.mvn/jvm.config
@@ -0,0 +1 @@
+-Xmx1024m -XX:MaxPermSize=256m -Djava.awt.headless=true
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-client/.mvn/maven.config b/samples/standalone/pact/pact-http-client/.mvn/maven.config
new file mode 100644
index 0000000000..affad39a42
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/.mvn/maven.config
@@ -0,0 +1 @@
+-T2
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-client/.mvn/wrapper/maven-wrapper.jar b/samples/standalone/pact/pact-http-client/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000000..c6feb8bb6f
Binary files /dev/null and b/samples/standalone/pact/pact-http-client/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/samples/standalone/pact/pact-http-client/.mvn/wrapper/maven-wrapper.properties b/samples/standalone/pact/pact-http-client/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000..6637cedb28
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/.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/pact/pact-http-client/README.adoc b/samples/standalone/pact/pact-http-client/README.adoc
new file mode 100644
index 0000000000..021bcdcdb5
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/README.adoc
@@ -0,0 +1,26 @@
+= Pact Http Client
+
+== Prerequisites
+
+First you have to publish to Maven Local the stubs of the *pact-http-server* module
+
+== How to run it?
+
+Run
+
+[source=groovy]
+--------
+./gradlew clean build
+--------
+
+or
+
+--------
+./mvnw clean package
+--------
+
+To
+
+- build the app
+- use spring-cloud-contract-stub-runner-spring[Stub Runner Spring] to download the stub of `Pact Http Server`
+- run the tests against stubbed server
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-client/build.gradle b/samples/standalone/pact/pact-http-client/build.gradle
new file mode 100644
index 0000000000..e9495023e8
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/build.gradle
@@ -0,0 +1,68 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ maven { url "http://repo.spring.io/snapshot" }
+ maven { url "http://repo.spring.io/milestone" }
+ maven { url "http://repo.spring.io/release" }
+ }
+ dependencies {
+ classpath "org.springframework.boot:spring-boot-gradle-plugin:1.5.0.BUILD-SNAPSHOT"
+ }
+}
+
+group = 'com.example'
+version = '0.0.1-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+ mavenLocal()
+ maven { url "http://repo.spring.io/snapshot" }
+ maven { url "http://repo.spring.io/milestone" }
+ maven { url "http://repo.spring.io/release" }
+}
+
+apply plugin: 'groovy'
+apply plugin: 'spring-boot'
+apply plugin: 'maven-publish'
+
+dependencyManagement {
+ imports {
+ mavenBom "org.springframework.cloud:spring-cloud-dependencies:$BOM_VERSION"
+ }
+}
+
+dependencies {
+ compile("org.springframework.boot:spring-boot-starter-web")
+ compile("org.springframework.boot:spring-boot-starter-actuator")
+
+ testCompile "org.springframework.cloud:spring-cloud-starter-contract-stub-runner"
+ //tag::pact_dependency[]
+ testCompile "org.springframework.cloud:spring-cloud-contract-spec-pact"
+ testCompile 'au.com.dius:pact-jvm-model:2.4.18'
+ //end::pact_dependency[]
+}
+
+test {
+ systemProperty 'spring.profiles.active', 'gradle'
+ testLogging {
+ exceptionFormat = 'full'
+ }
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '2.14'
+}
+
+task resolveDependencies {
+ doLast {
+ project.rootProject.allprojects.each { subProject ->
+ subProject.buildscript.configurations.each { configuration ->
+ configuration.resolve()
+ }
+ subProject.configurations.each { configuration ->
+ configuration.resolve()
+ }
+ }
+ }
+}
diff --git a/samples/standalone/pact/pact-http-client/gradle.properties b/samples/standalone/pact/pact-http-client/gradle.properties
new file mode 100644
index 0000000000..42cbd5f9cc
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/gradle.properties
@@ -0,0 +1,2 @@
+org.gradle.daemon=false
+BOM_VERSION=Dalston.BUILD-SNAPSHOT
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-client/gradle/wrapper/gradle-wrapper.jar b/samples/standalone/pact/pact-http-client/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..3baa851b28
Binary files /dev/null and b/samples/standalone/pact/pact-http-client/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/samples/standalone/pact/pact-http-client/gradle/wrapper/gradle-wrapper.properties b/samples/standalone/pact/pact-http-client/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..ff3de523bd
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Aug 19 15:39:05 CEST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip
diff --git a/samples/standalone/pact/pact-http-client/gradlew b/samples/standalone/pact/pact-http-client/gradlew
new file mode 100755
index 0000000000..27309d9231
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+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
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+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
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples/standalone/pact/pact-http-client/gradlew.bat b/samples/standalone/pact/pact-http-client/gradlew.bat
new file mode 100644
index 0000000000..832fdb6079
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/samples/standalone/pact/pact-http-client/mvnw b/samples/standalone/pact/pact-http-client/mvnw
new file mode 100755
index 0000000000..fc7efd17d0
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/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/pact/pact-http-client/mvnw.cmd b/samples/standalone/pact/pact-http-client/mvnw.cmd
new file mode 100644
index 0000000000..001048081d
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/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/pact/pact-http-client/pom.xml b/samples/standalone/pact/pact-http-client/pom.xml
new file mode 100644
index 0000000000..af94bb7204
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/pom.xml
@@ -0,0 +1,217 @@
+
+
+ 4.0.0
+
+ com.example
+ pact-http-client
+ 0.0.1-SNAPSHOT
+
+ Spring Cloud Contract Verifier Http Client Sample with Pact
+ Spring Cloud Contract Verifier Http Client Sample with Pact
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.0.BUILD-SNAPSHOT
+
+
+
+
+ UTF-8
+ 1.8
+ Dalston.BUILD-SNAPSHOT
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-contract-stub-runner
+ test
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-contract-spec-pact
+ test
+
+
+ au.com.dius
+ pact-jvm-model
+ 2.4.18
+ test
+
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud-dependencies.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ maven-deploy-plugin
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ 3.0.0
+
+
+
+ build
+
+
+ target
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+ integration
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ gradle
+ test
+
+ ./gradlew
+
+ clean
+ build
+ publishToMavenLocal
+ -PverifierVersion=${spring-cloud-contract.version}
+
+
+
+ exec
+
+
+
+
+
+
+
+
+ windows
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ gradle
+ test
+
+ gradlew.bat
+
+ clean
+ build
+ publishToMavenLocal
+ -PverifierVersion=${spring-cloud-contract.version}
+
+
+
+ exec
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/standalone/pact/pact-http-client/settings.gradle b/samples/standalone/pact/pact-http-client/settings.gradle
new file mode 100644
index 0000000000..d351972414
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'pact-http-client-gradle'
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/Application.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/Application.java
new file mode 100644
index 0000000000..6f36301172
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/Application.java
@@ -0,0 +1,13 @@
+package com.example.loan;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/LoanApplicationService.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/LoanApplicationService.java
new file mode 100644
index 0000000000..c91df67aea
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/LoanApplicationService.java
@@ -0,0 +1,95 @@
+package com.example.loan;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import com.example.loan.model.FraudCheckStatus;
+import com.example.loan.model.FraudServiceRequest;
+import com.example.loan.model.FraudServiceResponse;
+import com.example.loan.model.LoanApplication;
+import com.example.loan.model.LoanApplicationResult;
+import com.example.loan.model.LoanApplicationStatus;
+import com.example.loan.model.Response;
+
+@Service
+public class LoanApplicationService {
+
+ private static final String FRAUD_SERVICE_JSON_VERSION_1 =
+ "application/vnd.fraud.v1+json";
+
+ private final RestTemplate restTemplate;
+
+ private int port = 6565;
+
+ @Autowired
+ public LoanApplicationService(RestTemplateBuilder builder) {
+ this.restTemplate = builder.build();
+ }
+
+ public LoanApplicationResult loanApplication(LoanApplication loanApplication) {
+ FraudServiceRequest request =
+ new FraudServiceRequest(loanApplication);
+
+ FraudServiceResponse response =
+ sendRequestToFraudDetectionService(request);
+
+ return buildResponseFromFraudResult(response);
+ }
+
+ private FraudServiceResponse sendRequestToFraudDetectionService(
+ FraudServiceRequest request) {
+ HttpHeaders httpHeaders = new HttpHeaders();
+ httpHeaders.add(HttpHeaders.CONTENT_TYPE, FRAUD_SERVICE_JSON_VERSION_1);
+
+ // tag::client_call_server[]
+ ResponseEntity response =
+ restTemplate.exchange("http://localhost:" + port + "/fraudcheck", HttpMethod.PUT,
+ new HttpEntity<>(request, httpHeaders),
+ FraudServiceResponse.class);
+ // end::client_call_server[]
+
+ return response.getBody();
+ }
+
+ private LoanApplicationResult buildResponseFromFraudResult(FraudServiceResponse response) {
+ LoanApplicationStatus applicationStatus = null;
+ if (FraudCheckStatus.OK == response.getFraudCheckStatus()) {
+ applicationStatus = LoanApplicationStatus.LOAN_APPLIED;
+ } else if (FraudCheckStatus.FRAUD == response.getFraudCheckStatus()) {
+ applicationStatus = LoanApplicationStatus.LOAN_APPLICATION_REJECTED;
+ }
+
+ return new LoanApplicationResult(applicationStatus, response.getRejectionReason());
+ }
+
+ public int countAllFrauds() {
+ HttpHeaders httpHeaders = new HttpHeaders();
+ httpHeaders.add(HttpHeaders.CONTENT_TYPE, FRAUD_SERVICE_JSON_VERSION_1);
+ ResponseEntity response =
+ restTemplate.exchange("http://localhost:" + port + "/frauds", HttpMethod.GET,
+ new HttpEntity<>(httpHeaders),
+ Response.class);
+ return response.getBody().getCount();
+ }
+
+ public int countDrunks() {
+ HttpHeaders httpHeaders = new HttpHeaders();
+ httpHeaders.add(HttpHeaders.CONTENT_TYPE, FRAUD_SERVICE_JSON_VERSION_1);
+ ResponseEntity response =
+ restTemplate.exchange("http://localhost:" + port + "/drunks", HttpMethod.GET,
+ new HttpEntity<>(httpHeaders),
+ Response.class);
+ return response.getBody().getCount();
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+}
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/Client.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/Client.java
new file mode 100644
index 0000000000..abf66106b5
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/Client.java
@@ -0,0 +1,21 @@
+package com.example.loan.model;
+
+public class Client {
+
+ private String pesel;
+
+ public Client() {
+ }
+
+ public Client(String pesel) {
+ this.pesel = pesel;
+ }
+
+ public String getPesel() {
+ return pesel;
+ }
+
+ public void setPesel(String pesel) {
+ this.pesel = pesel;
+ }
+}
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/FraudCheckStatus.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/FraudCheckStatus.java
new file mode 100644
index 0000000000..c5217ef471
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/FraudCheckStatus.java
@@ -0,0 +1,5 @@
+package com.example.loan.model;
+
+public enum FraudCheckStatus {
+ OK, FRAUD
+}
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/FraudServiceRequest.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/FraudServiceRequest.java
new file mode 100644
index 0000000000..0230540131
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/FraudServiceRequest.java
@@ -0,0 +1,34 @@
+package com.example.loan.model;
+
+import java.math.BigDecimal;
+
+public class FraudServiceRequest {
+
+ private String clientId;
+
+ private BigDecimal loanAmount;
+
+ public FraudServiceRequest() {
+ }
+
+ public FraudServiceRequest(LoanApplication loanApplication) {
+ this.clientId = loanApplication.getClient().getPesel();
+ this.loanAmount = loanApplication.getAmount();
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public BigDecimal getLoanAmount() {
+ return loanAmount;
+ }
+
+ public void setLoanAmount(BigDecimal loanAmount) {
+ this.loanAmount = loanAmount;
+ }
+}
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/FraudServiceResponse.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/FraudServiceResponse.java
new file mode 100644
index 0000000000..50e64478b9
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/FraudServiceResponse.java
@@ -0,0 +1,27 @@
+package com.example.loan.model;
+
+public class FraudServiceResponse {
+
+ private FraudCheckStatus fraudCheckStatus;
+
+ private String rejectionReason;
+
+ public FraudServiceResponse() {
+ }
+
+ public FraudCheckStatus getFraudCheckStatus() {
+ return fraudCheckStatus;
+ }
+
+ public void setFraudCheckStatus(FraudCheckStatus fraudCheckStatus) {
+ this.fraudCheckStatus = fraudCheckStatus;
+ }
+
+ public String getRejectionReason() {
+ return rejectionReason;
+ }
+
+ public void setRejectionReason(String rejectionReason) {
+ this.rejectionReason = rejectionReason;
+ }
+}
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/LoanApplication.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/LoanApplication.java
new file mode 100644
index 0000000000..27d66f884c
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/LoanApplication.java
@@ -0,0 +1,44 @@
+package com.example.loan.model;
+
+import java.math.BigDecimal;
+
+public class LoanApplication {
+
+ private Client client;
+
+ private BigDecimal amount;
+
+ private String loanApplicationId;
+
+ public LoanApplication() {
+ }
+
+ public LoanApplication(Client client, double amount) {
+ this.client = client;
+ this.amount = BigDecimal.valueOf(amount);
+ }
+
+ public Client getClient() {
+ return client;
+ }
+
+ public void setClient(Client client) {
+ this.client = client;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public void setAmount(BigDecimal amount) {
+ this.amount = amount;
+ }
+
+ public String getLoanApplicationId() {
+ return loanApplicationId;
+ }
+
+ public void setLoanApplicationId(String loanApplicationId) {
+ this.loanApplicationId = loanApplicationId;
+ }
+}
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/LoanApplicationResult.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/LoanApplicationResult.java
new file mode 100644
index 0000000000..ffd44e1a26
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/LoanApplicationResult.java
@@ -0,0 +1,32 @@
+package com.example.loan.model;
+
+public class LoanApplicationResult {
+
+ private LoanApplicationStatus loanApplicationStatus;
+
+ private String rejectionReason;
+
+ public LoanApplicationResult() {
+ }
+
+ public LoanApplicationResult(LoanApplicationStatus loanApplicationStatus, String rejectionReason) {
+ this.loanApplicationStatus = loanApplicationStatus;
+ this.rejectionReason = rejectionReason;
+ }
+
+ public LoanApplicationStatus getLoanApplicationStatus() {
+ return loanApplicationStatus;
+ }
+
+ public void setLoanApplicationStatus(LoanApplicationStatus loanApplicationStatus) {
+ this.loanApplicationStatus = loanApplicationStatus;
+ }
+
+ public String getRejectionReason() {
+ return rejectionReason;
+ }
+
+ public void setRejectionReason(String rejectionReason) {
+ this.rejectionReason = rejectionReason;
+ }
+}
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/LoanApplicationStatus.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/LoanApplicationStatus.java
new file mode 100644
index 0000000000..34cf91a89f
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/LoanApplicationStatus.java
@@ -0,0 +1,5 @@
+package com.example.loan.model;
+
+public enum LoanApplicationStatus {
+ LOAN_APPLIED, LOAN_APPLICATION_REJECTED
+}
diff --git a/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/Response.java b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/Response.java
new file mode 100644
index 0000000000..8335ee0cc6
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/java/com/example/loan/model/Response.java
@@ -0,0 +1,20 @@
+package com.example.loan.model;
+
+public class Response {
+ private int count;
+
+ public Response(int count) {
+ this.count = count;
+ }
+
+ public Response() {
+ }
+
+ public int getCount() {
+ return this.count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+}
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-client/src/main/resources/application.yml b/samples/standalone/pact/pact-http-client/src/main/resources/application.yml
new file mode 100644
index 0000000000..4b7fe13158
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/main/resources/application.yml
@@ -0,0 +1 @@
+server.port: 8090
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java b/samples/standalone/pact/pact-http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java
new file mode 100644
index 0000000000..9877692b94
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java
@@ -0,0 +1,80 @@
+package com.example.loan;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
+import org.springframework.core.env.Environment;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.example.loan.model.Client;
+import com.example.loan.model.LoanApplication;
+import com.example.loan.model.LoanApplicationResult;
+import com.example.loan.model.LoanApplicationStatus;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment=WebEnvironment.NONE)
+@AutoConfigureStubRunner(ids = {"com.example:pact-http-server:+:stubs"}, workOffline = true)
+@DirtiesContext
+public class LoanApplicationServiceTests {
+
+ @Autowired
+ private LoanApplicationService service;
+
+ @Autowired
+ private Environment environment;
+
+ @Before
+ public void setup() {
+ this.service.setPort(this.environment.getProperty("stubrunner.runningstubs.pact-http-server.port", Integer.class));
+ }
+
+ @Test
+ public void shouldSuccessfullyApplyForLoan() {
+ // given:
+ LoanApplication application = new LoanApplication(new Client("1234567890"),
+ 123.123);
+ // when:
+ LoanApplicationResult loanApplication = service.loanApplication(application);
+ // then:
+ assertThat(loanApplication.getLoanApplicationStatus())
+ .isEqualTo(LoanApplicationStatus.LOAN_APPLIED);
+ assertThat(loanApplication.getRejectionReason()).isNull();
+ }
+
+ @Test
+ public void shouldBeRejectedDueToAbnormalLoanAmount() {
+ // given:
+ LoanApplication application = new LoanApplication(new Client("1234567890"),
+ 99999);
+ // when:
+ LoanApplicationResult loanApplication = service.loanApplication(application);
+ // then:
+ assertThat(loanApplication.getLoanApplicationStatus())
+ .isEqualTo(LoanApplicationStatus.LOAN_APPLICATION_REJECTED);
+ assertThat(loanApplication.getRejectionReason()).isEqualTo("Amount too high");
+ }
+
+ @Test
+ public void shouldSuccessfullyGetAllFrauds() {
+ // when:
+ int count = service.countAllFrauds();
+ // then:
+ assertThat(count).isEqualTo(200);
+ }
+
+ @Test
+ public void shouldSuccessfullyGetAllDrunks() {
+ // when:
+ int count = service.countDrunks();
+ // then:
+ assertThat(count).isEqualTo(100);
+ }
+
+}
diff --git a/samples/standalone/pact/pact-http-client/src/test/resources/application-gradle.yaml b/samples/standalone/pact/pact-http-client/src/test/resources/application-gradle.yaml
new file mode 100644
index 0000000000..25f0e2b24a
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/test/resources/application-gradle.yaml
@@ -0,0 +1,3 @@
+stubrunner:
+ work-offline: true
+ stubs.ids: 'com.example:pact-http-server-gradle:+:stubs'
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-client/src/test/resources/application.yaml b/samples/standalone/pact/pact-http-client/src/test/resources/application.yaml
new file mode 100644
index 0000000000..b4e8858671
--- /dev/null
+++ b/samples/standalone/pact/pact-http-client/src/test/resources/application.yaml
@@ -0,0 +1,2 @@
+server:
+ port: 0
diff --git a/samples/standalone/pact/pact-http-server/.gitignore b/samples/standalone/pact/pact-http-server/.gitignore
new file mode 100644
index 0000000000..b05ba7bd6d
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/.gitignore
@@ -0,0 +1,6 @@
+
+target/
+
+.gradle
+build/
+
diff --git a/samples/standalone/pact/pact-http-server/.mvn/jvm.config b/samples/standalone/pact/pact-http-server/.mvn/jvm.config
new file mode 100644
index 0000000000..894bef17a5
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/.mvn/jvm.config
@@ -0,0 +1 @@
+-Xmx1024m -XX:MaxPermSize=256m -Djava.awt.headless=true
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-server/.mvn/maven.config b/samples/standalone/pact/pact-http-server/.mvn/maven.config
new file mode 100644
index 0000000000..7681bc67b9
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/.mvn/maven.config
@@ -0,0 +1 @@
+-T2
diff --git a/samples/standalone/pact/pact-http-server/.mvn/wrapper/maven-wrapper.jar b/samples/standalone/pact/pact-http-server/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000000..c6feb8bb6f
Binary files /dev/null and b/samples/standalone/pact/pact-http-server/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/samples/standalone/pact/pact-http-server/.mvn/wrapper/maven-wrapper.properties b/samples/standalone/pact/pact-http-server/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000..6637cedb28
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/.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/pact/pact-http-server/README.adoc b/samples/standalone/pact/pact-http-server/README.adoc
new file mode 100644
index 0000000000..336b774815
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/README.adoc
@@ -0,0 +1,20 @@
+= Pact Http Server
+
+Run
+
+[source=groovy]
+--------
+./gradlew clean build publishToMavenLocal
+--------
+
+or
+
+--------
+./mvnw clean install
+--------
+
+To
+
+- build the app
+- generate and run Spring Cloud Contract Verifier tests
+- publish the fatJar and the stubs to Maven Local
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-server/build.gradle b/samples/standalone/pact/pact-http-server/build.gradle
new file mode 100644
index 0000000000..bd1b332b51
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/build.gradle
@@ -0,0 +1,78 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ maven { url "http://repo.spring.io/snapshot" }
+ maven { url "http://repo.spring.io/milestone" }
+ maven { url "http://repo.spring.io/release" }
+ }
+ dependencies {
+ classpath "org.springframework.boot:spring-boot-gradle-plugin:1.5.0.BUILD-SNAPSHOT"
+ classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${findProperty('verifierVersion') ?: verifierVersion}"
+ //tag::pact_dependency[]
+ classpath "org.springframework.cloud:spring-cloud-contract-spec-pact:${findProperty('verifierVersion') ?: verifierVersion}"
+ classpath 'au.com.dius:pact-jvm-model:2.4.18'
+ //end::pact_dependency[]
+ }
+}
+
+group = 'com.example'
+version = '0.0.1-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+ mavenLocal()
+ maven { url "http://repo.spring.io/snapshot" }
+ maven { url "http://repo.spring.io/milestone" }
+ maven { url "http://repo.spring.io/release" }
+}
+
+apply plugin: 'groovy'
+apply plugin: 'spring-boot'
+apply plugin: 'spring-cloud-contract'
+apply plugin: 'maven-publish'
+
+dependencyManagement {
+ imports {
+ mavenBom "org.springframework.cloud:spring-cloud-dependencies:$BOM_VERSION"
+ }
+}
+
+contracts {
+ packageWithBaseClasses = 'com.example.fraud'
+}
+
+dependencies {
+ compile("org.springframework.boot:spring-boot-starter-web")
+ compile("org.springframework.boot:spring-boot-starter-actuator")
+
+ testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
+}
+
+test {
+ systemProperty 'spring.profiles.active', 'gradle'
+ testLogging {
+ exceptionFormat = 'full'
+ }
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '2.14'
+}
+
+clean.doFirst {
+ delete "~/.m2/repository/com/example/http-server-pact-gradle"
+}
+
+task resolveDependencies {
+ doLast {
+ project.rootProject.allprojects.each { subProject ->
+ subProject.buildscript.configurations.each { configuration ->
+ configuration.resolve()
+ }
+ subProject.configurations.each { configuration ->
+ configuration.resolve()
+ }
+ }
+ }
+}
diff --git a/samples/standalone/pact/pact-http-server/gradle.properties b/samples/standalone/pact/pact-http-server/gradle.properties
new file mode 100644
index 0000000000..38dc897f59
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.daemon=false
+verifierVersion=1.1.0.BUILD-SNAPSHOT
+BOM_VERSION=Dalston.BUILD-SNAPSHOT
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-server/gradle/wrapper/gradle-wrapper.jar b/samples/standalone/pact/pact-http-server/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..3baa851b28
Binary files /dev/null and b/samples/standalone/pact/pact-http-server/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/samples/standalone/pact/pact-http-server/gradle/wrapper/gradle-wrapper.properties b/samples/standalone/pact/pact-http-server/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..95d7279349
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Aug 19 15:38:58 CEST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip
diff --git a/samples/standalone/pact/pact-http-server/gradlew b/samples/standalone/pact/pact-http-server/gradlew
new file mode 100755
index 0000000000..27309d9231
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+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
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+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
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples/standalone/pact/pact-http-server/gradlew.bat b/samples/standalone/pact/pact-http-server/gradlew.bat
new file mode 100644
index 0000000000..832fdb6079
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/samples/standalone/pact/pact-http-server/mvnw b/samples/standalone/pact/pact-http-server/mvnw
new file mode 100755
index 0000000000..fc7efd17d0
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/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/pact/pact-http-server/mvnw.cmd b/samples/standalone/pact/pact-http-server/mvnw.cmd
new file mode 100644
index 0000000000..001048081d
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/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/pact/pact-http-server/pom.xml b/samples/standalone/pact/pact-http-server/pom.xml
new file mode 100644
index 0000000000..26ead7abd4
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/pom.xml
@@ -0,0 +1,259 @@
+
+
+ 4.0.0
+
+ com.example
+ pact-http-server
+ 0.0.1-SNAPSHOT
+
+ Spring Cloud Contract Verifier Http Server Sample with Pact
+ Spring Cloud Contract Verifier Http Server Sample with Pact
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.0.BUILD-SNAPSHOT
+
+
+
+
+ UTF-8
+ 1.8
+ 1.1.0.BUILD-SNAPSHOT
+ Dalston.BUILD-SNAPSHOT
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-contract-verifier
+ test
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud-dependencies.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.springframework.cloud
+ spring-cloud-contract-maven-plugin
+ ${spring-cloud-contract.version}
+ true
+
+ com.example.fraud
+
+
+
+ org.springframework.cloud
+ spring-cloud-contract-spec-pact
+ ${spring-cloud-contract.version}
+
+
+ au.com.dius
+ pact-jvm-model
+ 2.4.18
+
+
+
+
+
+ maven-deploy-plugin
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ 3.0.0
+
+
+
+ build
+
+
+ target
+
+
+
+
+
+
+
+
+
+ org.eclipse.m2e
+ lifecycle-mapping
+ 1.0.0
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-contract-maven-plugin
+ [1.1.0.BUILD-SNAPSHOT,)
+
+ convert
+ generateTests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+ integration
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ gradle
+ test
+
+ ./gradlew
+
+ clean
+ build
+ publishToMavenLocal
+ -PverifierVersion=${spring-cloud-contract.version}
+
+
+
+ exec
+
+
+
+
+
+
+
+
+ windows
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ gradle
+ test
+
+ gradlew.bat
+
+ clean
+ build
+ publishToMavenLocal
+ -PverifierVersion=${spring-cloud-contract.version}
+
+
+
+ exec
+
+
+
+
+
+
+
+
+
diff --git a/samples/standalone/pact/pact-http-server/settings.gradle b/samples/standalone/pact/pact-http-server/settings.gradle
new file mode 100644
index 0000000000..9c202c3e04
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'pact-http-server-gradle'
diff --git a/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/Application.java b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/Application.java
new file mode 100644
index 0000000000..9117009b09
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/Application.java
@@ -0,0 +1,17 @@
+package com.example.fraud;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableAutoConfiguration
+@ComponentScan
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/FraudDetectionController.java b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/FraudDetectionController.java
new file mode 100644
index 0000000000..99e7fea84e
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/FraudDetectionController.java
@@ -0,0 +1,39 @@
+package com.example.fraud;
+
+import static org.springframework.web.bind.annotation.RequestMethod.PUT;
+
+import java.math.BigDecimal;
+
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.example.fraud.model.FraudCheck;
+import com.example.fraud.model.FraudCheckResult;
+import com.example.fraud.model.FraudCheckStatus;
+
+@RestController
+public class FraudDetectionController {
+
+ private static final String FRAUD_SERVICE_JSON_VERSION_1 = "application/vnd.fraud.v1+json";
+ private static final String NO_REASON = null;
+ private static final String AMOUNT_TOO_HIGH = "Amount too high";
+ private static final BigDecimal MAX_AMOUNT = new BigDecimal("5000");
+
+ @RequestMapping(
+ value = "/fraudcheck",
+ method = PUT,
+ consumes = FRAUD_SERVICE_JSON_VERSION_1,
+ produces = FRAUD_SERVICE_JSON_VERSION_1)
+ public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) {
+ if (amountGreaterThanThreshold(fraudCheck)) {
+ return new FraudCheckResult(FraudCheckStatus.FRAUD, AMOUNT_TOO_HIGH);
+ }
+ return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON);
+ }
+
+ private boolean amountGreaterThanThreshold(FraudCheck fraudCheck) {
+ return MAX_AMOUNT.compareTo(fraudCheck.getLoanAmount()) < 0;
+ }
+
+}
diff --git a/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/FraudStatsController.java b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/FraudStatsController.java
new file mode 100644
index 0000000000..5b27668600
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/FraudStatsController.java
@@ -0,0 +1,58 @@
+package com.example.fraud;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class FraudStatsController {
+
+ private static final String FRAUD_SERVICE_JSON_VERSION_1 = "application/vnd.fraud.v1+json";
+
+ private final StatsProvider statsProvider;
+
+ public FraudStatsController(StatsProvider statsProvider) {
+ this.statsProvider = statsProvider;
+ }
+
+ @GetMapping(
+ value = "/frauds",
+ produces = FRAUD_SERVICE_JSON_VERSION_1)
+ public Response countAllFrauds() {
+ return new Response(this.statsProvider.count(FraudType.ALL));
+ }
+
+ @GetMapping(
+ value = "/drunks",
+ produces = FRAUD_SERVICE_JSON_VERSION_1)
+ public Response countAllDrunks() {
+ return new Response(this.statsProvider.count(FraudType.DRUNKS));
+ }
+}
+
+enum FraudType {
+ DRUNKS, ALL
+}
+
+interface StatsProvider {
+ int count(FraudType fraudType);
+}
+
+class Response {
+ private int count;
+
+ public Response(int count) {
+ this.count = count;
+ }
+
+ public Response() {
+ }
+
+ public int getCount() {
+ return this.count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+}
+
diff --git a/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/model/FraudCheck.java b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/model/FraudCheck.java
new file mode 100644
index 0000000000..b0210573e2
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/model/FraudCheck.java
@@ -0,0 +1,29 @@
+package com.example.fraud.model;
+
+import java.math.BigDecimal;
+
+public class FraudCheck {
+
+ private String clientId;
+
+ private BigDecimal loanAmount;
+
+ public FraudCheck() {
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public BigDecimal getLoanAmount() {
+ return loanAmount;
+ }
+
+ public void setLoanAmount(BigDecimal loanAmount) {
+ this.loanAmount = loanAmount;
+ }
+}
diff --git a/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/model/FraudCheckResult.java b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/model/FraudCheckResult.java
new file mode 100644
index 0000000000..60b340276a
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/model/FraudCheckResult.java
@@ -0,0 +1,32 @@
+package com.example.fraud.model;
+
+public class FraudCheckResult {
+
+ private FraudCheckStatus fraudCheckStatus;
+
+ private String rejectionReason;
+
+ public FraudCheckResult() {
+ }
+
+ public FraudCheckResult(FraudCheckStatus fraudCheckStatus, String rejectionReason) {
+ this.fraudCheckStatus = fraudCheckStatus;
+ this.rejectionReason = rejectionReason;
+ }
+
+ public FraudCheckStatus getFraudCheckStatus() {
+ return fraudCheckStatus;
+ }
+
+ public void setFraudCheckStatus(FraudCheckStatus fraudCheckStatus) {
+ this.fraudCheckStatus = fraudCheckStatus;
+ }
+
+ public String getRejectionReason() {
+ return rejectionReason;
+ }
+
+ public void setRejectionReason(String rejectionReason) {
+ this.rejectionReason = rejectionReason;
+ }
+}
diff --git a/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/model/FraudCheckStatus.java b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/model/FraudCheckStatus.java
new file mode 100644
index 0000000000..5a6adcf274
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/main/java/com/example/fraud/model/FraudCheckStatus.java
@@ -0,0 +1,5 @@
+package com.example.fraud.model;
+
+public enum FraudCheckStatus {
+ OK, FRAUD
+}
diff --git a/samples/standalone/pact/pact-http-server/src/main/resources/application.yml b/samples/standalone/pact/pact-http-server/src/main/resources/application.yml
new file mode 100644
index 0000000000..24466e50d4
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/main/resources/application.yml
@@ -0,0 +1 @@
+server.port: 0
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-server/src/test/java/com/example/fraud/FraudBase.java b/samples/standalone/pact/pact-http-server/src/test/java/com/example/fraud/FraudBase.java
new file mode 100644
index 0000000000..639d686c2d
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/test/java/com/example/fraud/FraudBase.java
@@ -0,0 +1,30 @@
+package com.example.fraud;
+
+import org.junit.Before;
+
+import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;
+
+public class FraudBase {
+
+ @Before
+ public void setup() {
+ RestAssuredMockMvc.standaloneSetup(new FraudDetectionController(),
+ new FraudStatsController(stubbedStatsProvider()));
+ }
+
+ private StatsProvider stubbedStatsProvider() {
+ return fraudType -> {
+ switch (fraudType) {
+ case DRUNKS:
+ return 100;
+ case ALL:
+ return 200;
+ }
+ return 0;
+ };
+ }
+
+ public void assertThatRejectionReasonIsNull(Object rejectionReason) {
+ assert rejectionReason == null;
+ }
+}
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.json b/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.json
new file mode 100644
index 0000000000..fc172767cb
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.json
@@ -0,0 +1,54 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "PUT",
+ "path": "/fraudcheck",
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json"
+ },
+ "body": {
+ "clientId": "1234567890",
+ "loanAmount": 99999
+ },
+ "matchingRules": {
+ "$.body.clientId": {
+ "match": "regex",
+ "regex": "[0-9]{10}"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
+ },
+ "body": {
+ "fraudCheckStatus": "FRAUD",
+ "rejectionReason": "Amount too high"
+ },
+ "matchingRules": {
+ "$.body.fraudCheckStatus": {
+ "match": "regex",
+ "regex": "FRAUD"
+ }
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.json b/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.json
new file mode 100644
index 0000000000..0b947bc8c0
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.json
@@ -0,0 +1,42 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "PUT",
+ "path": "/fraudcheck",
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json"
+ },
+ "body": {
+ "clientId": "1234567890",
+ "loanAmount": 123.123
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
+ },
+ "body": {
+ "fraudCheckStatus": "OK",
+ "rejectionReason": null
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldReturnDrunksStats.json b/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldReturnDrunksStats.json
new file mode 100644
index 0000000000..f939e710c9
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldReturnDrunksStats.json
@@ -0,0 +1,34 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "GET",
+ "path": "/drunks"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
+ },
+ "body": {
+ "count": 100
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.json b/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.json
new file mode 100644
index 0000000000..160f1e618f
--- /dev/null
+++ b/samples/standalone/pact/pact-http-server/src/test/resources/contracts/fraud/shouldReturnFraudStats.json
@@ -0,0 +1,34 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "GET",
+ "path": "/frauds"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
+ },
+ "body": {
+ "count": 200
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/standalone/pact/pom.xml b/samples/standalone/pact/pom.xml
new file mode 100644
index 0000000000..16a1c71e7e
--- /dev/null
+++ b/samples/standalone/pact/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+
+ org.springframework.cloud
+ spring-cloud-contract-samples-standalone
+ 1.1.0.BUILD-SNAPSHOT
+ ..
+
+
+ spring-cloud-contract-samples-pact
+ pom
+
+ Spring Cloud Contract Standalone Pact Test Samples
+ Spring Cloud Contract Standalone Test Samples used for end to end tests with Pact
+
+
+ 1.1.0.BUILD-SNAPSHOT
+
+
+
+ pact-http-server
+ pact-http-client
+
+
+
+
+
+ maven-deploy-plugin
+
+ true
+
+
+
+
+
+
diff --git a/samples/standalone/pom.xml b/samples/standalone/pom.xml
index 2b6946ca6a..56f1211331 100644
--- a/samples/standalone/pom.xml
+++ b/samples/standalone/pom.xml
@@ -21,9 +21,11 @@
+ contracts
restdocs
dsl
messaging
+ pact
diff --git a/spring-cloud-contract-dependencies/pom.xml b/spring-cloud-contract-dependencies/pom.xml
index 7d6c78b7de..ed38eb15f0 100644
--- a/spring-cloud-contract-dependencies/pom.xml
+++ b/spring-cloud-contract-dependencies/pom.xml
@@ -40,6 +40,11 @@
spring-cloud-contract-converters
${project.version}
+
+ org.springframework.cloud
+ spring-cloud-contract-spec-pact
+ ${project.version}
+
org.springframework.cloud
spring-cloud-contract-stub-runner
diff --git a/spring-cloud-contract-spec/pom.xml b/spring-cloud-contract-spec/pom.xml
index 80bf92ed29..5a6f5b1812 100644
--- a/spring-cloud-contract-spec/pom.xml
+++ b/spring-cloud-contract-spec/pom.xml
@@ -17,11 +17,31 @@
org.codehaus.groovy
groovy
+
+ org.codehaus.groovy
+ groovy-nio
+
+
+ org.codehaus.groovy
+ groovy-json
+
+
+ org.codehaus.groovy
+ groovy-xml
+
dk.brics.automaton
automaton
1.11-8
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.slf4j
+ slf4j-api
+
org.spockframework
spock-core
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/Contract.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/Contract.groovy
index bf4717d451..4c91ea62ec 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/Contract.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/Contract.groovy
@@ -31,7 +31,7 @@ import org.springframework.cloud.contract.spec.internal.Response
*/
@TypeChecked
@EqualsAndHashCode
-@ToString(includeFields = true, includePackage = false, includeNames = true)
+@ToString(includePackage = false, includeNames = true)
class Contract {
/**
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy
index e7f0345620..5ce9775d57 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy
@@ -25,7 +25,7 @@ package org.springframework.cloud.contract.spec
* @author Marcin Grzejszczak
* @since 1.1.0
*/
-public interface ContractConverter {
+interface ContractConverter {
/**
* Should this file be accepted by the converter. Can use the file extension
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Body.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Body.groovy
index cba1c3d72f..cfbde590b4 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Body.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Body.groovy
@@ -25,7 +25,7 @@ import groovy.transform.ToString
*
* @since 1.0.0
*/
-@ToString(includePackage = false, includeFields = true, includeNames = true, includeSuper = true)
+@ToString(includePackage = false, includeNames = true, includeSuper = true)
@EqualsAndHashCode(callSuper = true)
@CompileStatic
class Body extends DslProperty {
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy
index 68d1206c58..5163f27c9f 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy
@@ -18,6 +18,7 @@ package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
+import groovy.transform.ToString
/**
* Represents a property that will become an executable method in the
@@ -27,6 +28,7 @@ import groovy.transform.EqualsAndHashCode
*/
@CompileStatic
@EqualsAndHashCode
+@ToString(includePackage = false, includeNames = true)
class ExecutionProperty {
private static final String PLACEHOLDER_VALUE = '\\$it'
@@ -46,7 +48,7 @@ class ExecutionProperty {
}
@Override
- public String toString() {
+ String toString() {
return executionCommand
}
}
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NamedProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NamedProperty.groovy
index 798ddcb0f2..cbf8a5f543 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NamedProperty.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/NamedProperty.groovy
@@ -26,7 +26,7 @@ import groovy.transform.ToString
*
* @since 1.0.0
*/
-@ToString(includePackage = false, includeFields = true, includeNames = true)
+@ToString(includePackage = false, includeNames = true)
@EqualsAndHashCode(includeFields = true)
@CompileStatic
class NamedProperty {
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy
index f1672a5ae0..8d5f963300 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy
@@ -17,6 +17,7 @@
package org.springframework.cloud.contract.spec.internal
import groovy.transform.CompileStatic
+import groovy.transform.ToString
/**
* Represents a property that may or may not be there
@@ -24,6 +25,7 @@ import groovy.transform.CompileStatic
* @since 1.0.0
*/
@CompileStatic
+@ToString(includePackage = false, includeNames = true)
class OptionalProperty {
final Object value
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameter.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameter.groovy
index 069d535b90..183385dd81 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameter.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameter.groovy
@@ -27,8 +27,8 @@ import static org.springframework.cloud.contract.spec.util.ValidateUtils.validat
*
* @since 1.0.0
*/
-@EqualsAndHashCode(includeFields = true, callSuper = true)
-@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true, includeSuper = true)
+@EqualsAndHashCode(callSuper = true)
+@ToString(includePackage = false, ignoreNulls = true, includeNames = true, includeSuper = true)
@CompileStatic
class QueryParameter extends DslProperty {
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameters.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameters.groovy
index 26cbec3730..123a9d4722 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameters.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/QueryParameters.groovy
@@ -20,8 +20,8 @@ import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import groovy.transform.TypeChecked
-@EqualsAndHashCode(includeFields = true)
-@ToString(includePackage = false, includeFields = true, ignoreNulls = true, includeNames = true)
+@EqualsAndHashCode
+@ToString(includePackage = false, ignoreNulls = true, includeNames = true)
@TypeChecked
class QueryParameters {
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy
index c9ca105382..780e79a39e 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Response.groovy
@@ -30,7 +30,7 @@ import java.util.regex.Pattern
* @since 1.0.0
*/
@TypeChecked
-@EqualsAndHashCode(includeFields = true)
+@EqualsAndHashCode
@ToString(includePackage = false, includeFields = true)
class Response extends Common {
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Url.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Url.groovy
index b7ebf2c2f5..e7782337cb 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Url.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/Url.groovy
@@ -27,7 +27,7 @@ import static org.springframework.cloud.contract.spec.util.ValidateUtils.validat
*
* @since 1.0.0
*/
-@ToString(includePackage = false, includeFields = true, includeNames = true, includeSuper = true)
+@ToString(includePackage = false, includeNames = true, includeSuper = true)
@EqualsAndHashCode(includeFields = true, callSuper = true)
@CompileStatic
class Url extends DslProperty {
diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/UrlPath.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/UrlPath.groovy
index 6fa2525fca..7a3ea8a3d1 100644
--- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/UrlPath.groovy
+++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/UrlPath.groovy
@@ -21,7 +21,8 @@ import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
/**
- * Represents a url path
+ * Represents a url path. Syntactic sugar when working with
+ * {@link QueryParameters}. It's logically equal to {@link Url}
*
* @since 1.0.0
*/
diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRepository.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRepository.java
index e0b43f0351..c3eada28fa 100644
--- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRepository.java
+++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRepository.java
@@ -56,6 +56,9 @@ class StubRepository {
"Missing descriptor repository under path [" + repository + "]");
}
this.contractConverters = SpringFactoriesLoader.loadFactories(ContractConverter.class, null);
+ if (log.isDebugEnabled()) {
+ log.debug("Found the following contract converters " + this.contractConverters);
+ }
this.httpServerStubs = httpServerStubs;
this.path = repository;
this.stubs = stubs();
diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java
index eee93df601..3c5b996acb 100644
--- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java
+++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java
@@ -25,10 +25,10 @@ import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.Header;
+import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
-import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.util.MethodBufferingJsonVerifiable;
import com.fasterxml.jackson.core.JsonProcessingException;
diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java
index 073cb2cfae..e056748ae4 100644
--- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java
+++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java
@@ -23,10 +23,10 @@ import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.Header;
+import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
-import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.util.MethodBufferingJsonVerifiable;
import org.springframework.integration.core.MessageSelector;
import org.springframework.messaging.Message;
diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java
index d75a2c4ae3..6fa0d5dd77 100644
--- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java
+++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java
@@ -23,10 +23,10 @@ import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.BodyMatcher;
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.Header;
+import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
-import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.util.MethodBufferingJsonVerifiable;
import org.springframework.integration.core.MessageSelector;
import org.springframework.messaging.Message;
diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java
index 60fe2e8d21..727de1c5d7 100644
--- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java
+++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java
@@ -119,7 +119,9 @@ public class WireMockHttpServerStub implements HttpServerStub {
}
}
catch (Exception e) {
- log.warn("Failed to register the stub mapping [" + mappingDescriptor + "]", e);
+ if (log.isDebugEnabled()) {
+ log.debug("Failed to register the stub mapping [" + mappingDescriptor + "]", e);
+ }
}
}
}
diff --git a/spring-cloud-contract-tools/pom.xml b/spring-cloud-contract-tools/pom.xml
index fe4634f212..dfe7b50728 100644
--- a/spring-cloud-contract-tools/pom.xml
+++ b/spring-cloud-contract-tools/pom.xml
@@ -19,6 +19,7 @@
spring-cloud-contract-converters
+ spring-cloud-contract-spec-pact
spring-cloud-contract-maven-plugin
spring-cloud-contract-gradle-plugin
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverter.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverter.groovy
index 38c6bc6a19..677c9cd369 100644
--- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverter.groovy
+++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverter.groovy
@@ -24,6 +24,7 @@ import org.springframework.cloud.contract.verifier.config.ContractVerifierConfig
import org.springframework.cloud.contract.verifier.file.ContractFileScanner
import org.springframework.cloud.contract.verifier.file.ContractMetadata
import org.springframework.cloud.contract.verifier.util.NamesUtil
+import org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter
import java.nio.charset.StandardCharsets
import java.nio.file.Files
@@ -64,8 +65,12 @@ class RecursiveFilesConverter {
}
contracts.asMap().entrySet().each { entry ->
entry.value.each { ContractMetadata contract ->
+ if (log.isDebugEnabled()) {
+ log.debug("Will create a stub for contract [${contract}]")
+ }
File sourceFile = contract.path.toFile()
- StubGenerator stubGenerator = holder.converterForName(sourceFile.name);
+ StubGenerator stubGenerator = contract.convertedContract ? holder.firstOrDefault(new DslToWireMockClientConverter()) :
+ holder.converterForName(sourceFile.name)
try {
String path = sourceFile.path
if (properties.isExcludeBuildFolders() && (matchesPath(path, "target") || matchesPath(path, "build"))) {
@@ -78,7 +83,11 @@ class RecursiveFilesConverter {
return
}
int contractsSize = contract.convertedContract.size()
- Map convertedContent = stubGenerator.convertContents(entry.key.last().toString(), contract)
+ def entryKey = entry.key
+ if (log.isDebugEnabled()) {
+ log.debug("Stub Generator [${stubGenerator}] will convert contents of [${entryKey}]")
+ }
+ Map convertedContent = stubGenerator.convertContents(entryKey.last().toString(), contract)
if (!convertedContent) {
return
}
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/StubGeneratorProvider.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/StubGeneratorProvider.groovy
index 9e187741ce..5afade974b 100644
--- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/StubGeneratorProvider.groovy
+++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/StubGeneratorProvider.groovy
@@ -25,4 +25,8 @@ class StubGeneratorProvider {
StubGenerator converterForName(String fileName) {
return this.converters.find { it.canHandleFileName(fileName) }
}
+
+ StubGenerator firstOrDefault(StubGenerator defaultStubGenerator) {
+ return this.converters.empty ? defaultStubGenerator : this.converters.first()
+ }
}
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/pom.xml
index cedc473db0..b6526ac27b 100644
--- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/pom.xml
+++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/pom.xml
@@ -369,6 +369,16 @@
assertj-core
test
+
+ org.springframework.cloud
+ spring-cloud-contract-spec-pact
+ test
+
+
+ au.com.dius
+ pact-jvm-model
+ test
+
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 01faf6555a..d9701852e0 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
@@ -16,20 +16,21 @@
*/
package org.springframework.cloud.contract.maven.verifier;
-import static io.takari.maven.testing.TestMavenRuntime.newParameter;
-import static io.takari.maven.testing.TestResources.assertFilesNotPresent;
-import static io.takari.maven.testing.TestResources.assertFilesPresent;
-import static org.assertj.core.api.BDDAssertions.then;
-
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
+import org.springframework.util.StringUtils;
import io.takari.maven.testing.TestMavenRuntime;
import io.takari.maven.testing.TestResources;
+import static io.takari.maven.testing.TestMavenRuntime.newParameter;
+import static io.takari.maven.testing.TestResources.assertFilesNotPresent;
+import static io.takari.maven.testing.TestResources.assertFilesPresent;
+import static org.assertj.core.api.BDDAssertions.then;
+
public class PluginUnitTest {
@Rule
@@ -256,4 +257,19 @@ public class PluginUnitTest {
assertFilesPresent(basedir, "target/stubs/contracts/consumer1/Messaging.groovy");
assertFilesPresent(basedir, "target/stubs/contracts/pom.xml");
}
+
+ @Test
+ public void shouldGenerateContractTestsForPactAndMaintainIndents() throws Exception {
+ File basedir = this.resources.getBasedir("pact");
+
+ this.maven.executeMojo(basedir, "generateTests");
+
+ assertFilesPresent(basedir,
+ "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/ContractVerifierTest.java");
+ File test = new File(basedir, "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/ContractVerifierTest.java");
+ String testContents = FileUtils.readFileToString(test);
+ int countOccurrencesOf = StringUtils
+ .countOccurrencesOf(testContents, "\t\tMockMvcRequestSpecification");
+ then(countOccurrencesOf).isEqualTo(4);
+ }
}
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/pom.xml
new file mode 100644
index 0000000000..297e4e005e
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+ 4.0.0
+
+ org.springframework.cloud.verifier.sample
+ sample-pact-project
+ 0.1
+
+
+ 1.1.0.BUILD-SNAPSHOT
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-contract-maven-plugin
+
+ com.example.FooBase
+
+
+ .*com.*
+ com.example.TestBase
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-contract-spec-pact
+ ${spring.cloud.contract.version}
+
+
+ au.com.dius
+ pact-jvm-model
+ 2.4.18
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldMarkClientAsFraud.json b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldMarkClientAsFraud.json
new file mode 100644
index 0000000000..cd81cc5fc5
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldMarkClientAsFraud.json
@@ -0,0 +1,42 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "PUT",
+ "path": "/fraudcheck",
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json"
+ },
+ "body": {
+ "clientId": "1234567890",
+ "loanAmount": 99999
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
+ },
+ "body": {
+ "fraudCheckStatus": "FRAUD",
+ "rejectionReason": "Amount too high"
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldMarkClientAsNotFraud.json b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldMarkClientAsNotFraud.json
new file mode 100644
index 0000000000..0b947bc8c0
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldMarkClientAsNotFraud.json
@@ -0,0 +1,42 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "PUT",
+ "path": "/fraudcheck",
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json"
+ },
+ "body": {
+ "clientId": "1234567890",
+ "loanAmount": 123.123
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
+ },
+ "body": {
+ "fraudCheckStatus": "OK",
+ "rejectionReason": null
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldReturnDrunksStats.json b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldReturnDrunksStats.json
new file mode 100644
index 0000000000..f939e710c9
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldReturnDrunksStats.json
@@ -0,0 +1,34 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "GET",
+ "path": "/drunks"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
+ },
+ "body": {
+ "count": 100
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldReturnFraudStats.json b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldReturnFraudStats.json
new file mode 100644
index 0000000000..160f1e618f
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/pact/src/test/resources/contracts/shouldReturnFraudStats.json
@@ -0,0 +1,34 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "GET",
+ "path": "/frauds"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json;charset=UTF-8"
+ },
+ "body": {
+ "count": 200
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/pom.xml
new file mode 100644
index 0000000000..bc55e13dab
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/pom.xml
@@ -0,0 +1,74 @@
+
+
+ 4.0.0
+
+ org.springframework.cloud
+ spring-cloud-contract-tools
+ 1.1.0.BUILD-SNAPSHOT
+ ..
+
+ spring-cloud-contract-spec-pact
+ jar
+ Spring Cloud Contract Spec Pact
+ Spring Cloud Contract Spec Pact
+
+
+ org.springframework
+ spring-context
+
+
+ org.springframework.cloud
+ spring-cloud-contract-verifier
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+ org.codehaus.groovy
+ groovy
+
+
+ org.codehaus.groovy
+ groovy-nio
+
+
+ au.com.dius
+ pact-jvm-model
+ true
+
+
+ org.spockframework
+ spock-core
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ info.solidsoft.spock
+ spock-global-unroll
+ test
+
+
+
+
+
+ org.codehaus.gmavenplus
+ gmavenplus-plugin
+
+
+
+ addSources
+ compile
+ testCompile
+
+
+
+
+
+
+
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverter.groovy b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverter.groovy
new file mode 100644
index 0000000000..4f1b7df1cc
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/main/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverter.groovy
@@ -0,0 +1,318 @@
+package org.springframework.cloud.contract.verifier.spec.pact
+
+import au.com.dius.pact.model.BasePact
+import au.com.dius.pact.model.Consumer
+import au.com.dius.pact.model.Interaction
+import au.com.dius.pact.model.OptionalBody
+import au.com.dius.pact.model.Pact
+import au.com.dius.pact.model.PactReader
+import au.com.dius.pact.model.Provider
+import au.com.dius.pact.model.Request
+import au.com.dius.pact.model.RequestResponseInteraction
+import au.com.dius.pact.model.RequestResponsePact
+import au.com.dius.pact.model.Response
+import groovy.json.JsonOutput
+import groovy.transform.CompileStatic
+import org.springframework.cloud.contract.spec.Contract
+import org.springframework.cloud.contract.spec.ContractConverter
+import org.springframework.cloud.contract.spec.internal.BodyMatchers
+import org.springframework.cloud.contract.spec.internal.DslProperty
+import org.springframework.cloud.contract.spec.internal.ExecutionProperty
+import org.springframework.cloud.contract.spec.internal.Headers
+import org.springframework.cloud.contract.spec.internal.MatchingType
+import org.springframework.cloud.contract.spec.internal.QueryParameters
+import org.springframework.cloud.contract.verifier.util.MapConverter
+
+/**
+ * Converter of JSON PACT file
+ *
+ * @author Marcin Grzejszczak
+ * @since 1.1.0
+ */
+@CompileStatic
+class PactContractConverter implements ContractConverter {
+
+ private static final String MATCH_KEY = "match"
+ private static final String REGEX_KEY = "regex"
+ private static final String MAX_KEY = "max"
+ private static final String MIN_KEY = "min"
+
+ @Override
+ boolean isAccepted(File file) {
+ try {
+ PactReader.loadPact(file)
+ return true
+ } catch (Exception e) {
+ return false
+ }
+ }
+
+ @Override
+ Collection convertFrom(File file) {
+ Pact pact = PactReader.loadPact(file)
+ List interactions = pact.interactions
+ return interactions.collect { Interaction interaction ->
+ Contract.make {
+ if (interaction instanceof RequestResponseInteraction) {
+ RequestResponseInteraction requestResponseInteraction = (RequestResponseInteraction) interaction
+ description("$requestResponseInteraction.description${providerState(interaction)}")
+ request {
+ method(requestResponseInteraction.request.method)
+ if (requestResponseInteraction.request.query) {
+ url(requestResponseInteraction.request.path) {
+ queryParameters {
+ requestResponseInteraction.request.query.each { String key, List value ->
+ value.each { String singleValue ->
+ parameter(key, singleValue)
+ }
+ }
+ }
+ }
+ } else {
+ url(requestResponseInteraction.request.path)
+ }
+ if (requestResponseInteraction.request.headers) {
+ headers {
+ requestResponseInteraction.request.headers.each { String key, String value ->
+ header(key, value)
+ }
+ }
+ }
+ if (requestResponseInteraction.request.body.state == OptionalBody.State.PRESENT) {
+ def parsedBody = BasePact.parseBody(requestResponseInteraction.request)
+ if (parsedBody instanceof Map) {
+ body(parsedBody as Map)
+ } else if (parsedBody instanceof List) {
+ body(parsedBody as List)
+ } else {
+ body(parsedBody.toString())
+ }
+ }
+ if (requestResponseInteraction.request?.matchingRules) {
+ stubMatchers {
+ requestResponseInteraction.request.matchingRules.each { String key, Map value ->
+ String keyFromBody = toKeyStartingFromBody(key)
+ if (value.containsKey(MATCH_KEY)) {
+ MatchingType matchingType = MatchingType.valueOf((value.get(MATCH_KEY) as String).toUpperCase())
+ switch (matchingType) {
+ case MatchingType.EQUALITY:
+ // equality is checked by default in the standard way
+ break
+ case MatchingType.DATE:
+ jsonPath(keyFromBody, byDate())
+ break
+ case MatchingType.TIME:
+ jsonPath(keyFromBody, byTime())
+ break
+ case MatchingType.TIMESTAMP:
+ jsonPath(keyFromBody, byTimestamp())
+ break
+ case MatchingType.REGEX:
+ jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
+ break
+ }
+ } else if (value.containsKey(REGEX_KEY)) {
+ jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
+ }
+ }
+ }
+ }
+ }
+ response {
+ status(requestResponseInteraction.response.status)
+ if (requestResponseInteraction.response.body.state == OptionalBody.State.PRESENT) {
+ def parsedBody = BasePact.parseBody(requestResponseInteraction.response)
+ if (parsedBody instanceof Map) {
+ body(parsedBody as Map)
+ } else if (parsedBody instanceof List) {
+ body(parsedBody as List)
+ } else {
+ body(parsedBody.toString())
+ }
+ }
+ if (requestResponseInteraction.response?.matchingRules) {
+ testMatchers {
+ requestResponseInteraction.response.matchingRules.each { String key, Map value ->
+ String keyFromBody = toKeyStartingFromBody(key)
+ if (value.containsKey(MATCH_KEY)) {
+ MatchingType matchingType = MatchingType.valueOf((value.get(MATCH_KEY) as String).toUpperCase())
+ switch (matchingType) {
+ case MatchingType.EQUALITY:
+ // equality is checked by default in the standard way
+ break
+ case MatchingType.DATE:
+ jsonPath(keyFromBody, byDate())
+ break
+ case MatchingType.TIME:
+ jsonPath(keyFromBody, byTime())
+ break
+ case MatchingType.TIMESTAMP:
+ jsonPath(keyFromBody, byTimestamp())
+ break
+ case MatchingType.REGEX:
+ jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
+ break
+ case MatchingType.TYPE:
+ jsonPath(keyFromBody, byType() {
+ if (value.containsKey(MIN_KEY)) {
+ minOccurrence(value.get(MIN_KEY) as Integer)
+ }
+ if (value.containsKey(MAX_KEY)) {
+ maxOccurrence(value.get(MAX_KEY) as Integer)
+ }
+ })
+ break
+ }
+ } else if (value.containsKey(REGEX_KEY)) {
+ jsonPath(keyFromBody, byRegex(value.get(REGEX_KEY) as String))
+ }
+ }
+ }
+ }
+ requestResponseInteraction.response.headers?.each { String key, String value ->
+ headers {
+ header(key, value)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected String providerState(Interaction interaction) {
+ return interaction.providerState ? " ${interaction.providerState}" : ""
+ }
+
+ protected String toKeyStartingFromBody(String key) {
+ return key.replace('$.body', '$')
+ }
+
+ @Override
+ Pact convertTo(Collection contract) {
+ Provider provider = new Provider()
+ provider.name = "Provider"
+ Consumer consumer = new Consumer()
+ consumer.name = "Consumer"
+ List interactions = contract.find { it.request }.collect { Contract dsl ->
+ RequestResponseInteraction interaction = new RequestResponseInteraction()
+ interaction.description = dsl.description ?: ""
+ Request request = new Request().with {
+ method = dsl.request.method.serverValue.toString()
+ path = url(dsl)
+ QueryParameters params = queryParams(dsl)
+ if (params) {
+ query = params.parameters.collectEntries {
+ String name = it.name
+ String value = it.serverValue
+ return [(name) : [value]]
+ }
+ }
+ if (dsl.request.headers) {
+ headers = headers(dsl.request.headers, { DslProperty property -> property.serverValue })
+ }
+ if (dsl.request.body) {
+ assertInputContract(dsl.request.body.serverValue)
+ def json = MapConverter.getTestSideValues(dsl.request.body.serverValue)
+ String jsonBody = JsonOutput.toJson(json)
+ body = new OptionalBody(OptionalBody.State.PRESENT, jsonBody)
+ }
+ if (dsl.request.matchers && dsl.request.matchers.hasMatchers()) {
+ matchingRules = matchingRules(dsl.request.matchers)
+ }
+ return it
+ }
+ Response response = new Response().with {
+ status = dsl.response.status.clientValue as Integer
+ if (dsl.response.headers) {
+ headers = headers(dsl.response.headers, { DslProperty property -> property.clientValue })
+ }
+ if (dsl.response.body) {
+ assertInputContract(dsl.response.body.clientValue)
+ def json = MapConverter.getStubSideValues(dsl.response.body.clientValue)
+ String jsonBody = JsonOutput.toJson(json)
+ body = new OptionalBody(OptionalBody.State.PRESENT, jsonBody)
+ }
+ if (dsl.response.matchers && dsl.response.matchers.hasMatchers()) {
+ matchingRules = matchingRules(dsl.response.matchers)
+ }
+ return it
+ }
+ interaction.request = request
+ interaction.response = response
+ return interaction
+ }
+ return new RequestResponsePact(provider, consumer, interactions)
+ }
+
+ protected void assertInputContract(parsedJson) {
+ boolean hasExecutionProp = false
+ MapConverter.transformValues(parsedJson, {
+ if (it instanceof ExecutionProperty) {
+ hasExecutionProp = true
+ }
+ return it
+ })
+ if (hasExecutionProp) {
+ throw new UnsupportedOperationException("We can't convert a contract that has execution property")
+ }
+ }
+
+ protected Map headers(Headers headers, Closure closure) {
+ return headers.entries.collectEntries {
+ String name = it.name
+ String value = closure(it)
+ return [(name) : value]
+ }
+ }
+
+ protected Map> matchingRules(BodyMatchers bodyMatchers) {
+ return bodyMatchers.jsonPathMatchers().collectEntries {
+ MatchingType matchingType = it.matchingType()
+ String key = it.path()
+ Object value = it.value()
+ Integer minTypeOccurrence = it.minTypeOccurrence()
+ Integer maxTypeOccurrence = it.maxTypeOccurrence()
+ Map matchingRule = [:]
+ switch (matchingType) {
+ case MatchingType.EQUALITY:
+ matchingRule << [(MATCH_KEY) : MatchingType.EQUALITY.toString().toLowerCase() as Object]
+ break
+ case MatchingType.TYPE:
+ Map map = [(MATCH_KEY) : MatchingType.TYPE.toString().toLowerCase() as Object]
+ if (minTypeOccurrence) map.put(MIN_KEY, minTypeOccurrence)
+ if (maxTypeOccurrence) map.put(MAX_KEY, maxTypeOccurrence)
+ matchingRule << map
+ break
+ case MatchingType.DATE:
+ case MatchingType.TIME:
+ case MatchingType.TIMESTAMP:
+ case MatchingType.REGEX:
+ matchingRule << [
+ (MATCH_KEY) : MatchingType.REGEX.toString().toLowerCase() as Object,
+ (REGEX_KEY) : value
+ ]
+ break
+ }
+ return [(key) : matchingRule]
+ }
+ }
+
+ protected String url(Contract dsl) {
+ if (dsl.request.urlPath) {
+ return dsl.request.urlPath.serverValue.toString()
+ } else if (dsl.request.url) {
+ return dsl.request.url.serverValue.toString()
+ }
+ throw new IllegalStateException("No url provided")
+ }
+
+ protected QueryParameters queryParams(Contract dsl) {
+ if (dsl.request.urlPath) {
+ return dsl.request.urlPath.queryParameters
+ } else if (dsl.request.url) {
+ return dsl.request.url.queryParameters
+ }
+ throw new IllegalStateException("No url provided")
+ }
+}
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/main/resources/META-INF/spring.factories b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..640c7019cf
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.cloud.contract.spec.ContractConverter=\
+org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverterSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverterSpec.groovy
new file mode 100644
index 0000000000..bc3f6375df
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/groovy/org/springframework/cloud/contract/verifier/spec/pact/PactContractConverterSpec.groovy
@@ -0,0 +1,290 @@
+package org.springframework.cloud.contract.verifier.spec.pact
+
+import au.com.dius.pact.model.Pact
+import au.com.dius.pact.model.PactSpecVersion
+import groovy.json.JsonOutput
+import org.skyscreamer.jsonassert.JSONAssert
+import org.springframework.cloud.contract.spec.Contract
+import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
+import org.springframework.core.io.Resource
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver
+import spock.lang.Specification
+import spock.lang.Subject
+/**
+ * @author Marcin Grzejszczak
+ */
+class PactContractConverterSpec extends Specification {
+
+ File pactJson = new File(PactContractConverterSpec.getResource("/pact/pact.json").toURI())
+ @Subject PactContractConverter converter = new PactContractConverter()
+
+ def "should accept json files that are pact files"() {
+ expect:
+ converter.isAccepted(pactJson)
+ }
+
+ def "should reject json files that are pact files"() {
+ given:
+ File invalidPact = new File(PactContractConverterSpec.getResource("/pact/invalid_pact.json").toURI())
+ expect:
+ converter.isAccepted(invalidPact)
+ }
+
+ def "should convert from pact to contract"() {
+ given:
+ Contract expectedContract = Contract.make {
+ description("a retrieve Mallory request a user with username 'username' and password 'password' exists")
+ request {
+ method(GET())
+ url("/mallory") {
+ queryParameters {
+ parameter("name", "ron")
+ parameter("status", "good")
+ }
+ }
+ headers {
+ contentType(applicationJson())
+ }
+ body(id: "123", method: "create")
+ stubMatchers {
+ jsonPath('$.id', byRegex("[0-9]{3}"))
+ }
+ }
+ response {
+ status(200)
+ headers {
+ contentType(applicationJson())
+ }
+ body([[
+ [email: "rddtGwwWMEhnkAPEmsyE",
+ id: "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
+ userName: "AJQrokEGPAVdOHprQpKP"]
+ ]])
+ testMatchers {
+ jsonPath('$[0][*].email', byType())
+ jsonPath('$[0][*].id', byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
+ jsonPath('$[0]', byType() {
+ maxOccurrence(5)
+ })
+ jsonPath('$[0][*].userName', byType())
+ }
+ }
+ }
+ when:
+ Collection contracts = converter.convertFrom(pactJson)
+ then:
+ contracts == [expectedContract]
+ }
+
+ def "should convert from contract to pact"() {
+ given:
+ Collection inputContracts = [
+ Contract.make {
+ description("a retrieve Mallory request")
+ request {
+ method(GET())
+ url("/mallory") {
+ queryParameters {
+ parameter("name", "ron")
+ parameter("status", "good")
+ }
+ }
+ headers {
+ contentType(applicationJson())
+ }
+ body(
+ id: 123,
+ method: $(stub(regex("[0][1][2]"))),
+ something: "foo"
+ )
+ stubMatchers {
+ jsonPath('$.id', byRegex("[0-9]{3}"))
+ jsonPath('$.something', byEquality())
+ }
+ }
+ response {
+ status(200)
+ headers {
+ contentType(applicationJson())
+ }
+ body([[
+ [email: "rddtGwwWMEhnkAPEmsyE",
+ id: "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
+ number: $(producer(regex("[0-9]{3}")), consumer(923)),
+ something: "foo",
+ userName: "AJQrokEGPAVdOHprQpKP"]
+ ]])
+ testMatchers {
+ jsonPath('$[0][*].email', byType())
+ jsonPath('$[0][*].id', byRegex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"))
+ jsonPath('$[0]', byType() {
+ minOccurrence(1)
+ maxOccurrence(5)
+ })
+ jsonPath('$[0][*].userName', byType())
+ jsonPath('$[0][*].something', byEquality())
+ }
+ }
+ }
+ ]
+ String expectedJson = '''
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "a retrieve Mallory request",
+ "request": {
+ "method": "GET",
+ "path": "\\/mallory",
+ "query": "name=ron&status=good",
+ "headers": {
+ "Content-Type": "application\\/json"
+ },
+ "body": {
+ "id": 123,
+ "method": "012",
+ "something": "foo"
+ },
+ "matchingRules": {
+ "$.id": {
+ "match": "regex",
+ "regex": "[0-9]{3}"
+ },
+ "$.something": {
+ "match": "equality"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application\\/json"
+ },
+ "body": [
+ [
+ {
+ "email": "rddtGwwWMEhnkAPEmsyE",
+ "id": "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
+ "number": 923,
+ "userName": "AJQrokEGPAVdOHprQpKP"
+ }
+ ]
+ ],
+ "matchingRules": {
+ "$[0][*].email": {
+ "match": "type"
+ },
+ "$[0][*].id": {
+ "match": "regex",
+ "regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
+ },
+ "$[0]": {
+ "match": "type",
+ "min": 1,
+ "max": 5
+ },
+ "$[0][*].userName": {
+ "match": "type"
+ },
+ "$[0][*].something": {
+ "match": "equality"
+ }
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
+'''
+ when:
+ Pact pact = converter.convertTo(inputContracts)
+ then:
+ String actual = JsonOutput.toJson(pact.toMap(PactSpecVersion.V2))
+ JSONAssert.assertEquals(expectedJson, actual, false)
+ }
+
+ def "should fail to convert from contract to pact when contract has execution property in request"() {
+ given:
+ Collection inputContracts = [
+ Contract.make {
+ request {
+ method(GET())
+ url("/mallory")
+ body(
+ id: $(c("foo"), p(execute("foo")))
+ )
+ }
+ response {
+ status(200)
+
+ }
+ }
+ ]
+ when:
+ converter.convertTo(inputContracts)
+ then:
+ def e = thrown(UnsupportedOperationException)
+ e.message.contains("execution property")
+ }
+
+ def "should fail to convert from contract to pact when contract has execution property in response"() {
+ given:
+ Collection inputContracts = [
+ Contract.make {
+ request {
+ method(GET())
+ url("/mallory")
+ }
+ response {
+ status(200)
+ body(
+ id: $(c(execute("foo")), p("foo"))
+ )
+ }
+ }
+ ]
+ when:
+ converter.convertTo(inputContracts)
+ then:
+ def e = thrown(UnsupportedOperationException)
+ e.message.contains("execution property")
+ }
+
+ def "should convert contracts from samples to pacts"() {
+ given:
+ Resource[] contractResources = new PathMatchingResourcePatternResolver().getResources("contracts/*.groovy")
+ Resource[] pactResources = new PathMatchingResourcePatternResolver().getResources("contracts/*.json")
+ Map> contracts = contractResources.collectEntries { [(it.filename) : ContractVerifierDslConverter.convertAsCollection(it.file)] }
+ Map jsonPacts = pactResources.collectEntries { [(it.filename) : it.file.text] }
+ when:
+ Map pacts = contracts.entrySet().collectEntries { [(it.key) : converter.convertTo(it.value)] }
+ then:
+ pacts.entrySet().each {
+ String convertedPactAsText = JsonOutput.toJson(it.value.toMap(PactSpecVersion.V2))
+ String pactFileName = it.key.replace("groovy", "json")
+ println "File name [${it.key}]"
+ JSONAssert.assertEquals(jsonPacts.get(pactFileName), convertedPactAsText, false)
+ }
+ }
+}
+
+
+
+// file creator
+/*
+pacts.entrySet().each {
+ new File("target/${it.key.replace("groovy", "json")}").text = JsonOutput.toJson(it.value.toMap(PactSpecVersion.V2))
+}
+ */
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsFraud.groovy b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsFraud.groovy
new file mode 100644
index 0000000000..bb6d491ce4
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsFraud.groovy
@@ -0,0 +1,66 @@
+package contracts
+
+org.springframework.cloud.contract.spec.Contract.make {
+ request { // (1)
+ method 'PUT' // (2)
+ url '/fraudcheck' // (3)
+ body([ // (4)
+ clientId: $(c(regex('[0-9]{10}')), p("8532032713")),
+ loanAmount: 99999
+ ])
+ headers { // (5)
+ contentType('application/vnd.fraud.v1+json')
+ }
+ }
+ response { // (6)
+ status 200 // (7)
+ body([ // (8)
+ fraudCheckStatus: "FRAUD",
+ rejectionReason: "Amount too high"
+ ])
+ headers { // (9)
+ contentType('application/vnd.fraud.v1+json')
+ }
+ }
+}
+
+/*
+Since we don't want to force on the user to hardcode values of fields that are dynamic
+(timestamps, database ids etc.), one can parametrize those entries. If you wrap your field's
+ value in a `$(...)` or `value(...)` and provide a dynamic value of a field then
+ the concrete value will be generated for you. If you want to be really explicit about
+ which side gets which value you can do that by using the `value(consumer(...), producer(...))` notation.
+ That way what's present in the `consumer` section will end up in the produced stub. What's
+ there in the `producer` will end up in the autogenerated test. If you provide only the
+ regular expression side without the concrete value then Spring Cloud Contract will generate one for you.
+
+From the Consumer perspective, when shooting a request in the integration test:
+
+(1) - If the consumer sends a request
+(2) - With the "PUT" method
+(3) - to the URL "/fraudcheck"
+(4) - with the JSON body that
+ * has a field `clientId` that matches a regular expression `[0-9]{10}`
+ * has a field `loanAmount` that is equal to `99999`
+(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
+(6) - then the response will be sent with
+(7) - status equal `200`
+(8) - and JSON body equal to
+ { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
+(9) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
+
+From the Producer perspective, in the autogenerated producer-side test:
+
+(1) - A request will be sent to the producer
+(2) - With the "PUT" method
+(3) - to the URL "/fraudcheck"
+(4) - with the JSON body that
+ * has a field `clientId` that will have a generated value that matches a regular expression `[0-9]{10}`
+ * has a field `loanAmount` that is equal to `99999`
+(5) - with header `Content-Type` equal to `application/vnd.fraud.v1+json`
+(6) - then the test will assert if the response has been sent with
+(7) - status equal `200`
+(8) - and JSON body equal to
+ { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
+(9) - with header `Content-Type` matching `application/vnd.fraud.v1+json.*`
+ */
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsFraud.json b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsFraud.json
new file mode 100644
index 0000000000..7de1aeddf0
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsFraud.json
@@ -0,0 +1,42 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "PUT",
+ "path": "/fraudcheck",
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json"
+ },
+ "body": {
+ "clientId": "8532032713",
+ "loanAmount": 99999
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json"
+ },
+ "body": {
+ "fraudCheckStatus": "FRAUD",
+ "rejectionReason": "Amount too high"
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsNotFraud.groovy b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsNotFraud.groovy
new file mode 100644
index 0000000000..c3dce3f32d
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsNotFraud.groovy
@@ -0,0 +1,30 @@
+package contracts
+
+org.springframework.cloud.contract.spec.Contract.make {
+ request {
+ method 'PUT'
+ url '/fraudcheck'
+ body("""
+ {
+ "clientId":"${value(consumer(regex('[0-9]{10}')), producer('1234567890'))}",
+ "loanAmount":123.123
+ }
+ """
+ )
+ headers {
+ contentType("application/vnd.fraud.v1+json")
+ }
+
+ }
+ response {
+ status 200
+ body(
+ fraudCheckStatus: "OK",
+ rejectionReason: $(consumer(null), producer(execute('assertThatRejectionReasonIsNull($it)')))
+ )
+ headers {
+ contentType("application/vnd.fraud.v1+json")
+ }
+ }
+
+}
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsNotFraud.json b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsNotFraud.json
new file mode 100644
index 0000000000..db5f37e92e
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldMarkClientAsNotFraud.json
@@ -0,0 +1,42 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "PUT",
+ "path": "/fraudcheck",
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json"
+ },
+ "body": {
+ "clientId": "1234567890",
+ "loanAmount": 123.123
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json"
+ },
+ "body": {
+ "fraudCheckStatus": "OK",
+ "rejectionReason": null
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldReturnFraudStats.groovy b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldReturnFraudStats.groovy
new file mode 100644
index 0000000000..a6ed5a97d7
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldReturnFraudStats.groovy
@@ -0,0 +1,37 @@
+package contracts
+
+import org.springframework.cloud.contract.spec.Contract
+
+[
+ Contract.make {
+ request {
+ name "should count all frauds"
+ method GET()
+ url '/frauds'
+ }
+ response {
+ status 200
+ body([
+ count: 200
+ ])
+ headers {
+ contentType("application/vnd.fraud.v1+json")
+ }
+ }
+ },
+ Contract.make {
+ request {
+ method GET()
+ url '/drunks'
+ }
+ response {
+ status 200
+ body([
+ count: 100
+ ])
+ headers {
+ contentType("application/vnd.fraud.v1+json")
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldReturnFraudStats.json b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldReturnFraudStats.json
new file mode 100644
index 0000000000..32a0a0b806
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/contracts/shouldReturnFraudStats.json
@@ -0,0 +1,34 @@
+{
+ "provider": {
+ "name": "Provider"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "",
+ "request": {
+ "method": "GET",
+ "path": "/frauds"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/vnd.fraud.v1+json"
+ },
+ "body": {
+ "count": 200
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pact-specification": {
+ "version": "2.0.0"
+ },
+ "pact-jvm": {
+ "version": "2.4.18"
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/pact/invalid_pact.json b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/pact/invalid_pact.json
new file mode 100644
index 0000000000..1b333cad4d
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/pact/invalid_pact.json
@@ -0,0 +1,3 @@
+{
+ "some" : "json"
+}
\ No newline at end of file
diff --git a/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/pact/pact.json b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/pact/pact.json
new file mode 100644
index 0000000000..c7fc03126b
--- /dev/null
+++ b/spring-cloud-contract-tools/spring-cloud-contract-spec-pact/src/test/resources/pact/pact.json
@@ -0,0 +1,59 @@
+{
+ "provider": {
+ "name": "Alice Service"
+ },
+ "consumer": {
+ "name": "Consumer"
+ },
+ "interactions": [
+ {
+ "description": "a retrieve Mallory request",
+ "provider_state": "a user with username 'username' and password 'password' exists",
+ "request": {
+ "method": "GET",
+ "path": "/mallory",
+ "query": "name=ron&status=good",
+ "body" : {"id": "123", "method": "create"},
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "matchingRules": {
+ "$.body.id": {
+ "match": "regex",
+ "regex": "[0-9]{3}"
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "body": [
+ [
+ {
+ "email": "rddtGwwWMEhnkAPEmsyE",
+ "id": "eb0f8c17-c06a-479e-9204-14f7c95b63a6",
+ "userName": "AJQrokEGPAVdOHprQpKP"
+ }
+ ]
+ ],
+ "matchingRules": {
+ "$.body[0][*].email": {
+ "match": "type"
+ },
+ "$.body[0][*].id": {
+ "regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
+ },
+ "$.body[0]": {
+ "max": 5,
+ "match": "type"
+ },
+ "$.body[0][*].userName": {
+ "match": "type"
+ }
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy
index eddc53d8d6..bc4167ec64 100644
--- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy
+++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy
@@ -23,11 +23,11 @@ import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import org.apache.commons.lang3.StringEscapeUtils
import org.springframework.cloud.contract.spec.internal.*
+import org.springframework.cloud.contract.verifier.util.MapConverter;
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.verifier.util.ContentType
import org.springframework.cloud.contract.verifier.util.JsonPaths
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
-import org.springframework.cloud.contract.verifier.util.MapConverter
import java.util.regex.Pattern
@@ -187,7 +187,7 @@ abstract class MethodBodyBuilder {
protected abstract void then(BlockBuilder bb)
/**
- * Returns a {@link org.springframework.cloud.contract.verifier.util.ContentType} for the given request
+ * Returns a {@link ContentType} for the given request
*/
protected abstract ContentType getResponseContentType()
@@ -415,7 +415,7 @@ abstract class MethodBodyBuilder {
}
private String stripFirstChar(String s) {
- return s.substring(1);
+ return s.substring(1)
}
/**
diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/RequestProcessingMethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/RequestProcessingMethodBodyBuilder.groovy
index 1ade040def..5ab743a258 100644
--- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/RequestProcessingMethodBodyBuilder.groovy
+++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/RequestProcessingMethodBodyBuilder.groovy
@@ -28,9 +28,9 @@ import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.QueryParameter
import org.springframework.cloud.contract.spec.internal.Response
import org.springframework.cloud.contract.spec.internal.Url
+import org.springframework.cloud.contract.verifier.util.MapConverter
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.verifier.util.ContentType
-import org.springframework.cloud.contract.verifier.util.MapConverter
import static org.springframework.cloud.contract.verifier.util.ContentUtils.recognizeContentTypeFromContent
import static org.springframework.cloud.contract.verifier.util.ContentUtils.recognizeContentTypeFromHeader
diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/BaseWireMockStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/BaseWireMockStubStrategy.groovy
index ca8fad8475..bbbf0748fc 100755
--- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/BaseWireMockStubStrategy.groovy
+++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/BaseWireMockStubStrategy.groovy
@@ -20,8 +20,8 @@ import groovy.json.JsonBuilder
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import org.springframework.cloud.contract.spec.internal.Headers
-import org.springframework.cloud.contract.verifier.util.MapConverter
import org.springframework.cloud.contract.spec.internal.DslProperty
+import org.springframework.cloud.contract.verifier.util.MapConverter
import org.springframework.cloud.contract.verifier.util.ContentType
import org.springframework.cloud.contract.verifier.util.ContentUtils
@@ -51,7 +51,7 @@ abstract class BaseWireMockStubStrategy {
}
/**
- * For the given {@link org.springframework.cloud.contract.verifier.util.ContentType} returns the String version of the body
+ * For the given {@link ContentType} returns the String version of the body
*/
String parseBody(Object value, ContentType contentType) {
return parseBody(value.toString(), contentType)
diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy
index a2f7e051ad..8ea7dfe983 100755
--- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy
+++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy
@@ -18,14 +18,28 @@ package org.springframework.cloud.contract.verifier.dsl.wiremock
import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.http.RequestMethod
-import com.github.tomakehurst.wiremock.matching.*
+import com.github.tomakehurst.wiremock.matching.RequestPattern
+import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder
+import com.github.tomakehurst.wiremock.matching.StringValuePattern
+import com.github.tomakehurst.wiremock.matching.UrlPattern
import groovy.json.JsonOutput
import groovy.transform.PackageScope
import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode
import org.springframework.cloud.contract.spec.Contract
-import org.springframework.cloud.contract.spec.internal.*
-import org.springframework.cloud.contract.verifier.util.*
+import org.springframework.cloud.contract.spec.internal.Body
+import org.springframework.cloud.contract.spec.internal.DslProperty
+import org.springframework.cloud.contract.spec.internal.MatchingStrategy
+import org.springframework.cloud.contract.spec.internal.NamedProperty
+import org.springframework.cloud.contract.spec.internal.OptionalProperty
+import org.springframework.cloud.contract.spec.internal.QueryParameters
+import org.springframework.cloud.contract.spec.internal.RegexPatterns
+import org.springframework.cloud.contract.spec.internal.Request
+import org.springframework.cloud.contract.verifier.util.MapConverter
+import org.springframework.cloud.contract.verifier.util.ContentType
+import org.springframework.cloud.contract.verifier.util.ContentUtils
+import org.springframework.cloud.contract.verifier.util.JsonPaths
+import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter
import java.util.regex.Pattern
@@ -108,7 +122,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
if (request.multipart.clientValue instanceof Map) {
List multipartPatterns = (request.multipart.clientValue as Map).collect {
- (it.value instanceof NamedProperty
+ (it.value instanceof NamedProperty
? WireMock.matching(RegexPatterns.multipartFile(it.key, (it.value as NamedProperty).name.clientValue, (it.value as NamedProperty).value.clientValue))
: WireMock.matching(RegexPatterns.multipartParam(it.key, it.value)) )
}
@@ -141,7 +155,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
}
Object url = getUrlIfGstring(request?.url?.clientValue)
if (url instanceof Pattern) {
- return WireMock.urlMatching(url.pattern())
+ return WireMock.urlMatching((url as Pattern).pattern())
}
return WireMock.urlEqualTo(url.toString())
}
diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy
index 713af9a1dc..ed810b89fe 100755
--- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy
+++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockResponseStubStrategy.groovy
@@ -28,6 +28,7 @@ import org.springframework.cloud.contract.spec.internal.Response
import org.springframework.cloud.contract.verifier.util.ContentType
import static org.springframework.cloud.contract.verifier.util.ContentUtils.recognizeContentTypeFromContent
+
import static org.springframework.cloud.contract.verifier.util.ContentUtils.recognizeContentTypeFromHeader
/**
* Converts a {@link Request} into {@link ResponseDefinition}
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 a9204a0c8e..670a1e4262 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
@@ -84,9 +84,12 @@ class ContractFileScanner {
*/
private void appendRecursively(File baseDir, ListMultimap result) {
List converters = SpringFactoriesLoader.loadFactories(ContractConverter, null)
+ if (log.isDebugEnabled()) {
+ log.debug("Found the following contract converters ${converters}")
+ }
File[] files = baseDir.listFiles()
if (!files) {
- return;
+ return
}
files.sort().eachWithIndex { File file, int index ->
boolean excluded = matchesPattern(file, excludeMatchers)
@@ -114,17 +117,19 @@ class ContractFileScanner {
private void addContractToTestGeneration(List converters, ListMultimap result,
File[] files, File file, int index) {
boolean converted = false
- for (ContractConverter converter : converters) {
- if (converter.isAccepted(file)) {
- addContractToTestGeneration(result, files, file, index, converter.convertFrom(file))
- converted = true
- break
+ if (!file.isDirectory()) {
+ for (ContractConverter converter : converters) {
+ if (converter.isAccepted(file)) {
+ addContractToTestGeneration(result, files, file, index, converter.convertFrom(file))
+ converted = true
+ break
+ }
}
}
if (!converted) {
appendRecursively(file, result)
if (log.isDebugEnabled()) {
- log.debug("File [$file] wasn't ignored but no converter was applicable.")
+ log.debug("File [$file] wasn't ignored but no converter was applicable. The file is a directory [${file.isDirectory()}]")
}
}
}
@@ -147,10 +152,10 @@ class ContractFileScanner {
private boolean matchesPattern(File file, Set matchers) {
for (PathMatcher matcher : matchers) {
if (matcher.matches(file.toPath())) {
- return true;
+ return true
}
}
- return false;
+ return false
}
private boolean isContractFile(File file) {
@@ -159,15 +164,15 @@ class ContractFileScanner {
private static String getFilenameExtension(String path) {
if (path == null) {
- return null;
+ return null
}
int extIndex = path.lastIndexOf('.');
if (extIndex == -1) {
- return null;
+ return null
}
int folderIndex = path.lastIndexOf('/');
if (folderIndex > extIndex) {
- return null;
+ return null
}
return path.substring(extIndex + 1);
}
diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentUtils.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentUtils.groovy
index 0e39559d13..74115b30fd 100644
--- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentUtils.groovy
+++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentUtils.groovy
@@ -15,17 +15,18 @@
*/
package org.springframework.cloud.contract.verifier.util
+
import groovy.json.JsonException
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.transform.TypeChecked
import groovy.util.logging.Slf4j
-import org.springframework.cloud.contract.spec.internal.Headers
import org.codehaus.groovy.runtime.GStringImpl
-import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.DslProperty
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
+import org.springframework.cloud.contract.spec.internal.Headers
import org.springframework.cloud.contract.spec.internal.MatchingStrategy
+import org.springframework.cloud.contract.spec.internal.NamedProperty
import org.springframework.cloud.contract.spec.internal.OptionalProperty
import java.util.regex.Matcher
@@ -35,6 +36,11 @@ import static org.apache.commons.lang3.StringEscapeUtils.escapeJava
import static org.apache.commons.lang3.StringEscapeUtils.escapeJson
import static org.apache.commons.lang3.StringEscapeUtils.escapeXml11
+/**
+ * A utility class that can operate on a message body basing on the provided Content Type.
+ *
+ * @since 1.0.0
+ */
@TypeChecked
@Slf4j
class ContentUtils {
@@ -66,7 +72,7 @@ class ContentUtils {
* @param valueProvider - provider of values either for server or client side
* @return JSON structure with replaced client / server side parts
*/
- public static Object extractValue(GString bodyAsValue, ContentType contentType, Closure valueProvider) {
+ static Object extractValue(GString bodyAsValue, ContentType contentType, Closure valueProvider) {
if (bodyAsValue.isEmpty()){
return bodyAsValue
}
@@ -95,7 +101,7 @@ class ContentUtils {
}
}
- public static ContentType getClientContentType(GString bodyAsValue) {
+ static ContentType getClientContentType(GString bodyAsValue) {
try {
extractValueForJSON(bodyAsValue, GET_STUB_SIDE)
return ContentType.JSON
@@ -110,7 +116,7 @@ class ContentUtils {
}
}
- public static ContentType getClientContentType(String bodyAsValue) {
+ static ContentType getClientContentType(String bodyAsValue) {
try {
new JsonSlurper().parseText(bodyAsValue)
return ContentType.JSON
@@ -124,11 +130,11 @@ class ContentUtils {
}
}
- public static ContentType getClientContentType(Object bodyAsValue) {
+ static ContentType getClientContentType(Object bodyAsValue) {
return ContentType.UNKNOWN
}
- public static ContentType getClientContentType(Map bodyAsValue) {
+ static ContentType getClientContentType(Map bodyAsValue) {
try {
JsonOutput.toJson(bodyAsValue)
return ContentType.JSON
@@ -137,7 +143,7 @@ class ContentUtils {
}
}
- public static ContentType getClientContentType(List bodyAsValue) {
+ static ContentType getClientContentType(List bodyAsValue) {
try {
JsonOutput.toJson(bodyAsValue)
return ContentType.JSON
@@ -153,7 +159,7 @@ class ContentUtils {
)
}
- public static Object extractValue(GString bodyAsValue, Closure valueProvider) {
+ static Object extractValue(GString bodyAsValue, Closure valueProvider) {
return extractValue(bodyAsValue, ContentType.UNKNOWN, valueProvider)
}
@@ -275,7 +281,7 @@ class ContentUtils {
return val[1]
}
- public static ContentType recognizeContentTypeFromHeader(Headers headers) {
+ static ContentType recognizeContentTypeFromHeader(Headers headers) {
String content = headers?.entries.find { it.name == "Content-Type" } ?.clientValue?.toString()
if (content?.endsWith("json")) {
return ContentType.JSON
@@ -289,7 +295,7 @@ class ContentUtils {
return ContentType.UNKNOWN
}
- public static MatchingStrategy.Type getEqualsTypeFromContentType(ContentType contentType) {
+ static MatchingStrategy.Type getEqualsTypeFromContentType(ContentType contentType) {
switch (contentType) {
case ContentType.JSON:
return MatchingStrategy.Type.EQUAL_TO_JSON
@@ -299,7 +305,7 @@ class ContentUtils {
return MatchingStrategy.Type.EQUAL_TO
}
- public static ContentType recognizeContentTypeFromContent(GString gstring) {
+ static ContentType recognizeContentTypeFromContent(GString gstring) {
if (isJsonType(gstring)) {
return ContentType.JSON
}
@@ -309,15 +315,15 @@ class ContentUtils {
return ContentType.UNKNOWN
}
- public static ContentType recognizeContentTypeFromContent(Map jsonMap) {
+ static ContentType recognizeContentTypeFromContent(Map jsonMap) {
return ContentType.JSON
}
- public static ContentType recognizeContentTypeFromContent(List jsonList) {
+ static ContentType recognizeContentTypeFromContent(List jsonList) {
return ContentType.JSON
}
- public static ContentType recognizeContentTypeFromContent(String string) {
+ static ContentType recognizeContentTypeFromContent(String string) {
try {
new JsonSlurper().parseText(string)
return ContentType.JSON
@@ -326,11 +332,11 @@ class ContentUtils {
}
}
- public static ContentType recognizeContentTypeFromContent(Object gstring) {
+ static ContentType recognizeContentTypeFromContent(Object gstring) {
return ContentType.UNKNOWN
}
- public static boolean isJsonType(GString gstring) {
+ static boolean isJsonType(GString gstring) {
if (gstring.isEmpty()) {
return false
}
@@ -349,7 +355,7 @@ class ContentUtils {
return false
}
- public static boolean isXmlType(GString gstring) {
+ static boolean isXmlType(GString gstring) {
GString stringWithoutValues = new GStringImpl(
gstring.values.collect({
it instanceof String || it instanceof GString ? it.toString() : escapeXml11(it.toString())
@@ -365,7 +371,7 @@ class ContentUtils {
return false
}
- public static ContentType recognizeContentTypeFromMatchingStrategy(MatchingStrategy.Type type) {
+ static ContentType recognizeContentTypeFromMatchingStrategy(MatchingStrategy.Type type) {
switch (type) {
case MatchingStrategy.Type.EQUAL_TO_XML:
return ContentType.XML
diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy
index 3e5fc06487..0f499ef57a 100644
--- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy
+++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy
@@ -23,7 +23,11 @@ import com.toomuchcoding.jsonassert.JsonAssertion
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.util.logging.Slf4j
-import org.springframework.cloud.contract.spec.internal.*
+import org.springframework.cloud.contract.spec.internal.BodyMatcher
+import org.springframework.cloud.contract.spec.internal.BodyMatchers
+import org.springframework.cloud.contract.spec.internal.ExecutionProperty
+import org.springframework.cloud.contract.spec.internal.MatchingType
+import org.springframework.cloud.contract.spec.internal.OptionalProperty
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import java.util.regex.Pattern
diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MapConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MapConverter.groovy
index 31c5aa1899..6a8d1e3cf0 100644
--- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MapConverter.groovy
+++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/MapConverter.groovy
@@ -27,7 +27,7 @@ import org.springframework.cloud.contract.spec.internal.DslProperty
*
* @author Marcin Grzejszczak
*
- * @since 1.0.0
+ * @since 1.1.0
*/
class MapConverter {
@@ -58,7 +58,7 @@ class MapConverter {
}
} catch (Exception ignore) {
}
- return extractValue(value, closure);
+ return extractValue(value, closure)
} else if (value instanceof Map) {
return convert(value as Map, closure)
} else if (value instanceof List) {
@@ -77,7 +77,7 @@ class MapConverter {
if (newValue instanceof Map || newValue instanceof List || newValue instanceof String && value) {
return transformValues(newValue, closure)
}
- return newValue;
+ return newValue
})
}
diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/RegexpBuilders.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/RegexpBuilders.groovy
index a07700d914..a13924269f 100644
--- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/RegexpBuilders.groovy
+++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/RegexpBuilders.groovy
@@ -23,8 +23,9 @@ import org.springframework.cloud.contract.spec.util.RegexpUtils
import java.util.regex.Pattern
-import static ContentUtils.extractValue
import static org.apache.commons.lang3.StringEscapeUtils.escapeJson
+import static ContentType.*
+import static ContentUtils.extractValue
/**
* Useful utility methods to work with regular expresisons
@@ -105,7 +106,7 @@ class RegexpBuilders {
private final static String WS = /\s*/
static String buildJSONRegexpMatch(GString gString) {
- return buildJSONRegexpMatch(extractValue(gString, ContentType.JSON, { DslProperty dslProperty -> dslProperty.clientValue }))
+ return buildJSONRegexpMatch(extractValue(gString, JSON, { DslProperty dslProperty -> dslProperty.clientValue }))
}
static String buildJSONRegexpMatch(Map jsonMap) {
diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/ContentUtilsSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/ContentUtilsSpec.groovy
new file mode 100644
index 0000000000..98c2229de7
--- /dev/null
+++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/ContentUtilsSpec.groovy
@@ -0,0 +1,25 @@
+package org.springframework.cloud.contract.verifier.util
+
+import org.springframework.cloud.contract.spec.internal.DslProperty
+import org.springframework.cloud.contract.verifier.util.ContentUtils
+import spock.lang.Specification
+
+/**
+ * @author Marcin Grzejszczak
+ */
+class ContentUtilsSpec extends Specification {
+
+ def "should return the stub side"() {
+ given:
+ DslProperty dslProperty = new DslProperty<>("stub", "test")
+ expect:
+ "stub" == ContentUtils.GET_STUB_SIDE(dslProperty)
+ }
+
+ def "should return the test side"() {
+ given:
+ DslProperty dslProperty = new DslProperty<>("stub", "test")
+ expect:
+ "test" == ContentUtils.GET_TEST_SIDE(dslProperty)
+ }
+}