Compare commits
1 Commits
pact-messa
...
pact-jvm-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8216c90168 |
@@ -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,11 +1,5 @@
|
|||||||
before_install:
|
before_install:
|
||||||
- chmod +x gradlew
|
- chmod +x gradlew
|
||||||
- |
|
|
||||||
if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(.md)|^(LICENSE)'
|
|
||||||
then
|
|
||||||
echo "Not running CI since only docs were changed."
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
language: java
|
language: java
|
||||||
|
|
||||||
|
|||||||
15
deprecated/pact-feign-consumer/README.md
Normal file
15
deprecated/pact-feign-consumer/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 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.
|
||||||
46
deprecated/pact-feign-consumer/build.gradle
Normal file
46
deprecated/pact-feign-consumer/build.gradle
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
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,4 +1,4 @@
|
|||||||
package io.reflectoring.springbootlogging;
|
package com.example.demo;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -7,7 +7,7 @@ import org.springframework.test.context.junit4.SpringRunner;
|
|||||||
|
|
||||||
@RunWith(SpringRunner.class)
|
@RunWith(SpringRunner.class)
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
public class SpringBootLoggingApplicationTests {
|
public class DemoApplicationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void contextLoads() {
|
public void contextLoads() {
|
||||||
16
deprecated/pact-spring-data-rest-provider/README.md
Normal file
16
deprecated/pact-spring-data-rest-provider/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 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,6 +1,6 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
springBootVersion = '2.0.2.RELEASE'
|
springBootVersion = '1.5.4.RELEASE'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -13,22 +13,23 @@ buildscript {
|
|||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
apply plugin: 'org.springframework.boot'
|
apply plugin: 'org.springframework.boot'
|
||||||
apply plugin: 'io.spring.dependency-management'
|
|
||||||
|
|
||||||
group = 'reflectoring.io'
|
|
||||||
version = '0.0.1-SNAPSHOT'
|
version = '0.0.1-SNAPSHOT'
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
||||||
compile('org.springframework.boot:spring-boot-starter-web')
|
compile('org.springframework.boot:spring-boot-starter-data-rest')
|
||||||
compileOnly('org.projectlombok:lombok')
|
compile group: 'au.com.dius', name: 'pact-jvm-provider-junit_2.11', version: '3.5.2'
|
||||||
runtime('com.h2database:h2')
|
compile('com.h2database:h2:1.4.196')
|
||||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||||
testCompile 'org.junit.jupiter:junit-jupiter-engine:5.2.0'
|
}
|
||||||
|
|
||||||
|
bootRun{
|
||||||
|
jvmArgs = ["-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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,13 +1,12 @@
|
|||||||
package io.reflectoring;
|
package com.example.demo;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class Application {
|
public class DemoApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(Application.class, args);
|
SpringApplication.run(DemoApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.framework.PactDatabaseStatesAutoConfiguration
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
insert into address (id, street) values (1, 'Elm Street');
|
||||||
|
insert into address (id, street) values (2, 'High Street');
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
create table CUSTOMER (
|
||||||
|
id NUMBER,
|
||||||
|
name VARCHAR
|
||||||
|
);
|
||||||
|
|
||||||
|
create table ADDRESS (
|
||||||
|
id NUMBER,
|
||||||
|
customer_id NUMBER,
|
||||||
|
street VARCHAR,
|
||||||
|
FOREIGN KEY (customer_id) REFERENCES CUSTOMER(id)
|
||||||
|
);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
insert into address (id, street) values (1, 'Elm Street');
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# 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/)
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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,18 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
userservice:
|
userservice:
|
||||||
|
ribbon:
|
||||||
|
eureka:
|
||||||
|
enabled: false
|
||||||
|
listOfServers: localhost:8080
|
||||||
|
|
||||||
|
rootservice:
|
||||||
ribbon:
|
ribbon:
|
||||||
eureka:
|
eureka:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
apply plugin: 'org.springframework.boot'
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@@ -8,43 +10,18 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
|
||||||
id "au.com.dius.pact" version "3.5.20"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'java'
|
|
||||||
apply plugin: 'org.springframework.boot'
|
|
||||||
apply plugin: 'io.spring.dependency-management'
|
|
||||||
|
|
||||||
version '1.0.0.SNAPSHOT'
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyManagement {
|
|
||||||
imports {
|
|
||||||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springcloud_version}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
compile("org.springframework.boot:spring-boot-starter-data-jpa:${springboot_version}")
|
||||||
compile('org.springframework.boot:spring-boot-starter-web')
|
compile("org.springframework.boot:spring-boot-starter-web:${springboot_version}")
|
||||||
compile('org.springframework.cloud:spring-cloud-starter-openfeign')
|
compile("org.springframework.cloud:spring-cloud-starter-feign:1.4.1.RELEASE")
|
||||||
compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
|
|
||||||
compile('com.h2database:h2:1.4.196')
|
compile('com.h2database:h2:1.4.196')
|
||||||
testCompile('org.codehaus.groovy:groovy-all:2.4.6')
|
testCompile('org.codehaus.groovy:groovy-all:2.4.6')
|
||||||
testCompile("au.com.dius:pact-jvm-consumer-junit5_2.12:${pact_version}")
|
compile("au.com.dius:pact-jvm-consumer-junit_2.11:3.5.16")
|
||||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
testCompile("org.springframework.boot:spring-boot-starter-test:${springboot_version}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pact {
|
|
||||||
publish {
|
|
||||||
pactDirectory = 'target/pacts'
|
|
||||||
pactBrokerUrl = 'URL'
|
|
||||||
pactBrokerUsername = 'USERNAME'
|
|
||||||
pactBrokerPassword = 'PASSWORD'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
springboot_version=2.0.4.RELEASE
|
springboot_version=1.5.9.RELEASE
|
||||||
springcloud_version=Finchley.SR1
|
verifier_version=1.2.2.RELEASE
|
||||||
pact_version=3.5.20
|
|
||||||
@@ -2,12 +2,10 @@ package io.reflectoring;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
import org.springframework.cloud.netflix.feign.EnableFeignClients;
|
||||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableFeignClients
|
@EnableFeignClients
|
||||||
@RibbonClient(name = "userservice", configuration = RibbonConfiguration.class)
|
|
||||||
public class ConsumerApplication {
|
public class ConsumerApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package io.reflectoring;
|
|
||||||
|
|
||||||
import com.netflix.client.config.IClientConfig;
|
|
||||||
import com.netflix.loadbalancer.IRule;
|
|
||||||
import com.netflix.loadbalancer.RandomRule;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
|
|
||||||
public class RibbonConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public IRule ribbonRule(IClientConfig config) {
|
|
||||||
return new RandomRule();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package io.reflectoring;
|
package io.reflectoring;
|
||||||
|
|
||||||
import org.springframework.cloud.openfeign.FeignClient;
|
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package io.reflectoring.dsl;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
|
||||||
|
import au.com.dius.pact.consumer.dsl.PactDslJsonRootValue;
|
||||||
|
|
||||||
|
public class PactDslJsonBodyLikeMapper {
|
||||||
|
|
||||||
|
private static final Set<Class<?>> SIMPLE_TYPES = new HashSet<>(Arrays.asList(
|
||||||
|
Boolean.class,
|
||||||
|
boolean.class,
|
||||||
|
Integer.class,
|
||||||
|
int.class,
|
||||||
|
Double.class,
|
||||||
|
double.class,
|
||||||
|
Float.class,
|
||||||
|
float.class,
|
||||||
|
BigDecimal.class,
|
||||||
|
Number.class,
|
||||||
|
String.class,
|
||||||
|
Long.class,
|
||||||
|
long.class
|
||||||
|
));
|
||||||
|
|
||||||
|
public static PactDslJsonBody like(Object object) {
|
||||||
|
return like(object, new PactDslJsonBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PactDslJsonBody like(Object object, PactDslJsonBody body) {
|
||||||
|
try {
|
||||||
|
return recursiveLike(object, body);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new IllegalStateException("could not create PactDslJsonBody due to exception!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PactDslJsonBody recursiveLike(Object object, PactDslJsonBody body) throws IllegalAccessException {
|
||||||
|
Field[] fields = object.getClass().getDeclaredFields();
|
||||||
|
for (Field field : fields) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
|
||||||
|
Object fieldValue = field.get(object);
|
||||||
|
|
||||||
|
if (fieldValue == null) {
|
||||||
|
// fields with null values will not be mapped
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSimpleType(field.getType())) {
|
||||||
|
mapSimpleFieldWithName(field.getName(), fieldValue, body);
|
||||||
|
} else if (isCollectionType(field.getType())) {
|
||||||
|
mapCollectionField(field.getName(), (Collection) fieldValue, body);
|
||||||
|
} else {
|
||||||
|
mapComplexField(field.getName(), fieldValue, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void mapSimpleFieldWithName(String fieldName, Object fieldValue, PactDslJsonBody body) throws IllegalAccessException {
|
||||||
|
Class<?> type = fieldValue.getClass();
|
||||||
|
if (String.class == type) {
|
||||||
|
body.stringType(fieldName, (String) fieldValue);
|
||||||
|
} else if (Boolean.class == type || boolean.class == type) {
|
||||||
|
body.booleanType(fieldName, (Boolean) fieldValue);
|
||||||
|
} else if (Integer.class == type || int.class == type || Long.class == type || long.class == type) {
|
||||||
|
body.integerType(fieldName, (Integer) fieldValue);
|
||||||
|
} else if (Double.class == type || double.class == type) {
|
||||||
|
body.decimalType(fieldName, (Double) fieldValue);
|
||||||
|
} else if (Float.class == type || float.class == type) {
|
||||||
|
body.decimalType(fieldName, ((Float) fieldValue).doubleValue());
|
||||||
|
} else if (BigDecimal.class == type) {
|
||||||
|
body.decimalType(fieldName, (BigDecimal) fieldValue);
|
||||||
|
} else if (Number.class.isAssignableFrom(type)) {
|
||||||
|
body.numberType(fieldName, (Number) fieldValue);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(String.format("field '%s' of type '%s' is not a simple field", fieldName, type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PactDslJsonRootValue getRootValueForType(Class<?> type) {
|
||||||
|
if (String.class == type) {
|
||||||
|
return PactDslJsonRootValue.stringType();
|
||||||
|
} else if (Boolean.class == type || boolean.class == type) {
|
||||||
|
return PactDslJsonRootValue.booleanType();
|
||||||
|
} else if (Integer.class == type || int.class == type || Long.class == type || long.class == type) {
|
||||||
|
return PactDslJsonRootValue.integerType();
|
||||||
|
} else if (Double.class == type || double.class == type) {
|
||||||
|
return PactDslJsonRootValue.decimalType();
|
||||||
|
} else if (Float.class == type || float.class == type) {
|
||||||
|
return PactDslJsonRootValue.decimalType();
|
||||||
|
} else if (BigDecimal.class == type) {
|
||||||
|
return PactDslJsonRootValue.decimalType();
|
||||||
|
} else if (Number.class.isAssignableFrom(type)) {
|
||||||
|
return PactDslJsonRootValue.numberType();
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(String.format("unsupported type '%s'", type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void mapCollectionField(String fieldName, Collection<?> collection, PactDslJsonBody body) throws IllegalAccessException {
|
||||||
|
if (collection.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("matching empty lists is not supported!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> listType = collection.iterator().next().getClass();
|
||||||
|
|
||||||
|
if (isSimpleType(listType)) {
|
||||||
|
PactDslJsonRootValue rootValue = getRootValueForType(listType);
|
||||||
|
body.eachLike(fieldName, rootValue);
|
||||||
|
} else if (isCollectionType(listType)) {
|
||||||
|
throw new IllegalArgumentException("collections of collections are not supported");
|
||||||
|
} else {
|
||||||
|
PactDslJsonBody nestedBody = body.eachLike(fieldName);
|
||||||
|
for (Object complexObject : collection) {
|
||||||
|
mapComplexFieldWithoutOpeningObject(complexObject, nestedBody);
|
||||||
|
}
|
||||||
|
nestedBody.closeObject().closeArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void mapComplexField(String fieldName, Object fieldValue, PactDslJsonBody body) throws IllegalAccessException {
|
||||||
|
PactDslJsonBody nestedBody = body.object(fieldName);
|
||||||
|
mapComplexFieldWithoutOpeningObject(fieldValue, nestedBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void mapComplexFieldWithoutOpeningObject(Object fieldValue, PactDslJsonBody nestedBody) throws IllegalAccessException {
|
||||||
|
recursiveLike(fieldValue, nestedBody);
|
||||||
|
nestedBody.closeObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSimpleType(Class<?> type) {
|
||||||
|
return SIMPLE_TYPES.contains(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isCollectionType(Class<?> type) {
|
||||||
|
return Collection.class.isAssignableFrom(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,85 +1,83 @@
|
|||||||
package io.reflectoring;
|
package io.reflectoring;
|
||||||
|
|
||||||
import au.com.dius.pact.consumer.Pact;
|
import au.com.dius.pact.consumer.Pact;
|
||||||
|
import au.com.dius.pact.consumer.PactProviderRuleMk2;
|
||||||
|
import au.com.dius.pact.consumer.PactVerification;
|
||||||
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
|
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
|
||||||
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
|
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
|
||||||
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
|
|
||||||
import au.com.dius.pact.consumer.junit5.PactTestFor;
|
|
||||||
import au.com.dius.pact.model.RequestResponsePact;
|
import au.com.dius.pact.model.RequestResponsePact;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.Rule;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ExtendWith(PactConsumerTestExt.class)
|
@RunWith(SpringRunner.class)
|
||||||
@ExtendWith(SpringExtension.class)
|
@SpringBootTest(properties = {
|
||||||
@PactTestFor(providerName = "userservice", port = "8888")
|
// overriding provider address
|
||||||
@SpringBootTest({
|
"userservice.ribbon.listOfServers: localhost:8888"
|
||||||
// overriding provider address
|
|
||||||
"userservice.ribbon.listOfServers: localhost:8888"
|
|
||||||
})
|
})
|
||||||
class UserServiceConsumerTest {
|
public class UserServiceConsumerTest {
|
||||||
|
|
||||||
@Autowired
|
@Rule
|
||||||
private UserClient userClient;
|
public PactProviderRuleMk2 stubProvider = new PactProviderRuleMk2("userservice", "localhost", 8888, this);
|
||||||
|
|
||||||
@Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient")
|
@Autowired
|
||||||
RequestResponsePact createPersonPact(PactDslWithProvider builder) {
|
private UserClient userClient;
|
||||||
// @formatter:off
|
|
||||||
return builder
|
|
||||||
.given("provider accepts a new person")
|
|
||||||
.uponReceiving("a request to POST a person")
|
|
||||||
.path("/user-service/users")
|
|
||||||
.method("POST")
|
|
||||||
.willRespondWith()
|
|
||||||
.status(201)
|
|
||||||
.matchHeader("Content-Type", "application/json")
|
|
||||||
.body(new PactDslJsonBody()
|
|
||||||
.integerType("id", 42))
|
|
||||||
.toPact();
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pact(state = "person 42 exists", provider = "userservice", consumer = "userclient")
|
@Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient")
|
||||||
RequestResponsePact updatePersonPact(PactDslWithProvider builder) {
|
public RequestResponsePact createPersonPact(PactDslWithProvider builder) {
|
||||||
// @formatter:off
|
return builder
|
||||||
return builder
|
.given("provider accepts a new person")
|
||||||
.given("person 42 exists")
|
.uponReceiving("a request to POST a person")
|
||||||
.uponReceiving("a request to PUT a person")
|
.path("/user-service/users")
|
||||||
.path("/user-service/users/42")
|
.method("POST")
|
||||||
.method("PUT")
|
.willRespondWith()
|
||||||
.willRespondWith()
|
.status(201)
|
||||||
.status(200)
|
.matchHeader("Content-Type", "application/json")
|
||||||
.matchHeader("Content-Type", "application/json")
|
.body(new PactDslJsonBody()
|
||||||
.body(new PactDslJsonBody()
|
.integerType("id", 42))
|
||||||
.stringType("firstName", "Zaphod")
|
.toPact();
|
||||||
.stringType("lastName", "Beeblebrox"))
|
}
|
||||||
.toPact();
|
|
||||||
// @formatter:on
|
@Pact(state = "person 42 exists", provider = "userservice", consumer = "userclient")
|
||||||
}
|
public RequestResponsePact updatePersonPact(PactDslWithProvider builder) {
|
||||||
|
return builder
|
||||||
|
.given("person 42 exists")
|
||||||
|
.uponReceiving("a request to PUT a person")
|
||||||
|
.path("/user-service/users/42")
|
||||||
|
.method("PUT")
|
||||||
|
.willRespondWith()
|
||||||
|
.status(200)
|
||||||
|
.matchHeader("Content-Type", "application/json")
|
||||||
|
.body(new PactDslJsonBody()
|
||||||
|
.stringType("firstName", "Zaphod")
|
||||||
|
.stringType("lastName", "Beeblebrox"))
|
||||||
|
.toPact();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@PactTestFor(pactMethod = "createPersonPact")
|
@PactVerification(fragment = "createPersonPact")
|
||||||
void verifyCreatePersonPact() {
|
public void verifyCreatePersonPact() {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setFirstName("Zaphod");
|
user.setFirstName("Zaphod");
|
||||||
user.setLastName("Beeblebrox");
|
user.setLastName("Beeblebrox");
|
||||||
IdObject id = userClient.createUser(user);
|
IdObject id = userClient.createUser(user);
|
||||||
assertThat(id.getId()).isEqualTo(42);
|
assertThat(id.getId()).isEqualTo(42);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@PactTestFor(pactMethod = "updatePersonPact")
|
@PactVerification(fragment = "updatePersonPact")
|
||||||
void verifyUpdatePersonPact() {
|
public void verifyUpdatePersonPact() {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setFirstName("Zaphod");
|
user.setFirstName("Zaphod");
|
||||||
user.setLastName("Beeblebrox");
|
user.setLastName("Beeblebrox");
|
||||||
User updatedUser = userClient.updateUser(42L, user);
|
User updatedUser = userClient.updateUser(42L, user);
|
||||||
assertThat(updatedUser.getFirstName()).isEqualTo("Zaphod");
|
assertThat(updatedUser.getFirstName()).isEqualTo("Zaphod");
|
||||||
assertThat(updatedUser.getLastName()).isEqualTo("Beeblebrox");
|
assertThat(updatedUser.getLastName()).isEqualTo("Beeblebrox");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package io.reflectoring.dsl;
|
||||||
|
|
||||||
|
public class Nested {
|
||||||
|
|
||||||
|
private String stringField = "nested string";
|
||||||
|
private Integer integerField = 42;
|
||||||
|
private String nullField = null;
|
||||||
|
|
||||||
|
public String getStringField() {
|
||||||
|
return stringField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStringField(String stringField) {
|
||||||
|
this.stringField = stringField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getIntegerField() {
|
||||||
|
return integerField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIntegerField(Integer integerField) {
|
||||||
|
this.integerField = integerField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNullField() {
|
||||||
|
return nullField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNullField(String nullField) {
|
||||||
|
this.nullField = nullField;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package io.reflectoring.dsl;
|
||||||
|
|
||||||
|
import au.com.dius.pact.consumer.Pact;
|
||||||
|
import au.com.dius.pact.consumer.PactProviderRuleMk2;
|
||||||
|
import au.com.dius.pact.consumer.PactVerification;
|
||||||
|
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
|
||||||
|
import au.com.dius.pact.consumer.dsl.PactDslJsonRootValue;
|
||||||
|
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
|
||||||
|
import au.com.dius.pact.model.RequestResponsePact;
|
||||||
|
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(properties = {
|
||||||
|
// overriding provider address
|
||||||
|
"rootservice.ribbon.listOfServers: localhost:8888"
|
||||||
|
})
|
||||||
|
public class PactDslJsonBodyLikeMapperConsumerTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public PactProviderRuleMk2 stubProvider = new PactProviderRuleMk2("testprovider", "localhost", 8888, this);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RootClient rootClient;
|
||||||
|
|
||||||
|
@Pact(state = "teststate", provider = "testprovider", consumer = "testclient")
|
||||||
|
public RequestResponsePact createPact(PactDslWithProvider builder) {
|
||||||
|
return builder
|
||||||
|
.given("teststate")
|
||||||
|
.uponReceiving("a POST request with a Root object")
|
||||||
|
.path("/root")
|
||||||
|
.method("POST")
|
||||||
|
// .body(PactDslJsonBodyLikeMapper.like(new PactDslJsonBodyLikeMapperTest.Root()))
|
||||||
|
.willRespondWith()
|
||||||
|
.status(201)
|
||||||
|
.matchHeader("Content-Type", "application/json")
|
||||||
|
.body(PactDslJsonBodyLikeMapper.like(new Root()))
|
||||||
|
.toPact();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pact(state = "teststate2", provider = "testprovider", consumer = "testclient")
|
||||||
|
public RequestResponsePact createPact2(PactDslWithProvider builder) {
|
||||||
|
return builder
|
||||||
|
.given("teststate2")
|
||||||
|
.uponReceiving("a POST request with a Root object")
|
||||||
|
.path("/root")
|
||||||
|
.method("POST")
|
||||||
|
.willRespondWith()
|
||||||
|
.status(201)
|
||||||
|
.matchHeader("Content-Type", "application/json")
|
||||||
|
.body(new PactDslJsonBody()
|
||||||
|
.eachLike("arrayField", PactDslJsonRootValue.numberType()))
|
||||||
|
.toPact();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@PactVerification(fragment = "createPact")
|
||||||
|
public void verifyPact() {
|
||||||
|
rootClient.createRoot(new Root());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@PactVerification(fragment = "createPact2")
|
||||||
|
public void verifyPact2() {
|
||||||
|
rootClient.createRoot(new Root());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package io.reflectoring.dsl;
|
||||||
|
|
||||||
|
|
||||||
|
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class PactDslJsonBodyLikeMapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createsMatchersForAllFields() {
|
||||||
|
Root object = new Root();
|
||||||
|
PactDslJsonBody jsonBody = PactDslJsonBodyLikeMapper.like(object);
|
||||||
|
assertNoMatcher(jsonBody, ".nullField");
|
||||||
|
assertMatcherType(jsonBody, ".stringField", "type");
|
||||||
|
assertMatcherType(jsonBody, ".booleanField", "type");
|
||||||
|
assertMatcherType(jsonBody, ".primitiveBooleanField", "type");
|
||||||
|
assertMatcherType(jsonBody, ".integerField", "integer");
|
||||||
|
assertMatcherType(jsonBody, ".primitiveIntegerField", "integer");
|
||||||
|
assertMatcherType(jsonBody, ".doubleField", "decimal");
|
||||||
|
assertMatcherType(jsonBody, ".primitiveDoubleField", "decimal");
|
||||||
|
assertMatcherType(jsonBody, ".floatField", "decimal");
|
||||||
|
assertMatcherType(jsonBody, ".primitiveFloatField", "decimal");
|
||||||
|
assertMatcherType(jsonBody, ".bigDecimalField", "decimal");
|
||||||
|
assertMatcherType(jsonBody, ".numberField", "number");
|
||||||
|
assertMatcherType(jsonBody, ".nested.stringField", "type");
|
||||||
|
assertMatcherType(jsonBody, ".nested.integerField", "integer");
|
||||||
|
assertNoMatcher(jsonBody, ".nested.nullField");
|
||||||
|
assertMatcherType(jsonBody, ".complexListField[*].stringField", "type");
|
||||||
|
assertMatcherType(jsonBody, ".complexListField[*].integerField", "integer");
|
||||||
|
assertMatcherType(jsonBody, ".simpleListField[*]", "integer");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMatcherType(PactDslJsonBody jsonBody, String fieldName, String expectedMatcher) {
|
||||||
|
assertMatcher(jsonBody, fieldName);
|
||||||
|
assertEquals(String.format("expected matcher for field '%s' to be of type '%s'", fieldName, expectedMatcher),
|
||||||
|
ImmutableMap.of("match", expectedMatcher),
|
||||||
|
jsonBody.getMatchers().getMatchingRules().get(fieldName).getRules().get(0).toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMatcher(PactDslJsonBody jsonBody, String fieldName) {
|
||||||
|
assertNotNull(String.format("expected a matcher for field '%s'", fieldName),
|
||||||
|
jsonBody.getMatchers().getMatchingRules().get(fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNoMatcher(PactDslJsonBody jsonBody, String fieldName) {
|
||||||
|
assertNull(jsonBody.getMatchers().getMatchingRules().get(fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package io.reflectoring.dsl;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Root {
|
||||||
|
|
||||||
|
private String nullField = null;
|
||||||
|
private String stringField = "string";
|
||||||
|
private Boolean booleanField = Boolean.TRUE;
|
||||||
|
private Integer integerField = 1;
|
||||||
|
private Double doubleField = 1d;
|
||||||
|
private Float floatField = 1f;
|
||||||
|
private BigDecimal bigDecimalField = BigDecimal.ONE;
|
||||||
|
private boolean primitiveBooleanField = true;
|
||||||
|
private int primitiveIntegerField = 1;
|
||||||
|
private double primitiveDoubleField = 1d;
|
||||||
|
private float primitiveFloatField = 1f;
|
||||||
|
private Number numberField = BigInteger.valueOf(1L);
|
||||||
|
private Nested nested = new Nested();
|
||||||
|
private List<Nested> complexListField = Arrays.asList(new Nested(), new Nested());
|
||||||
|
private List<Integer> simpleListField = Arrays.asList(1,2);
|
||||||
|
|
||||||
|
public String getNullField() {
|
||||||
|
return nullField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNullField(String nullField) {
|
||||||
|
this.nullField = nullField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStringField() {
|
||||||
|
return stringField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStringField(String stringField) {
|
||||||
|
this.stringField = stringField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getBooleanField() {
|
||||||
|
return booleanField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBooleanField(Boolean booleanField) {
|
||||||
|
this.booleanField = booleanField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getIntegerField() {
|
||||||
|
return integerField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIntegerField(Integer integerField) {
|
||||||
|
this.integerField = integerField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getDoubleField() {
|
||||||
|
return doubleField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDoubleField(Double doubleField) {
|
||||||
|
this.doubleField = doubleField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Float getFloatField() {
|
||||||
|
return floatField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFloatField(Float floatField) {
|
||||||
|
this.floatField = floatField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getBigDecimalField() {
|
||||||
|
return bigDecimalField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBigDecimalField(BigDecimal bigDecimalField) {
|
||||||
|
this.bigDecimalField = bigDecimalField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrimitiveBooleanField() {
|
||||||
|
return primitiveBooleanField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrimitiveBooleanField(boolean primitiveBooleanField) {
|
||||||
|
this.primitiveBooleanField = primitiveBooleanField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPrimitiveIntegerField() {
|
||||||
|
return primitiveIntegerField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrimitiveIntegerField(int primitiveIntegerField) {
|
||||||
|
this.primitiveIntegerField = primitiveIntegerField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getPrimitiveDoubleField() {
|
||||||
|
return primitiveDoubleField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrimitiveDoubleField(double primitiveDoubleField) {
|
||||||
|
this.primitiveDoubleField = primitiveDoubleField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPrimitiveFloatField() {
|
||||||
|
return primitiveFloatField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrimitiveFloatField(float primitiveFloatField) {
|
||||||
|
this.primitiveFloatField = primitiveFloatField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Number getNumberField() {
|
||||||
|
return numberField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumberField(Number numberField) {
|
||||||
|
this.numberField = numberField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Nested getNested() {
|
||||||
|
return nested;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNested(Nested nested) {
|
||||||
|
this.nested = nested;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Nested> getComplexListField() {
|
||||||
|
return complexListField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComplexListField(List<Nested> complexListField) {
|
||||||
|
this.complexListField = complexListField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getSimpleListField() {
|
||||||
|
return simpleListField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSimpleListField(List<Integer> simpleListField) {
|
||||||
|
this.simpleListField = simpleListField;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package io.reflectoring.dsl;
|
||||||
|
|
||||||
|
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
@FeignClient(name = "rootservice")
|
||||||
|
public interface RootClient {
|
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.POST, path = "/root")
|
||||||
|
Root createRoot(@RequestBody Root root);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
{
|
|
||||||
"provider": {
|
|
||||||
"name": "userservice"
|
|
||||||
},
|
|
||||||
"consumer": {
|
|
||||||
"name": "userclient"
|
|
||||||
},
|
|
||||||
"interactions": [
|
|
||||||
{
|
|
||||||
"description": "a request to PUT a person",
|
|
||||||
"request": {
|
|
||||||
"method": "PUT",
|
|
||||||
"path": "/user-service/users/42"
|
|
||||||
},
|
|
||||||
"response": {
|
|
||||||
"status": 200,
|
|
||||||
"headers": {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"firstName": "Zaphod",
|
|
||||||
"lastName": "Beeblebrox"
|
|
||||||
},
|
|
||||||
"matchingRules": {
|
|
||||||
"body": {
|
|
||||||
"$.firstName": {
|
|
||||||
"matchers": [
|
|
||||||
{
|
|
||||||
"match": "type"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combine": "AND"
|
|
||||||
},
|
|
||||||
"$.lastName": {
|
|
||||||
"matchers": [
|
|
||||||
{
|
|
||||||
"match": "type"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combine": "AND"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"header": {
|
|
||||||
"Content-Type": {
|
|
||||||
"matchers": [
|
|
||||||
{
|
|
||||||
"match": "regex",
|
|
||||||
"regex": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combine": "AND"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"providerStates": [
|
|
||||||
{
|
|
||||||
"name": "person 42 exists"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "a request to POST a person",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/user-service/users"
|
|
||||||
},
|
|
||||||
"response": {
|
|
||||||
"status": 201,
|
|
||||||
"headers": {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"id": 42
|
|
||||||
},
|
|
||||||
"matchingRules": {
|
|
||||||
"header": {
|
|
||||||
"Content-Type": {
|
|
||||||
"matchers": [
|
|
||||||
{
|
|
||||||
"match": "regex",
|
|
||||||
"regex": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combine": "AND"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"$.id": {
|
|
||||||
"matchers": [
|
|
||||||
{
|
|
||||||
"match": "integer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"combine": "AND"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"providerStates": [
|
|
||||||
{
|
|
||||||
"name": "provider accepts a new person"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"pactSpecification": {
|
|
||||||
"version": "3.0.0"
|
|
||||||
},
|
|
||||||
"pact-jvm": {
|
|
||||||
"version": "3.5.20"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,36 +0,0 @@
|
|||||||
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 = 1.8
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
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')
|
|
||||||
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"]
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
springboot_version=2.0.4.RELEASE
|
|
||||||
pact_version=3.5.20
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
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.setConnectionFactory(connectionFactory);
|
|
||||||
container.setQueueNames(QUEUE_NAME);
|
|
||||||
container.setMessageListener(listenerAdapter);
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public MessageListenerAdapter listenerAdapter(UserCreatedMessageConsumer userCreatedMessageConsumer) {
|
|
||||||
return new MessageListenerAdapter(userCreatedMessageConsumer, "consumeStringMessage");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public UserCreatedMessageConsumer eventReceiver(ObjectMapper objectMapper) {
|
|
||||||
return new UserCreatedMessageConsumer(objectMapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package io.reflectoring;
|
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class User {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private long id;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package io.reflectoring;
|
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class UserCreatedMessage {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String messageUuid;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private User user;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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 UserCreatedMessageConsumer {
|
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(UserCreatedMessageConsumer.class);
|
|
||||||
|
|
||||||
private ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
public UserCreatedMessageConsumer(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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
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 UserCreatedMessageConsumerTest {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public MessagePactProviderRule mockProvider = new MessagePactProviderRule(this);
|
|
||||||
private byte[] currentMessage;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserCreatedMessageConsumer userCreatedMessageConsumer;
|
|
||||||
|
|
||||||
@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 {
|
|
||||||
userCreatedMessageConsumer.consumeStringMessage(new String(this.currentMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called by the Pact framework.
|
|
||||||
*/
|
|
||||||
public void setMessage(byte[] message) {
|
|
||||||
this.currentMessage = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,35 +0,0 @@
|
|||||||
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 = 1.8
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
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')
|
|
||||||
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"]
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
springboot_version=2.0.4.RELEASE
|
|
||||||
pact_version=3.5.20
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package io.reflectoring;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
public class DemoApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(DemoApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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
|
|
||||||
UserCreatedMessageProvider messageProvider(ObjectMapper objectMapper, UserCreatedMessagePublisher publisher) {
|
|
||||||
return new UserCreatedMessageProvider(objectMapper, publisher);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
UserCreatedMessagePublisher messagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
|
||||||
return new UserCreatedMessagePublisher(rabbitTemplate, topicExchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
SendMessageJob job(UserCreatedMessageProvider messageProvider) {
|
|
||||||
return new SendMessageJob(messageProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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 UserCreatedMessageProvider messageProvider;
|
|
||||||
|
|
||||||
SendMessageJob(UserCreatedMessageProvider messageProvider) {
|
|
||||||
this.messageProvider = messageProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
messageProvider.sendUserCreatedMessage(userCreatedMessage);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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,24 +0,0 @@
|
|||||||
package io.reflectoring;
|
|
||||||
|
|
||||||
import org.springframework.amqp.core.TopicExchange;
|
|
||||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publishes a String message to RabbitMQ.
|
|
||||||
*/
|
|
||||||
class UserCreatedMessagePublisher {
|
|
||||||
|
|
||||||
private RabbitTemplate rabbitTemplate;
|
|
||||||
|
|
||||||
private TopicExchange topicExchange;
|
|
||||||
|
|
||||||
UserCreatedMessagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
|
||||||
this.rabbitTemplate = rabbitTemplate;
|
|
||||||
this.topicExchange = topicExchange;
|
|
||||||
}
|
|
||||||
|
|
||||||
void publishMessage(String message, String routingKey) {
|
|
||||||
rabbitTemplate.convertAndSend(topicExchange.getName(), routingKey, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
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.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)
|
|
||||||
@Provider("userservice")
|
|
||||||
@PactFolder("../pact-message-consumer/target/pacts")
|
|
||||||
public class UserCreatedMessageProviderTest {
|
|
||||||
|
|
||||||
@TestTarget
|
|
||||||
public final Target target = new CustomAmqpTarget(Collections.singletonList("io.reflectoring"));
|
|
||||||
|
|
||||||
private UserCreatedMessagePublisher publisher = Mockito.mock(UserCreatedMessagePublisher.class);
|
|
||||||
|
|
||||||
private UserCreatedMessageProvider messageProvider = new UserCreatedMessageProvider(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();
|
|
||||||
messageProvider.sendUserCreatedMessage(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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
|
ext {
|
||||||
|
springBootVersion = '1.5.4.RELEASE'
|
||||||
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}")
|
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
apply plugin: 'org.springframework.boot'
|
apply plugin: 'org.springframework.boot'
|
||||||
apply plugin: 'io.spring.dependency-management'
|
|
||||||
|
|
||||||
version = '0.0.1-SNAPSHOT'
|
version = '0.0.1-SNAPSHOT'
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.8
|
||||||
@@ -24,7 +26,8 @@ dependencies {
|
|||||||
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
||||||
compile('org.springframework.boot:spring-boot-starter-web')
|
compile('org.springframework.boot:spring-boot-starter-web')
|
||||||
compile('com.h2database:h2:1.4.196')
|
compile('com.h2database:h2:1.4.196')
|
||||||
testCompile("au.com.dius:pact-jvm-provider-junit5_2.12:${pact_version}")
|
testCompile('au.com.dius:pact-jvm-provider-spring_2.12:3.5.11')
|
||||||
|
testCompile('junit:junit:4.12')
|
||||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
springboot_version=2.0.4.RELEASE
|
|
||||||
pact_version=3.5.20
|
|
||||||
@@ -26,7 +26,7 @@ public class UserController {
|
|||||||
|
|
||||||
@PutMapping(path = "/user-service/users/{id}")
|
@PutMapping(path = "/user-service/users/{id}")
|
||||||
public ResponseEntity<User> updateUser(@RequestBody @Valid User user, @PathVariable long id) {
|
public ResponseEntity<User> updateUser(@RequestBody @Valid User user, @PathVariable long id) {
|
||||||
User userFromDb = userRepository.findById(id).get();
|
User userFromDb = userRepository.findOne(id);
|
||||||
userFromDb.updateFrom(user);
|
userFromDb.updateFrom(user);
|
||||||
userFromDb = userRepository.save(userFromDb);
|
userFromDb = userRepository.save(userFromDb);
|
||||||
return ResponseEntity.ok(userFromDb);
|
return ResponseEntity.ok(userFromDb);
|
||||||
@@ -34,7 +34,7 @@ public class UserController {
|
|||||||
|
|
||||||
@GetMapping(path = "/user-service/users/{id}")
|
@GetMapping(path = "/user-service/users/{id}")
|
||||||
public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
|
public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
|
||||||
return ResponseEntity.ok(userRepository.findById(id).get());
|
return ResponseEntity.ok(userRepository.findOne(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,44 @@
|
|||||||
package io.reflectoring;
|
package io.reflectoring;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import au.com.dius.pact.provider.junit.Provider;
|
import au.com.dius.pact.provider.junit.Provider;
|
||||||
import au.com.dius.pact.provider.junit.State;
|
import au.com.dius.pact.provider.junit.State;
|
||||||
import au.com.dius.pact.provider.junit.loader.PactFolder;
|
import au.com.dius.pact.provider.junit.loader.PactFolder;
|
||||||
import au.com.dius.pact.provider.junit5.HttpTestTarget;
|
import au.com.dius.pact.provider.junit.target.HttpTarget;
|
||||||
import au.com.dius.pact.provider.junit5.PactVerificationContext;
|
import au.com.dius.pact.provider.junit.target.Target;
|
||||||
import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider;
|
import au.com.dius.pact.provider.junit.target.TestTarget;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import au.com.dius.pact.provider.spring.SpringRestPactRunner;
|
||||||
import org.junit.jupiter.api.TestTemplate;
|
import io.reflectoring.User;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import io.reflectoring.UserRepository;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.Mockito.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
@ExtendWith(SpringExtension.class)
|
@RunWith(SpringRestPactRunner.class)
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = {
|
|
||||||
"server.port=8080"
|
|
||||||
})
|
|
||||||
@Provider("userservice")
|
@Provider("userservice")
|
||||||
@PactFolder("../pact-angular/pacts")
|
@PactFolder("../pact-angular/pacts")
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = {
|
||||||
|
"server.port=8080"
|
||||||
|
})
|
||||||
public class UserControllerProviderTest {
|
public class UserControllerProviderTest {
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
|
||||||
@BeforeEach
|
@TestTarget
|
||||||
void setupTestTarget(PactVerificationContext context) {
|
public final Target target = new HttpTarget(8080);
|
||||||
context.setTarget(new HttpTestTarget("localhost", 8080, "/"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestTemplate
|
@State({"provider accepts a new person",
|
||||||
@ExtendWith(PactVerificationInvocationContextProvider.class)
|
"person 42 exists"})
|
||||||
void pactVerificationTestTemplate(PactVerificationContext context) {
|
public void toCreatePersonState() {
|
||||||
context.verifyInteraction();
|
User user = new User();
|
||||||
}
|
user.setId(42L);
|
||||||
|
user.setFirstName("Arthur");
|
||||||
@State({"provider accepts a new person",
|
user.setLastName("Dent");
|
||||||
"person 42 exists"})
|
when(userRepository.findOne(eq(42L))).thenReturn(user);
|
||||||
public void toCreatePersonState() {
|
when(userRepository.save(any(User.class))).thenReturn(user);
|
||||||
User user = new User();
|
}
|
||||||
user.setId(42L);
|
|
||||||
user.setFirstName("Arthur");
|
|
||||||
user.setLastName("Dent");
|
|
||||||
when(userRepository.findById(eq(42L))).thenReturn(Optional.of(user));
|
|
||||||
when(userRepository.save(any(User.class))).thenReturn(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,20 +7,16 @@ include 'spring-cloud:sleuth-upstream-service'
|
|||||||
include 'spring-cloud:spring-cloud-contract-provider'
|
include 'spring-cloud:spring-cloud-contract-provider'
|
||||||
include 'spring-cloud:spring-cloud-contract-consumer'
|
include 'spring-cloud:spring-cloud-contract-consumer'
|
||||||
|
|
||||||
|
include 'deprecated:pact-feign-consumer' // must run before pact-spring-data-rest-provider
|
||||||
|
include 'deprecated:pact-spring-data-rest-provider'
|
||||||
include 'pact:pact-spring-provider'
|
include 'pact:pact-spring-provider'
|
||||||
include 'pact:pact-feign-consumer'
|
include 'pact:pact-feign-consumer'
|
||||||
include 'pact:pact-message-consumer'
|
|
||||||
include 'pact:pact-message-provider'
|
|
||||||
|
|
||||||
|
|
||||||
include 'spring-boot:rabbitmq-event-brokering'
|
include 'spring-boot:rabbitmq-event-brokering'
|
||||||
include 'spring-boot:modular:security-module'
|
include 'spring-boot:modular:security-module'
|
||||||
include 'spring-boot:modular:booking-module'
|
include 'spring-boot:modular:booking-module'
|
||||||
include 'spring-boot:modular:application'
|
include 'spring-boot:modular:application'
|
||||||
include 'spring-boot:spring-boot-testing'
|
|
||||||
include 'spring-boot:spring-boot-logging'
|
|
||||||
|
|
||||||
include 'logging'
|
|
||||||
|
|
||||||
include 'junit:conditions'
|
include 'junit:conditions'
|
||||||
|
|
||||||
|
|||||||
26
spring-boot/spring-boot-logging/.gitignore
vendored
26
spring-boot/spring-boot-logging/.gitignore
vendored
@@ -1,26 +0,0 @@
|
|||||||
.gradle
|
|
||||||
/build/
|
|
||||||
!gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
### 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/
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
buildscript {
|
|
||||||
ext {
|
|
||||||
springBootVersion = '2.0.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'
|
|
||||||
apply plugin: 'io.spring.dependency-management'
|
|
||||||
|
|
||||||
group = 'io.reflectoring'
|
|
||||||
version = '0.0.1-SNAPSHOT'
|
|
||||||
sourceCompatibility = 1.8
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile('org.springframework.boot:spring-boot-starter')
|
|
||||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
|
||||||
testCompile('org.junit.jupiter:junit-jupiter-engine:5.2.0')
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
|||||||
#Tue Feb 06 12:27:20 CET 2018
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip
|
|
||||||
172
spring-boot/spring-boot-logging/gradlew
vendored
172
spring-boot/spring-boot-logging/gradlew
vendored
@@ -1,172 +0,0 @@
|
|||||||
#!/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" "$@"
|
|
||||||
84
spring-boot/spring-boot-logging/gradlew.bat
vendored
84
spring-boot/spring-boot-logging/gradlew.bat
vendored
@@ -1,84 +0,0 @@
|
|||||||
@if "%DEBUG%" == "" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS=
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windows variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user