diff --git a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc index 49069facb7..1fc643d0a2 100644 --- a/docs/src/main/asciidoc/spring-cloud-wiremock.adoc +++ b/docs/src/main/asciidoc/spring-cloud-wiremock.adoc @@ -176,12 +176,13 @@ include::{wiremock_tests}/src/test/java/org/springframework/cloud/contract/wirem === Generating Stubs using REST Docs https://projects.spring.io/spring-restdocs[Spring REST Docs] can be used to generate -documentation (for example in Asciidoctor format) for an HTTP API with Spring MockMvc or +documentation (for example in Asciidoctor format) for an HTTP API with Spring MockMvc +or `WebTestClient` or Rest Assured. At the same time that you generate documentation for your API, you can also generate WireMock stubs by using Spring Cloud Contract WireMock. To do so, write your normal REST Docs test cases and use `@AutoConfigureRestDocs` to have stubs be automatically generated in the REST Docs output directory. The following code shows an -example: +example using `MockMvc`: [source,java,indent=0] ---- @@ -204,9 +205,30 @@ public class ApplicationTests { ---- This test generates a WireMock stub at "target/snippets/stubs/resource.json". It matches -all GET requests to the "/resource" path. +all GET requests to the "/resource" path. The same example with `WebTestClient` (used +for testing Spring WebFlux applications) would look like this: -Without any additional configuration, this tests creates a stub with a request matcher +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureRestDocs(outputDir = "target/snippets") +@AutoConfigureWebTestClient +public class ApplicationTests { + + @Autowired + private WebTestClient client; + + @Test + public void contextLoads() throws Exception { + client.get().uri("/resource").exchange() + .expectBody(String.class).isEqualTo("Hello World") + .consumeWith(document("resource")); + } +} +---- + +Without any additional configuration, these tests create a stub with a request matcher for the HTTP method and all headers except "host" and "content-length". To match the request more precisely (for example, to match the body of a POST or PUT), we need to explicitly create a request matcher. Doing so has two effects: @@ -219,6 +241,9 @@ as a substitute for the `document()` convenience method, as shown in the followi example: [source,java,indent=0] + +import static org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocs.verify; + ---- @RunWith(SpringRunner.class) @SpringBootTest @@ -243,7 +268,8 @@ public class ApplicationTests { This contract specifies that any valid POST with an "id" field receives the response defined in this test. You can chain together calls to `.jsonPath()` to add additional matchers. If JSON Path is unfamiliar, The https://github.com/jayway/JsonPath[JayWay -documentation] can help you get up to speed. +documentation] can help you get up to speed. The `WebTestClient` version of this test +has a similar `verify()` static helper that you insert in the same place. Instead of the `jsonPath` and `contentType` convenience methods, you can also use the WireMock APIs to verify that the request matches the created stub, as shown in the diff --git a/samples/standalone/restdocs/http-client/pom.xml b/samples/standalone/restdocs/http-client/pom.xml index 15941c38e3..ae1a5d5051 100644 --- a/samples/standalone/restdocs/http-client/pom.xml +++ b/samples/standalone/restdocs/http-client/pom.xml @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 2.0.0.M6 + 2.0.0.BUILD-SNAPSHOT @@ -96,7 +96,6 @@ maven-deploy-plugin - 2.8.2 true diff --git a/samples/standalone/restdocs/http-server/pom.xml b/samples/standalone/restdocs/http-server/pom.xml index fd51a7329b..36dd63c1e1 100644 --- a/samples/standalone/restdocs/http-server/pom.xml +++ b/samples/standalone/restdocs/http-server/pom.xml @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 2.0.0.M6 + 2.0.0.BUILD-SNAPSHOT @@ -71,7 +71,6 @@ maven-deploy-plugin - 2.8.2 true @@ -79,7 +78,6 @@ org.apache.maven.plugins maven-clean-plugin - 3.0.0 diff --git a/samples/standalone/webclient/http-client/.gitignore b/samples/standalone/webclient/http-client/.gitignore new file mode 100644 index 0000000000..a275b7b864 --- /dev/null +++ b/samples/standalone/webclient/http-client/.gitignore @@ -0,0 +1,7 @@ + +target/ + +.gradle +build/ + +/.apt_generated/ diff --git a/samples/standalone/webclient/http-client/.mvn/jvm.config b/samples/standalone/webclient/http-client/.mvn/jvm.config new file mode 100644 index 0000000000..894bef17a5 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/.mvn/maven.config b/samples/standalone/webclient/http-client/.mvn/maven.config new file mode 100644 index 0000000000..affad39a42 --- /dev/null +++ b/samples/standalone/webclient/http-client/.mvn/maven.config @@ -0,0 +1 @@ +-T2 \ No newline at end of file diff --git a/samples/standalone/webclient/http-client/.mvn/wrapper/maven-wrapper.jar b/samples/standalone/webclient/http-client/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000..c6feb8bb6f Binary files /dev/null and b/samples/standalone/webclient/http-client/.mvn/wrapper/maven-wrapper.jar differ diff --git a/samples/standalone/webclient/http-client/.mvn/wrapper/maven-wrapper.properties b/samples/standalone/webclient/http-client/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..6637cedb28 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/README.adoc b/samples/standalone/webclient/http-client/README.adoc new file mode 100644 index 0000000000..cdcb0e4a56 --- /dev/null +++ b/samples/standalone/webclient/http-client/README.adoc @@ -0,0 +1,26 @@ += Http Client + +== Prerequisites + +First you have to publish to Maven Local the stubs of the *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 `Http Server` +- run the tests against stubbed server \ No newline at end of file diff --git a/samples/standalone/webclient/http-client/build.gradle b/samples/standalone/webclient/http-client/build.gradle new file mode 100644 index 0000000000..bb76c36a03 --- /dev/null +++ b/samples/standalone/webclient/http-client/build.gradle @@ -0,0 +1,69 @@ +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:2.0.0.M6" + } +} + +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: 'org.springframework.boot' +apply plugin: 'io.spring.dependency-management' +apply plugin: 'maven-publish' + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:$BOM_VERSION" + mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:${project.findProperty('verifierVersion') ?: verifierVersion}" + } +} + +dependencies { + compile("org.springframework.boot:spring-boot-starter-web") + compile("org.springframework.boot:spring-boot-starter-actuator") + + testCompile 'org.springframework.cloud:spring-cloud-contract-wiremock' + testCompile 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner' + testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "com.example:http-server-restdocs:0.0.1-SNAPSHOT:stubs" +} + +test { + systemProperty 'spring.profiles.active', 'gradle' + testLogging { + exceptionFormat = 'full' + } +} + +task wrapper(type: Wrapper) { + gradleVersion = '4.0.2' +} + +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/webclient/http-client/gradle.properties b/samples/standalone/webclient/http-client/gradle.properties new file mode 100644 index 0000000000..cb360cc45f --- /dev/null +++ b/samples/standalone/webclient/http-client/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.daemon=false +verifierVersion=2.0.0.BUILD-SNAPSHOT +BOM_VERSION=Finchley.BUILD-SNAPSHOT \ No newline at end of file diff --git a/samples/standalone/webclient/http-client/gradle/wrapper/gradle-wrapper.jar b/samples/standalone/webclient/http-client/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..96d36c23c8 Binary files /dev/null and b/samples/standalone/webclient/http-client/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/standalone/webclient/http-client/gradle/wrapper/gradle-wrapper.properties b/samples/standalone/webclient/http-client/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..64ffcab3bf --- /dev/null +++ b/samples/standalone/webclient/http-client/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jul 31 11:13:36 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.2-bin.zip diff --git a/samples/standalone/webclient/http-client/gradlew b/samples/standalone/webclient/http-client/gradlew new file mode 100755 index 0000000000..4453ccea33 --- /dev/null +++ b/samples/standalone/webclient/http-client/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## 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 + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/samples/standalone/webclient/http-client/gradlew.bat b/samples/standalone/webclient/http-client/gradlew.bat new file mode 100644 index 0000000000..e95643d6a2 --- /dev/null +++ b/samples/standalone/webclient/http-client/gradlew.bat @@ -0,0 +1,84 @@ +@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 + +: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=%* + +: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/webclient/http-client/mvnw b/samples/standalone/webclient/http-client/mvnw new file mode 100755 index 0000000000..fc7efd17d0 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/mvnw.cmd b/samples/standalone/webclient/http-client/mvnw.cmd new file mode 100644 index 0000000000..001048081d --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/pom.xml b/samples/standalone/webclient/http-client/pom.xml new file mode 100644 index 0000000000..13bfad44ea --- /dev/null +++ b/samples/standalone/webclient/http-client/pom.xml @@ -0,0 +1,222 @@ + + + 4.0.0 + + com.example + http-client-webclient + 0.0.1-SNAPSHOT + + Spring Cloud Contract RestDocs Http Client Sample + Spring Cloud Contract RestDocs Http Client Sample + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + 1.8 + 2.0.0.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-contract-wiremock + test + + + org.springframework.cloud + spring-cloud-starter-contract-stub-runner + test + + + com.example + http-server-webclient + stubs + 0.0.1-SNAPSHOT + test + + + * + * + + + + + + + org.springframework.boot + spring-boot-starter-activemq + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + org.springframework.cloud + spring-cloud-contract-dependencies + ${spring-cloud-contract.version} + pom + import + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + maven-deploy-plugin + + true + + + + + + + + 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 + 1.6.0 + + + gradle + test + + ./gradlew + + clean + build + publishToMavenLocal + -PverifierVersion=${spring-cloud-contract.version} + + + + exec + + + + + + + + + windows + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + gradle + test + + ./gradlew + + clean + build + publishToMavenLocal + -PverifierVersion=${spring-cloud-contract.version} + + + + exec + + + + + + + + + + diff --git a/samples/standalone/webclient/http-client/settings.gradle b/samples/standalone/webclient/http-client/settings.gradle new file mode 100644 index 0000000000..da0111bebb --- /dev/null +++ b/samples/standalone/webclient/http-client/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'http-client-webclient-gradle' diff --git a/samples/standalone/webclient/http-client/src/main/java/com/example/loan/Application.java b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/Application.java new file mode 100644 index 0000000000..6f36301172 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/src/main/java/com/example/loan/LoanApplicationService.java b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/LoanApplicationService.java new file mode 100644 index 0000000000..6c5d0861f5 --- /dev/null +++ b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/LoanApplicationService.java @@ -0,0 +1,73 @@ +package com.example.loan; + +import org.springframework.boot.context.properties.ConfigurationProperties; +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; + +@Service +@ConfigurationProperties("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 = 8080; + + public LoanApplicationService() { + this.restTemplate = new RestTemplate(); + } + + 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 void setPort(int port) { + this.port = port; + } + +} diff --git a/samples/standalone/webclient/http-client/src/main/java/com/example/loan/model/Client.java b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/model/Client.java new file mode 100644 index 0000000000..abf66106b5 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/src/main/java/com/example/loan/model/FraudCheckStatus.java b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/model/FraudCheckStatus.java new file mode 100644 index 0000000000..c5217ef471 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/src/main/java/com/example/loan/model/FraudServiceRequest.java b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/model/FraudServiceRequest.java new file mode 100644 index 0000000000..0230540131 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/src/main/java/com/example/loan/model/FraudServiceResponse.java b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/model/FraudServiceResponse.java new file mode 100644 index 0000000000..50e64478b9 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/src/main/java/com/example/loan/model/LoanApplication.java b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/model/LoanApplication.java new file mode 100644 index 0000000000..27d66f884c --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/src/main/java/com/example/loan/model/LoanApplicationResult.java b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/model/LoanApplicationResult.java new file mode 100644 index 0000000000..ffd44e1a26 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/src/main/java/com/example/loan/model/LoanApplicationStatus.java b/samples/standalone/webclient/http-client/src/main/java/com/example/loan/model/LoanApplicationStatus.java new file mode 100644 index 0000000000..34cf91a89f --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-client/src/main/resources/application.yml b/samples/standalone/webclient/http-client/src/main/resources/application.yml new file mode 100644 index 0000000000..056c802c77 --- /dev/null +++ b/samples/standalone/webclient/http-client/src/main/resources/application.yml @@ -0,0 +1,5 @@ +server.port: 8097 +logging: + level: + org.springframework.web.client: debug + com.github.tomakehurst.wiremock: trace \ No newline at end of file diff --git a/samples/standalone/webclient/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java b/samples/standalone/webclient/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java new file mode 100644 index 0000000000..8f6ae95c58 --- /dev/null +++ b/samples/standalone/webclient/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java @@ -0,0 +1,71 @@ +package com.example.loan; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.charset.Charset; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.core.io.Resource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.StreamUtils; + +import com.example.loan.model.Client; +import com.example.loan.model.LoanApplication; +import com.example.loan.model.LoanApplicationResult; +import com.example.loan.model.LoanApplicationStatus; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; + +@RunWith(SpringRunner.class) +@SpringBootTest(properties="service.port=${wiremock.server.port}") +@AutoConfigureWireMock(port=0) +public class LoanApplicationServiceTests { + + @Autowired + private LoanApplicationService service; + + @Value("classpath:META-INF/com.example/http-server-webclient/0.0.1-SNAPSHOT/mappings/markClientAsFraud.json") + private Resource markClientAsFraud; + + @Value("classpath:META-INF/com.example/http-server-webclient/0.0.1-SNAPSHOT/mappings/markClientAsNotFraud.json") + private Resource markClientAsNotFraud; + + @Autowired + private WireMockServer server; + + @Test + public void shouldSuccessfullyApplyForLoan() throws Exception { + server.addStubMapping(StubMapping.buildFrom(StreamUtils.copyToString( + markClientAsNotFraud.getInputStream(), Charset.forName("UTF-8")))); + // 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() throws Exception { + server.addStubMapping(StubMapping.buildFrom(StreamUtils.copyToString( + markClientAsFraud.getInputStream(), Charset.forName("UTF-8")))); + // 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"); + } + +} diff --git a/samples/standalone/webclient/http-client/src/test/java/com/example/loan/LoanApplicationServiceusingStubRunnerTests.java b/samples/standalone/webclient/http-client/src/test/java/com/example/loan/LoanApplicationServiceusingStubRunnerTests.java new file mode 100644 index 0000000000..86f86af257 --- /dev/null +++ b/samples/standalone/webclient/http-client/src/test/java/com/example/loan/LoanApplicationServiceusingStubRunnerTests.java @@ -0,0 +1,58 @@ +package com.example.loan; + +import com.example.loan.model.Client; +import com.example.loan.model.LoanApplication; +import com.example.loan.model.LoanApplicationResult; +import com.example.loan.model.LoanApplicationStatus; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureStubRunner(ids = "com.example:http-server-webclient") +public class LoanApplicationServiceusingStubRunnerTests { + + @Autowired LoanApplicationService service; + @Value("${stubrunner.runningstubs.http-server-webclient.port}") int port; + + @Before + public void setup() { + this.service.setPort(this.port); + } + + @Test + public void shouldSuccessfullyApplyForLoan() throws Exception { + // 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() throws Exception { + // 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"); + } + +} diff --git a/samples/standalone/webclient/http-server/.gitignore b/samples/standalone/webclient/http-server/.gitignore new file mode 100644 index 0000000000..b05ba7bd6d --- /dev/null +++ b/samples/standalone/webclient/http-server/.gitignore @@ -0,0 +1,6 @@ + +target/ + +.gradle +build/ + diff --git a/samples/standalone/webclient/http-server/.mvn/jvm.config b/samples/standalone/webclient/http-server/.mvn/jvm.config new file mode 100644 index 0000000000..894bef17a5 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-server/.mvn/maven.config b/samples/standalone/webclient/http-server/.mvn/maven.config new file mode 100644 index 0000000000..7681bc67b9 --- /dev/null +++ b/samples/standalone/webclient/http-server/.mvn/maven.config @@ -0,0 +1 @@ +-T2 diff --git a/samples/standalone/webclient/http-server/.mvn/wrapper/maven-wrapper.jar b/samples/standalone/webclient/http-server/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000..c6feb8bb6f Binary files /dev/null and b/samples/standalone/webclient/http-server/.mvn/wrapper/maven-wrapper.jar differ diff --git a/samples/standalone/webclient/http-server/.mvn/wrapper/maven-wrapper.properties b/samples/standalone/webclient/http-server/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..6637cedb28 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-server/README.adoc b/samples/standalone/webclient/http-server/README.adoc new file mode 100644 index 0000000000..2fd3fbc40d --- /dev/null +++ b/samples/standalone/webclient/http-server/README.adoc @@ -0,0 +1,20 @@ += 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/webclient/http-server/build.gradle b/samples/standalone/webclient/http-server/build.gradle new file mode 100644 index 0000000000..59426bb7ea --- /dev/null +++ b/samples/standalone/webclient/http-server/build.gradle @@ -0,0 +1,104 @@ +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" } + maven { url 'http://repo.spring.io/plugins-snapshot' } + maven { url "http://repo.spring.io/plugins-release-local" } + maven { url "http://repo.spring.io/plugins-staging-local/" } + } + dependencies { + classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.M6" + } +} + +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: 'org.springframework.boot' +apply plugin: 'io.spring.dependency-management' +apply plugin: 'maven-publish' +apply plugin: 'maven' + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:$BOM_VERSION" + mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:${project.findProperty('verifierVersion') ?: verifierVersion}" + } +} + +dependencies { + compile("org.springframework.boot:spring-boot-starter-web") + compile("org.springframework.boot:spring-boot-starter-actuator") + + testCompile 'org.springframework.boot:spring-boot-starter-test' + testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc' + testCompile 'org.springframework.cloud:spring-cloud-contract-wiremock' +} + +test { + systemProperty 'spring.profiles.active', 'gradle' + testLogging { + exceptionFormat = 'full' + } +} + +task wrapper(type: Wrapper) { + gradleVersion = '4.0.2' +} + +task stubsJar(type: Jar, dependsOn: ['copySnippets', 'copySources', 'copyClasses']) { + baseName = project.name + classifier = 'stubs' + from project.file("${project.buildDir}/stubs") +} + +artifacts { + archives stubsJar +} + +task copySnippets(type: Copy, dependsOn: test) { + from "target/snippets/stubs" + into "${project.buildDir}/stubs/META-INF/${project.group}/${project.name}/${project.version}/mappings" +} + +task copySources(type: Copy) { + from "src/main/java" + include '**/model/Fraud*.*' + into "${project.buildDir}/stubs/" +} + +task copyClasses(type: Copy) { + from "${project.buildDir}/classes/main/" + include '**/model/Fraud*.*' + into "${project.buildDir}/stubs/" +} + +clean.doFirst { + delete 'target/snippets/stubs' + delete "~/.m2/repository/com/example/http-server-restdocs-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/webclient/http-server/gradle.properties b/samples/standalone/webclient/http-server/gradle.properties new file mode 100644 index 0000000000..cb360cc45f --- /dev/null +++ b/samples/standalone/webclient/http-server/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.daemon=false +verifierVersion=2.0.0.BUILD-SNAPSHOT +BOM_VERSION=Finchley.BUILD-SNAPSHOT \ No newline at end of file diff --git a/samples/standalone/webclient/http-server/gradle/wrapper/gradle-wrapper.jar b/samples/standalone/webclient/http-server/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..96d36c23c8 Binary files /dev/null and b/samples/standalone/webclient/http-server/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/standalone/webclient/http-server/gradle/wrapper/gradle-wrapper.properties b/samples/standalone/webclient/http-server/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..64ffcab3bf --- /dev/null +++ b/samples/standalone/webclient/http-server/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jul 31 11:13:36 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.2-bin.zip diff --git a/samples/standalone/webclient/http-server/gradlew b/samples/standalone/webclient/http-server/gradlew new file mode 100755 index 0000000000..4453ccea33 --- /dev/null +++ b/samples/standalone/webclient/http-server/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## 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 + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/samples/standalone/webclient/http-server/gradlew.bat b/samples/standalone/webclient/http-server/gradlew.bat new file mode 100644 index 0000000000..e95643d6a2 --- /dev/null +++ b/samples/standalone/webclient/http-server/gradlew.bat @@ -0,0 +1,84 @@ +@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 + +: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=%* + +: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/webclient/http-server/mvnw b/samples/standalone/webclient/http-server/mvnw new file mode 100755 index 0000000000..fc7efd17d0 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-server/mvnw.cmd b/samples/standalone/webclient/http-server/mvnw.cmd new file mode 100644 index 0000000000..001048081d --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-server/pom.xml b/samples/standalone/webclient/http-server/pom.xml new file mode 100644 index 0000000000..8f845cc49a --- /dev/null +++ b/samples/standalone/webclient/http-server/pom.xml @@ -0,0 +1,232 @@ + + + 4.0.0 + + com.example + http-server-webclient + 0.0.1-SNAPSHOT + + Spring Cloud Contract RestDocs Http Server Sample + Spring Cloud RestDocs Verifier Http Server Sample + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + 1.8 + 2.0.0.BUILD-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.restdocs + spring-restdocs-webtestclient + test + + + org.springframework.cloud + spring-cloud-contract-wiremock + test + + + + + + + org.springframework.cloud + spring-cloud-contract-dependencies + ${spring-cloud-contract.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-clean-plugin + + + + build + + + target + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + stub + prepare-package + + single + + false + + true + + ${basedir}/src/assembly/stub.xml + + + + + + + + + + + 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 + 1.6.0 + + + gradle + test + + ./gradlew + + clean + build + + install + -PverifierVersion=${spring-cloud-contract.version} + + + + exec + + + + + + + + + windows + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + gradle + test + + gradlew.bat + + clean + build + + install + -PverifierVersion=${spring-cloud-contract.version} + + + + exec + + + + + + + + + diff --git a/samples/standalone/webclient/http-server/settings.gradle b/samples/standalone/webclient/http-server/settings.gradle new file mode 100644 index 0000000000..2cc07be60c --- /dev/null +++ b/samples/standalone/webclient/http-server/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'http-server-webclient-gradle' diff --git a/samples/standalone/webclient/http-server/src/assembly/stub.xml b/samples/standalone/webclient/http-server/src/assembly/stub.xml new file mode 100644 index 0000000000..914f265c70 --- /dev/null +++ b/samples/standalone/webclient/http-server/src/assembly/stub.xml @@ -0,0 +1,33 @@ + + stubs + + jar + + false + + + src/main/java + / + + **/model/Fraud*.* + + + + ${project.build.directory}/classes + / + + **/model/Fraud*.* + + + + ${project.build.directory}/snippets/stubs + META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings + + **/* + + + + \ No newline at end of file diff --git a/samples/standalone/webclient/http-server/src/main/java/com/example/fraud/Application.java b/samples/standalone/webclient/http-server/src/main/java/com/example/fraud/Application.java new file mode 100644 index 0000000000..9117009b09 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-server/src/main/java/com/example/fraud/FraudDetectionController.java b/samples/standalone/webclient/http-server/src/main/java/com/example/fraud/FraudDetectionController.java new file mode 100644 index 0000000000..82ae6bee85 --- /dev/null +++ b/samples/standalone/webclient/http-server/src/main/java/com/example/fraud/FraudDetectionController.java @@ -0,0 +1,45 @@ +package com.example.fraud; + +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; + +import java.math.BigDecimal; + +import static org.springframework.web.bind.annotation.RequestMethod.PUT; + +@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"); + + // tag::server_api[] + @RequestMapping( + value = "/fraudcheck", + method = PUT, + consumes = FRAUD_SERVICE_JSON_VERSION_1, + produces = FRAUD_SERVICE_JSON_VERSION_1) + public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) { + // end::server_api[] + // tag::new_impl[] + if (amountGreaterThanThreshold(fraudCheck)) { + return new FraudCheckResult(FraudCheckStatus.FRAUD, AMOUNT_TOO_HIGH); + } + // end::new_impl[] + // tag::initial_impl[] + return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON); + // end::initial_impl[] + } + + private boolean amountGreaterThanThreshold(FraudCheck fraudCheck) { + return MAX_AMOUNT.compareTo(fraudCheck.getLoanAmount()) < 0; + } + +} diff --git a/samples/standalone/webclient/http-server/src/main/java/com/example/fraud/model/FraudCheck.java b/samples/standalone/webclient/http-server/src/main/java/com/example/fraud/model/FraudCheck.java new file mode 100644 index 0000000000..b0210573e2 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-server/src/main/java/com/example/fraud/model/FraudCheckResult.java b/samples/standalone/webclient/http-server/src/main/java/com/example/fraud/model/FraudCheckResult.java new file mode 100644 index 0000000000..60b340276a --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-server/src/main/java/com/example/fraud/model/FraudCheckStatus.java b/samples/standalone/webclient/http-server/src/main/java/com/example/fraud/model/FraudCheckStatus.java new file mode 100644 index 0000000000..5a6adcf274 --- /dev/null +++ b/samples/standalone/webclient/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/webclient/http-server/src/main/resources/application.yml b/samples/standalone/webclient/http-server/src/main/resources/application.yml new file mode 100644 index 0000000000..1c421cf2b7 --- /dev/null +++ b/samples/standalone/webclient/http-server/src/main/resources/application.yml @@ -0,0 +1 @@ +server.port=0 \ No newline at end of file diff --git a/samples/standalone/webclient/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java b/samples/standalone/webclient/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java new file mode 100644 index 0000000000..5363234e1d --- /dev/null +++ b/samples/standalone/webclient/http-server/src/test/java/com/example/fraud/StubGeneratorTests.java @@ -0,0 +1,78 @@ +package com.example.fraud; + +import java.math.BigDecimal; + +import com.example.fraud.model.FraudCheck; +import com.fasterxml.jackson.databind.ObjectMapper; + +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.autoconfigure.json.AutoConfigureJsonTesters; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.BodyInserters; + +import static org.springframework.cloud.contract.wiremock.restdocs.WireMockWebTestClient.verify; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class) +@AutoConfigureRestDocs(outputDir = "target/snippets") +@AutoConfigureWebTestClient +@AutoConfigureJsonTesters +@DirtiesContext +public class StubGeneratorTests { + + @Autowired + private WebTestClient client; + + private JacksonTester json; + + @Before + public void setup() { + ObjectMapper objectMappper = new ObjectMapper(); + // Possibly configure the mapper + JacksonTester.initFields(this, objectMappper); + } + + @Test + public void shouldMarkClientAsFraud() throws Exception { + FraudCheck fraudCheck = new FraudCheck(); + fraudCheck.setClientId("1234567890"); + fraudCheck.setLoanAmount(BigDecimal.valueOf(99999.0)); + client.put().uri("/fraudcheck") + .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) + .body(BodyInserters.fromObject(json.write(fraudCheck).getJson())) + .exchange().expectBody().jsonPath("$.fraudCheckStatus").isEqualTo("FRAUD") + .jsonPath("$.rejectionReason").isEqualTo("Amount too high") + .consumeWith(verify().jsonPath("$.clientId") + .jsonPath("$[?(@.loanAmount > 1000)]") + .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) + .stub("markClientAsFraud")); + } + + @Test + public void shouldMarkClientAsNotFraud() throws Exception { + FraudCheck fraudCheck = new FraudCheck(); + fraudCheck.setClientId("1234567890"); + fraudCheck.setLoanAmount(BigDecimal.valueOf(123.123)); + client.put().uri("/fraudcheck") + .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) + .body(BodyInserters.fromObject(json.write(fraudCheck).getJson())) + .exchange().expectBody().jsonPath("$.fraudCheckStatus").isEqualTo("OK") + .jsonPath("$.rejectionReason").doesNotExist() + .consumeWith(verify().jsonPath("$.clientId") + .jsonPath("$[?(@.loanAmount <= 1000)]") + .contentType(MediaType.valueOf("application/vnd.fraud.v1+json")) + .stub("markClientAsNotFraud")); + } + +} \ No newline at end of file diff --git a/samples/standalone/webclient/pom.xml b/samples/standalone/webclient/pom.xml new file mode 100644 index 0000000000..91c397521a --- /dev/null +++ b/samples/standalone/webclient/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + + org.springframework.cloud + spring-cloud-contract-samples-standalone + 2.0.0.BUILD-SNAPSHOT + .. + + + spring-cloud-contract-samples-webclient + pom + + Spring Cloud Contract Standalone Restdocs Test Samples + Spring Cloud Contract Standalone Test Samples used for end to end tests + + + 2.0.0.BUILD-SNAPSHOT + + + + http-server + http-client + + + + + + maven-deploy-plugin + 2.8.2 + + true + + + + + + diff --git a/spring-cloud-contract-wiremock/pom.xml b/spring-cloud-contract-wiremock/pom.xml index dde0ef9318..b9b983d31b 100644 --- a/spring-cloud-contract-wiremock/pom.xml +++ b/spring-cloud-contract-wiremock/pom.xml @@ -40,6 +40,11 @@ spring-boot-starter-tomcat test + + org.springframework.boot + spring-boot-starter-webflux + test + org.springframework spring-web @@ -49,6 +54,11 @@ spring-restdocs-mockmvc true + + org.springframework.restdocs + spring-restdocs-webtestclient + true + org.springframework.restdocs spring-restdocs-restassured diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractExchangeHandler.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractExchangeHandler.java new file mode 100644 index 0000000000..8f0d67a42c --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractExchangeHandler.java @@ -0,0 +1,230 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.contract.wiremock.restdocs; + +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.http.ContentTypeHeader; +import com.github.tomakehurst.wiremock.http.Cookie; +import com.github.tomakehurst.wiremock.http.HttpHeader; +import com.github.tomakehurst.wiremock.http.QueryParameter; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.http.RequestMethod; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation; +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer; +import org.springframework.test.web.reactive.server.EntityExchangeResult; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import wiremock.com.google.common.base.Optional; +import wiremock.org.apache.commons.codec.binary.Base64; + +/** + * @author Dave Syer + * + */ +public class ContractExchangeHandler extends + WireMockVerifyHelper, ContractExchangeHandler> + implements Consumer> { + + @Override + public void accept(EntityExchangeResult result) { + configure(result); + WebTestClientRestDocumentation.document(getName()).accept(result); + } + + @Override + protected ResponseDefinitionBuilder getResponseDefinition( + EntityExchangeResult result) { + ResponseDefinitionBuilder definition = ResponseDefinitionBuilder + .responseDefinition().withBody(result.getResponseBodyContent()) + .withStatus(result.getStatus().value()); + addResponseHeaders(definition, result.getResponseHeaders()); + return definition; + } + + private void addResponseHeaders(ResponseDefinitionBuilder definition, + HttpHeaders httpHeaders) { + for (String name : httpHeaders.keySet()) { + definition.withHeader(name, httpHeaders.get(name).toArray(new String[0])); + } + } + + @Override + protected Map getConfiguration(EntityExchangeResult result) { + Field field = ReflectionUtils.findField( + WebTestClientRestDocumentationConfigurer.class, "configurations"); + ReflectionUtils.makeAccessible(field); + String index = result.getRequestHeaders() + .getFirst(WebTestClient.WEBTESTCLIENT_REQUEST_ID); + @SuppressWarnings("unchecked") + Map map = (((Map>) ReflectionUtils + .getField(field, null)).get(index)); + return map; + } + + @Override + protected Request getWireMockRequest(EntityExchangeResult result) { + return new WireMockHttpRequestAdapter(result); + } + + @Override + protected MediaType getContentType(EntityExchangeResult result) { + return result.getRequestHeaders().getContentType(); + } + + @Override + protected byte[] getRequestBodyContent(EntityExchangeResult result) { + return result.getRequestBodyContent(); + } + +} + +class WireMockHttpRequestAdapter implements Request { + + private EntityExchangeResult result; + + public WireMockHttpRequestAdapter(EntityExchangeResult result) { + this.result = result; + } + + @Override + public String getUrl() { + return this.result.getUrl().getRawPath(); + } + + @Override + public String getAbsoluteUrl() { + return this.result.getUrl().toString(); + } + + @Override + public RequestMethod getMethod() { + return new RequestMethod(this.result.getMethod().name()); + } + + @Override + public String getClientIp() { + return "127.0.0.1"; + } + + @Override + public String getHeader(String key) { + HttpHeaders headers = this.result.getRequestHeaders(); + return headers.containsKey(key) ? headers.getFirst(key) : null; + } + + @Override + public HttpHeader header(String key) { + HttpHeaders headers = this.result.getRequestHeaders(); + return headers.containsKey(key) + ? new HttpHeader(key, headers.getValuesAsList(key)) + : null; + } + + @Override + public ContentTypeHeader contentTypeHeader() { + MediaType contentType = this.result.getRequestHeaders().getContentType(); + if (contentType == null) { + return null; + } + return new ContentTypeHeader(contentType.toString()); + } + + @Override + public com.github.tomakehurst.wiremock.http.HttpHeaders getHeaders() { + com.github.tomakehurst.wiremock.http.HttpHeaders target = new com.github.tomakehurst.wiremock.http.HttpHeaders(); + HttpHeaders headers = this.result.getRequestHeaders(); + for (String key : headers.keySet()) { + target = target.plus(new HttpHeader(key, headers.getValuesAsList(key))); + } + return target; + } + + @Override + public boolean containsHeader(String key) { + return this.result.getRequestHeaders().containsKey(key); + } + + @Override + public Set getAllHeaderKeys() { + return this.result.getRequestHeaders().keySet(); + } + + @Override + public Map getCookies() { + return new LinkedHashMap<>(); + } + + @Override + public QueryParameter queryParameter(String key) { + String query = this.result.getUrl().getRawQuery(); + if (query == null) { + return null; + } + List values = new ArrayList<>(); + for (String name : StringUtils.split(query, "&")) { + if (name.equals(key)) { + values.add(""); + } + else if (name.startsWith(key + "=")) { + values.add(name.substring(name.indexOf("=") + 1)); + } + } + if (values.isEmpty()) { + return null; + } + return new QueryParameter(key, values); + } + + @Override + public byte[] getBody() { + return this.result.getRequestBodyContent(); + } + + @Override + public String getBodyAsString() { + return new String(this.result.getRequestBodyContent(), Charset.forName("UTF-8")); + } + + @Override + public String getBodyAsBase64() { + return Base64.encodeBase64String(this.result.getRequestBodyContent()); + } + + @Override + public boolean isBrowserProxyRequest() { + return false; + } + + @Override + public Optional getOriginalRequest() { + return Optional.absent(); + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractRequestHandler.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractRequestHandler.java deleted file mode 100644 index c6f312eb71..0000000000 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractRequestHandler.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2012-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.contract.wiremock.restdocs; - -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultHandler; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StreamUtils; -import org.springframework.util.StringUtils; - -import com.github.tomakehurst.wiremock.client.MappingBuilder; -import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; -import com.github.tomakehurst.wiremock.matching.MatchResult; -import com.github.tomakehurst.wiremock.servlet.WireMockHttpServletRequestAdapter; -import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.jayway.jsonpath.JsonPath; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ContractRequestHandler implements ResultHandler { - - static final String ATTRIBUTE_NAME_CONFIGURATION = "org.springframework.restdocs.configuration"; - - private Map jsonPaths = new LinkedHashMap<>(); - private MediaType contentType; - private String name; - - private MappingBuilder builder; - - public ContractRequestHandler() { - } - - public ResultHandler stub(String name) { - this.name = name; - // TODO: try and get access to the internals of this so we don't need to store - // state in the snippet - return this; - } - - @Override - public void handle(MvcResult result) throws Exception { - MockHttpServletRequest request = result.getRequest(); - Map configuration = getConfiguration(result); - String actual = StreamUtils.copyToString(request.getInputStream(), - Charset.forName("UTF-8")); - for (JsonPath jsonPath : this.jsonPaths.values()) { - new JsonPathValue(jsonPath, actual).assertHasValue(Object.class, "an object"); - } - configuration.put("contract.jsonPaths", this.jsonPaths.keySet()); - if (this.contentType != null) { - configuration.put("contract.contentType", this.contentType); - String resultType = request.getContentType(); - assertThat(resultType).isNotNull().as("no content type"); - assertThat(this.contentType.includes(MediaType.valueOf(resultType))).isTrue() - .as("content type did not match"); - } - if (this.builder != null) { - this.builder.willReturn(getResponseDefinition(result)); - StubMapping stubMapping = this.builder.build(); - MatchResult match = stubMapping.getRequest() - .match(new WireMockHttpServletRequestAdapter(request)); - assertThat(match.isExactMatch()).as("wiremock did not match request").isTrue(); - configuration.put("contract.stubMapping", stubMapping); - } - MockMvcRestDocumentation.document(this.name).handle(result); - } - - private ResponseDefinitionBuilder getResponseDefinition(MvcResult result) - throws UnsupportedEncodingException { - MockHttpServletResponse response = result.getResponse(); - ResponseDefinitionBuilder definition = ResponseDefinitionBuilder - .responseDefinition().withBody(response.getContentAsString()) - .withStatus(response.getStatus()); - addResponseHeaders(definition, response); - return definition; - } - - private void addResponseHeaders(ResponseDefinitionBuilder definition, - MockHttpServletResponse input) { - for (String name : input.getHeaderNames()) { - definition.withHeader(name, input.getHeader(name)); - } - } - - private Map getConfiguration(MvcResult result) { - @SuppressWarnings("unchecked") - Map map = (Map) result.getRequest() - .getAttribute(ATTRIBUTE_NAME_CONFIGURATION); - if (map == null) { - map = new HashMap<>(); - result.getRequest().setAttribute(ATTRIBUTE_NAME_CONFIGURATION, map); - } - return map; - } - - public ContractRequestHandler wiremock(MappingBuilder builder) { - this.builder = builder; - return this; - } - - public ContractRequestHandler jsonPath(String expression, Object... args) { - compile(expression, args); - return this; - } - - public ContractRequestHandler contentType(MediaType contentType) { - this.contentType = contentType; - return this; - } - - private void compile(String expression, Object... args) { - org.springframework.util.Assert.hasText( - (expression == null ? null : expression), - "expression must not be null or empty"); - expression = String.format(expression, args); - this.jsonPaths.put(expression, JsonPath.compile(expression)); - } - -} - -class JsonPathValue { - - private final JsonPath jsonPath; - private final String expression; - private final CharSequence actual; - - JsonPathValue(JsonPath jsonPath, CharSequence actual) { - this.jsonPath = jsonPath; - this.actual = actual; - this.expression = jsonPath.getPath(); - } - - public void assertHasValue(Class type, String expectedDescription) { - Object value = getValue(true); - if (value == null || isIndefiniteAndEmpty()) { - throw new AssertionError(getNoValueMessage()); - } - if (type != null && !type.isInstance(value)) { - throw new AssertionError(getExpectedValueMessage(expectedDescription)); - } - } - - private boolean isIndefiniteAndEmpty() { - return !isDefinite() && isEmpty(); - } - - private boolean isDefinite() { - return this.jsonPath.isDefinite(); - } - - private boolean isEmpty() { - return ObjectUtils.isEmpty(getValue(false)); - } - - public Object getValue(boolean required) { - try { - CharSequence json = this.actual; - return this.jsonPath.read(json == null ? null : json.toString()); - } - catch (Exception ex) { - if (!required) { - return null; - } - throw new AssertionError(getNoValueMessage() + ". " + ex.getMessage()); - } - } - - private String getNoValueMessage() { - return "No value at JSON path \"" + this.expression + "\""; - } - - private String getExpectedValueMessage(String expectedDescription) { - return String.format("Expected %s at JSON path \"%s\" but found: %s", - expectedDescription, this.expression, - ObjectUtils.nullSafeToString(StringUtils.quoteIfString(getValue(false)))); - } - -} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractResultHandler.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractResultHandler.java new file mode 100644 index 0000000000..db1f8cbb4b --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/ContractResultHandler.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.wiremock.restdocs; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.servlet.WireMockHttpServletRequestAdapter; + +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultHandler; +import org.springframework.util.StreamUtils; + +public class ContractResultHandler extends + WireMockVerifyHelper implements ResultHandler { + + static final String ATTRIBUTE_NAME_CONFIGURATION = "org.springframework.restdocs.configuration"; + + @Override + public void handle(MvcResult result) throws Exception { + configure(result); + MockMvcRestDocumentation.document(getName()).handle(result); + } + + @Override + protected ResponseDefinitionBuilder getResponseDefinition(MvcResult result) { + MockHttpServletResponse response = result.getResponse(); + ResponseDefinitionBuilder definition; + try { + definition = ResponseDefinitionBuilder.responseDefinition() + .withBody(response.getContentAsString()) + .withStatus(response.getStatus()); + addResponseHeaders(definition, response); + return definition; + } + catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Cannot create response body", e); + } + } + + private void addResponseHeaders(ResponseDefinitionBuilder definition, + MockHttpServletResponse input) { + for (String name : input.getHeaderNames()) { + definition.withHeader(name, input.getHeader(name)); + } + } + + @Override + protected Map getConfiguration(MvcResult result) { + @SuppressWarnings("unchecked") + Map map = (Map) result.getRequest() + .getAttribute(ATTRIBUTE_NAME_CONFIGURATION); + if (map == null) { + map = new HashMap<>(); + result.getRequest().setAttribute(ATTRIBUTE_NAME_CONFIGURATION, map); + } + return map; + } + + @Override + protected Request getWireMockRequest(MvcResult result) { + return new WireMockHttpServletRequestAdapter(result.getRequest()); + } + + @Override + protected MediaType getContentType(MvcResult result) { + return MediaType.valueOf(result.getRequest().getContentType()); + } + + @Override + protected byte[] getRequestBodyContent(MvcResult result) { + try { + return StreamUtils.copyToByteArray(result.getRequest().getInputStream()); + } + catch (IOException e) { + throw new IllegalStateException("Cannot create request body", e); + } + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/JsonPathValue.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/JsonPathValue.java new file mode 100644 index 0000000000..8bab373c5f --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/JsonPathValue.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.contract.wiremock.restdocs; + +import com.jayway.jsonpath.JsonPath; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +class JsonPathValue { + + private final JsonPath jsonPath; + private final String expression; + private final CharSequence actual; + + JsonPathValue(JsonPath jsonPath, CharSequence actual) { + this.jsonPath = jsonPath; + this.actual = actual; + this.expression = jsonPath.getPath(); + } + + public void assertHasValue(Class type, String expectedDescription) { + Object value = getValue(true); + if (value == null || isIndefiniteAndEmpty()) { + throw new AssertionError(getNoValueMessage()); + } + if (type != null && !type.isInstance(value)) { + throw new AssertionError(getExpectedValueMessage(expectedDescription)); + } + } + + private boolean isIndefiniteAndEmpty() { + return !isDefinite() && isEmpty(); + } + + private boolean isDefinite() { + return this.jsonPath.isDefinite(); + } + + private boolean isEmpty() { + return ObjectUtils.isEmpty(getValue(false)); + } + + public Object getValue(boolean required) { + try { + CharSequence json = this.actual; + return this.jsonPath.read(json == null ? null : json.toString()); + } + catch (Exception ex) { + if (!required) { + return null; + } + throw new AssertionError(getNoValueMessage() + ". " + ex.getMessage()); + } + } + + private String getNoValueMessage() { + return "No value at JSON path \"" + this.expression + "\""; + } + + private String getExpectedValueMessage(String expectedDescription) { + return String.format("Expected %s at JSON path \"%s\" but found: %s", + expectedDescription, this.expression, + ObjectUtils.nullSafeToString(StringUtils.quoteIfString(getValue(false)))); + } + +} \ No newline at end of file diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java index f14601594c..a598cfd9a2 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockRestDocs.java @@ -47,8 +47,8 @@ package org.springframework.cloud.contract.wiremock.restdocs; */ public class WireMockRestDocs { - public static ContractRequestHandler verify() { - return new ContractRequestHandler(); + public static ContractResultHandler verify() { + return new ContractResultHandler(); } } diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockVerifyHelper.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockVerifyHelper.java new file mode 100644 index 0000000000..9caee5b0b7 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockVerifyHelper.java @@ -0,0 +1,116 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.contract.wiremock.restdocs; + +import java.nio.charset.Charset; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.github.tomakehurst.wiremock.client.MappingBuilder; +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.matching.MatchResult; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import com.jayway.jsonpath.JsonPath; + +import org.springframework.http.MediaType; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Dave Syer + * + */ +public abstract class WireMockVerifyHelper> { + + private Map jsonPaths = new LinkedHashMap<>(); + private MediaType contentType; + private String name; + + private MappingBuilder builder; + + @SuppressWarnings("unchecked") + public S stub(String name) { + this.name = name; + return (S) this; + } + + protected String getName() { + return this.name; + } + + public void configure(T result) { + Map configuration = getConfiguration(result); + String actual = new String(getRequestBodyContent(result), + Charset.forName("UTF-8")); + for (JsonPath jsonPath : this.jsonPaths.values()) { + new JsonPathValue(jsonPath, actual).assertHasValue(Object.class, "an object"); + } + configuration.put("contract.jsonPaths", this.jsonPaths.keySet()); + if (this.contentType != null) { + configuration.put("contract.contentType", this.contentType); + MediaType resultType = getContentType(result); + assertThat(resultType).isNotNull().as("no content type"); + assertThat(this.contentType.includes(resultType)).isTrue() + .as("content type did not match"); + } + if (this.builder != null) { + this.builder.willReturn(getResponseDefinition(result)); + StubMapping stubMapping = this.builder.build(); + MatchResult match = stubMapping.getRequest() + .match(getWireMockRequest(result)); + assertThat(match.isExactMatch()).as("wiremock did not match request") + .isTrue(); + configuration.put("contract.stubMapping", stubMapping); + } + } + + protected abstract Request getWireMockRequest(T result); + + protected abstract MediaType getContentType(T result); + + protected abstract byte[] getRequestBodyContent(T result); + + protected abstract ResponseDefinitionBuilder getResponseDefinition(T result); + + protected abstract Map getConfiguration(T result); + + @SuppressWarnings("unchecked") + public S wiremock(MappingBuilder builder) { + this.builder = builder; + return (S) this; + } + + @SuppressWarnings("unchecked") + public S jsonPath(String expression, Object... args) { + compile(expression, args); + return (S) this; + } + + @SuppressWarnings("unchecked") + public S contentType(MediaType contentType) { + this.contentType = contentType; + return (S) this; + } + + private void compile(String expression, Object... args) { + org.springframework.util.Assert.hasText((expression == null ? null : expression), + "expression must not be null or empty"); + expression = String.format(expression, args); + this.jsonPaths.put(expression, JsonPath.compile(expression)); + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockWebTestClient.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockWebTestClient.java new file mode 100644 index 0000000000..2693a26580 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockWebTestClient.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.wiremock.restdocs; + +/** + * Convenience class for setting up RestDocs to record WireMock stubs. Example usage: + * + *
+ * @RunWith(SpringRunner.class)
+ * @SpringBootTest
+ * @AutoConfigureRestDocs(outputDir = "target/snippets")
+ * @AutoConfigureWebTestClient
+ * public class WiremockServerRestDocsApplicationTests {
+ * 
+ * 	@Autowired
+ * 	private WebTestClient client;
+ * 
+ * 	@Test
+ * 	public void contextLoads() throws Exception {
+ * 		client.get().uri("/resource").exchange()
+ * 				.expectBody(String.class).isEqualTo("Hello World")
+ * 				.consumeWith(verify().stub("resource"));
+ * 	}
+ * 
+ * + * which creates a file "target/snippets/stubs/resource.json" matching any GET request to + * "/resource". To match POST and PUT, you can also specify the content type using + * verify().contentType(...) and JSON content of the body using + * verify().jsonPath(...). + * + * @author Dave Syer + * + */ +public class WireMockWebTestClient { + + public static ContractExchangeHandler verify() { + return new ContractExchangeHandler(); + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockWebTestClientConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockWebTestClientConfiguration.java new file mode 100644 index 0000000000..685e9a9b7a --- /dev/null +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/restdocs/WireMockWebTestClientConfiguration.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.wiremock.restdocs; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer; +import org.springframework.context.annotation.Configuration; +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer; + +/** + * Custom configuration for Spring RestDocs that adds a WireMock snippet (for generating + * JSON stubs). Applied automatically if you use + * {@link org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs @AutoConfigureRestDocs} + * in your test case and this class is available. JSON stubs are generated and added to + * the restdocs path under "stubs". + * + * @see WireMockRestDocs for a convenient entry point for customizing and asserting the + * stub behaviour + * + * @author Dave Syer + * + */ +@Configuration +@ConditionalOnClass(WebTestClientRestDocumentationConfigurer.class) +public class WireMockWebTestClientConfiguration + implements RestDocsWebTestClientConfigurationCustomizer { + + @Override + public void customize(WebTestClientRestDocumentationConfigurer configurer) { + configurer.snippets().withAdditionalDefaults(new WireMockSnippet()); + } + +} diff --git a/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories b/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories index c0e49b7d51..3ee4bf2e9f 100644 --- a/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-contract-wiremock/src/main/resources/META-INF/spring.factories @@ -5,4 +5,5 @@ org.springframework.cloud.contract.wiremock.WireMockApplicationListener # RestDocs Auto Configuration org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs=\ org.springframework.cloud.contract.wiremock.restdocs.WireMockRestDocsConfiguration,\ +org.springframework.cloud.contract.wiremock.restdocs.WireMockWebTestClientConfiguration,\ org.springframework.cloud.contract.wiremock.restdocs.WireMockRestAssuredConfiguration diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerWebTestClientApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerWebTestClientApplicationTests.java new file mode 100644 index 0000000000..d16d6c7c44 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerWebTestClientApplicationTests.java @@ -0,0 +1,64 @@ +package org.springframework.cloud.contract.wiremock; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.cloud.contract.wiremock.WiremockServerWebTestClientApplicationTests.TestConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; + +import wiremock.org.eclipse.jetty.http.HttpStatus; + +@RunWith(SpringRunner.class) +@WebFluxTest +@ContextConfiguration(classes = TestConfiguration.class) +@AutoConfigureRestDocs(outputDir = "target/snippets/webtestclient") +@AutoConfigureWebTestClient +@DirtiesContext +public class WiremockServerWebTestClientApplicationTests { + + @Autowired + private WebTestClient client; + + @Test + public void contextLoads() throws Exception { + this.client.get().uri("/resource").exchange().expectBody(String.class) + .isEqualTo("Hello World").consumeWith(document("resource")); + } + + @Test + public void statusIsMaintained() throws Exception { + this.client.get().uri("/status").exchange().expectStatus().isAccepted() + .expectBody(String.class).isEqualTo("Hello World") + .consumeWith(document("status")); + } + + @Configuration + @RestController + protected static class TestConfiguration { + + @RequestMapping("/resource") + public String resource() { + return "Hello World"; + } + + @RequestMapping("/status") + public ResponseEntity status() { + return ResponseEntity.status(HttpStatus.ACCEPTED_202).body("Hello World"); + } + + } + +}