Compare commits
101 Commits
pact-messa
...
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 | ||
|
|
d60f95b6c0 | ||
|
|
52bc347d12 |
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:spring-boot-testing" external.linked.project.path="$MODULE_DIR$/../../spring-boot/spring-boot-testing" external.root.project.path="$MODULE_DIR$/../.." 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,6 +1,5 @@
|
||||
#Sun Jul 30 16:58:54 CEST 2017
|
||||
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-all.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
gradlew.bat → aws/aws-hello-world/gradlew.bat
vendored
Normal file → Executable file
0
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 "+++"
|
||||
@@ -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
|
||||
|
||||
5
logging/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
logging/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
gradlew → logging/gradlew
vendored
0
gradlew → logging/gradlew
vendored
@@ -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")
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
# Consumer-Driven-Contract Test for a Spring Boot Provider
|
||||
|
||||
This repo contains an example of consumer-driven-contract testing for a Spring
|
||||
Boot API provider. The corresponding consumer to the contract is
|
||||
implemented in the module `pact-angular`.
|
||||
# Consumer-Driven-Contract Test for a Spring Boot Message Consumer
|
||||
|
||||
The contract is created and verified with [Pact](https://docs.pact.io/).
|
||||
This module shows how to use Pact to implement a contract test for a message provider.
|
||||
|
||||
Before running the build, you need to follow the instructions on the [consumer-side](../pact-angular/)
|
||||
to create the consumer-driven contract file (pact file).
|
||||
## Companion Articles
|
||||
[Testing a Spring Message Producer and Consumer against a Contract with Pact](https://reflectoring.io/cdc-pact-messages/)
|
||||
|
||||
## Running the application
|
||||
|
||||
The interesting part in this code base is the class `UserControllerProviderTest`.
|
||||
You can run the tests with `gradlew test` on Windows or `./gradlew test` on Unix.
|
||||
@@ -1,11 +1 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
driverClassName: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
|
||||
logging.level.org.hibernate.SQL: OFF
|
||||
spring.rabbitmq.connection-timeout=10
|
||||
@@ -13,7 +13,7 @@ apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 1.8
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@@ -21,11 +21,18 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
||||
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')
|
||||
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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,13 +13,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UserCreatedMessageConsumer {
|
||||
public class MessageConsumer {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(UserCreatedMessageConsumer.class);
|
||||
private Logger logger = LoggerFactory.getLogger(MessageConsumer.class);
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public UserCreatedMessageConsumer(ObjectMapper objectMapper) {
|
||||
public MessageConsumer(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public class MessageConsumerConfiguration {
|
||||
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);
|
||||
@@ -47,13 +48,13 @@ public class MessageConsumerConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageListenerAdapter listenerAdapter(UserCreatedMessageConsumer userCreatedMessageConsumer) {
|
||||
return new MessageListenerAdapter(userCreatedMessageConsumer, "consumeStringMessage");
|
||||
public MessageListenerAdapter listenerAdapter(MessageConsumer messageConsumer) {
|
||||
return new MessageListenerAdapter(messageConsumer, "consumeStringMessage");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserCreatedMessageConsumer eventReceiver(ObjectMapper objectMapper) {
|
||||
return new UserCreatedMessageConsumer(objectMapper);
|
||||
public MessageConsumer eventReceiver(ObjectMapper objectMapper) {
|
||||
return new MessageConsumer(objectMapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class UserCreatedMessageConsumerTest {
|
||||
public class MessageConsumerTest {
|
||||
|
||||
@Rule
|
||||
public MessagePactProviderRule mockProvider = new MessagePactProviderRule(this);
|
||||
private byte[] currentMessage;
|
||||
|
||||
@Autowired
|
||||
private UserCreatedMessageConsumer userCreatedMessageConsumer;
|
||||
private MessageConsumer messageConsumer;
|
||||
|
||||
@Pact(provider = "userservice", consumer = "userclient")
|
||||
public MessagePact userCreatedMessagePact(MessagePactBuilder builder) {
|
||||
@@ -46,7 +46,7 @@ public class UserCreatedMessageConsumerTest {
|
||||
@Test
|
||||
@PactVerification("userCreatedMessagePact")
|
||||
public void verifyCreatePersonPact() throws IOException {
|
||||
userCreatedMessageConsumer.consumeStringMessage(new String(this.currentMessage));
|
||||
messageConsumer.consumeStringMessage(new String(this.currentMessage));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -14,8 +14,8 @@
|
||||
"contents": {
|
||||
"messageUuid": "string",
|
||||
"user": {
|
||||
"name": "Zaphod Beeblebrox",
|
||||
"id": 42
|
||||
"id": 42,
|
||||
"name": "Zaphod Beeblebrox"
|
||||
}
|
||||
},
|
||||
"matchingRules": {
|
||||
|
||||
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.
|
||||
@@ -13,7 +13,7 @@ apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 1.8
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@@ -21,11 +21,22 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
||||
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')
|
||||
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')
|
||||
}
|
||||
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,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,18 +18,18 @@ class MessageProviderConfiguration {
|
||||
|
||||
|
||||
@Bean
|
||||
UserCreatedMessageProvider messageProvider(ObjectMapper objectMapper, UserCreatedMessagePublisher publisher) {
|
||||
return new UserCreatedMessageProvider(objectMapper, publisher);
|
||||
MessageProducer messageProvider(ObjectMapper objectMapper, MessagePublisher publisher) {
|
||||
return new MessageProducer(objectMapper, publisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserCreatedMessagePublisher messagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
||||
return new UserCreatedMessagePublisher(rabbitTemplate, topicExchange);
|
||||
MessagePublisher messagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
||||
return new MessagePublisher(rabbitTemplate, topicExchange);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SendMessageJob job(UserCreatedMessageProvider messageProvider) {
|
||||
return new SendMessageJob(messageProvider);
|
||||
SendMessageJob job(MessageProducer messageProducer) {
|
||||
return new SendMessageJob(messageProducer);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
/**
|
||||
* Publishes a String message to RabbitMQ.
|
||||
*/
|
||||
class UserCreatedMessagePublisher {
|
||||
class MessagePublisher {
|
||||
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
private TopicExchange topicExchange;
|
||||
|
||||
UserCreatedMessagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
||||
MessagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
this.topicExchange = topicExchange;
|
||||
}
|
||||
@@ -10,10 +10,10 @@ class SendMessageJob {
|
||||
|
||||
private Random random = new Random();
|
||||
|
||||
private UserCreatedMessageProvider messageProvider;
|
||||
private MessageProducer messageProducer;
|
||||
|
||||
SendMessageJob(UserCreatedMessageProvider messageProvider) {
|
||||
this.messageProvider = messageProvider;
|
||||
SendMessageJob(MessageProducer messageProducer) {
|
||||
this.messageProducer = messageProducer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,7 +29,7 @@ class SendMessageJob {
|
||||
.name("Zaphpod Beeblebrox")
|
||||
.build())
|
||||
.build();
|
||||
messageProvider.sendUserCreatedMessage(userCreatedMessage);
|
||||
messageProducer.produceUserCreatedMessage(userCreatedMessage);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -10,6 +10,12 @@ 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) {
|
||||
@@ -8,16 +8,12 @@ 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.AmqpTarget;
|
||||
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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(PactRunner.class)
|
||||
@@ -28,9 +24,9 @@ public class UserCreatedMessageProviderTest {
|
||||
@TestTarget
|
||||
public final Target target = new CustomAmqpTarget(Collections.singletonList("io.reflectoring"));
|
||||
|
||||
private UserCreatedMessagePublisher publisher = Mockito.mock(UserCreatedMessagePublisher.class);
|
||||
private MessagePublisher publisher = Mockito.mock(MessagePublisher.class);
|
||||
|
||||
private UserCreatedMessageProvider messageProvider = new UserCreatedMessageProvider(new ObjectMapper(), publisher);
|
||||
private MessageProducer messageProducer = new MessageProducer(new ObjectMapper(), publisher);
|
||||
|
||||
@PactVerifyProvider("a user created message")
|
||||
public String verifyUserCreatedMessage() throws IOException {
|
||||
@@ -45,7 +41,7 @@ public class UserCreatedMessageProviderTest {
|
||||
.name("Zaphod Beeblebrox")
|
||||
.build())
|
||||
.build();
|
||||
messageProvider.sendUserCreatedMessage(message);
|
||||
messageProducer.produceUserCreatedMessage(message);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<String> messageCapture = ArgumentCaptor.forClass(String.class);
|
||||
@@ -1,15 +0,0 @@
|
||||
# Consumer-Driven-Contract Test for a Spring Boot Provider
|
||||
|
||||
This repo contains an example of consumer-driven-contract testing for a Spring
|
||||
Boot API provider. The corresponding consumer to the contract is
|
||||
implemented in the module `pact-angular`.
|
||||
|
||||
The contract is created and verified with [Pact](https://docs.pact.io/).
|
||||
|
||||
Before running the build, you need to follow the instructions on the [consumer-side](../pact-angular/)
|
||||
to create the consumer-driven contract file (pact file).
|
||||
|
||||
## Running the application
|
||||
|
||||
The interesting part in this code base is the class `UserControllerProviderTest`.
|
||||
You can run the tests with `gradlew test` on Windows or `./gradlew test` on Unix.
|
||||
@@ -1,11 +0,0 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
driverClassName: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
|
||||
logging.level.org.hibernate.SQL: OFF
|
||||
@@ -1,31 +0,0 @@
|
||||
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 UserCreatedMessageProvider {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(UserCreatedMessageProvider.class);
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private UserCreatedMessagePublisher userCreatedMessagePublisher;
|
||||
|
||||
UserCreatedMessageProvider(ObjectMapper objectMapper, UserCreatedMessagePublisher userCreatedMessagePublisher) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.userCreatedMessagePublisher = userCreatedMessagePublisher;
|
||||
}
|
||||
|
||||
void sendUserCreatedMessage(UserCreatedMessage message) throws IOException {
|
||||
String stringMessage = objectMapper.writeValueAsString(message);
|
||||
userCreatedMessagePublisher.publishMessage(stringMessage, "user.created");
|
||||
logger.info("Published message '{}'", stringMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"consumer": {
|
||||
"name": "userclient"
|
||||
},
|
||||
"provider": {
|
||||
"name": "userservice"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"description": "a user created message",
|
||||
"metaData": {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
},
|
||||
"contents": {
|
||||
"messageUuid": "string",
|
||||
"user": {
|
||||
"name": "Zaphod Beeblebrox",
|
||||
"id": 42
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
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
17
pact/pact-node-messages/package.json
Normal file
17
pact/pact-node-messages/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "pact-node-messages",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test:pact:consumer": "mocha src/consumer/*.spec.js --exit",
|
||||
"test:pact:provider": "mocha src/provider/*.spec.js --exit",
|
||||
"publish:pact": "node pact/publish.js"
|
||||
},
|
||||
"author": "Tom Hombergs",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@pact-foundation/pact": "^7.0.3",
|
||||
"mocha": "^5.2.0"
|
||||
}
|
||||
}
|
||||
12
pact/pact-node-messages/pact/publish.js
Normal file
12
pact/pact-node-messages/pact/publish.js
Normal file
@@ -0,0 +1,12 @@
|
||||
let publisher = require('@pact-foundation/pact-node');
|
||||
let path = require('path');
|
||||
|
||||
let opts = {
|
||||
pactFilesOrDirs: [path.resolve(process.cwd(), 'pacts')],
|
||||
pactBroker: 'https://adesso.pact.dius.com.au/',
|
||||
pactBrokerUsername: process.env.PACT_USERNAME,
|
||||
pactBrokerPassword: process.env.PACT_PASSWORD,
|
||||
consumerVersion: '2.0.0'
|
||||
};
|
||||
|
||||
publisher.publishPacts(opts).then(() => console.log("Pacts successfully published"));
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"consumer": {
|
||||
"name": "node-message-consumer"
|
||||
},
|
||||
"provider": {
|
||||
"name": "node-message-provider"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"description": "a hero created message",
|
||||
"providerStates": [
|
||||
|
||||
],
|
||||
"contents": {
|
||||
"id": 42,
|
||||
"name": "Superman",
|
||||
"superpower": "flying",
|
||||
"universe": "DC"
|
||||
},
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$.id": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
]
|
||||
},
|
||||
"$.name": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
]
|
||||
},
|
||||
"$.superpower": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
]
|
||||
},
|
||||
"$.universe": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "^(DC|Marvel)$"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"metaData": {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
35
pact/pact-node-messages/src/common/hero-created-event.js
Normal file
35
pact/pact-node-messages/src/common/hero-created-event.js
Normal file
@@ -0,0 +1,35 @@
|
||||
class HeroCreatedEvent {
|
||||
|
||||
constructor(name, superpower, universe, id) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.superpower = superpower;
|
||||
this.universe = universe;
|
||||
}
|
||||
|
||||
static validateUniverse(event) {
|
||||
if (typeof event.universe !== 'string') {
|
||||
throw new Error(`Hero universe must be a string! Invalid value: ${event.universe}`)
|
||||
}
|
||||
}
|
||||
|
||||
static validateSuperpower(event) {
|
||||
if (typeof event.superpower !== 'string') {
|
||||
throw new Error(`Hero superpower must be a string! Invalid value: ${event.superpower}`)
|
||||
}
|
||||
}
|
||||
|
||||
static validateName(event) {
|
||||
if (typeof event.name !== 'string') {
|
||||
throw new Error(`Hero name must be a string! Invalid value: ${event.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
static validateId(event) {
|
||||
if (typeof event.id !== 'number') {
|
||||
throw new Error(`Hero id must be a number! Invalid value: ${event.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HeroCreatedEvent;
|
||||
15
pact/pact-node-messages/src/consumer/hero-event-handler.js
Normal file
15
pact/pact-node-messages/src/consumer/hero-event-handler.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const HeroCreatedEvent = require('../common/hero-created-event');
|
||||
|
||||
exports.HeroEventHandler = {
|
||||
handleHeroCreatedEvent: (message) => {
|
||||
|
||||
HeroCreatedEvent.validateId(message);
|
||||
HeroCreatedEvent.validateName(message);
|
||||
HeroCreatedEvent.validateSuperpower(message);
|
||||
HeroCreatedEvent.validateUniverse(message);
|
||||
|
||||
// pass message into business logic
|
||||
// note that the business logic should be mocked away for the contract test
|
||||
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
const {MessageConsumerPact, Matchers, synchronousBodyHandler} = require('@pact-foundation/pact');
|
||||
const {HeroEventHandler} = require('./hero-event-handler');
|
||||
const path = require('path');
|
||||
|
||||
describe("message consumer", () => {
|
||||
|
||||
const messagePact = new MessageConsumerPact({
|
||||
consumer: "node-message-consumer",
|
||||
provider: "node-message-provider",
|
||||
dir: path.resolve(process.cwd(), "pacts"),
|
||||
pactfileWriteMode: "update",
|
||||
logLevel: "info",
|
||||
});
|
||||
|
||||
describe("'hero created' message Handler", () => {
|
||||
|
||||
it("should accept a valid hero created message", (done) => {
|
||||
messagePact
|
||||
.expectsToReceive("a hero created message")
|
||||
.withContent({
|
||||
id: Matchers.like(42),
|
||||
name: Matchers.like("Superman"),
|
||||
superpower: Matchers.like("flying"),
|
||||
universe: Matchers.term({generate: "DC", matcher: "^(DC|Marvel)$"})
|
||||
})
|
||||
.withMetadata({
|
||||
"content-type": "application/json",
|
||||
})
|
||||
.verify(synchronousBodyHandler(HeroEventHandler.handleHeroCreatedEvent))
|
||||
.then(() => done(), (error) => done(error));
|
||||
}).timeout(5000);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
const HeroCreatedEvent = require('../common/hero-created-event');
|
||||
|
||||
exports.CreateHeroEventProducer = {
|
||||
produceHeroCreatedEvent: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(new HeroCreatedEvent("Superman", "Flying", "DC", 42));
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
const {MessageProviderPact} = require('@pact-foundation/pact');
|
||||
const {CreateHeroEventProducer} = require('./hero-event-producer');
|
||||
const path = require('path');
|
||||
|
||||
describe("message producer", () => {
|
||||
|
||||
const messagePact = new MessageProviderPact({
|
||||
messageProviders: {
|
||||
"a hero created message": () => CreateHeroEventProducer.produceHeroCreatedEvent(),
|
||||
},
|
||||
log: path.resolve(process.cwd(), "logs", "pact.log"),
|
||||
logLevel: "info",
|
||||
provider: "node-message-provider",
|
||||
|
||||
pactUrls: [path.resolve(process.cwd(), "pacts", "node-message-consumer-node-message-provider.json")],
|
||||
|
||||
// Pact seems not to load a pact file from a pact broker, so we have to make do with the local pact file
|
||||
// see https://github.com/pact-foundation/pact-js/issues/248
|
||||
// pactBrokerUrl: "https://adesso.pact.dius.com.au",
|
||||
// pactBrokerUsername: process.env.PACT_USERNAME,
|
||||
// pactBrokerPassword: process.env.PACT_PASSWORD,
|
||||
// publishVerificationResult: true,
|
||||
// providerVersion: '1.0.0',
|
||||
tags: ['latest']
|
||||
});
|
||||
|
||||
describe("'hero created' message producer", () => {
|
||||
|
||||
it("should create a valid hero created message", (done) => {
|
||||
messagePact
|
||||
.verify()
|
||||
.then(() => done(), (error) => done(error));
|
||||
}).timeout(5000);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
21
pact/pact-node-provider/.gitignore
vendored
Normal file
21
pact/pact-node-provider/.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# 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*
|
||||
7
pact/pact-node-provider/README.md
Normal file
7
pact/pact-node-provider/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Node Pact example
|
||||
|
||||
This example project shows how to setup an Express server with Node and create
|
||||
a contract test for the provider side of a contract with Pact Node.
|
||||
|
||||
## Relevant Blog Post
|
||||
[Step By Step Tutorial for Implementing Consumer-Driven Contracts for a Node Express Server with Pact](https://reflectoring.io/pact-node-provider/)
|
||||
29
pact/pact-node-provider/app.js
Normal file
29
pact/pact-node-provider/app.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const logger = require('morgan');
|
||||
|
||||
const indexRouter = require('./routes/index');
|
||||
const usersRouter = require('./routes/users');
|
||||
const heroesRouter = require('./routes/heroes');
|
||||
const graphqlRouter = require('./routes/graphql');
|
||||
const providerStateRouter = require ('./routes/provider_state');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/', indexRouter);
|
||||
app.use('/users', usersRouter);
|
||||
app.use('/heroes', heroesRouter);
|
||||
app.use('/graphql', graphqlRouter);
|
||||
|
||||
if (process.env.PACT_MODE === 'true') {
|
||||
app.use('/provider-state', providerStateRouter);
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
90
pact/pact-node-provider/bin/www
Normal file
90
pact/pact-node-provider/bin/www
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('test:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
2802
pact/pact-node-provider/package-lock.json
generated
Normal file
2802
pact/pact-node-provider/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
pact/pact-node-provider/package.json
Normal file
23
pact/pact-node-provider/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "test",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www",
|
||||
"pact:providerTests": "node ./pact/provider_tests.js",
|
||||
"pact:providerTests:graphql": "node ./pact/provider_tests_graphql.js",
|
||||
"test:pact": "start-server-and-test start http://localhost:3000 pact:providerTests",
|
||||
"test:pact:graphql": "start-server-and-test start http://localhost:3000 pact:providerTests:graphql"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.3",
|
||||
"debug": "~2.6.9",
|
||||
"express": "~4.16.0",
|
||||
"express-graphql": "^0.7.1",
|
||||
"morgan": "~1.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pact-foundation/pact": "7.0.3",
|
||||
"start-server-and-test": "^1.7.5"
|
||||
}
|
||||
}
|
||||
17
pact/pact-node-provider/pact/provider_tests.js
Normal file
17
pact/pact-node-provider/pact/provider_tests.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const { Verifier } = require('@pact-foundation/pact');
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
let opts = {
|
||||
providerBaseUrl: 'http://localhost:3000',
|
||||
provider: 'hero-provider',
|
||||
pactBrokerUrl: 'https://adesso.pact.dius.com.au',
|
||||
pactBrokerUsername: process.env.PACT_USERNAME,
|
||||
pactBrokerPassword: process.env.PACT_PASSWORD,
|
||||
publishVerificationResult: true,
|
||||
providerVersion: packageJson.version,
|
||||
providerStatesSetupUrl: 'http://localhost:3000/provider-state'
|
||||
};
|
||||
|
||||
new Verifier().verifyProvider(opts).then(function () {
|
||||
console.log("Pacts successfully verified!");
|
||||
});
|
||||
16
pact/pact-node-provider/pact/provider_tests_graphql.js
Normal file
16
pact/pact-node-provider/pact/provider_tests_graphql.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const { Verifier } = require('@pact-foundation/pact');
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
let opts = {
|
||||
providerBaseUrl: 'http://localhost:3000',
|
||||
provider: 'graphql-hero-provider',
|
||||
pactBrokerUrl: 'https://adesso.pact.dius.com.au',
|
||||
pactBrokerUsername: process.env.PACT_USERNAME,
|
||||
pactBrokerPassword: process.env.PACT_PASSWORD,
|
||||
publishVerificationResult: true,
|
||||
providerVersion: packageJson.version,
|
||||
};
|
||||
|
||||
new Verifier().verifyProvider(opts).then(function () {
|
||||
console.log("Pacts successfully verified!");
|
||||
});
|
||||
13
pact/pact-node-provider/public/index.html
Normal file
13
pact/pact-node-provider/public/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Express</title>
|
||||
<link rel="stylesheet" href="/stylesheets/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Express</h1>
|
||||
<p>Welcome to Express</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
8
pact/pact-node-provider/public/stylesheets/style.css
Normal file
8
pact/pact-node-provider/public/stylesheets/style.css
Normal file
@@ -0,0 +1,8 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
36
pact/pact-node-provider/routes/graphql.js
Normal file
36
pact/pact-node-provider/routes/graphql.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const graphqlHTTP = require('express-graphql');
|
||||
const {buildSchema} = require("graphql");
|
||||
|
||||
const heroesSchema = buildSchema(`
|
||||
type Query {
|
||||
hero(id: Int!): Hero
|
||||
}
|
||||
|
||||
type Hero {
|
||||
id: Int!
|
||||
name: String!
|
||||
superpower: String!
|
||||
universe: String!
|
||||
}
|
||||
`);
|
||||
|
||||
const getHero = function () {
|
||||
return {
|
||||
id: 42,
|
||||
name: "Superman",
|
||||
superpower: "Flying",
|
||||
universe: "DC"
|
||||
}
|
||||
};
|
||||
|
||||
const root = {
|
||||
hero: getHero
|
||||
};
|
||||
|
||||
const router = graphqlHTTP({
|
||||
schema: heroesSchema,
|
||||
graphiql: true,
|
||||
rootValue: root
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
27
pact/pact-node-provider/routes/heroes.js
Normal file
27
pact/pact-node-provider/routes/heroes.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/:hero_id')
|
||||
.get(function (req, res) {
|
||||
const heroId = parseInt(req.params['hero_id']);
|
||||
res.status(200);
|
||||
res.json({
|
||||
id: heroId,
|
||||
superpower: 'flying',
|
||||
name: 'Superman',
|
||||
universe: 'DC'
|
||||
});
|
||||
});
|
||||
|
||||
router.route('/')
|
||||
.post(function (req, res) {
|
||||
res.status(201);
|
||||
res.json({
|
||||
id: 42,
|
||||
superpower: 'flying',
|
||||
name: 'Superman',
|
||||
universe: 'DC'
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
9
pact/pact-node-provider/routes/index.js
Normal file
9
pact/pact-node-provider/routes/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
13
pact/pact-node-provider/routes/provider_state.js
Normal file
13
pact/pact-node-provider/routes/provider_state.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/')
|
||||
.post(function (req, res) {
|
||||
const consumer = req.query['consumer'];
|
||||
const providerState = req.query['state'];
|
||||
// imagine we're setting the server into a certain state
|
||||
res.send(`changed to provider state "${providerState}" for consumer "${consumer}"`);
|
||||
res.status(200);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
9
pact/pact-node-provider/routes/users.js
Normal file
9
pact/pact-node-provider/routes/users.js
Normal file
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
23
pact/pact-react-consumer/.gitignore
vendored
Normal file
23
pact/pact-react-consumer/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# 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*
|
||||
|
||||
/.idea
|
||||
8
pact/pact-react-consumer/README.md
Normal file
8
pact/pact-react-consumer/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# React Pact example
|
||||
|
||||
This example project shows how to setup a React application to use [Pact](http://pact.io)
|
||||
in order to create Pact files from a consumer test and validate the
|
||||
a consumer against the Pact.
|
||||
|
||||
## Relevant Blog Post
|
||||
[Step By Step Tutorial for Implementing Consumer-Driven Contracts for a React App with Pact and Jest](https://reflectoring.io/pact-react-consumer/)
|
||||
15763
pact/pact-react-consumer/package-lock.json
generated
Normal file
15763
pact/pact-react-consumer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
pact/pact-react-consumer/package.json
Normal file
40
pact/pact-react-consumer/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "pact-react-consumer",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"apollo-cache-inmemory": "^1.3.9",
|
||||
"apollo-client": "^2.4.5",
|
||||
"apollo-link-http": "^1.5.5",
|
||||
"axios": "0.18.0",
|
||||
"graphql": "^14.0.2",
|
||||
"graphql-tag": "^2.10.0",
|
||||
"node-fetch": "^2.2.1",
|
||||
"react": "^16.5.2",
|
||||
"react-dom": "^16.5.2",
|
||||
"react-scripts": "2.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pact-foundation/pact": "7.0.3",
|
||||
"@pact-foundation/pact-node": "6.20.0",
|
||||
"cross-env": "^5.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"test:pact": "cross-env CI=true react-scripts test --runInBand --setupFiles ./pact/setup.js --setupTestFrameworkScriptFile ./pact/jest-wrapper.js --testMatch \"**/*.test.pact.js\"",
|
||||
"test:pact:graphql": "cross-env CI=true react-scripts test --runInBand --setupFiles ./pact/setup-graphql.js --setupTestFrameworkScriptFile ./pact/jest-wrapper.js --testMatch \"**/*.test.graphql.pact.js\"",
|
||||
"publish:pact": "node pact/publish.js",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
||||
7
pact/pact-react-consumer/pact/jest-wrapper.js
Normal file
7
pact/pact-react-consumer/pact/jest-wrapper.js
Normal file
@@ -0,0 +1,7 @@
|
||||
beforeAll((done) => {
|
||||
global.provider.setup().then(() => done());
|
||||
});
|
||||
|
||||
afterAll((done) => {
|
||||
global.provider.finalize().then(() => done());
|
||||
});
|
||||
13
pact/pact-react-consumer/pact/publish.js
Normal file
13
pact/pact-react-consumer/pact/publish.js
Normal file
@@ -0,0 +1,13 @@
|
||||
let publisher = require('@pact-foundation/pact-node');
|
||||
let path = require('path');
|
||||
|
||||
let opts = {
|
||||
providerBaseUrl: 'http://localhost:8080',
|
||||
pactFilesOrDirs: [path.resolve(process.cwd(), 'pacts')],
|
||||
pactBroker: 'https://adesso.pact.dius.com.au/',
|
||||
pactBrokerUsername: process.env.PACT_USERNAME,
|
||||
pactBrokerPassword: process.env.PACT_PASSWORD,
|
||||
consumerVersion: '2.0.0'
|
||||
};
|
||||
|
||||
publisher.publishPacts(opts).then(() => console.log("Pacts successfully published"));
|
||||
16
pact/pact-react-consumer/pact/setup-graphql.js
Normal file
16
pact/pact-react-consumer/pact/setup-graphql.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const path = require('path');
|
||||
const Pact = require('@pact-foundation/pact').Pact;
|
||||
|
||||
global.port = 8080;
|
||||
global.provider = new Pact({
|
||||
cors: true,
|
||||
port: global.port,
|
||||
log: path.resolve(process.cwd(), 'logs', 'pact.log'),
|
||||
loglevel: 'debug',
|
||||
dir: path.resolve(process.cwd(), 'pacts'),
|
||||
spec: 2,
|
||||
pactfileWriteMode: 'update',
|
||||
consumer: 'graphql-hero-consumer',
|
||||
provider: 'graphql-hero-provider',
|
||||
host: '127.0.0.1'
|
||||
});
|
||||
16
pact/pact-react-consumer/pact/setup.js
Normal file
16
pact/pact-react-consumer/pact/setup.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const path = require('path');
|
||||
const Pact = require('@pact-foundation/pact').Pact;
|
||||
|
||||
global.port = 8080;
|
||||
global.provider = new Pact({
|
||||
cors: true,
|
||||
port: global.port,
|
||||
log: path.resolve(process.cwd(), 'logs', 'pact.log'),
|
||||
loglevel: 'debug',
|
||||
dir: path.resolve(process.cwd(), 'pacts'),
|
||||
spec: 2,
|
||||
pactfileWriteMode: 'update',
|
||||
consumer: 'hero-consumer',
|
||||
provider: 'hero-provider',
|
||||
host: '127.0.0.1'
|
||||
});
|
||||
BIN
pact/pact-react-consumer/public/favicon.ico
Normal file
BIN
pact/pact-react-consumer/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
40
pact/pact-react-consumer/public/index.html
Normal file
40
pact/pact-react-consumer/public/index.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
15
pact/pact-react-consumer/public/manifest.json
Normal file
15
pact/pact-react-consumer/public/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
32
pact/pact-react-consumer/src/App.css
Normal file
32
pact/pact-react-consumer/src/App.css
Normal file
@@ -0,0 +1,32 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
height: 40vmin;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
28
pact/pact-react-consumer/src/App.js
Normal file
28
pact/pact-react-consumer/src/App.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import React, { Component } from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
10
pact/pact-react-consumer/src/App.test.js
Normal file
10
pact/pact-react-consumer/src/App.test.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App/>, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
52
pact/pact-react-consumer/src/graphql/hero.service.graphql.js
Normal file
52
pact/pact-react-consumer/src/graphql/hero.service.graphql.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import {ApolloClient} from "apollo-client"
|
||||
import {InMemoryCache} from "apollo-cache-inmemory"
|
||||
import {HttpLink} from "apollo-link-http"
|
||||
import gql from "graphql-tag"
|
||||
import Hero from "../hero";
|
||||
|
||||
class GraphQLHeroService {
|
||||
|
||||
constructor(baseUrl, port, fetch) {
|
||||
this.client = new ApolloClient({
|
||||
link: new HttpLink({
|
||||
uri: `${baseUrl}:${port}/graphql`,
|
||||
fetch: fetch
|
||||
}),
|
||||
cache: new InMemoryCache()
|
||||
});
|
||||
}
|
||||
|
||||
getHero(heroId) {
|
||||
if (heroId == null) {
|
||||
throw new Error("heroId must not be null!");
|
||||
}
|
||||
return this.client.query({
|
||||
query: gql`
|
||||
query GetHero($heroId: Int!) {
|
||||
hero(id: $heroId) {
|
||||
name
|
||||
superpower
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
heroId: heroId
|
||||
}
|
||||
}).then((response) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const hero = new Hero(response.data.hero.name, response.data.hero.superpower, null, heroId);
|
||||
Hero.validateName(hero);
|
||||
Hero.validateSuperpower(hero);
|
||||
resolve(hero);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export default GraphQLHeroService;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user