Compare commits
138 Commits
pact-feign
...
argumentre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34e0bfc156 | ||
|
|
dfbc0446c9 | ||
|
|
71d2a32f2f | ||
|
|
1e4e88c05e | ||
|
|
5fab70b931 | ||
|
|
3be25df928 | ||
|
|
f66aa05afb | ||
|
|
66d275d667 | ||
|
|
34211bfac9 | ||
|
|
1380621113 | ||
|
|
6e0af82475 | ||
|
|
7494ff7f89 | ||
|
|
327e849575 | ||
|
|
a0ad81a332 | ||
|
|
0be1335d24 | ||
|
|
1437ac4429 | ||
|
|
5b76cbac62 | ||
|
|
8ca0e6e380 | ||
|
|
b6d167f4ae | ||
|
|
7934eed0ab | ||
|
|
ec2bdc9327 | ||
|
|
5fe5deca66 | ||
|
|
3fb809911a | ||
|
|
1809361301 | ||
|
|
01828e504d | ||
|
|
3461549263 | ||
|
|
a2bb00faf1 | ||
|
|
9e9b030c3f | ||
|
|
71af6ee95d | ||
|
|
b8e0845744 | ||
|
|
8baf275c8f | ||
|
|
92c266586c | ||
|
|
802046466f | ||
|
|
895d48c163 | ||
|
|
6667bcde4d | ||
|
|
5975aff396 | ||
|
|
44fb22ff50 | ||
|
|
dff7cf37f0 | ||
|
|
659a602e97 | ||
|
|
d34df7ad4e | ||
|
|
4c1318cc50 | ||
|
|
aa09ca31cb | ||
|
|
7712f41e3e | ||
|
|
2690f6b14d | ||
|
|
fa74e78e5d | ||
|
|
2c50fa9a12 | ||
|
|
c48921f554 | ||
|
|
df9575152f | ||
|
|
203c8d053a | ||
|
|
9cf783c0a4 | ||
|
|
9049c0f970 | ||
|
|
58c58e02d4 | ||
|
|
9f84fcd49f | ||
|
|
5646e0f169 | ||
|
|
43e82f71d2 | ||
|
|
8877f30ebc | ||
|
|
b0ae545cd8 | ||
|
|
fb0aeb0a36 | ||
|
|
b3532e2f1e | ||
|
|
2c8d828ca1 | ||
|
|
d660463d74 | ||
|
|
3207a3bc82 | ||
|
|
14039b84d5 | ||
|
|
7528cd0069 | ||
|
|
f7e158a83c | ||
|
|
d01cccb574 | ||
|
|
71fad7e002 | ||
|
|
93f8074914 | ||
|
|
5ecb279f64 | ||
|
|
d5713b6ca3 | ||
|
|
188b53f526 | ||
|
|
86e8458980 | ||
|
|
8d8937c6bf | ||
|
|
5352631522 | ||
|
|
5f85a1cf67 | ||
|
|
576a726cbd | ||
|
|
027abc1271 | ||
|
|
037dca0127 | ||
|
|
b245679a55 | ||
|
|
375b46309b | ||
|
|
38d9b4ee72 | ||
|
|
93edb8c6f8 | ||
|
|
a4c3f21391 | ||
|
|
382c307ae9 | ||
|
|
3e2c23cf45 | ||
|
|
01242c5674 | ||
|
|
a58b5e670d | ||
|
|
a5a1381e1b | ||
|
|
da30072399 | ||
|
|
65c49a3ea7 | ||
|
|
5fb2d69ca7 | ||
|
|
a2df3dde0b | ||
|
|
ff2030eafd | ||
|
|
4e45af2fbd | ||
|
|
8b384c602b | ||
|
|
c11403d8fa | ||
|
|
e8786b1bc3 | ||
|
|
dab12b2c3c | ||
|
|
31515b8a35 | ||
|
|
aa05723b18 | ||
|
|
cac0c62b71 | ||
|
|
24d00fae37 | ||
|
|
03f1680103 | ||
|
|
884546b69c | ||
|
|
be8be2efc4 | ||
|
|
0e2f10862e | ||
|
|
441cd4f801 | ||
|
|
de7fa53d55 | ||
|
|
b4bec09a3e | ||
|
|
818e162c7f | ||
|
|
a18ad84da2 | ||
|
|
d60f95b6c0 | ||
|
|
52bc347d12 | ||
|
|
5e93dcb84b | ||
|
|
3b0eb77b5d | ||
|
|
a9c9faba1d | ||
|
|
8672d163e2 | ||
|
|
3eefd80192 | ||
|
|
5a36a2365e | ||
|
|
5f675f91a8 | ||
|
|
3559378d1a | ||
|
|
b72ab0d580 | ||
|
|
ed45066496 | ||
|
|
ddb3e08ee5 | ||
|
|
888a89e541 | ||
|
|
fb148fcd14 | ||
|
|
a07407dc9f | ||
|
|
5e4c962b21 | ||
|
|
500ee0a04f | ||
|
|
4d67dd44d0 | ||
|
|
a4d70d5835 | ||
|
|
f2b83425ac | ||
|
|
fe9e15b813 | ||
|
|
9e66b87b18 | ||
|
|
9bae6d560a | ||
|
|
9d58b1323e | ||
|
|
612863cb2a | ||
|
|
f854262f80 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
**/.idea/
|
||||
**/*.iml
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@@ -1,7 +1,18 @@
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
- chmod +x build-all.sh
|
||||
- |
|
||||
if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(.md)|^(LICENSE)'
|
||||
then
|
||||
echo "Not running CI since only docs were changed."
|
||||
exit
|
||||
fi
|
||||
|
||||
install: skip
|
||||
|
||||
script:
|
||||
- ./build-all.sh
|
||||
|
||||
language: java
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk11
|
||||
17
README.md
17
README.md
@@ -2,7 +2,20 @@
|
||||
|
||||
[](https://travis-ci.org/thombergs/code-examples)
|
||||
|
||||
This repo contains example projects which show how to use different java technologies.
|
||||
This repo contains example projects which show how to use different (not only) Java technologies.
|
||||
The examples are usually accompanied by a blog post on [https://reflectoring.io](https://reflectoring.io).
|
||||
|
||||
See the READMEs in each subdirectory of this repo for more information.
|
||||
See the READMEs in each subdirectory of this repo for more information on each module.
|
||||
|
||||
## Java Modules
|
||||
All Java modules require **Java 11** to compile and run.
|
||||
|
||||
### Building with Gradle
|
||||
|
||||
Each module should be an independent build and can be built by calling `./gradlew clean build` in the module directory.
|
||||
|
||||
All modules are listed in [build-all.sh](build-all.sh) to run in the CI pipeline.
|
||||
|
||||
### Non-Java Modules
|
||||
|
||||
Some folders contain non-Java projects. For those, refer to the README within the module folder.
|
||||
|
||||
32
aws/aws-hello-world/.gitignore
vendored
Normal file
32
aws/aws-hello-world/.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
HELP.md
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**
|
||||
!**/src/test/**
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
5
aws/aws-hello-world/Dockerfile
Normal file
5
aws/aws-hello-world/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM openjdk:8-jdk-alpine
|
||||
ARG JAR_FILE=build/libs/*.jar
|
||||
COPY ${JAR_FILE} app.jar
|
||||
ENTRYPOINT ["java","-jar","/app.jar"]
|
||||
EXPOSE 8080
|
||||
24
aws/aws-hello-world/build.gradle
Normal file
24
aws/aws-hello-world/build.gradle
Normal file
@@ -0,0 +1,24 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '2.2.4.RELEASE'
|
||||
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'io.reflectoring'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = '1.8'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
testImplementation('org.springframework.boot:spring-boot-starter-test') {
|
||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
BIN
aws/aws-hello-world/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
aws/aws-hello-world/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip
|
||||
172
aws/aws-hello-world/gradlew
vendored
Executable file
172
aws/aws-hello-world/gradlew
vendored
Executable file
@@ -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" "$@"
|
||||
0
deprecated/pact-feign-consumer/gradlew.bat → aws/aws-hello-world/gradlew.bat
vendored
Normal file → Executable file
0
deprecated/pact-feign-consumer/gradlew.bat → aws/aws-hello-world/gradlew.bat
vendored
Normal file → Executable file
1
aws/aws-hello-world/settings.gradle
Normal file
1
aws/aws-hello-world/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'aws-hello-world'
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.reflectoring.awshelloworld;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class AwsHelloWorldApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AwsHelloWorldApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.reflectoring.awshelloworld;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class HelloWorldController {
|
||||
|
||||
@GetMapping("/hello")
|
||||
public String helloWorld(){
|
||||
return "Hello AWS!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.reflectoring.awshelloworld;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class AwsHelloWorldApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
70
build-all.sh
Normal file
70
build-all.sh
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
MAIN_DIR=$PWD
|
||||
|
||||
build_gradle_module() {
|
||||
MODULE_PATH=$1
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ BUILDING MODULE $MODULE_PATH"
|
||||
echo "+++"
|
||||
cd $MODULE_PATH && {
|
||||
chmod +x gradlew
|
||||
./gradlew clean build --info --stacktrace
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ BUILDING MODULE $MODULE_PATH FAILED"
|
||||
echo "+++"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ BUILDING MODULE $MODULE_PATH SUCCESSFUL"
|
||||
echo "+++"
|
||||
fi
|
||||
cd $MAIN_DIR
|
||||
}
|
||||
}
|
||||
|
||||
chmod +x gradlew
|
||||
|
||||
build_gradle_module "spring-boot/argumentresolver"
|
||||
build_gradle_module "solid"
|
||||
build_gradle_module "spring-boot/data-migration/flyway"
|
||||
build_gradle_module "reactive"
|
||||
build_gradle_module "junit/assumptions"
|
||||
build_gradle_module "logging"
|
||||
build_gradle_module "pact/pact-feign-consumer"
|
||||
# currently disabled since the consumer build won't run
|
||||
# build_gradle_module "pact/pact-message-consumer"
|
||||
# build_gradle_module "pact/pact-message-producer"
|
||||
build_gradle_module "pact/pact-spring-provider"
|
||||
build_gradle_module "patterns"
|
||||
build_gradle_module "spring-boot/conditionals"
|
||||
build_gradle_module "spring-boot/configuration"
|
||||
build_gradle_module "spring-boot/mocking"
|
||||
build_gradle_module "spring-boot/modular"
|
||||
build_gradle_module "spring-boot/paging"
|
||||
build_gradle_module "spring-boot/rabbitmq-event-brokering"
|
||||
build_gradle_module "spring-boot/spring-boot-logging"
|
||||
build_gradle_module "spring-boot/spring-boot-testing"
|
||||
build_gradle_module "spring-boot/starter"
|
||||
build_gradle_module "spring-boot/startup"
|
||||
build_gradle_module "spring-boot/static"
|
||||
build_gradle_module "spring-boot/validation"
|
||||
build_gradle_module "spring-boot/profiles"
|
||||
build_gradle_module "spring-cloud/feign-with-spring-data-rest"
|
||||
build_gradle_module "spring-cloud/sleuth-downstream-service"
|
||||
build_gradle_module "spring-cloud/sleuth-upstream-service"
|
||||
build_gradle_module "spring-cloud/spring-cloud-contract-consumer"
|
||||
build_gradle_module "spring-cloud/spring-cloud-contract-provider"
|
||||
build_gradle_module "spring-data/spring-data-rest-associations"
|
||||
build_gradle_module "spring-data/spring-data-rest-springfox"
|
||||
build_gradle_module "tools/jacoco"
|
||||
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ ALL MODULES SUCCESSFUL"
|
||||
echo "+++"
|
||||
@@ -1,15 +0,0 @@
|
||||
# Consumer-Driven-Contract Test for a Feign Consumer
|
||||
|
||||
This repo contains an example of consumer-driven-contract testing for a Feign client
|
||||
that consumes a REST API provided by the module `pact-spring-data-rest-provider`.
|
||||
|
||||
The contract is created and verified with [Pact](https://docs.pact.io/).
|
||||
|
||||
## Companion Blog Post
|
||||
|
||||
The Companion Blog Post to this project can be found [here](https://reflectoring.io/consumer-driven-contracts-with-pact-feign-spring-data-rest/).
|
||||
|
||||
## Running the application
|
||||
|
||||
The interesting part in this code base is the class `ConsumerPactVerificationTest`.
|
||||
You can run the tests with `gradlew test` on Windows or `./gradlew test` on Unix.
|
||||
@@ -1,46 +0,0 @@
|
||||
buildscript {
|
||||
ext {
|
||||
springBootVersion = '1.5.4.RELEASE'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
ext {
|
||||
springCloudVersion = 'Dalston.SR2'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.cloud:spring-cloud-starter-feign')
|
||||
// locking transitive guava version to work around "java.lang.IllegalAccessError: tried to access method com.google.common.collect.Lists.cartesianProduct"
|
||||
compile('com.google.guava:guava:22.0')
|
||||
compile('org.springframework.boot:spring-boot-starter-hateoas')
|
||||
testCompile group: 'au.com.dius', name: 'pact-jvm-consumer-junit_2.11', version: '3.5.2'
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
|
||||
}
|
||||
}
|
||||
|
||||
bootRun{
|
||||
jvmArgs = ["-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5006"]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
public class Address {
|
||||
|
||||
private long id;
|
||||
|
||||
private String street;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
|
||||
public void setStreet(String street) {
|
||||
this.street = street;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
@FeignClient(value = "addresses", path = "/addresses")
|
||||
public interface AddressClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/")
|
||||
Resources<Address> getAddresses();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/{id}")
|
||||
Resource<Address> getAddress(@PathVariable("id") long id);
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
public class Customer {
|
||||
|
||||
private long id;
|
||||
|
||||
private String name;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
@FeignClient(value = "customers", path = "/customers_mto")
|
||||
public interface CustomerClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
Resources<Customer> getCustomers();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/{id}")
|
||||
Resource<Customer> getCustomer(@PathVariable("id") long id);
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.netflix.feign.EnableFeignClients;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableFeignClients
|
||||
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import feign.Logger;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class FeignConfiguration {
|
||||
|
||||
@Bean
|
||||
public Logger.Level logLevel(){
|
||||
return Logger.Level.FULL;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
server.port: 8081
|
||||
|
||||
logging.level.com.example.demo.CustomerClient: DEBUG
|
||||
logging.level.com.example.demo.AddressClient: DEBUG
|
||||
logging.level.org.hibernate.SQL: DEBUG
|
||||
|
||||
customers:
|
||||
ribbon:
|
||||
eureka:
|
||||
enabled: false
|
||||
listOfServers: localhost:8080
|
||||
|
||||
addresses:
|
||||
ribbon:
|
||||
eureka:
|
||||
enabled: false
|
||||
listOfServers: localhost:8080
|
||||
@@ -1,137 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import au.com.dius.pact.consumer.Pact;
|
||||
import au.com.dius.pact.consumer.PactProviderRuleMk2;
|
||||
import au.com.dius.pact.consumer.PactVerification;
|
||||
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
|
||||
import au.com.dius.pact.model.RequestResponsePact;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = {
|
||||
// overriding provider address
|
||||
"addresses.ribbon.listOfServers: localhost:8888"
|
||||
})
|
||||
public class ConsumerPactVerificationTest {
|
||||
|
||||
@Rule
|
||||
public PactProviderRuleMk2 stubProvider = new PactProviderRuleMk2("customerServiceProvider", "localhost", 8888, this);
|
||||
|
||||
@Autowired
|
||||
private AddressClient addressClient;
|
||||
|
||||
@Pact(state = "a collection of 2 addresses", provider = "customerServiceProvider", consumer = "addressClient")
|
||||
public RequestResponsePact createAddressCollectionResourcePact(PactDslWithProvider builder) {
|
||||
return builder
|
||||
.given("a collection of 2 addresses")
|
||||
.uponReceiving("a request to the address collection resource")
|
||||
.path("/addresses/")
|
||||
.method("GET")
|
||||
.willRespondWith()
|
||||
.status(200)
|
||||
.body("{\n" +
|
||||
" \"_embedded\": {\n" +
|
||||
" \"addresses\": [\n" +
|
||||
" {\n" +
|
||||
" \"street\": \"Elm Street\",\n" +
|
||||
" \"_links\": {\n" +
|
||||
" \"self\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/1\"\n" +
|
||||
" },\n" +
|
||||
" \"address\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/1\"\n" +
|
||||
" },\n" +
|
||||
" \"customer\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/1/customer\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"street\": \"High Street\",\n" +
|
||||
" \"_links\": {\n" +
|
||||
" \"self\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/2\"\n" +
|
||||
" },\n" +
|
||||
" \"address\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/2\"\n" +
|
||||
" },\n" +
|
||||
" \"customer\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/2/customer\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
" },\n" +
|
||||
" \"_links\": {\n" +
|
||||
" \"self\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses{?page,size,sort}\",\n" +
|
||||
" \"templated\": true\n" +
|
||||
" },\n" +
|
||||
" \"profile\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/profile/addresses\"\n" +
|
||||
" },\n" +
|
||||
" \"search\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/search\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"page\": {\n" +
|
||||
" \"size\": 20,\n" +
|
||||
" \"totalElements\": 2,\n" +
|
||||
" \"totalPages\": 1,\n" +
|
||||
" \"number\": 0\n" +
|
||||
" }\n" +
|
||||
"}", "application/hal+json")
|
||||
.toPact();
|
||||
}
|
||||
|
||||
@Pact(state = "a single address", provider = "customerServiceProvider", consumer = "addressClient")
|
||||
public RequestResponsePact createAddressResourcePact(PactDslWithProvider builder) {
|
||||
return builder
|
||||
.given("a single address")
|
||||
.uponReceiving("a request to the address resource")
|
||||
.path("/addresses/1")
|
||||
.method("GET")
|
||||
.willRespondWith()
|
||||
.status(200)
|
||||
.body("{\n" +
|
||||
" \"street\": \"Elm Street\",\n" +
|
||||
" \"_links\": {\n" +
|
||||
" \"self\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/1\"\n" +
|
||||
" },\n" +
|
||||
" \"address\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/1\"\n" +
|
||||
" },\n" +
|
||||
" \"customer\": {\n" +
|
||||
" \"href\": \"http://localhost:8080/addresses/1/customer\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}", "application/hal+json")
|
||||
.toPact();
|
||||
}
|
||||
|
||||
@Test
|
||||
@PactVerification(fragment = "createAddressCollectionResourcePact")
|
||||
public void verifyAddressCollectionPact() {
|
||||
Resources<Address> addresses = addressClient.getAddresses();
|
||||
assertThat(addresses).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@PactVerification(fragment = "createAddressResourcePact")
|
||||
public void verifyAddressPact() {
|
||||
Resource<Address> address = addressClient.getAddress(1L);
|
||||
assertThat(address).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
# Consumer-Driven-Contract Test for a Spring Data Rest Provider
|
||||
|
||||
This repo contains an example of consumer-driven-contract testing for a Spring
|
||||
Data REST API provider. The corresponding consumer to the contract is
|
||||
implemented in the module `pact-feign-consumer`.
|
||||
|
||||
The contract is created and verified with [Pact](https://docs.pact.io/).
|
||||
|
||||
## Companion Blog Post
|
||||
|
||||
The Companion Blog Post to this project can be found [here](https://reflectoring.io/consumer-driven-contracts-with-pact-feign-spring-data-rest/).
|
||||
|
||||
## Running the application
|
||||
|
||||
The interesting part in this code base is the class `ProviderPactVerificationTest`.
|
||||
You can run the tests with `gradlew test` on Windows or `./gradlew test` on Unix.
|
||||
@@ -1,35 +0,0 @@
|
||||
buildscript {
|
||||
ext {
|
||||
springBootVersion = '1.5.4.RELEASE'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
||||
compile('org.springframework.boot:spring-boot-starter-data-rest')
|
||||
compile group: 'au.com.dius', name: 'pact-jvm-provider-junit_2.11', version: '3.5.2'
|
||||
compile('com.h2database:h2:1.4.196')
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||
}
|
||||
|
||||
bootRun{
|
||||
jvmArgs = ["-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"]
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity
|
||||
public class Address {
|
||||
|
||||
@GeneratedValue
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Column
|
||||
private String street;
|
||||
|
||||
@ManyToOne
|
||||
private Customer customer;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
|
||||
public void setStreet(String street) {
|
||||
this.street = street;
|
||||
}
|
||||
|
||||
public Customer getCustomer() {
|
||||
return customer;
|
||||
}
|
||||
|
||||
public void setCustomer(Customer customer) {
|
||||
this.customer = customer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RepositoryRestResource(path = "addresses")
|
||||
public interface AddressRepository extends PagingAndSortingRepository<Address, Long> {
|
||||
|
||||
List<Address> findByCustomerId(long customerId);
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class Customer {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private long id;
|
||||
|
||||
@Column
|
||||
private String name;
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
|
||||
@RepositoryRestResource(path = "customers")
|
||||
public interface CustomerRepository extends CrudRepository<Customer, Long> {
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
spring.datasource.url=jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
spring.datasource.driverClassName=org.h2.Driver
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=
|
||||
spring.h2.console.enabled=true
|
||||
|
||||
logging.level.org.hibernate.SQL=OFF
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import au.com.dius.pact.provider.junit.PactRunner;
|
||||
import au.com.dius.pact.provider.junit.Provider;
|
||||
import au.com.dius.pact.provider.junit.State;
|
||||
import au.com.dius.pact.provider.junit.loader.PactFolder;
|
||||
import au.com.dius.pact.provider.junit.target.HttpTarget;
|
||||
import au.com.dius.pact.provider.junit.target.Target;
|
||||
import au.com.dius.pact.provider.junit.target.TestTarget;
|
||||
import com.example.framework.DatabaseStateHolder;
|
||||
import com.example.framework.SpringBootStarter;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(PactRunner.class)
|
||||
@Provider("customerServiceProvider")
|
||||
@PactFolder("../pact-feign-consumer/target/pacts")
|
||||
public class ProviderPactVerificationTest {
|
||||
|
||||
@ClassRule
|
||||
public static SpringBootStarter appStarter = SpringBootStarter.builder()
|
||||
.withApplicationClass(DemoApplication.class)
|
||||
.withArgument("--spring.config.location=classpath:/application-pact.properties")
|
||||
.withDatabaseState("single-address", "/initial-schema.sql", "/single-address.sql")
|
||||
.withDatabaseState("address-collection", "/initial-schema.sql", "/address-collection.sql")
|
||||
.build();
|
||||
|
||||
@State("a single address")
|
||||
public void toSingleAddressState() {
|
||||
DatabaseStateHolder.setCurrentDatabaseState("single-address");
|
||||
}
|
||||
|
||||
@State("a collection of 2 addresses")
|
||||
public void toAddressCollectionState() {
|
||||
DatabaseStateHolder.setCurrentDatabaseState("address-collection");
|
||||
}
|
||||
|
||||
@TestTarget
|
||||
public final Target target = new HttpTarget(8080);
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.example.framework;
|
||||
|
||||
/**
|
||||
* Defines a state of the database, which is defined by a set of SQL scripts.
|
||||
*/
|
||||
public class DatabaseState {
|
||||
|
||||
private final String stateName;
|
||||
|
||||
private final String[] sqlscripts;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param stateName unique name of this database state.
|
||||
* @param sqlscripts paths to SQL scripts within the classpath. These scripts will be executed to put the
|
||||
* database into the database state described by this object.
|
||||
*/
|
||||
public DatabaseState(String stateName, String... sqlscripts) {
|
||||
this.stateName = stateName;
|
||||
this.sqlscripts = sqlscripts;
|
||||
}
|
||||
|
||||
public String getStateName() {
|
||||
return stateName;
|
||||
}
|
||||
|
||||
public String[] getSqlscripts() {
|
||||
return sqlscripts;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.example.framework;
|
||||
|
||||
/**
|
||||
* Holds the current database state.
|
||||
* <p/>
|
||||
* TODO: replace the static state variable with a thread-safe alternative. Potentially use a special HTTP header
|
||||
* that is intercepted by a Bean defined in {@link PactDatabaseStatesAutoConfiguration} and sets the database
|
||||
* state in a {@link ThreadLocal} variable.
|
||||
*/
|
||||
public class DatabaseStateHolder {
|
||||
|
||||
private static String currentDatabaseState;
|
||||
|
||||
/**
|
||||
* Sets the database to the state with the specified name.
|
||||
* <p/>
|
||||
* <strong>WARNING:</strong> the database state is not thread safe. If there are multiple threads accessing
|
||||
* the database in different states at the same time, apocalypse will come!
|
||||
*
|
||||
* @param databaseStateName the name of the {@link DatabaseState} to put the database in.
|
||||
*/
|
||||
public static void setCurrentDatabaseState(String databaseStateName) {
|
||||
currentDatabaseState = databaseStateName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current {@link DatabaseState}.
|
||||
* <p/>
|
||||
* <strong>WARNING:</strong> the database state is not thread safe. If there are multiple threads accessing
|
||||
* the database in different states at the same time, apocalypse will come!
|
||||
*/
|
||||
public static String getCurrentDatabaseState() {
|
||||
return currentDatabaseState;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.example.framework;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Initializes a {@link DataSource} to a specified List of {@link DatabaseState}s. It is assumed that the {@link DataSource}
|
||||
* can switch between several states.
|
||||
*/
|
||||
public class DatabaseStatesInitializer {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final List<DatabaseState> databaseStates;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dataSource the {@link DataSource} to execute SQL scripts against.
|
||||
* @param databaseStates the {@link DatabaseState}s to create within the {@link DataSource}.
|
||||
*/
|
||||
public DatabaseStatesInitializer(DataSource dataSource, List<DatabaseState> databaseStates) {
|
||||
this.dataSource = dataSource;
|
||||
this.databaseStates = databaseStates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes SQL scripts to initialize the {@link DataSource} with several states.
|
||||
* <p/>
|
||||
* For each {@link DatabaseState}, the {@link DatabaseStateHolder} will be called to set the {@link DataSource}
|
||||
* into that state. Then, the SQL scripts of that {@link DatabaseState} are executed against the {@link DataSource}
|
||||
* to initialize that state.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
for (DatabaseState databaseState : this.databaseStates) {
|
||||
DatabaseStateHolder.setCurrentDatabaseState(databaseState.getStateName());
|
||||
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
||||
for (String script : databaseState.getSqlscripts()) {
|
||||
populator.addScript(new ClassPathResource(script));
|
||||
}
|
||||
populator.execute(this.dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package com.example.framework;
|
||||
|
||||
import au.com.dius.pact.provider.junit.PactRunner;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration AutoConfiguration} that is activated when
|
||||
* the pact-jvm-provider-junit module is in the classpath.
|
||||
* </p>
|
||||
* <p>
|
||||
* This configuration provides a {@link DataSource} which allows to switch between multiple database states.
|
||||
* Each database state is defined by a name and a set of SQL scripts which set the database into the desired state.
|
||||
* The database states are configured via properties:
|
||||
* <pre>
|
||||
* pact.databaseStates.<NAME>=/path/to/script1.sql,/path/to/script2.sql,...
|
||||
* </pre>
|
||||
* The NAME of the databaseState can be used with {@link DatabaseStateHolder#setCurrentDatabaseState(String)}
|
||||
* to set the {@link DataSource} into that state.
|
||||
* </p>
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(PactRunner.class)
|
||||
@EnableConfigurationProperties(PactProperties.class)
|
||||
public class PactDatabaseStatesAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource(PactProperties pactProperties) {
|
||||
AbstractRoutingDataSource dataSource = new AbstractRoutingDataSource() {
|
||||
@Override
|
||||
protected Object determineCurrentLookupKey() {
|
||||
return DatabaseStateHolder.getCurrentDatabaseState();
|
||||
}
|
||||
};
|
||||
|
||||
Map<Object, Object> targetDataSources = new HashMap<>();
|
||||
dataSource.setTargetDataSources(targetDataSources);
|
||||
|
||||
// create a DataSource for each DatabaseState
|
||||
for (DatabaseState databaseState : pactProperties.getDatabaseStatesList()) {
|
||||
DataSource ds = DataSourceBuilder
|
||||
.create()
|
||||
.url(String.format("jdbc:h2:mem:%s;DB_CLOSE_ON_EXIT=FALSE", databaseState.getStateName()))
|
||||
.driverClassName("org.h2.Driver")
|
||||
.username("sa")
|
||||
.password("")
|
||||
.build();
|
||||
targetDataSources.put(databaseState.getStateName(), ds);
|
||||
DatabaseStateHolder.setCurrentDatabaseState(databaseState.getStateName());
|
||||
}
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DatabaseStatesInitializer databaseStatesInitializer(DataSource routingDataSource, PactProperties pactProperties) {
|
||||
return new DatabaseStatesInitializer(routingDataSource, pactProperties.getDatabaseStatesList());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.example.framework;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Loads the properties "pact.databaseStates.<NAME>" into the Spring environment.
|
||||
*/
|
||||
@ConfigurationProperties("pact")
|
||||
public class PactProperties {
|
||||
|
||||
private Map<String, String> databaseStates;
|
||||
|
||||
/**
|
||||
* Retrieves a map with the names of the configured database states as keys and {@link DatabaseState} objects
|
||||
* as values.
|
||||
*/
|
||||
public List<DatabaseState> getDatabaseStatesList() {
|
||||
List<DatabaseState> databaseStatesList = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry : databaseStates.entrySet()) {
|
||||
String stateName = entry.getKey();
|
||||
|
||||
// When reading a property as a Map, as is done for databaseStates, Spring Boot automatically adds numeric
|
||||
// keys and moves the actual key into the value, separated by a comma. Thus, we have all entries duplicated
|
||||
// and have to remove the entries with numeric keys.
|
||||
|
||||
if (!stateName.matches("^[0-9]+$")) {
|
||||
String sqlScriptsString = entry.getValue();
|
||||
String[] sqlScripts = sqlScriptsString.split(",");
|
||||
databaseStatesList.add(new DatabaseState(stateName, sqlScripts));
|
||||
}
|
||||
}
|
||||
return databaseStatesList;
|
||||
}
|
||||
|
||||
|
||||
static List<String> toCommandLineArguments(List<DatabaseState> databaseStates) {
|
||||
List<String> args = new ArrayList<>();
|
||||
for (DatabaseState databaseState : databaseStates) {
|
||||
String argString = String.format("--pact.databaseStates.%s=", databaseState.getStateName());
|
||||
int i = 0;
|
||||
for (String scriptPath : databaseState.getSqlscripts()) {
|
||||
argString += String.format("%s", scriptPath);
|
||||
if (i < databaseState.getSqlscripts().length - 1) {
|
||||
argString += ",";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
args.add(argString);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public Map<String, String> getDatabaseStates() {
|
||||
return databaseStates;
|
||||
}
|
||||
|
||||
public void setDatabaseStates(Map<String, String> databaseStates) {
|
||||
this.databaseStates = databaseStates;
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package com.example.framework;
|
||||
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Starts a Spring Boot application.
|
||||
* </p>
|
||||
* <p>
|
||||
* When included in a JUnit test with the {@link org.junit.ClassRule} annotation as in the example below, the Spring Boot application will be
|
||||
* started before any of the test methods are run.
|
||||
* <pre>
|
||||
* public class MyTest {
|
||||
*
|
||||
* @ClassRule
|
||||
* public static SpringBootStarter starter = SpringBootStarter.builder()
|
||||
* .withApplicationClass(MyApplication.class)
|
||||
* ...
|
||||
* .build();
|
||||
*
|
||||
* @Test
|
||||
* public void test(){
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
public class SpringBootStarter implements TestRule {
|
||||
|
||||
private final Class<?> applicationClass;
|
||||
|
||||
private final List<String> args;
|
||||
|
||||
private final List<DatabaseState> databaseStates;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param applicationClass the Spring Boot application class.
|
||||
* @param databaseStates list containing {@link DatabaseState} objects, each describing a database state
|
||||
* in form of one or more SQL scripts.
|
||||
* @param args the command line arguments the application should be started with.
|
||||
*/
|
||||
public SpringBootStarter(Class<?> applicationClass, List<DatabaseState> databaseStates, List<String> args) {
|
||||
this.args = args;
|
||||
this.applicationClass = applicationClass;
|
||||
this.databaseStates = databaseStates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement apply(Statement base, Description description) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
List<String> args = new ArrayList<>();
|
||||
args.addAll(SpringBootStarter.this.args);
|
||||
args.addAll(PactProperties.toCommandLineArguments(SpringBootStarter.this.databaseStates));
|
||||
ApplicationContext context = SpringApplication.run(SpringBootStarter.this.applicationClass, args.toArray(new String[]{}));
|
||||
base.evaluate();
|
||||
SpringApplication.exit(context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder that provides a fluent API to create a new {@link SpringBootStarter} instance.
|
||||
*/
|
||||
public static SpringBootStarterBuilder builder() {
|
||||
return new SpringBootStarterBuilder();
|
||||
}
|
||||
|
||||
public static class SpringBootStarterBuilder {
|
||||
|
||||
private Class<?> applicationClass;
|
||||
|
||||
private List<String> args = new ArrayList<>();
|
||||
|
||||
private List<DatabaseState> databaseStates = new ArrayList<>();
|
||||
|
||||
public SpringBootStarterBuilder withApplicationClass(Class<?> clazz) {
|
||||
this.applicationClass = clazz;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpringBootStarterBuilder withArgument(String argument) {
|
||||
this.args.add(argument);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpringBootStarterBuilder withDatabaseState(String stateName, String... sqlScripts) {
|
||||
this.databaseStates.add(new DatabaseState(stateName, sqlScripts));
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpringBootStarter build() {
|
||||
return new SpringBootStarter(this.applicationClass, this.databaseStates, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.framework.PactDatabaseStatesAutoConfiguration
|
||||
@@ -1,2 +0,0 @@
|
||||
insert into address (id, street) values (1, 'Elm Street');
|
||||
insert into address (id, street) values (2, 'High Street');
|
||||
@@ -1,6 +0,0 @@
|
||||
server.port=8080
|
||||
#spring.jpa.generate-ddl=false
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
|
||||
pact.databaseStates[0]=single-address,/initial-schema.sql,/single-address.sql
|
||||
pact.databaseStates[1]=address-collection,/initial-schema.sql,/address-collection.sql
|
||||
@@ -1,11 +0,0 @@
|
||||
create table CUSTOMER (
|
||||
id NUMBER,
|
||||
name VARCHAR
|
||||
);
|
||||
|
||||
create table ADDRESS (
|
||||
id NUMBER,
|
||||
customer_id NUMBER,
|
||||
street VARCHAR,
|
||||
FOREIGN KEY (customer_id) REFERENCES CUSTOMER(id)
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
insert into address (id, street) values (1, 'Elm Street');
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
apply plugin: 'java'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 1.8
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@@ -23,3 +23,6 @@ dependencies {
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
|
||||
|
||||
7
logging/README.md
Normal file
7
logging/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Logging Code Examples
|
||||
|
||||
## Related Blog Articles
|
||||
|
||||
* [Use Logging Levels Consistently](https://reflectoring.io/logging-levels/)
|
||||
* [Use a Human-Readable Logging Format](https://reflectoring.io/logging-format/)
|
||||
* [Configuring a Human-Readable Logging Format with Logback and Descriptive Logger](https://reflectoring.io/logging-format-logback/)
|
||||
20
logging/build.gradle
Normal file
20
logging/build.gradle
Normal file
@@ -0,0 +1,20 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile("ch.qos.logback:logback-classic:1.2.3")
|
||||
compile("io.reflectoring:descriptive-logger:1.0")
|
||||
testCompile("org.junit.jupiter:junit-jupiter-engine:5.0.1")
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#Sun Jul 30 16:58:54 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.reflectoring.logging;
|
||||
|
||||
import io.reflectoring.descriptivelogger.LoggerFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoggingFormatTest {
|
||||
|
||||
private MyLogger logger = LoggerFactory.getLogger(MyLogger.class, LoggingFormatTest.class);
|
||||
|
||||
@Test
|
||||
public void testLogPattern(){
|
||||
Thread.currentThread().setName("very-long-thread-name");
|
||||
logger.logDebugMessage();
|
||||
Thread.currentThread().setName("short");
|
||||
logger.logInfoMessage();
|
||||
logger.logMessageWithLongId();
|
||||
}
|
||||
}
|
||||
19
logging/src/test/java/io/reflectoring/logging/MyLogger.java
Normal file
19
logging/src/test/java/io/reflectoring/logging/MyLogger.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package io.reflectoring.logging;
|
||||
|
||||
import io.reflectoring.descriptivelogger.DescriptiveLogger;
|
||||
import io.reflectoring.descriptivelogger.LogMessage;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
@DescriptiveLogger
|
||||
public interface MyLogger {
|
||||
|
||||
@LogMessage(level=Level.DEBUG, message="This is a DEBUG message.", id=14556)
|
||||
void logDebugMessage();
|
||||
|
||||
@LogMessage(level=Level.INFO, message="This is an INFO message.", id=5456)
|
||||
void logInfoMessage();
|
||||
|
||||
@LogMessage(level=Level.ERROR, message="This is an ERROR message with a very long ID.", id=1548654)
|
||||
void logMessageWithLongId();
|
||||
|
||||
}
|
||||
21
logging/src/test/resources/logback.xml
Normal file
21
logging/src/test/resources/logback.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="truncatedThread"
|
||||
converterClass="io.reflectoring.logging.TruncatedThreadConverter" />
|
||||
|
||||
<conversionRule conversionWord="truncatedLogger"
|
||||
converterClass="io.reflectoring.logging.TruncatedLoggerConverter" />
|
||||
|
||||
<!-- Appender to log to console -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd} | %d{HH:mm:ss.SSS} | %-20.20thread | %5p | %-25.25logger{25} | %12(ID: %8mdc{id}) | %m%n</pattern>
|
||||
<charset>utf8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -4,6 +4,9 @@ This example project shows how to setup an Angular application to use [Pact](htt
|
||||
in order to create Pact files from a consumer test and validate the
|
||||
a consumer against the Pact.
|
||||
|
||||
## Relevant Blog Post
|
||||
[Creating a Consumer-Driven Contract with Angular and Pact](https://reflectoring.io/consumer-driven-contracts-with-angular-and-pact/)
|
||||
|
||||
## Key Files
|
||||
|
||||
* [`user.service.ts`](src/app/user.service.ts): Angular service that calls a REST
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
# Testing a Spring Boot REST API Consumer against a Contract with Spring Cloud Contract
|
||||
# Testing a Spring Boot REST API Consumer against a Contract with Pact
|
||||
|
||||
## Companion Blog Article
|
||||
Read the [companion blog article](https://reflectoring.io/consumer-driven-contract-consumer-spring-cloud-contract/) to this repository.
|
||||
Read the [companion blog article](http://localhost:4000/consumer-driven-contract-feign-pact/) to this repository.
|
||||
|
||||
## Getting Started
|
||||
|
||||
* have a look at the [contract](/src/test/resources/contracts)
|
||||
* have a look at the [feign client](/src/main/java/io/reflectoring/UserClient.java)
|
||||
* have a look at the [consumer test](/src/test/java/io/reflectoring/UserClientTest.java)
|
||||
* run `./gradlew publishToMavenLocal` in the [producer project](../spring-cloud-contract-provider)
|
||||
to create a provider stubs
|
||||
* run `./gradlew build` in this project to verify the feign client against the stub
|
||||
* have a look at the [feign client](src/main/java/io/reflectoring/UserClient.java)
|
||||
* have a look at the [consumer test](src/test/java/io/reflectoring/UserServiceConsumerTest.java)
|
||||
* run `./gradlew build` in this project to create a pact and run the consumer test
|
||||
* afterwards, find the pact contract file in the folder `target/pacts`
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
userservice:
|
||||
ribbon:
|
||||
eureka:
|
||||
enabled: false
|
||||
listOfServers: localhost:8080
|
||||
@@ -1,5 +1,3 @@
|
||||
apply plugin: 'org.springframework.boot'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@@ -10,18 +8,56 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "au.com.dius.pact" version "3.5.20"
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
version '1.0.0.SNAPSHOT'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile("org.springframework.boot:spring-boot-starter-data-jpa:${springboot_version}")
|
||||
compile("org.springframework.boot:spring-boot-starter-web:${springboot_version}")
|
||||
compile("org.springframework.cloud:spring-cloud-starter-feign:1.4.1.RELEASE")
|
||||
compile('com.h2database:h2:1.4.196')
|
||||
testCompile('org.codehaus.groovy:groovy-all:2.4.6')
|
||||
testCompile("au.com.dius:pact-jvm-consumer-junit_2.11:3.5.2")
|
||||
testCompile("org.springframework.boot:spring-boot-starter-test:${springboot_version}")
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springcloud_version}"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
||||
compile('org.springframework.boot:spring-boot-starter-web')
|
||||
compile('org.springframework.cloud:spring-cloud-starter-openfeign')
|
||||
compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
|
||||
compile('com.h2database:h2:1.4.196')
|
||||
|
||||
// add jaxb since it's no longer available in Java 11
|
||||
runtime('javax.xml.bind:jaxb-api:2.3.1')
|
||||
|
||||
// add javassist >= 3.23.1-GA since earlier versions are broken in Java 11
|
||||
// see https://github.com/jboss-javassist/javassist/issues/194
|
||||
runtime('org.javassist:javassist:3.23.1-GA')
|
||||
|
||||
testCompile('org.codehaus.groovy:groovy-all:2.4.6')
|
||||
testCompile("au.com.dius:pact-jvm-consumer-junit5_2.12:${pact_version}")
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||
testRuntimeOnly( 'org.junit.jupiter:junit-jupiter-engine:5.1.0')
|
||||
}
|
||||
|
||||
pact {
|
||||
publish {
|
||||
pactDirectory = 'target/pacts'
|
||||
pactBrokerUrl = 'TODO'
|
||||
pactBrokerUsername = 'TODO'
|
||||
pactBrokerPassword = 'TODO'
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
springboot_version=1.5.9.RELEASE
|
||||
verifier_version=1.2.2.RELEASE
|
||||
springboot_version=2.0.4.RELEASE
|
||||
springcloud_version=Finchley.SR1
|
||||
pact_version=3.5.20
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
|
||||
|
||||
@@ -2,10 +2,12 @@ package io.reflectoring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.netflix.feign.EnableFeignClients;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableFeignClients
|
||||
@RibbonClient(name = "userservice", configuration = RibbonConfiguration.class)
|
||||
public class ConsumerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.IRule;
|
||||
import com.netflix.loadbalancer.RandomRule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
public class RibbonConfiguration {
|
||||
|
||||
@Bean
|
||||
public IRule ribbonRule(IClientConfig config) {
|
||||
return new RandomRule();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import au.com.dius.pact.consumer.Pact;
|
||||
import au.com.dius.pact.consumer.PactProviderRuleMk2;
|
||||
import au.com.dius.pact.consumer.PactVerification;
|
||||
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
|
||||
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
|
||||
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
|
||||
import au.com.dius.pact.consumer.junit5.PactTestFor;
|
||||
import au.com.dius.pact.model.RequestResponsePact;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = {
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@ExtendWith(PactConsumerTestExt.class)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@PactTestFor(providerName = "userservice", port = "8888")
|
||||
@SpringBootTest({
|
||||
// overriding provider address
|
||||
"userservice.ribbon.listOfServers: localhost:8888"
|
||||
})
|
||||
public class UserServiceConsumerTest {
|
||||
|
||||
@Rule
|
||||
public PactProviderRuleMk2 stubProvider = new PactProviderRuleMk2("userservice", "localhost", 8888, this);
|
||||
class UserServiceConsumerTest {
|
||||
|
||||
@Autowired
|
||||
private UserClient userClient;
|
||||
|
||||
@Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient")
|
||||
public RequestResponsePact createPersonPact(PactDslWithProvider builder) {
|
||||
RequestResponsePact createPersonPact(PactDslWithProvider builder) {
|
||||
|
||||
// @formatter:off
|
||||
return builder
|
||||
.given("provider accepts a new person")
|
||||
.uponReceiving("a request to POST a person")
|
||||
@@ -40,10 +42,12 @@ public class UserServiceConsumerTest {
|
||||
.body(new PactDslJsonBody()
|
||||
.integerType("id", 42))
|
||||
.toPact();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Pact(state = "person 42 exists", provider = "userservice", consumer = "userclient")
|
||||
public RequestResponsePact updatePersonPact(PactDslWithProvider builder) {
|
||||
RequestResponsePact updatePersonPact(PactDslWithProvider builder) {
|
||||
// @formatter:off
|
||||
return builder
|
||||
.given("person 42 exists")
|
||||
.uponReceiving("a request to PUT a person")
|
||||
@@ -56,12 +60,13 @@ public class UserServiceConsumerTest {
|
||||
.stringType("firstName", "Zaphod")
|
||||
.stringType("lastName", "Beeblebrox"))
|
||||
.toPact();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@PactVerification(fragment = "createPersonPact")
|
||||
public void verifyCreatePersonPact() {
|
||||
@PactTestFor(pactMethod = "createPersonPact")
|
||||
void verifyCreatePersonPact() {
|
||||
User user = new User();
|
||||
user.setFirstName("Zaphod");
|
||||
user.setLastName("Beeblebrox");
|
||||
@@ -70,8 +75,8 @@ public class UserServiceConsumerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@PactVerification(fragment = "updatePersonPact")
|
||||
public void verifyUpdatePersonPact() {
|
||||
@PactTestFor(pactMethod = "updatePersonPact")
|
||||
void verifyUpdatePersonPact() {
|
||||
User user = new User();
|
||||
user.setFirstName("Zaphod");
|
||||
user.setLastName("Beeblebrox");
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "userservice"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "userclient"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "a request to PUT a person",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"path": "/user-service/users/42"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"firstName": "Zaphod",
|
||||
"lastName": "Beeblebrox"
|
||||
},
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$.firstName": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.lastName": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"providerStates": [
|
||||
{
|
||||
"name": "person 42 exists"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "a request to POST a person",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/user-service/users"
|
||||
},
|
||||
"response": {
|
||||
"status": 201,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"id": 42
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.id": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "integer"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"providerStates": [
|
||||
{
|
||||
"name": "provider accepts a new person"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.20"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
pact/pact-message-consumer/README.md
Normal file
7
pact/pact-message-consumer/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Consumer-Driven-Contract Test for a Spring Boot Message Consumer
|
||||
|
||||
This module shows how to use Pact to implement a contract test for a message provider.
|
||||
|
||||
## Companion Articles
|
||||
[Testing a Spring Message Producer and Consumer against a Contract with Pact](https://reflectoring.io/cdc-pact-messages/)
|
||||
|
||||
1
pact/pact-message-consumer/application.yml
Normal file
1
pact/pact-message-consumer/application.yml
Normal file
@@ -0,0 +1 @@
|
||||
spring.rabbitmq.connection-timeout=10
|
||||
43
pact/pact-message-consumer/build.gradle
Normal file
43
pact/pact-message-consumer/build.gradle
Normal file
@@ -0,0 +1,43 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-web')
|
||||
compile('org.springframework.boot:spring-boot-starter-amqp')
|
||||
compile('com.h2database:h2:1.4.196')
|
||||
compileOnly('org.projectlombok:lombok:1.18.2')
|
||||
|
||||
// add jaxb since it's no longer available in Java 11
|
||||
runtime('javax.xml.bind:jaxb-api:2.3.1')
|
||||
|
||||
// add javassist >= 3.23.1-GA since earlier versions are broken in Java 11
|
||||
// see https://github.com/jboss-javassist/javassist/issues/194
|
||||
runtime('org.javassist:javassist:3.23.1-GA')
|
||||
|
||||
testCompile("au.com.dius:pact-jvm-consumer-junit_2.12:${pact_version}")
|
||||
testCompile("au.com.dius:pact-jvm-consumer-groovy_2.12:${pact_version}")
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||
}
|
||||
|
||||
bootRun {
|
||||
jvmArgs = ["-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"]
|
||||
}
|
||||
2
pact/pact-message-consumer/gradle.properties
Normal file
2
pact/pact-message-consumer/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
springboot_version=2.0.4.RELEASE
|
||||
pact_version=3.5.20
|
||||
5
pact/pact-message-consumer/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
pact/pact-message-consumer/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableRabbit
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.reflectoring;
|
||||
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MessageConsumer {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MessageConsumer.class);
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public MessageConsumer(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public void consumeStringMessage(String messageString) throws IOException {
|
||||
logger.info("Consuming message '{}'", messageString);
|
||||
UserCreatedMessage message = objectMapper.readValue(messageString, UserCreatedMessage.class);
|
||||
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
Set<ConstraintViolation<UserCreatedMessage>> violations = validator.validate(message);
|
||||
if(!violations.isEmpty()){
|
||||
throw new ConstraintViolationException(violations);
|
||||
}
|
||||
// pass message into business use case
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.amqp.core.Binding;
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MessageConsumerConfiguration {
|
||||
|
||||
private static final String QUEUE_NAME = "myQueue";
|
||||
|
||||
private static final String EXCHANGE_NAME = "myExchange";
|
||||
|
||||
@Bean
|
||||
public TopicExchange receiverExchange() {
|
||||
return new TopicExchange(EXCHANGE_NAME);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue queue() {
|
||||
return new Queue(QUEUE_NAME);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding(Queue eventReceivingQueue, TopicExchange receiverExchange) {
|
||||
return BindingBuilder
|
||||
.bind(eventReceivingQueue)
|
||||
.to(receiverExchange)
|
||||
.with("*.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
|
||||
MessageListenerAdapter listenerAdapter) {
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
|
||||
container.setConsumerStartTimeout(1000); // we don't want to wait in this example project
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
container.setQueueNames(QUEUE_NAME);
|
||||
container.setMessageListener(listenerAdapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageListenerAdapter listenerAdapter(MessageConsumer messageConsumer) {
|
||||
return new MessageListenerAdapter(messageConsumer, "consumeStringMessage");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageConsumer eventReceiver(ObjectMapper objectMapper) {
|
||||
return new MessageConsumer(objectMapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class User {
|
||||
|
||||
@NotNull
|
||||
private long id;
|
||||
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UserCreatedMessage {
|
||||
|
||||
@NotNull
|
||||
private String messageUuid;
|
||||
|
||||
@NotNull
|
||||
private User user;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import au.com.dius.pact.consumer.MessagePactBuilder;
|
||||
import au.com.dius.pact.consumer.MessagePactProviderRule;
|
||||
import au.com.dius.pact.consumer.Pact;
|
||||
import au.com.dius.pact.consumer.PactVerification;
|
||||
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
|
||||
import au.com.dius.pact.model.v3.messaging.MessagePact;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MessageConsumerTest {
|
||||
|
||||
@Rule
|
||||
public MessagePactProviderRule mockProvider = new MessagePactProviderRule(this);
|
||||
private byte[] currentMessage;
|
||||
|
||||
@Autowired
|
||||
private MessageConsumer messageConsumer;
|
||||
|
||||
@Pact(provider = "userservice", consumer = "userclient")
|
||||
public MessagePact userCreatedMessagePact(MessagePactBuilder builder) {
|
||||
PactDslJsonBody body = new PactDslJsonBody();
|
||||
body.stringType("messageUuid");
|
||||
body.object("user")
|
||||
.numberType("id", 42L)
|
||||
.stringType("name", "Zaphod Beeblebrox")
|
||||
.closeObject();
|
||||
|
||||
// @formatter:off
|
||||
return builder
|
||||
.expectsToReceive("a user created message")
|
||||
.withContent(body)
|
||||
.toPact();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
@PactVerification("userCreatedMessagePact")
|
||||
public void verifyCreatePersonPact() throws IOException {
|
||||
messageConsumer.consumeStringMessage(new String(this.currentMessage));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by the Pact framework.
|
||||
*/
|
||||
public void setMessage(byte[] message) {
|
||||
this.currentMessage = message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"consumer": {
|
||||
"name": "userclient"
|
||||
},
|
||||
"provider": {
|
||||
"name": "userservice"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"description": "a user created message",
|
||||
"metaData": {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
},
|
||||
"contents": {
|
||||
"messageUuid": "string",
|
||||
"user": {
|
||||
"id": 42,
|
||||
"name": "Zaphod Beeblebrox"
|
||||
}
|
||||
},
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$.messageUuid": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.user.id": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "number"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.user.name": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.20"
|
||||
}
|
||||
}
|
||||
}
|
||||
3
pact/pact-message-producer/README.md
Normal file
3
pact/pact-message-producer/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Consumer-Driven-Contract Test for a Spring Boot Message Provider
|
||||
|
||||
This module shows how to use Pact to implement a contract test for a message provider.
|
||||
46
pact/pact-message-producer/build.gradle
Normal file
46
pact/pact-message-producer/build.gradle
Normal file
@@ -0,0 +1,46 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-web')
|
||||
compile('org.springframework.boot:spring-boot-starter-amqp')
|
||||
compile('com.h2database:h2:1.4.196')
|
||||
compileOnly('org.projectlombok:lombok:1.18.2')
|
||||
|
||||
// add jaxb since it's no longer available in Java 11
|
||||
runtime('javax.xml.bind:jaxb-api:2.3.1')
|
||||
|
||||
// add javassist >= 3.23.1-GA since earlier versions are broken in Java 11
|
||||
// see https://github.com/jboss-javassist/javassist/issues/194
|
||||
runtime('org.javassist:javassist:3.23.1-GA')
|
||||
|
||||
// overriding bytebuddy to newer version that supports Java 11
|
||||
// see https://github.com/raphw/byte-buddy/issues/428
|
||||
testCompile('net.bytebuddy:byte-buddy:1.9.12')
|
||||
|
||||
testCompile("au.com.dius:pact-jvm-provider-junit_2.12:${pact_version}")
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||
}
|
||||
|
||||
bootRun {
|
||||
jvmArgs = ["-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"]
|
||||
}
|
||||
2
pact/pact-message-producer/gradle.properties
Normal file
2
pact/pact-message-producer/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
springboot_version=2.0.4.RELEASE
|
||||
pact_version=3.5.20
|
||||
5
pact/pact-message-producer/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
pact/pact-message-producer/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Takes a {@link UserCreatedMessage}, converts it to a {@link String} and sends it to be published.
|
||||
*/
|
||||
class MessageProducer {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MessageProducer.class);
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private MessagePublisher messagePublisher;
|
||||
|
||||
MessageProducer(ObjectMapper objectMapper, MessagePublisher messagePublisher) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.messagePublisher = messagePublisher;
|
||||
}
|
||||
|
||||
void produceUserCreatedMessage(UserCreatedMessage message) throws IOException {
|
||||
String stringMessage = objectMapper.writeValueAsString(message);
|
||||
messagePublisher.publishMessage(stringMessage, "user.created");
|
||||
logger.info("Published message '{}'", stringMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
class MessageProviderConfiguration {
|
||||
|
||||
@Bean
|
||||
TopicExchange topicExchange() {
|
||||
return new TopicExchange("myExchange");
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
MessageProducer messageProvider(ObjectMapper objectMapper, MessagePublisher publisher) {
|
||||
return new MessageProducer(objectMapper, publisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessagePublisher messagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
||||
return new MessagePublisher(rabbitTemplate, topicExchange);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SendMessageJob job(MessageProducer messageProducer) {
|
||||
return new SendMessageJob(messageProducer);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
|
||||
/**
|
||||
* Publishes a String message to RabbitMQ.
|
||||
*/
|
||||
class MessagePublisher {
|
||||
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
private TopicExchange topicExchange;
|
||||
|
||||
MessagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
this.topicExchange = topicExchange;
|
||||
}
|
||||
|
||||
void publishMessage(String message, String routingKey) {
|
||||
rabbitTemplate.convertAndSend(topicExchange.getName(), routingKey, message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
class SendMessageJob {
|
||||
|
||||
private Random random = new Random();
|
||||
|
||||
private MessageProducer messageProducer;
|
||||
|
||||
SendMessageJob(MessageProducer messageProducer) {
|
||||
this.messageProducer = messageProducer;
|
||||
}
|
||||
|
||||
/**
|
||||
* This scheduled job simulates the "real" business logic that should produce messages.
|
||||
*/
|
||||
@Scheduled(fixedDelay = 1000)
|
||||
void sendUserCreatedMessage() {
|
||||
try {
|
||||
UserCreatedMessage userCreatedMessage = UserCreatedMessage.builder()
|
||||
.messageUuid(UUID.randomUUID().toString())
|
||||
.user(User.builder()
|
||||
.id(random.nextLong())
|
||||
.name("Zaphpod Beeblebrox")
|
||||
.build())
|
||||
.build();
|
||||
messageProducer.produceUserCreatedMessage(userCreatedMessage);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
class User {
|
||||
|
||||
@NotNull
|
||||
private long id;
|
||||
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
class UserCreatedMessage {
|
||||
|
||||
@NotNull
|
||||
private String messageUuid;
|
||||
|
||||
@NotNull
|
||||
private User user;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import au.com.dius.pact.model.Interaction;
|
||||
import au.com.dius.pact.model.ProviderState;
|
||||
import au.com.dius.pact.provider.ConsumerInfo;
|
||||
import au.com.dius.pact.provider.ProviderInfo;
|
||||
import au.com.dius.pact.provider.ProviderVerifier;
|
||||
import au.com.dius.pact.provider.junit.target.AmqpTarget;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Custom implementation of {@link AmqpTarget} since with {@link AmqpTarget}
|
||||
* I had classpath troubles (see https://github.com/DiUS/pact-jvm/issues/763).
|
||||
* Use at own risk, since the implementation is not complete and may behave
|
||||
* a little different that the original class.
|
||||
*/
|
||||
public class CustomAmqpTarget extends AmqpTarget {
|
||||
|
||||
public CustomAmqpTarget(List<String> packagesToScan) {
|
||||
super(packagesToScan);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected ProviderVerifier setupVerifier(Interaction interaction, ProviderInfo provider, ConsumerInfo consumer) {
|
||||
ProviderVerifier verifier = new CustomProviderVerifier(getPackagesToScan());
|
||||
setupReporters(verifier, provider.getName(), interaction.getDescription());
|
||||
verifier.initialiseReporters(provider);
|
||||
verifier.reportVerificationForConsumer(consumer, provider);
|
||||
|
||||
if (!interaction.getProviderStates().isEmpty()) {
|
||||
for (ProviderState state : interaction.getProviderStates()) {
|
||||
verifier.reportStateForInteraction(state.getName(), provider, consumer, true);
|
||||
}
|
||||
}
|
||||
verifier.reportInteractionDescription(interaction);
|
||||
return verifier;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user