Compare commits
113 Commits
cdc-update
...
reflect-92
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e316c2dc04 | ||
|
|
c36bedd679 | ||
|
|
eff3c3c935 | ||
|
|
94f88c4489 | ||
|
|
bb7ac442b1 | ||
|
|
5da17c5a20 | ||
|
|
26739aada2 | ||
|
|
dabc601384 | ||
|
|
0be1335d24 | ||
|
|
1437ac4429 | ||
|
|
5b76cbac62 | ||
|
|
b6d167f4ae | ||
|
|
ec2bdc9327 | ||
|
|
5fe5deca66 | ||
|
|
3fb809911a | ||
|
|
3461549263 | ||
|
|
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 | ||
|
|
ddb3e08ee5 | ||
|
|
888a89e541 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
**/.idea/
|
||||
**/*.iml
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.projectlombok:lombok:1.16.20">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.16.20/ac76d9b956045631d1561a09289cbf472e077c01/lombok-1.16.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.16.20/69ebf81bb97bdb3c9581c171762bb4929cb5289c/lombok-1.16.20-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
13
.idea/modules/spring-boot-testing.iml
generated
13
.idea/modules/spring-boot-testing.iml
generated
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id="spring-boot-testing" external.linked.project.path="$MODULE_DIR$/../../spring-boot/spring-boot-testing" external.root.project.path="$MODULE_DIR$/../../spring-boot/spring-boot-testing" external.system.id="GRADLE" external.system.module.group="reflectoring.io" external.system.module.version="0.0.1-SNAPSHOT" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing">
|
||||
<excludeFolder url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing/out" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,5 +1,5 @@
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
- chmod +x build-all.sh
|
||||
- |
|
||||
if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(.md)|^(LICENSE)'
|
||||
then
|
||||
@@ -7,7 +7,12 @@ before_install:
|
||||
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() {
|
||||
}
|
||||
|
||||
}
|
||||
68
build-all.sh
Executable file
68
build-all.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/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/spring-boot-springdoc"
|
||||
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,5 +0,0 @@
|
||||
userservice:
|
||||
ribbon:
|
||||
eureka:
|
||||
enabled: false
|
||||
listOfServers: localhost:8080
|
||||
@@ -35,16 +35,29 @@ dependencies {
|
||||
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 = 'URL'
|
||||
pactBrokerUsername = 'USERNAME'
|
||||
pactBrokerPassword = 'PASSWORD'
|
||||
pactBrokerUrl = 'TODO'
|
||||
pactBrokerUsername = 'TODO'
|
||||
pactBrokerPassword = 'TODO'
|
||||
}
|
||||
}
|
||||
|
||||
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-4.10.1-bin.zip
|
||||
|
||||
@@ -11,6 +11,8 @@ 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.junit.jupiter.SpringExtension;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@ExtendWith(PactConsumerTestExt.class)
|
||||
@@ -27,6 +29,7 @@ class UserServiceConsumerTest {
|
||||
|
||||
@Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient")
|
||||
RequestResponsePact createPersonPact(PactDslWithProvider builder) {
|
||||
|
||||
// @formatter:off
|
||||
return builder
|
||||
.given("provider accepts a new person")
|
||||
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.demo;
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@SpringBootApplication
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import au.com.dius.pact.model.Interaction;
|
||||
import au.com.dius.pact.model.v3.messaging.Message;
|
||||
import au.com.dius.pact.provider.ConsumerInfo;
|
||||
import au.com.dius.pact.provider.PactVerifyProvider;
|
||||
import au.com.dius.pact.provider.ProviderInfo;
|
||||
import au.com.dius.pact.provider.ProviderVerifier;
|
||||
import org.reflections.Reflections;
|
||||
import org.reflections.scanners.MethodAnnotationsScanner;
|
||||
import org.reflections.util.ConfigurationBuilder;
|
||||
|
||||
public class CustomProviderVerifier extends ProviderVerifier {
|
||||
|
||||
private List<String> packagesToScan;
|
||||
|
||||
public CustomProviderVerifier(List<String> packagesToScan) {
|
||||
this.packagesToScan = packagesToScan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyResponseByInvokingProviderMethods(ProviderInfo providerInfo, ConsumerInfo consumer,
|
||||
Object interaction, String interactionMessage, Map failures) {
|
||||
try {
|
||||
|
||||
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
|
||||
.setScanners(new MethodAnnotationsScanner())
|
||||
.forPackages(packagesToScan.toArray(new String[]{}));
|
||||
|
||||
Reflections reflections = new Reflections(configurationBuilder);
|
||||
Set<Method> methodsAnnotatedWith = reflections.getMethodsAnnotatedWith(PactVerifyProvider.class);
|
||||
Set<Method> providerMethods = methodsAnnotatedWith.stream()
|
||||
.filter(m -> {
|
||||
PactVerifyProvider annotation = m.getAnnotation(PactVerifyProvider.class);
|
||||
return annotation.value().equals(((Interaction)interaction).getDescription());
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (providerMethods.isEmpty()) {
|
||||
throw new RuntimeException("No annotated methods were found for interaction " +
|
||||
"'${interaction.description}'. You need to provide a method annotated with " +
|
||||
"@PactVerifyProvider(\"${interaction.description}\") that returns the message contents.");
|
||||
} else {
|
||||
if (interaction instanceof Message) {
|
||||
verifyMessagePact(providerMethods, (Message) interaction, interactionMessage, failures);
|
||||
} else {
|
||||
throw new RuntimeException("only supports Message interactions!");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("verification failed", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import au.com.dius.pact.provider.PactVerifyProvider;
|
||||
import au.com.dius.pact.provider.junit.PactRunner;
|
||||
import au.com.dius.pact.provider.junit.Provider;
|
||||
import au.com.dius.pact.provider.junit.loader.PactFolder;
|
||||
import au.com.dius.pact.provider.junit.target.Target;
|
||||
import au.com.dius.pact.provider.junit.target.TestTarget;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(PactRunner.class)
|
||||
@Provider("userservice")
|
||||
@PactFolder("../pact-message-consumer/target/pacts")
|
||||
public class UserCreatedMessageProviderTest {
|
||||
|
||||
@TestTarget
|
||||
public final Target target = new CustomAmqpTarget(Collections.singletonList("io.reflectoring"));
|
||||
|
||||
private MessagePublisher publisher = Mockito.mock(MessagePublisher.class);
|
||||
|
||||
private MessageProducer messageProducer = new MessageProducer(new ObjectMapper(), publisher);
|
||||
|
||||
@PactVerifyProvider("a user created message")
|
||||
public String verifyUserCreatedMessage() throws IOException {
|
||||
// given
|
||||
doNothing().when(publisher).publishMessage(any(String.class), eq("user.created"));
|
||||
|
||||
// when
|
||||
UserCreatedMessage message = UserCreatedMessage.builder()
|
||||
.messageUuid(UUID.randomUUID().toString())
|
||||
.user(User.builder()
|
||||
.id(42L)
|
||||
.name("Zaphod Beeblebrox")
|
||||
.build())
|
||||
.build();
|
||||
messageProducer.produceUserCreatedMessage(message);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<String> messageCapture = ArgumentCaptor.forClass(String.class);
|
||||
verify(publisher, times(1)).publishMessage(messageCapture.capture(), eq("user.created"));
|
||||
|
||||
// returning the message
|
||||
return messageCapture.getValue();
|
||||
}
|
||||
}
|
||||
24
pact/pact-node-messages/.gitignore
vendored
Normal file
24
pact/pact-node-messages/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.nyc_output/*
|
||||
coverage/*
|
||||
2543
pact/pact-node-messages/package-lock.json
generated
Normal file
2543
pact/pact-node-messages/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user