From 96b34475a7f9ca28d4e9920edd575f12c33a1493 Mon Sep 17 00:00:00 2001 From: Chris Richardson Date: Fri, 2 Feb 2018 12:25:31 -0800 Subject: [PATCH] Added examples tests for Chapter 9 - testing --- .circleci/config.yml | 2 + .gitignore | 3 + README.adoc | 6 + build-and-test-all.sh | 6 +- build-contracts.sh | 7 + build.gradle | 6 + buildSrc/build.gradle | 1 + .../main/groovy/ComponentTestsPlugin.groovy | 34 +++ .../main/groovy/IntegrationTestsPlugin.groovy | 34 +++ .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 49502 bytes .../.mvn/wrapper/maven-wrapper.properties | 1 + common-contracts/META-INF/MANIFEST.MF | 2 + common-contracts/TODO.txt | 1 + common-contracts/build.gradle | 50 ++++ common-contracts/gradle.properties | 9 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 50514 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + common-contracts/gradlew | 164 ++++++++++++ common-contracts/gradlew.bat | 90 +++++++ common-contracts/mvnw | 233 ++++++++++++++++++ common-contracts/pom.xml | 110 +++++++++ .../main/resources/contracts/Authorize.groovy | 19 ++ .../resources/contracts/VerifyConsumer.groovy | 23 ++ common-swagger/build.gradle | 10 +- docker-compose.yml | 4 +- .../build.gradle | 24 ++ ...ventuateContractVerifierConfiguration.java | 23 ++ .../EventuateContractVerifierMessaging.java | 23 ++ .../EventuateTramMessageVerifier.java | 77 ++++++ ftgo-accounting-service/build.gradle | 4 +- ftgo-api-gateway/build.gradle | 15 +- .../orders/OrderConfiguration.java | 4 +- .../apiagateway/orders/OrderHandlers.java | 6 +- .../proxies/OrderNotFoundException.java | 6 + ...derService.java => OrderServiceProxy.java} | 15 +- .../ApiGatewayIntegrationTest.java | 98 ++++---- .../OrderServiceProxyIntegrationTest.java | 54 ++++ .../contract/TestConfiguration.java | 8 + .../src/test/resources/application.properties | 7 + ftgo-common-jpa/build.gradle | 5 + .../src/main/resources/META-INF/orm.xml | 13 + ftgo-common/build.gradle | 3 + .../chrisrichardson/ftgo/common/Money.java | 23 +- .../ftgo/common/MoneyTest.java | 43 ++++ .../api/ValidateOrderByConsumer.java | 17 ++ ftgo-consumer-service/build.gradle | 4 +- .../contracts/create-revise-cancel.feature | 15 ++ ftgo-order-history-service/build.gradle | 31 ++- .../messaging/OrderHistoryEventHandlers.java | 3 +- .../OrderHistoryEventHandlersTest.java | 76 ++++++ .../src/test/resources/application.properties | 6 + .../api/events/OrderCreatedEvent.java | 27 +- .../orderservice/api/events/OrderDetails.java | 19 ++ .../api/events/OrderLineItem.java | 27 +- .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 49502 bytes .../.mvn/wrapper/maven-wrapper.properties | 1 + ftgo-order-service-contracts/build.gradle | 31 +++ ftgo-order-service-contracts/mvnw | 233 ++++++++++++++++++ ftgo-order-service-contracts/pom.xml | 110 +++++++++ .../contracts/http/GetNonExistentOrder.groovy | 11 + .../resources/contracts/http/GetOrder.groovy | 15 ++ .../messaging/OrderCreatedEvent.groovy | 18 ++ ftgo-order-service/build.gradle | 86 ++++++- .../docker-compose-component-test.yml | 58 +++++ .../AbstractOrderServiceComponentTest.java | 126 ++++++++++ .../OrderServiceExternalComponentTest.java | 41 +++ .../OrderServiceInProcessComponentTest.java | 47 ++++ ...OrderServiceOutOfProcessComponentTest.java | 33 +++ ...derServiceOutOfProcessComponentV0Test.java | 128 ++++++++++ .../cucumber/OrderServiceComponentTest.java | 10 + ...mponentTestSpringContextConfiguration.java | 71 ++++++ ...erServiceComponentTestStepDefinitions.java | 199 +++++++++++++++ .../resources/features/createorder.feature | 20 ++ .../ftgo/orderservice/contract/HttpBase.java | 37 +++ .../orderservice/contract/MessagingBase.java | 56 +++++ .../orderservice/domain/OrderJpaTest.java | 50 ++++ .../domain}/OrderJpaTestConfiguration.java | 2 +- .../domain/OrderServiceIntegrationTest.java | 54 +++- ...urantOrderServiceProxyIntegrationTest.java | 114 +++++++++ .../SagaMessagingTestHelper.java | 39 +++ .../AbstractAggregateEventPublisher.java | 40 +++ .../ftgo/orderservice/domain/Order.java | 2 +- .../domain/OrderAggregateEventPublisher.java | 20 ++ .../orderservice/domain/OrderAuthorized.java | 13 + .../orderservice/domain/OrderService.java | 36 ++- .../domain/OrderServiceConfiguration.java | 82 +++--- ...rServiceWithRepositoriesConfiguration.java | 15 ++ .../messaging/OrderEventConsumer.java | 6 +- .../OrderServiceMessagingConfiguration.java | 9 +- .../sagaparticipants/OrderServiceProxy.java | 22 ++ .../RestaurantOrderServiceProxy.java | 25 ++ .../sagas/createorder/CreateOrderSaga.java | 73 ++++-- .../createorder/CreateOrderSagaData.java | 12 + .../orderservice/web/OrderController.java | 9 +- .../web/OrderWebConfiguration.java | 4 +- .../EventuateTramRoutesConfigurer.java | 161 ++++++++++++ .../ftgo/orderservice/MessageTracker.java | 63 +++++ .../MessageTrackerConfiguration.java | 26 ++ .../MessagingStubConfiguration.java | 22 ++ .../orderservice/MyCommandDispatcher.java | 55 +++++ .../ftgo/orderservice/MyCommandHandler.java | 28 +++ .../ftgo/orderservice/OrderDetailsMother.java | 38 ++- .../ftgo/orderservice/OrderJpaTest.java | 53 ---- .../ftgo/orderservice/RestaurantMother.java | 25 ++ .../orderservice/SagaParticipantStub.java | 20 ++ .../SagaParticipantStubConfiguration.java | 39 +++ .../SagaParticipantStubManager.java | 140 +++++++++++ .../orderservice/domain/OrderServiceTest.java | 76 ++++++ .../ftgo/orderservice/domain/OrderTest.java | 48 +++- .../domain/TestMessageConsumer2.java | 63 +++++ .../MockTramMessageSpecification.java | 58 +++++ .../MockTramMessagingTestSupport.java | 7 + .../messaging/OrderEventConsumerTest.java | 46 ++++ .../createorder/CreateOrderSagaTest.java | 62 +++++ .../createorder/MessageWithDestination.java | 21 ++ .../sagas/createorder/MockSagaTest.java | 150 +++++++++++ .../orderservice/web/OrderControllerTest.java | 71 ++++++ .../src/test/resources/application.properties | 3 + .../api/CreateRestaurantOrder.java | 8 +- .../api/CreateRestaurantOrderReply.java | 17 +- .../api/RestaurantOrderDetails.java | 14 ++ .../api/RestaurantOrderLineItem.java | 33 +++ .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 49502 bytes .../.mvn/wrapper/maven-wrapper.properties | 1 + .../META-INF/MANIFEST.MF | 2 + .../TODO.txt | 1 + .../build.gradle | 50 ++++ .../gradle.properties | 9 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 50514 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + .../gradlew | 164 ++++++++++++ .../gradlew.bat | 90 +++++++ ftgo-restaurant-order-service-contracts/mvnw | 233 ++++++++++++++++++ .../pom.xml | 110 +++++++++ .../ConfirmCreateRestaurantOrder.groovy | 22 ++ .../contracts/CreateRestaurantOrder.groovy | 25 ++ ftgo-restaurant-order-service/build.gradle | 34 ++- ...ctRestaurantOrderConsumerContractTest.java | 52 ++++ ...ntOrderServiceInMemoryIntegrationTest.java | 4 +- .../src/test/resources/application.properties | 6 + ftgo-restaurant-service-api/build.gradle | 2 +- .../restaurantservice/events/MenuItem.java | 18 ++ .../events/RestaurantMenu.java | 19 ++ ftgo-restaurant-service/build.gradle | 6 +- gradle.properties | 8 +- mysql-cli.sh | 2 +- settings.gradle | 5 + start-infrastructure-services.sh | 3 + 148 files changed, 5382 insertions(+), 292 deletions(-) create mode 100755 build-contracts.sh create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/src/main/groovy/ComponentTestsPlugin.groovy create mode 100644 buildSrc/src/main/groovy/IntegrationTestsPlugin.groovy create mode 100644 common-contracts/.mvn/wrapper/maven-wrapper.jar create mode 100644 common-contracts/.mvn/wrapper/maven-wrapper.properties create mode 100644 common-contracts/META-INF/MANIFEST.MF create mode 100644 common-contracts/TODO.txt create mode 100644 common-contracts/build.gradle create mode 100644 common-contracts/gradle.properties create mode 100644 common-contracts/gradle/wrapper/gradle-wrapper.jar create mode 100644 common-contracts/gradle/wrapper/gradle-wrapper.properties create mode 100755 common-contracts/gradlew create mode 100644 common-contracts/gradlew.bat create mode 100755 common-contracts/mvnw create mode 100644 common-contracts/pom.xml create mode 100644 common-contracts/src/main/resources/contracts/Authorize.groovy create mode 100644 common-contracts/src/main/resources/contracts/VerifyConsumer.groovy create mode 100644 eventuate-tram-spring-cloud-contract-support/build.gradle create mode 100644 eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateContractVerifierConfiguration.java create mode 100644 eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateContractVerifierMessaging.java create mode 100644 eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateTramMessageVerifier.java create mode 100644 ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderNotFoundException.java rename ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/{OrderService.java => OrderServiceProxy.java} (62%) create mode 100644 ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/contract/OrderServiceProxyIntegrationTest.java create mode 100644 ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/contract/TestConfiguration.java create mode 100644 ftgo-api-gateway/src/test/resources/application.properties create mode 100644 ftgo-common-jpa/build.gradle create mode 100644 ftgo-common-jpa/src/main/resources/META-INF/orm.xml create mode 100644 ftgo-common/src/test/java/net/chrisrichardson/ftgo/common/MoneyTest.java create mode 100644 ftgo-end-to-end-tests/src/test/resources/contracts/create-revise-cancel.feature create mode 100644 ftgo-order-history-service/src/test/java/net/chrisrichardson/ftgo/orderhistory/contracts/OrderHistoryEventHandlersTest.java create mode 100644 ftgo-order-history-service/src/test/resources/application.properties create mode 100644 ftgo-order-service-contracts/.mvn/wrapper/maven-wrapper.jar create mode 100644 ftgo-order-service-contracts/.mvn/wrapper/maven-wrapper.properties create mode 100644 ftgo-order-service-contracts/build.gradle create mode 100755 ftgo-order-service-contracts/mvnw create mode 100644 ftgo-order-service-contracts/pom.xml create mode 100644 ftgo-order-service-contracts/src/main/resources/contracts/http/GetNonExistentOrder.groovy create mode 100644 ftgo-order-service-contracts/src/main/resources/contracts/http/GetOrder.groovy create mode 100644 ftgo-order-service-contracts/src/main/resources/contracts/messaging/OrderCreatedEvent.groovy create mode 100755 ftgo-order-service/docker-compose-component-test.yml create mode 100644 ftgo-order-service/src/attic/AbstractOrderServiceComponentTest.java create mode 100644 ftgo-order-service/src/attic/OrderServiceExternalComponentTest.java create mode 100644 ftgo-order-service/src/attic/OrderServiceInProcessComponentTest.java create mode 100644 ftgo-order-service/src/attic/OrderServiceOutOfProcessComponentTest.java create mode 100644 ftgo-order-service/src/attic/OrderServiceOutOfProcessComponentV0Test.java create mode 100644 ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTest.java create mode 100644 ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTestSpringContextConfiguration.java create mode 100644 ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTestStepDefinitions.java create mode 100644 ftgo-order-service/src/component-test/resources/features/createorder.feature create mode 100644 ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/contract/HttpBase.java create mode 100644 ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/contract/MessagingBase.java create mode 100644 ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderJpaTest.java rename ftgo-order-service/src/{test/java/net/chrisrichardson/ftgo/orderservice => integration-test/java/net/chrisrichardson/ftgo/orderservice/domain}/OrderJpaTestConfiguration.java (85%) rename ftgo-order-service/src/{test => integration-test}/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceIntegrationTest.java (65%) create mode 100644 ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/RestaurantOrderServiceProxyIntegrationTest.java create mode 100644 ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/SagaMessagingTestHelper.java create mode 100644 ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/AbstractAggregateEventPublisher.java create mode 100644 ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderAggregateEventPublisher.java create mode 100644 ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceWithRepositoriesConfiguration.java create mode 100644 ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/OrderServiceProxy.java create mode 100644 ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/RestaurantOrderServiceProxy.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/EventuateTramRoutesConfigurer.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessageTracker.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessageTrackerConfiguration.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessagingStubConfiguration.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MyCommandDispatcher.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MyCommandHandler.java delete mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderJpaTest.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/RestaurantMother.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStub.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStubConfiguration.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStubManager.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceTest.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/TestMessageConsumer2.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/MockTramMessageSpecification.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/MockTramMessagingTestSupport.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderEventConsumerTest.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSagaTest.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/MessageWithDestination.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/MockSagaTest.java create mode 100644 ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/web/OrderControllerTest.java create mode 100644 ftgo-restaurant-order-service-contracts/.mvn/wrapper/maven-wrapper.jar create mode 100644 ftgo-restaurant-order-service-contracts/.mvn/wrapper/maven-wrapper.properties create mode 100644 ftgo-restaurant-order-service-contracts/META-INF/MANIFEST.MF create mode 100644 ftgo-restaurant-order-service-contracts/TODO.txt create mode 100644 ftgo-restaurant-order-service-contracts/build.gradle create mode 100644 ftgo-restaurant-order-service-contracts/gradle.properties create mode 100644 ftgo-restaurant-order-service-contracts/gradle/wrapper/gradle-wrapper.jar create mode 100644 ftgo-restaurant-order-service-contracts/gradle/wrapper/gradle-wrapper.properties create mode 100755 ftgo-restaurant-order-service-contracts/gradlew create mode 100644 ftgo-restaurant-order-service-contracts/gradlew.bat create mode 100755 ftgo-restaurant-order-service-contracts/mvnw create mode 100644 ftgo-restaurant-order-service-contracts/pom.xml create mode 100644 ftgo-restaurant-order-service-contracts/src/main/resources/contracts/ConfirmCreateRestaurantOrder.groovy create mode 100644 ftgo-restaurant-order-service-contracts/src/main/resources/contracts/CreateRestaurantOrder.groovy create mode 100644 ftgo-restaurant-order-service/src/test/java/net/chrisrichardson/ftgo/restaurantorderservice/contract/AbstractRestaurantOrderConsumerContractTest.java rename ftgo-restaurant-order-service/src/test/java/{restaurantorderservice => net/chrisrichardson/ftgo/restaurantorderservice/domain}/RestaurantOrderServiceInMemoryIntegrationTest.java (95%) create mode 100644 ftgo-restaurant-order-service/src/test/resources/application.properties create mode 100755 start-infrastructure-services.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 58259f56..31347f40 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,10 +7,12 @@ jobs: - checkout - restore_cache: key: ftgo-application-{{ checksum "build.gradle" }} + - run: TERM=dumb ./build-contract.sh - run: TERM=dumb ./gradlew testClasses - save_cache: paths: - ~/.gradle + - ~/.m2 key: ftgo-application-{{ checksum "build.gradle" }} - run: command: | diff --git a/.gitignore b/.gitignore index afe247a7..7fa21e8a 100755 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ build/ *.idea/ *.iml *.log +out +target +.serverless diff --git a/README.adoc b/README.adoc index ec8befa3..9dfb6c1b 100644 --- a/README.adoc +++ b/README.adoc @@ -115,6 +115,12 @@ All the services' business logic is implemented using Domain-Driven design aggre === Building +Temporary: Build the Spring Cloud Contracts using this command: + +``` +./build-contracts.sh +``` + Build the services using this command: ``` diff --git a/build-and-test-all.sh b/build-and-test-all.sh index 5ae585fd..bb1d2a18 100755 --- a/build-and-test-all.sh +++ b/build-and-test-all.sh @@ -18,7 +18,11 @@ echo data is prepared docker-compose up -d --build eventuate-local-cdc-service tram-cdc-service -./gradlew -x :ftgo-end-to-end-tests:test build +# TODO Temporarily + +./build-contracts.sh + +./gradlew -x :ftgo-end-to-end-tests:test $* build docker-compose up -d --build diff --git a/build-contracts.sh b/build-contracts.sh new file mode 100755 index 00000000..fab5dcbc --- /dev/null +++ b/build-contracts.sh @@ -0,0 +1,7 @@ +#! /bin/bash -e + +CONTRACT_DIRS="common-contracts ftgo-order-service-contracts ftgo-restaurant-order-service-contracts" + +for dir in $CONTRACT_DIRS ; do + (cd $dir ; ./mvnw install) + done diff --git a/build.gradle b/build.gradle index 24bbe0a6..3c16ebd8 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,9 @@ subprojects { apply plugin: "java" + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + repositories { mavenCentral() jcenter() @@ -19,6 +22,9 @@ subprojects { url "https://dl.bintray.com/eventuateio-oss/eventuate-maven-release" } eventuateMavenRepoUrl.split(',').each { repoUrl -> maven { url repoUrl } } + + // For the contracts + mavenLocal() } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 00000000..2ea37ef4 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1 @@ +test.enabled=false \ No newline at end of file diff --git a/buildSrc/src/main/groovy/ComponentTestsPlugin.groovy b/buildSrc/src/main/groovy/ComponentTestsPlugin.groovy new file mode 100644 index 00000000..0e994c9e --- /dev/null +++ b/buildSrc/src/main/groovy/ComponentTestsPlugin.groovy @@ -0,0 +1,34 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.testing.Test + +class ComponentTestsPlugin implements Plugin { + + @Override + void apply(Project project) { + project.sourceSets { + componentTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir project.file('src/component-test/java') + } + resources.srcDir project.file('src/component-test/resources') + } + } + + project.configurations { + componentTestCompile.extendsFrom testCompile + componentTestRuntime.extendsFrom testRuntime + } + + project.task("componentTest", type: Test) { + testClassesDir = project.sourceSets.componentTest.output.classesDir + classpath = project.sourceSets.componentTest.runtimeClasspath + } + + project.tasks.withType(Test) { + reports.html.destination = project.file("${project.reporting.baseDir}/${name}") + } + } +} diff --git a/buildSrc/src/main/groovy/IntegrationTestsPlugin.groovy b/buildSrc/src/main/groovy/IntegrationTestsPlugin.groovy new file mode 100644 index 00000000..9f5bce54 --- /dev/null +++ b/buildSrc/src/main/groovy/IntegrationTestsPlugin.groovy @@ -0,0 +1,34 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.testing.Test + +class IntegrationTestsPlugin implements Plugin { + + @Override + void apply(Project project) { + project.sourceSets { + integrationTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir project.file('src/integration-test/java') + } + resources.srcDir project.file('src/integration-test/resources') + } + } + + project.configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime + } + + project.task("integrationTest", type: Test) { + testClassesDir = project.sourceSets.integrationTest.output.classesDir + classpath = project.sourceSets.integrationTest.runtimeClasspath + } + + project.tasks.withType(Test) { + reports.html.destination = project.file("${project.reporting.baseDir}/${name}") + } + } +} diff --git a/common-contracts/.mvn/wrapper/maven-wrapper.jar b/common-contracts/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5fd4d5023f1463b5ba3970e33c460c1eb26d748d GIT binary patch literal 49502 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%0A$pwhjXzyA0ZzErH{1@M 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 OJOEd-=rxTO#Qy`$*1Hh^ literal 0 HcmV?d00001 diff --git a/common-contracts/.mvn/wrapper/maven-wrapper.properties b/common-contracts/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..c3150437 --- /dev/null +++ b/common-contracts/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip diff --git a/common-contracts/META-INF/MANIFEST.MF b/common-contracts/META-INF/MANIFEST.MF new file mode 100644 index 00000000..58630c02 --- /dev/null +++ b/common-contracts/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/common-contracts/TODO.txt b/common-contracts/TODO.txt new file mode 100644 index 00000000..7cb0e8c2 --- /dev/null +++ b/common-contracts/TODO.txt @@ -0,0 +1 @@ +This directory needs to be per-service \ No newline at end of file diff --git a/common-contracts/build.gradle b/common-contracts/build.gradle new file mode 100644 index 00000000..444c1eac --- /dev/null +++ b/common-contracts/build.gradle @@ -0,0 +1,50 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath "io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE" + // if using Stub Runner (consumer side) only remove this dependency + classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.1.1.RELEASE" + } +} + +repositories { + mavenCentral() +} + +group = "net.chrisrichardson.ftgo.contracts" +version = "1.0-SNAPSHOT" +apply plugin: "io.spring.dependency-management" +apply plugin: 'java' +apply plugin: 'spring-cloud-contract' +apply plugin: 'maven' + +uploadArchives { + repositories { + mavenDeployer { + repository(url: deployUrl) + pom.withXml { + asNode().appendNode('classifier', 'stubs') + } + } + } +} + +dependencyManagement { + imports { + mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:1.1.4.RELEASE' + } +} + +dependencies { + // testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier' +} + +/* +// In this section you define all Spring Cloud Contract Verifier Gradle Plugin properties +contracts { + baseClassForTests = 'com.example.MvcTest' + // fully qualified name to a class that will be the base class for your generated test classes +} +*/ \ No newline at end of file diff --git a/common-contracts/gradle.properties b/common-contracts/gradle.properties new file mode 100644 index 00000000..9dc4d247 --- /dev/null +++ b/common-contracts/gradle.properties @@ -0,0 +1,9 @@ + +org.gradle.jvmargs=-XX:MaxPermSize=512m + +deployUrl=file:///Users/cer/.m2/repository + +springBootVersion=1.5.4.RELEASE + +eventuateClientVersion=0.14.0.RELEASE +eventuateLocalVersion=0.9.0.RELEASE diff --git a/common-contracts/gradle/wrapper/gradle-wrapper.jar b/common-contracts/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..667288ad6c2b3b87c990ece1267e56f0bcbf3622 GIT binary patch literal 50514 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +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" ] ; 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"` + + # 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 + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/common-contracts/gradlew.bat b/common-contracts/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/common-contracts/gradlew.bat @@ -0,0 +1,90 @@ +@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 + +@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= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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 Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/common-contracts/mvnw b/common-contracts/mvnw new file mode 100755 index 00000000..a1ba1bf5 --- /dev/null +++ b/common-contracts/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/common-contracts/pom.xml b/common-contracts/pom.xml new file mode 100644 index 00000000..05054c5e --- /dev/null +++ b/common-contracts/pom.xml @@ -0,0 +1,110 @@ + + + + 4.0.0 + + net.chrisrichardson.ftgo.contracts + common-contracts + 1.0-SNAPSHOT + + POM used to install locally stubs for consumer side + + + org.springframework.boot + spring-boot-starter-parent + 1.5.4.RELEASE + + + + + UTF-8 + 1.8 + 1.2.0.RELEASE + Dalston.SR1 + + true + + true + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud-dependencies.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + true + + + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + + + ${project.basedir} + + + + + + + + spring + + true + + + + + spring-snapshots + Spring Snapshots + http://repo.spring.io/libs-snapshot-local + + true + + + + spring-milestones + Spring Milestones + http://repo.spring.io/libs-milestone-local + + false + + + + spring-plugin-snapshots + Spring Snapshots + http://repo.spring.io/plugins-snapshot-local + + true + + + + spring-plugin-milestones + Spring Milestones + http://repo.spring.io/plugins-release-local + + false + + + + + + + \ No newline at end of file diff --git a/common-contracts/src/main/resources/contracts/Authorize.groovy b/common-contracts/src/main/resources/contracts/Authorize.groovy new file mode 100644 index 00000000..f21b600e --- /dev/null +++ b/common-contracts/src/main/resources/contracts/Authorize.groovy @@ -0,0 +1,19 @@ +package contracts; + +org.springframework.cloud.contract.spec.Contract.make { + label 'authorize' + input { + messageFrom('accountingService') + messageBody('''{"consumerId":1511300065921,"orderId":1,"orderTotal":"61.70"}''') + messageHeaders { + } + } + outputMessage { + sentTo('net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga-reply') + body('''{}''') + headers { + header('reply_type', 'io.eventuate.tram.commands.common.Success') + header('reply_outcome-type', 'SUCCESS') + } + } +} \ No newline at end of file diff --git a/common-contracts/src/main/resources/contracts/VerifyConsumer.groovy b/common-contracts/src/main/resources/contracts/VerifyConsumer.groovy new file mode 100644 index 00000000..4574ad86 --- /dev/null +++ b/common-contracts/src/main/resources/contracts/VerifyConsumer.groovy @@ -0,0 +1,23 @@ +package contracts; + +org.springframework.cloud.contract.spec.Contract.make { + label 'verifyConsumer' + input { + messageFrom('consumerService') + messageBody([ + consumerId: 1511300065921L, + orderId: 1, + orderTotal: "61.70" + ]) + messageHeaders { + } + } + outputMessage { + sentTo('net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga-reply') + body('''{}''') + headers { + header('reply_type', 'io.eventuate.tram.commands.common.Success') + header('reply_outcome-type', 'SUCCESS') + } + } +} \ No newline at end of file diff --git a/common-swagger/build.gradle b/common-swagger/build.gradle index da415012..76e787ef 100644 --- a/common-swagger/build.gradle +++ b/common-swagger/build.gradle @@ -1,4 +1,10 @@ dependencies { - compile "io.springfox:springfox-swagger2:2.3.0" - compile "io.springfox:springfox-swagger-ui:2.3.0" + compile ("io.springfox:springfox-swagger2:2.3.0") { + exclude group: "org.springframework" + } + compile ("io.springfox:springfox-swagger-ui:2.3.0"){ + exclude group: "org.springframework" + } + compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion" + } diff --git a/docker-compose.yml b/docker-compose.yml index 7c370b93..f07142c3 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,13 @@ version: '3' services: zookeeper: - image: eventuateio/eventuateio-local-zookeeper:0.14.0 + image: eventuateio/eventuateio-local-zookeeper:0.15.0 ports: - 2181:2181 - 2888:2888 - 3888:3888 kafka: - image: eventuateio/eventuateio-local-kafka:0.14.0 + image: eventuateio/eventuateio-local-kafka:0.15.0 ports: - 9092:9092 links: diff --git a/eventuate-tram-spring-cloud-contract-support/build.gradle b/eventuate-tram-spring-cloud-contract-support/build.gradle new file mode 100644 index 00000000..81abe568 --- /dev/null +++ b/eventuate-tram-spring-cloud-contract-support/build.gradle @@ -0,0 +1,24 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath "io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE" + // if using Stub Runner (consumer side) only remove this dependency + classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.2.0.RELEASE" + } +} + +apply plugin: "io.spring.dependency-management" +apply plugin: 'spring-cloud-contract' + +dependencyManagement { + imports { + mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:1.2.0.RELEASE' + } +} + +dependencies { + compile 'org.springframework.cloud:spring-cloud-starter-contract-verifier:1.2.0.RELEASE' + compile "io.eventuate.tram.core:eventuate-tram-messaging:$eventuateTramVersion" +} diff --git a/eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateContractVerifierConfiguration.java b/eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateContractVerifierConfiguration.java new file mode 100644 index 00000000..00127566 --- /dev/null +++ b/eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateContractVerifierConfiguration.java @@ -0,0 +1,23 @@ +package io.eventuate.tram.springcloudcontractsupport; + +import io.eventuate.tram.messaging.common.Message; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EventuateContractVerifierConfiguration { + + @Bean + public MessageVerifier eventuateTramMessageVerifier() { + + return new EventuateTramMessageVerifier(); + } + + @Bean + public ContractVerifierMessaging eventuateContractVerifierMessaging(MessageVerifier exchange) { + return new EventuateContractVerifierMessaging(exchange); + } + +} diff --git a/eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateContractVerifierMessaging.java b/eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateContractVerifierMessaging.java new file mode 100644 index 00000000..9985f75d --- /dev/null +++ b/eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateContractVerifierMessaging.java @@ -0,0 +1,23 @@ +package io.eventuate.tram.springcloudcontractsupport; + +import io.eventuate.tram.messaging.common.Message; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage; +import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; + +import javax.inject.Inject; + +public class EventuateContractVerifierMessaging extends ContractVerifierMessaging { + + @Inject + ContractVerifierMessaging contractVerifierMessaging; + + public EventuateContractVerifierMessaging(MessageVerifier exchange) { + super(exchange); + } + + @Override + protected ContractVerifierMessage convert(Message m) { + return m == null ? null : contractVerifierMessaging.create(m.getPayload(), m.getHeaders()); + } +} diff --git a/eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateTramMessageVerifier.java b/eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateTramMessageVerifier.java new file mode 100644 index 00000000..3849a6ea --- /dev/null +++ b/eventuate-tram-spring-cloud-contract-support/src/main/java/io/eventuate/tram/springcloudcontractsupport/EventuateTramMessageVerifier.java @@ -0,0 +1,77 @@ +package io.eventuate.tram.springcloudcontractsupport; + +import io.eventuate.tram.messaging.common.Message; +import io.eventuate.tram.messaging.consumer.MessageConsumer; +import io.eventuate.tram.messaging.producer.MessageBuilder; +import io.eventuate.tram.messaging.producer.MessageProducer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; + +import javax.annotation.PostConstruct; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import static java.util.Collections.singleton; + +public class EventuateTramMessageVerifier implements MessageVerifier { + + @Autowired + private MessageProducer messageProducer; + + @Autowired + private MessageConsumer messageConsumer; + + @Override + public void send(Message message, String destination) { + throw new UnsupportedOperationException(); + } + + private ConcurrentHashMap> messagesByDestination = new ConcurrentHashMap<>(); + + public EventuateTramMessageVerifier() { + } + + @PostConstruct + public void subscribe() { + messageConsumer.subscribe("etmv", singleton("*"), m -> { + String destination = m.getRequiredHeader(Message.DESTINATION); + getForDestination(destination).add(m); + }); + } + + private LinkedBlockingQueue getForDestination(String destination) { + return messagesByDestination.computeIfAbsent(destination, k -> new LinkedBlockingQueue<>()); + } + + @Override + public void send(T payload, Map headers, String destination) { + String p = (String) payload; + MessageBuilder mb = MessageBuilder.withPayload(p); + + headers.forEach((key, value) -> { + mb.withHeader(key, (String) value); + }); + + messageProducer.send(destination, mb.build()); + } + + @Override + public Message receive(String destination, long timeout, TimeUnit timeUnit) { + Message m; + try { + m = getForDestination(destination).poll(timeout, timeUnit); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (m == null) + return null; + return m; + } + + @Override + public Message receive(String destination) { + return receive(destination, 5, TimeUnit.SECONDS); + } +} diff --git a/ftgo-accounting-service/build.gradle b/ftgo-accounting-service/build.gradle index 94ea6b27..569c494f 100644 --- a/ftgo-accounting-service/build.gradle +++ b/ftgo-accounting-service/build.gradle @@ -1,5 +1,5 @@ -apply plugin: "spring-boot" +apply plugin: 'org.springframework.boot' dependencies { @@ -26,7 +26,7 @@ dependencies { testCompile "io.eventuate.tram.core:eventuate-tram-test-util:$eventuateTramVersion" testCompile "io.eventuate.tram.sagas:eventuate-tram-sagas-in-memory:$eventuateTramSagasVersion" - testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testCompile 'com.jayway.restassured:rest-assured:2.3.0' testCompile "com.jayway.jsonpath:json-path:2.3.0" diff --git a/ftgo-api-gateway/build.gradle b/ftgo-api-gateway/build.gradle index d8a7015a..f66fea1b 100755 --- a/ftgo-api-gateway/build.gradle +++ b/ftgo-api-gateway/build.gradle @@ -20,10 +20,16 @@ apply plugin: 'org.springframework.boot' dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-gateway:2.0.0.M2' + mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:1.2.1.RELEASE' } } + +configurations.all { + exclude module: "spring-boot-starter-web" +} + repositories { mavenCentral() maven { @@ -32,11 +38,14 @@ repositories { } dependencies { - compile("org.springframework.boot:spring-boot-starter-webflux") + compile "org.springframework.boot:spring-boot-starter-webflux" compile 'org.springframework.cloud:spring-cloud-starter-gateway' compile "org.apache.commons:commons-lang3:3.6" - testCompile "com.github.tomakehurst:wiremock:2.7.1" - testCompile ("org.springframework.boot:spring-boot-starter-test") + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion2" testCompile "junit:junit:4.12" + testCompile "net.chrisrichardson.ftgo.contracts:ftgo-order-service-contracts:1.0-SNAPSHOT:stubs" + testCompile "org.springframework.cloud:spring-cloud-contract-wiremock" + testCompile "org.springframework.cloud:spring-cloud-starter-contract-stub-runner" + } diff --git a/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/orders/OrderConfiguration.java b/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/orders/OrderConfiguration.java index ee25c3a6..2e4c1fd4 100644 --- a/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/orders/OrderConfiguration.java +++ b/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/orders/OrderConfiguration.java @@ -2,7 +2,7 @@ import net.chrisrichardson.ftgo.apiagateway.proxies.AccountingService; import net.chrisrichardson.ftgo.apiagateway.proxies.DeliveryService; -import net.chrisrichardson.ftgo.apiagateway.proxies.OrderService; +import net.chrisrichardson.ftgo.apiagateway.proxies.OrderServiceProxy; import net.chrisrichardson.ftgo.apiagateway.proxies.RestaurantOrderService; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.gateway.route.RouteLocator; @@ -42,7 +42,7 @@ public RouterFunction orderHandlerRouting(OrderHandlers orderHan } @Bean - public OrderHandlers orderHandlers(OrderService orderService, RestaurantOrderService restaurantOrderService, + public OrderHandlers orderHandlers(OrderServiceProxy orderService, RestaurantOrderService restaurantOrderService, DeliveryService deliveryService, AccountingService accountingService) { return new OrderHandlers(orderService, restaurantOrderService, deliveryService, accountingService); } diff --git a/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/orders/OrderHandlers.java b/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/orders/OrderHandlers.java index 392f8532..658f0e39 100644 --- a/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/orders/OrderHandlers.java +++ b/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/orders/OrderHandlers.java @@ -5,7 +5,7 @@ import net.chrisrichardson.ftgo.apiagateway.proxies.DeliveryInfo; import net.chrisrichardson.ftgo.apiagateway.proxies.DeliveryService; import net.chrisrichardson.ftgo.apiagateway.proxies.OrderInfo; -import net.chrisrichardson.ftgo.apiagateway.proxies.OrderService; +import net.chrisrichardson.ftgo.apiagateway.proxies.OrderServiceProxy; import net.chrisrichardson.ftgo.apiagateway.proxies.RestaurantOrderInfo; import net.chrisrichardson.ftgo.apiagateway.proxies.RestaurantOrderService; import org.springframework.http.MediaType; @@ -20,12 +20,12 @@ public class OrderHandlers { - private OrderService orderService; + private OrderServiceProxy orderService; private RestaurantOrderService restaurantOrderService; private DeliveryService deliveryService; private AccountingService accountingService; - public OrderHandlers(OrderService orderService, + public OrderHandlers(OrderServiceProxy orderService, RestaurantOrderService restaurantOrderService, DeliveryService deliveryService, AccountingService accountingService) { diff --git a/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderNotFoundException.java b/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderNotFoundException.java new file mode 100644 index 00000000..32a74b75 --- /dev/null +++ b/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderNotFoundException.java @@ -0,0 +1,6 @@ +package net.chrisrichardson.ftgo.apiagateway.proxies; + +public class OrderNotFoundException extends RuntimeException { + public OrderNotFoundException() { + } +} diff --git a/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderService.java b/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderServiceProxy.java similarity index 62% rename from ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderService.java rename to ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderServiceProxy.java index 925f8f35..88a9e512 100644 --- a/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderService.java +++ b/ftgo-api-gateway/src/main/java/net/chrisrichardson/ftgo/apiagateway/proxies/OrderServiceProxy.java @@ -7,14 +7,14 @@ import reactor.core.publisher.Mono; @Service -public class OrderService { +public class OrderServiceProxy { private OrderDestinations orderDestinations; private WebClient client; - public OrderService(OrderDestinations orderDestinations, WebClient client) { + public OrderServiceProxy(OrderDestinations orderDestinations, WebClient client) { this.orderDestinations = orderDestinations; this.client = client; } @@ -24,7 +24,16 @@ public Mono findOrderById(String orderId) { .get() .uri(orderDestinations.getOrderServiceUrl() + "/orders/{orderId}", orderId) .exchange(); - return response.flatMap(resp -> resp.bodyToMono(OrderInfo.class)); + return response.flatMap(resp -> { + switch (resp.statusCode()) { + case OK: + return resp.bodyToMono(OrderInfo.class); + case NOT_FOUND: + return Mono.error(new OrderNotFoundException()); + default: + return Mono.error(new RuntimeException("Unknown" + resp.statusCode())); + } + }); } diff --git a/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/ApiGatewayIntegrationTest.java b/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/ApiGatewayIntegrationTest.java index c702cfe5..924b6283 100644 --- a/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/ApiGatewayIntegrationTest.java +++ b/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/ApiGatewayIntegrationTest.java @@ -34,78 +34,78 @@ //import static org.springframework.web.reactive.function.server.RouterFunctions.route; @RunWith(SpringRunner.class) -@SpringBootTest(classes=ApiGatewayIntegrationTestConfiguration.class, +@SpringBootTest(classes = ApiGatewayIntegrationTestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties={"order.destinations.orderServiceUrl=http://localhost:8082", + properties = {"order.destinations.orderServiceUrl=http://localhost:8082", "order.destinations.orderHistoryServiceUrl=http://localhost:8083"}) -public class ApiGatewayIntegrationTest { +public class ApiGatewayIntegrationTest { - @LocalServerPort - private int port; + @LocalServerPort + private int port; - @Rule - public WireMockRule wireMockRule = new WireMockRule(8082); // No-args constructor defaults to port 8080 + @Rule + public WireMockRule wireMockRule = new WireMockRule(8082); // No-args constructor defaults to port 8080 - @Test - public void shouldProxyCreateOrder() { + @Test + public void shouldProxyCreateOrder() { - stubFor(post(urlEqualTo("/orders")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/xml") - .withBody("Some content"))); + stubFor(post(urlEqualTo("/orders")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/xml") + .withBody("Some content"))); - WebClient client = WebClient.create("http://localhost:" + port + "/orders"); + WebClient client = WebClient.create("http://localhost:" + port + "/orders"); - ResponseEntity z = client - .post() - .body(BodyInserters.fromObject("{}")) - .exchange() - .flatMap(r -> r.toEntity(String.class)) - .block(); + ResponseEntity z = client + .post() + .body(BodyInserters.fromObject("{}")) + .exchange() + .flatMap(r -> r.toEntity(String.class)) + .block(); - assertNotNull(z); - assertEquals(HttpStatus.OK, z.getStatusCode()); - assertEquals("Some content", z.getBody()); + assertNotNull(z); + assertEquals(HttpStatus.OK, z.getStatusCode()); + assertEquals("Some content", z.getBody()); - verify(postRequestedFor(urlMatching("/orders"))); + verify(postRequestedFor(urlMatching("/orders"))); - } + } - @Test - public void shouldProxyGetOrderDetails() throws JsonProcessingException { + @Test + public void shouldProxyGetOrderDetails() throws JsonProcessingException { - String orderId = "1"; + String orderId = "1"; - OrderDetails expectedOrderDetails = new OrderDetails(new OrderInfo(orderId, "CREATED")); - ObjectMapper objectMapper = new ObjectMapper(); + OrderDetails expectedOrderDetails = new OrderDetails(new OrderInfo(orderId, "CREATED")); + ObjectMapper objectMapper = new ObjectMapper(); - String body = objectMapper.writeValueAsString(expectedOrderDetails.getOrderInfo()); + String body = objectMapper.writeValueAsString(expectedOrderDetails.getOrderInfo()); - String expectedPath = "/orders/" + orderId; + String expectedPath = "/orders/" + orderId; - stubFor(get(urlEqualTo(expectedPath)) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", MediaType.APPLICATION_JSON.toString()) - .withBody(body))); + stubFor(get(urlEqualTo(expectedPath)) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", MediaType.APPLICATION_JSON.toString()) + .withBody(body))); - WebClient client = WebClient.create("http://localhost:" + port + "/orders/1"); + WebClient client = WebClient.create("http://localhost:" + port + "/orders/1"); - ResponseEntity z = client - .get() - .exchange() - .flatMap(r -> r.toEntity(OrderDetails.class)) - .block(); + ResponseEntity z = client + .get() + .exchange() + .flatMap(r -> r.toEntity(OrderDetails.class)) + .block(); - assertNotNull(z); - assertEquals(HttpStatus.OK, z.getStatusCode()); - assertEquals(body, expectedOrderDetails, z.getBody()); + assertNotNull(z); + assertEquals(HttpStatus.OK, z.getStatusCode()); + assertEquals(body, expectedOrderDetails, z.getBody()); - verify(getRequestedFor(urlMatching(expectedPath))); + verify(getRequestedFor(urlMatching(expectedPath))); - } + } } diff --git a/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/contract/OrderServiceProxyIntegrationTest.java b/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/contract/OrderServiceProxyIntegrationTest.java new file mode 100644 index 00000000..607fe735 --- /dev/null +++ b/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/contract/OrderServiceProxyIntegrationTest.java @@ -0,0 +1,54 @@ +package net.chrisrichardson.ftgo.apiagateway.contract; + +import net.chrisrichardson.ftgo.apiagateway.orders.OrderDestinations; +import net.chrisrichardson.ftgo.apiagateway.proxies.OrderInfo; +import net.chrisrichardson.ftgo.apiagateway.proxies.OrderNotFoundException; +import net.chrisrichardson.ftgo.apiagateway.proxies.OrderServiceProxy; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.junit.Assert.assertEquals; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes=TestConfiguration.class, + webEnvironment= SpringBootTest.WebEnvironment.NONE) +@AutoConfigureStubRunner(ids = + {"net.chrisrichardson.ftgo.contracts:ftgo-order-service-contracts"}, + workOffline = false) +@DirtiesContext +public class OrderServiceProxyIntegrationTest { + + @Value("${stubrunner.runningstubs.ftgo-order-service-contracts.port}") + private int port; + private OrderDestinations orderDestinations; + private OrderServiceProxy orderService; + + @Before + public void setUp() throws Exception { + orderDestinations = new OrderDestinations(); + String orderServiceUrl = "http://localhost:" + port; + System.out.println("orderServiceUrl=" + orderServiceUrl); + orderDestinations.setOrderServiceUrl(orderServiceUrl); + orderService = new OrderServiceProxy(orderDestinations, WebClient.create()); + } + + @Test + public void shouldVerifyExistingCustomer() { + OrderInfo result = orderService.findOrderById("99").block(); + assertEquals("99", result.getOrderId()); + assertEquals("CREATE_PENDING", result.getState()); + } + + @Test(expected = OrderNotFoundException.class) + public void shouldFailToFindMissingOrder() { + orderService.findOrderById("555").block(); + } + +} diff --git a/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/contract/TestConfiguration.java b/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/contract/TestConfiguration.java new file mode 100644 index 00000000..92a7030e --- /dev/null +++ b/ftgo-api-gateway/src/test/java/net/chrisrichardson/ftgo/apiagateway/contract/TestConfiguration.java @@ -0,0 +1,8 @@ +package net.chrisrichardson.ftgo.apiagateway.contract; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TestConfiguration { + +} diff --git a/ftgo-api-gateway/src/test/resources/application.properties b/ftgo-api-gateway/src/test/resources/application.properties new file mode 100644 index 00000000..86d20c66 --- /dev/null +++ b/ftgo-api-gateway/src/test/resources/application.properties @@ -0,0 +1,7 @@ +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.springframework.cloud=DEBUG +logging.level.org.springframework.web=DEBUG +logging.level.io.eventuate=DEBUG +spring.jpa.generate-ddl=true +stubrunner.stream.enabled=false +stubrunner.integration.enabled=false \ No newline at end of file diff --git a/ftgo-common-jpa/build.gradle b/ftgo-common-jpa/build.gradle new file mode 100644 index 00000000..a822ece3 --- /dev/null +++ b/ftgo-common-jpa/build.gradle @@ -0,0 +1,5 @@ +dependencies { + + compile "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" + +} \ No newline at end of file diff --git a/ftgo-common-jpa/src/main/resources/META-INF/orm.xml b/ftgo-common-jpa/src/main/resources/META-INF/orm.xml new file mode 100644 index 00000000..bd621400 --- /dev/null +++ b/ftgo-common-jpa/src/main/resources/META-INF/orm.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ftgo-common/build.gradle b/ftgo-common/build.gradle index dfe85825..971b941d 100644 --- a/ftgo-common/build.gradle +++ b/ftgo-common/build.gradle @@ -1,4 +1,7 @@ dependencies { + // TODO eliminate this - problem is value objects like Money need to be embeddable. + // TODO https://en.wikibooks.org/wiki/Java_Persistence/Embeddables#Example_of_an_Embeddable_object_XML + compile "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" compile "io.eventuate.client.java:eventuate-client-java-common-impl:$eventuateClientVersion" diff --git a/ftgo-common/src/main/java/net/chrisrichardson/ftgo/common/Money.java b/ftgo-common/src/main/java/net/chrisrichardson/ftgo/common/Money.java index 863ee5ae..02d69d8d 100644 --- a/ftgo-common/src/main/java/net/chrisrichardson/ftgo/common/Money.java +++ b/ftgo-common/src/main/java/net/chrisrichardson/ftgo/common/Money.java @@ -4,13 +4,10 @@ import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; -import javax.persistence.Access; -import javax.persistence.AccessType; -import javax.persistence.Embeddable; import java.math.BigDecimal; -@Embeddable -@Access(AccessType.FIELD) +//@Embeddable +//@Access(AccessType.FIELD) public class Money { public static Money ZERO = new Money(0); @@ -24,6 +21,14 @@ public Money(BigDecimal amount) { this.amount = amount; } + public Money(String s) { + this.amount = new BigDecimal(s); + } + + public Money(int i) { + this.amount = new BigDecimal(i); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -51,13 +56,6 @@ public String toString() { .toString(); } - public Money(String s) { - this.amount = new BigDecimal(s); - } - - public Money(int i) { - this.amount = new BigDecimal(i); - } public Money add(Money delta) { return new Money(amount.add(delta.amount)); @@ -74,4 +72,5 @@ public String asString() { public Money multiply(int x) { return new Money(amount.multiply(new BigDecimal(x))); } + } diff --git a/ftgo-common/src/test/java/net/chrisrichardson/ftgo/common/MoneyTest.java b/ftgo-common/src/test/java/net/chrisrichardson/ftgo/common/MoneyTest.java new file mode 100644 index 00000000..f65bf3cc --- /dev/null +++ b/ftgo-common/src/test/java/net/chrisrichardson/ftgo/common/MoneyTest.java @@ -0,0 +1,43 @@ +package net.chrisrichardson.ftgo.common; + + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class MoneyTest { + + private final int M1_AMOUNT = 10; + private final int M2_AMOUNT = 15; + + private Money m1 = new Money(M1_AMOUNT); + private Money m2 = new Money(M2_AMOUNT); + + @Test + public void shouldReturnAsString() { + assertEquals(Integer.toString(M1_AMOUNT), new Money(M1_AMOUNT).asString()); + } + + @Test + public void shouldCompare() { + assertTrue(m2.isGreaterThanOrEqual(m2)); + assertTrue(m2.isGreaterThanOrEqual(m1)); + assertFalse(m1.isGreaterThanOrEqual(m2)); + } + + @Test + public void shouldAdd() { + assertEquals(new Money(M1_AMOUNT + M2_AMOUNT), m1.add(m2)); + } + + @Test + public void shouldMultiply() { + int multiplier = 12; + assertEquals(new Money(M2_AMOUNT * multiplier), m2.multiply(multiplier)); + } + + + +} \ No newline at end of file diff --git a/ftgo-consumer-service-api/src/main/java/net/chrisrichardson/ftgo/consumerservice/api/ValidateOrderByConsumer.java b/ftgo-consumer-service-api/src/main/java/net/chrisrichardson/ftgo/consumerservice/api/ValidateOrderByConsumer.java index ee1ec075..3019df99 100644 --- a/ftgo-consumer-service-api/src/main/java/net/chrisrichardson/ftgo/consumerservice/api/ValidateOrderByConsumer.java +++ b/ftgo-consumer-service-api/src/main/java/net/chrisrichardson/ftgo/consumerservice/api/ValidateOrderByConsumer.java @@ -2,6 +2,9 @@ import io.eventuate.tram.commands.common.Command; import net.chrisrichardson.ftgo.common.Money; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; public class ValidateOrderByConsumer implements Command { @@ -12,6 +15,20 @@ public class ValidateOrderByConsumer implements Command { private ValidateOrderByConsumer() { } + @Override + public boolean equals(Object o) { + return EqualsBuilder.reflectionEquals(this, o); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } public ValidateOrderByConsumer(long consumerId, long orderId, Money orderTotal) { this.consumerId = consumerId; diff --git a/ftgo-consumer-service/build.gradle b/ftgo-consumer-service/build.gradle index d222d182..fde5e4aa 100644 --- a/ftgo-consumer-service/build.gradle +++ b/ftgo-consumer-service/build.gradle @@ -1,5 +1,5 @@ -apply plugin: "spring-boot" +apply plugin: 'org.springframework.boot' dependencies { @@ -22,7 +22,7 @@ dependencies { testCompile "io.eventuate.tram.core:eventuate-tram-test-util:$eventuateTramVersion" testCompile "io.eventuate.tram.sagas:eventuate-tram-sagas-in-memory:$eventuateTramSagasVersion" - testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testCompile 'com.jayway.restassured:rest-assured:2.3.0' testCompile "com.jayway.jsonpath:json-path:2.3.0" diff --git a/ftgo-end-to-end-tests/src/test/resources/contracts/create-revise-cancel.feature b/ftgo-end-to-end-tests/src/test/resources/contracts/create-revise-cancel.feature new file mode 100644 index 00000000..0ec266e4 --- /dev/null +++ b/ftgo-end-to-end-tests/src/test/resources/contracts/create-revise-cancel.feature @@ -0,0 +1,15 @@ +Feature: Create revise and cancel + + As a consumer of the Order Service + I should be able to create, revise and cancel an order + + Scenario: Order authorized + Given A valid consumer + Given using a valid credit card + Given the restaurant is accepting orders + When I place an order for Chicken Vindaloo at Ajanta + Then the order should be AUTHORIZED + And when I revise the order + Then it should be revised + And when I cancel the order + Then the order should be CANCELED diff --git a/ftgo-order-history-service/build.gradle b/ftgo-order-history-service/build.gradle index d8215346..842b54aa 100644 --- a/ftgo-order-history-service/build.gradle +++ b/ftgo-order-history-service/build.gradle @@ -1,5 +1,24 @@ -apply plugin: "spring-boot" +buildscript { + dependencies { + classpath "io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE" + classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.2.0.RELEASE" + } + repositories { + mavenCentral() + jcenter() + } +} + +apply plugin: 'org.springframework.boot' +apply plugin: "io.spring.dependency-management" +apply plugin: 'spring-cloud-contract' + +dependencyManagement { + imports { + mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:1.2.0.RELEASE' + } +} dependencies { compile 'com.amazonaws:aws-java-sdk-dynamodb:1.11.158' @@ -23,8 +42,16 @@ dependencies { testCompile "io.eventuate.tram.core:eventuate-tram-test-util:$eventuateTramVersion" testCompile "io.eventuate.tram.sagas:eventuate-tram-sagas-in-memory:$eventuateTramSagasVersion" - testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testCompile 'com.jayway.restassured:rest-assured:2.3.0' testCompile "com.jayway.jsonpath:json-path:2.3.0" + // Added this stuff + + testCompile "net.chrisrichardson.ftgo.contracts:ftgo-order-service-contracts:1.0-SNAPSHOT:stubs" + testCompile "org.springframework.cloud:spring-cloud-contract-wiremock" + testCompile "org.springframework.cloud:spring-cloud-starter-contract-stub-runner" + testCompile project(":eventuate-tram-spring-cloud-contract-support") + + } \ No newline at end of file diff --git a/ftgo-order-history-service/src/main/java/net/chrisrichardson/ftgo/cqrs/orderhistory/messaging/OrderHistoryEventHandlers.java b/ftgo-order-history-service/src/main/java/net/chrisrichardson/ftgo/cqrs/orderhistory/messaging/OrderHistoryEventHandlers.java index a5f6e479..e4373b16 100644 --- a/ftgo-order-history-service/src/main/java/net/chrisrichardson/ftgo/cqrs/orderhistory/messaging/OrderHistoryEventHandlers.java +++ b/ftgo-order-history-service/src/main/java/net/chrisrichardson/ftgo/cqrs/orderhistory/messaging/OrderHistoryEventHandlers.java @@ -10,6 +10,7 @@ import net.chrisrichardson.ftgo.cqrs.orderhistory.dynamodb.Order; import net.chrisrichardson.ftgo.cqrs.orderhistory.dynamodb.SourceEvent; import net.chrisrichardson.ftgo.orderservice.api.events.OrderCreatedEvent; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +56,7 @@ public void handleOrderCreated(DomainEventEnvelope dee) { private Order makeOrder(String orderId, OrderCreatedEvent event) { return new Order(orderId, Long.toString(event.getOrderDetails().getConsumerId()), - event.getOrderState(), + OrderState.CREATE_PENDING, event.getOrderDetails().getLineItems(), event.getOrderDetails().getOrderTotal(), "name-of-" + event.getOrderDetails().getRestaurantId()); diff --git a/ftgo-order-history-service/src/test/java/net/chrisrichardson/ftgo/orderhistory/contracts/OrderHistoryEventHandlersTest.java b/ftgo-order-history-service/src/test/java/net/chrisrichardson/ftgo/orderhistory/contracts/OrderHistoryEventHandlersTest.java new file mode 100644 index 00000000..c110b430 --- /dev/null +++ b/ftgo-order-history-service/src/test/java/net/chrisrichardson/ftgo/orderhistory/contracts/OrderHistoryEventHandlersTest.java @@ -0,0 +1,76 @@ +package net.chrisrichardson.ftgo.orderhistory.contracts; + +import io.eventuate.tram.commands.common.ChannelMapping; +import io.eventuate.tram.commands.common.DefaultChannelMapping; +import io.eventuate.tram.commands.producer.TramCommandProducerConfiguration; +import io.eventuate.tram.inmemory.TramInMemoryConfiguration; +import io.eventuate.tram.springcloudcontractsupport.EventuateContractVerifierConfiguration; +import net.chrisrichardson.ftgo.cqrs.orderhistory.OrderHistoryDao; +import net.chrisrichardson.ftgo.cqrs.orderhistory.dynamodb.Order; +import net.chrisrichardson.ftgo.cqrs.orderhistory.messaging.OrderHistoryServiceMessagingConfiguration; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.stubrunner.StubFinder; +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Optional; + +import static io.eventuate.util.test.async.Eventually.eventually; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = OrderHistoryEventHandlersTest.TestConfiguration.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE) +@AutoConfigureStubRunner(ids = + {"net.chrisrichardson.ftgo.contracts:ftgo-order-service-contracts"}, + workOffline = true) // true - download from .m2/repository, false = error not on classpath +@DirtiesContext +public class OrderHistoryEventHandlersTest { + + @Configuration + @EnableAutoConfiguration + @Import({OrderHistoryServiceMessagingConfiguration.class, + TramCommandProducerConfiguration.class, + TramInMemoryConfiguration.class, EventuateContractVerifierConfiguration.class}) + public static class TestConfiguration { + + @Bean + public ChannelMapping channelMapping() { + return new DefaultChannelMapping.DefaultChannelMappingBuilder().build(); + } + + @Bean + public OrderHistoryDao orderHistoryDao() { + return mock(OrderHistoryDao.class); + } + } + + @Autowired + private StubFinder stubFinder; + + @Autowired + private OrderHistoryDao orderHistoryDao; + + @Test + public void shouldHandleOrderCreatedEvent() throws InterruptedException { + when(orderHistoryDao.addOrder(any(Order.class), any(Optional.class))).thenReturn(false); + + stubFinder.trigger("orderCreatedEvent"); + + eventually(() -> { + verify(orderHistoryDao).addOrder(any(Order.class), any(Optional.class)); + }); + } + +} diff --git a/ftgo-order-history-service/src/test/resources/application.properties b/ftgo-order-history-service/src/test/resources/application.properties new file mode 100644 index 00000000..d0030836 --- /dev/null +++ b/ftgo-order-history-service/src/test/resources/application.properties @@ -0,0 +1,6 @@ +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.springframework.cloud.contract=DEBUG +logging.level.io.eventuate=DEBUG +spring.jpa.generate-ddl=true +stubrunner.stream.enabled=false +stubrunner.integration.enabled=false \ No newline at end of file diff --git a/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderCreatedEvent.java b/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderCreatedEvent.java index de9e75ad..18a7f805 100644 --- a/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderCreatedEvent.java +++ b/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderCreatedEvent.java @@ -1,10 +1,12 @@ package net.chrisrichardson.ftgo.orderservice.api.events; import io.eventuate.tram.events.common.DomainEvent; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; public class OrderCreatedEvent implements DomainEvent { private OrderDetails orderDetails; - private OrderState orderState; private OrderCreatedEvent() { } @@ -13,12 +15,7 @@ public void setOrderDetails(OrderDetails orderDetails) { this.orderDetails = orderDetails; } - public void setOrderState(OrderState orderState) { - this.orderState = orderState; - } - - public OrderCreatedEvent(OrderState orderState, OrderDetails orderDetails) { - this.orderState = orderState; + public OrderCreatedEvent(OrderDetails orderDetails) { this.orderDetails = orderDetails; } @@ -27,7 +24,19 @@ public OrderDetails getOrderDetails() { return orderDetails; } - public OrderState getOrderState() { - return orderState; + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + + @Override + public boolean equals(Object o) { + return EqualsBuilder.reflectionEquals(this, o); } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + } diff --git a/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderDetails.java b/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderDetails.java index 5fb2e065..59b03be4 100644 --- a/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderDetails.java +++ b/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderDetails.java @@ -1,6 +1,9 @@ package net.chrisrichardson.ftgo.orderservice.api.events; import net.chrisrichardson.ftgo.common.Money; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; import java.util.List; @@ -30,6 +33,11 @@ public OrderDetails(long consumerId, long restaurantId, List line this.orderTotal = orderTotal; } + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + public List getLineItems() { return lineItems; } @@ -56,4 +64,15 @@ public void setConsumerId(long consumerId) { this.consumerId = consumerId; } + @Override + public boolean equals(Object o) { + return EqualsBuilder.reflectionEquals(this, o); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + + } diff --git a/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderLineItem.java b/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderLineItem.java index edb58949..13cf82e6 100644 --- a/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderLineItem.java +++ b/ftgo-order-service-api/src/main/java/net/chrisrichardson/ftgo/orderservice/api/events/OrderLineItem.java @@ -27,38 +27,17 @@ public OrderLineItem() { @Override public String toString() { - return new ToStringBuilder(this) - .append("quantity", quantity) - .append("menuItemId", menuItemId) - .append("name", name) - .append("price", price) - .toString(); + return ToStringBuilder.reflectionToString(this); } @Override public boolean equals(Object o) { - if (this == o) return true; - - if (o == null || getClass() != o.getClass()) return false; - - OrderLineItem that = (OrderLineItem) o; - - return new EqualsBuilder() - .append(quantity, that.quantity) - .append(menuItemId, that.menuItemId) - .append(name, that.name) - .append(price, that.price) - .isEquals(); + return EqualsBuilder.reflectionEquals(this, o); } @Override public int hashCode() { - return new HashCodeBuilder(17, 37) - .append(quantity) - .append(menuItemId) - .append(name) - .append(price) - .toHashCode(); + return HashCodeBuilder.reflectionHashCode(this); } public OrderLineItem(String menuItemId, String name, Money price, int quantity) { diff --git a/ftgo-order-service-contracts/.mvn/wrapper/maven-wrapper.jar b/ftgo-order-service-contracts/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5fd4d5023f1463b5ba3970e33c460c1eb26d748d GIT binary patch literal 49502 zcmb@tV|1n6wzeBvGe*U>ZQHh;%-Bg)Y}={WHY%yuwkkF%MnzxVwRUS~wY|@J_gP;% z^VfXZ{5793?z><89(^dufT2xlYVOQnYG>@?lA@vQF|UF0&X7tk8BUf?wq2J& zZe&>>paKUg4@;fwk0yeUPvM$yk)=f>TSFFB^a8f|_@mbE#MaBnd5qf6;hXq}c%IeK zn7gB0Kldbedq-vl@2wxJi{$%lufroKUjQLSFmt|<;M8~<5otM5ur#Dgc@ivmwRiYZW(Oco7kb8DWmo|a{coqYMU2raB9r6e9viK6MI3c&%jp05-Tf*O#6@8Ra=egYy01 z-V!G;_omANEvU-8!*>*)lWka9M<+IkNsrsenbXOfLc6qrYe`;lpst;vfs*70$z9UM zq%L>pFCOr$X*|9&3L2h;?VA9-IU*iR6FiGlJ=b~DzE5s^thxXUs4%~*zD#K&k>wZAU8 zpaa!M+Z-zjkfGK15N!&o<3=cgbZV7%ex@j^)Q9V`q^i;Fsbkbe6eHJ;dx{QbdCCs1 zdxq^WxoPsr`eiK3D0Ep}k$ank-0G&+lY!ZHDZBYEx%% z2FyE?Lb0cflLB)kDIj;G=m`^UO<4h(RWdF-DT>p{1J5J90!K!AgC0)?jxPbm$KUjg zJED+#7xQmAmr`(S%BQTV-c97As~r3zD$E;3S)@}p5udA@m6pLgRL5h-;m>LvCq?&Q zokC7Vnk-zBEaa;=Y;6(LJHS>mOJV&%0YfRdUOqbKZy~b z(905jIW0Pg;y`Yv2t+RnDvL4yGEUX*tK)JT6TWn4ik~L)fX#tAV!d8)+A)qWtSjcr z7s|f%f;*%XW!jiRvv9ayj@f&dc|1tKDc{O3BWcLGsn-OYyXRLXEOEwP4k?c`nIut0 z?4S;eO@EoynmkxHq>QpDL1q^wOQxrl))2qya?dk05^5hK? z{P6;WKHUaHw9B0dd&|xw&CYN2fVrn};Gq<=Z^QZk3e~HzzY~JrnPCs0XwMp#B<9Gm zw0?7h#4EY%O-ub6mi&O2vcpIkuM?st;RtEpKSz^Xr#3WHhpsZd!gh|_jGQ`KA30T- zKlz9vgB;pY^}Uh??nQKSzk>2&J+Qi*r3DeX4^$%2ag9^x_YckA-f9p_;8ulh(8j9~ zes{O#{v!m%n^el(VryTF-C%xfJJ$rZj)|Y|8o&))q9CEwg2;Wz&xzyHD=@T_B%b}C z=8G^*4*J4#jUJn{7-3^U(_uUp6E8+GDt#le)nya-Q4kL5ZGiFxT4bF+mX`whcif*? z>CL&Ryn3HHT^^QmWYr<}Q1_Jj7fOh}cS8r+^R#at-CnNl3!1_$96&7nR}gh}))7a0J&z-_eI))+{RCt)r8|7|sV9o01^9nv?aePxMqwPP!x|sNmnn&6{K$K*mVX9lxSAmcqAV1(hKA-=coeTb*otxTOGYXsh zW$31^q7L@<#y~SUYoNKP1JK?4|FQNQb$i8mCG@WhX9i_^;@M2f#!nq7_K*M!4lGz1 z5tfADkO7BZDLgVQ?k7C)f;$eqjHI&zgxhf}x$8^ZEwFfm-qY=+M+fbS)9r8fFE5H9 zv{WPU35cR8%z;(W%5<>y+E&v84J4^Y##N!$B++RI`CZ1i3IW9Nau=*pSxW&^Ov-F> zex=&9XYLVcm1Y?am>2VC`%gMev9$#~; zYwxYvMfeKFsd!OBB@eOb2QNHFcsfKm;&z{OVEUiYmQ}~L@>$Ms@|Ptf3jQO-=Q;1+ zFCw+p+Z3lK_FmIAYnk2V;o915cDM}%Ht5RH%w}P>Yg9{h1mZ}~R6tUII4X7i4-2i% z2Uiw3_uHR!d~5(s;p6btI@-xhAkRg9K|n#}PNT9Dw9P>z$3>30lP1(=mcQ|tpyv3@ ze1qU!69OAx4s7$8r7Y-#5I`m!BXq`f!6C(BtUlG-oq+liqMCS_D@0nSFc%y+N6_Zh zi%L3LhF3zZP{d1)L&SXxPD(fp@T@J;jZeNaf$zl>vAh7=tI z2;wS^QyRdZm~)Ur&!af;8eB8*7(F96K^=WbC$)#TWvB~Awo5AtPf8Il4snD}Xsqd< z>cH+gcg72nTg5tl>oFbwdT{BDyy1=f=4~h~L$)UX;FXa;NdSlyF{(YLrx&VDp`pQI zh3pQtC=d8i1V6yUmFon*LQsNYWen?eO-gSZ4cvYcdEd0klSxcBYw+|5AyCv6TT96h z{7Yh9`h}biU?3oBFn=d8>Hn`1Q*w6rgeX^QbC-WFwjY}Int0;qUny4WMjIee@#0%l z>YAWLVCNo1lp$>9L$Tx`t!dp?>5Pfbhc*!*wzfWkj_x`Q?`3Jc@9r8uq~dgb+lgeh zlA`eUal3e2ZnWQSSYB>qy#85^>j7!=uO-hG5*erp22NaC81#Ytioc>r?D9$b_JiC+ zSp)8KR$%}FjFNRkeE#c5vKbXNJDBoO< z)73Jt7Y|3v45efud1xkg2GO3OwYfsuBV`f6S_D>Aoh2%=`1Y$bHP>0kBvTSowX57H z&1nbbx=IT>X^ScKYL&&{LNq~^UNgR|at`D;SxTYpLvnj_F*bGgNV2tEl1k$ccA&NW zmX(LV*>Op)BOgoric(98mIU)$eUa&jM5bKlnOrHm$p^v@u;W0J)!@XWg+#X=9En(-tiw!l?65rD=zzl(+%<)bI{ZN;SRco{jO;>7 zlSY|TIxuN|d#YHx^^~>iYj2V>cC>wQwWzGVI!6#epjJ6tl_`7tDY17WMKMB@s*Jr& zXOs*@>EwQ6s>M13eZEBJ#q0|;8jao{wK4keesH9?$OSk~_3#*x`8fAzQa7fprQ6(Z zi$}B%m81y*S)RxaX;wW!5{{EDw8)IE3XDRO1Y^%TMr}c|Y>WBAKT=b*K&uMT(?JSl zO>gVtl_bKQ$??TeWr7wYO+Vbl?CTQj?JrW&td`|#@;R2Gca9jq^p`{@)KY97o3}Af zfTh{pUUWD;P7sq=I!lA6;*hq0Nq`F56T)x$K?BMOk}tptYw(%$?*otp2N6IF3#GgqM46Cda!qzvGZcMgcGV`bY5ZIfOB6^;US#WgRai zq#vS8ZqPY953|eFw<-p2Cakx|z#_{4pG}mk{EANI{PnK*CUslvS8whko=OTe13|It z>{O2p=mmanR2-n>LQHaMo}noWCmjFO@7^z~`Y{V>O`@rT{yBS=VXsb}*Pi_zDqM3? zjCZqWR}fEzAkms+Hiq8~qRAFvo}dVW{1gcZ?v&PdX?UG*yS}zT9g7nZ!F1WRH}sHA zJ4~B2Br~8?uhbaX!3g+7=3fVM)q^wEzv**rk5e34==NRCV z3G$G5B!DICFslm)c){oesa_0muLxGoq`xYVNURl*NhE#v2>y9vDz&vJwrB`Q>DhN# zY2GnY!Y^8E%PU0}haXL$8a5QN1-&7NWuC~{62j| z2ozmFyx8GpOzj?&KK1JF28;E8H_p4N^LMm9K0y}!lCxcK79eFGTtGm?7jy?t94Q@X zli|our1#|>f*68fyA0bSn=YisYSl8HB(dFN4Y$qb7p4DR0YQt=^eEMnJkgiM48$>QV6x5*^a|D|t zMPDk}u<^YEYrt|H&hy)DRk%rDIb{LTo;h7=fp^J9Lr&`{9`8_pS*tQ_$KXB$2#5{h z-&yPbN-zInq{7aYZuaItS8-2Mb4OQe2jD*&)0~898E|HlAq`o!M&It@vvnj z_y@))>~_oR%S8OfmFTGYIat^#8_YKMqWLac<^}RZFDcJqvSJa>&6HaLS7p-$)QyL= zHrO|t75`d41Bp37RZtKR%g^%o@9C5Ce=CjuvVQ-KI#Uw2WWa>cho;jztUt~Le*_pT zkfA2iif9QFp;vhd)|A?tdAQ?9o~?EqgL;=)eKFQ{E^u?OIP}fl^5A;$^ZVutCIqj5 z&*i+G?!Px|5~~6zTYf>~uw*kM`5p&Hju&#w!7^An3*mQwTK22wC7p^OsvMjWf`$MY zLX|ZFV#+>Uq2!QyRD9cgbI9nswteMAMWtK(_=d%r?TLrx?_rkjbjI(rbK#T9Gn}J| z5ajow3ZErpw+%}YfVL-q^{r~##xJ^_ux2yO1!LJZXg)>F70STV=&Ruwp&XP^_?$h0 zn>$a?!>N+Kt$UXzg`e+szB}*uw)Z$uL6?>*!0IrE)SgV~#a?Qgg7HuTsu3ncrcs|l z=sQSMtr}S!sQ4SriKg=M`1Y|bC`XJ+J(YT)op!Q);kj0_e)YNVNw8SI|1f%9%X?i5>$lLE(Wfc$wY?(O985d5e*)UPtF!7gG3(Kd z-^=-%-wWCEK`r4oFh^{|;Ci%W^P>K%9dBNDqi%c$Q{iY#(zbwN7~pQI=SHd%WuV7Z zO?0P;Zc6yeN;)IbJIP0=>W)EgE!76jM^?IyQ*D(T})1NGmP z~YAb6T^#R6;)Ls;cV~LWk z33lcLpbSjxStw9Z>Nv&+rPOXxCGB=?ttZs?{OF7;GYlV&w7-82POb$XrogqFpLA2`j&MLZXr=IG>PAFSb2np~x;E_kV{ zsDwbK$?iYRn7$;mHYZhQn6P2#_hXAHd?;q~!Zy}%;@%wT3u|Sa-!WxxOE_fwyFv*Db@>X;Rl+fK1oP?55*dN0#2%SuikZ)y7Kx>`8*9d?}5 zKvXF7J5&Ey6{A8qUFxrFOh<$xdSWV^dw7z|`7RVZJhAwO72V zRrM_3*wI`^ycl7~>6KaCYBr#WGR>}B)Q(V%&$MhVrU>u~ql zjGeZF&>=_ld$oY!V}5}Gb> z*iP38KOav9RHY)0uITwgz99w- zJX-0BGCdY*$c7pi@>@-`2>#>}c(DHaI62ntpKz z`c01Z#u7WuMZ71!jl7hv5|o61+uv5nG?*dffEL~328P5HlKh2&RQ;9X@f>c1x<>v= zZWNSz3Ii~oyAsKCmbd}|$2%ZN&3gc9>(NV=Z4Fnz2F@)PPbx1wwVMsUn=-G=cqE3# zjY{G4OI~2o$|*iuswTg1=hcZK$C=0^rOt-aOwXuxU=*uT?yF00)6sE}ZAZyy*$ZTH zk!P*xILX#5RygHy{k?2((&pRQv9_Ew+wZ>KPho_o1-{~I*s1h8 zBse@ONdkk-8EG?r5qof}lwTxdmmEN|%qw(STW|PFsw1LD!h_Vjo;C4?@h|da4Y;*; zvApQ=T&=jWU39Uz=_yN@Bn0{{)yn8RZ2&X!<*KBv-7tcWdkF1Ij8D0mU zwbcs}0vDaLGd@xx%S_QZ1H)GTt`~>+#z}HXJTl9S!sd9seVJc|_wUMSdD$>k`K_RG zlq(fsnR@KM^;C}}&vG2t+}_nGPuI5ovg$6TYeMPIREGxP@2r~RKd@>gV`mq0XENsh z%IRZ-ZNP+4#J`o-yRpP;w@;CrSr3wiix3e9Qc|s(WapRq950P->g|JYC$A)$YrGeH zz5dKlAHAPJ>%?llqqB&#+#VU3sp=9>Xms1J;tSYN>LMwNtU68yr!})K4X>%^IrIDp z>SHy&6fJHybwS^BW>okFeaQp6wxaVP`hy;ZX#e+=w3c?PGD&_LmeqL8oZ*YaM1+#S z5WNAKo4+99JW(+qcMjh;+c%R#R?t;(aQ`2`C=bo((ERzgAwKKazXy*0wHN;v;P|f> zBW&?`h#_I^?Bc5GX7XP@|MOiw%&-#?EQ|w+FdCl_&qPN&s$|Z17UCF9oXS#N z)px6>zm&}0osTnCGI;AXsj`q=LpIsW4x}q~70uey5N_NpdJ*Gv^@$g@f2{EB>LP7Y zE5P`jZh1vHNgk7LfMT({jLCjRZa4ubW;UA#%<@Zj?efrPdm{W3J5UEFgm`YkVqz;AMFetZuM5uQpvORb1GDX`WZGwTrF z46+&sAri5QXCfGYpdgonWR5`>ZEa;?jrKvfNvXF<&l)1uU-3q#4X16R2~?P0yg3H` zfw82QWZo^cac+%(g^_6`+2>~Fvy{pOCGnj86+=-!N`GPWAjus1ejhn6f4|mDkU6EE z&u~;xfdRMkj=h;4d~~+4(>L8weT3cz9e@E11EH!tX<IC!@kS+dsIQA`HQ2vdoS zzSD0U?mb1M0@qXu{yhZk2Y6}2B-AvvYg|tRr6z*_*2l*VLiR6G;M{O^Znq~LI%=I_ zCEU{htx&Bo+69G`p|A@R>KlY1*;;!{aWq?Pc0Cu!mT-0S`!>3<@s%Ri;utYNQ+CXDj+LC5<*$4*$-mogGg^S~3JRv{ry zPJzKJg!XKb>P}yJVc^1V@T&MV{z;@DLhvV{dG?RogCcPkROivliSr58>5Zw&&A2?n z9`JOLU;eQGaOr6GB(u{t3!+$NaLge$x#M&*sg!J;m~rRc)Ij5|?KX_4WiM-eE%t8e zqUM7eZ~ZonavR;K4g2t$4Fj=UVyEHM7LPb%8#0?Ks{~?!qhx9)2^>rg8{0npLtFKR zJB)19TFiD^T7IUXA8wt!@n5gj&@OK~EO}MR6^qd?^-?%-0~b2K9RWh+_mSEQQWsLCFOt#JlAQMgNxvv-m z;sF*r;WZ*Wi@I|6pMN+|_rLYKlWwvpKZY9rA;fo8l8hFQGI?4#kt1-r4UL;nPF@{~ z2T~a@2>yD|GuU55boxoIIe_BFo2Vq&rs&2itv|B>OC*bIeOqMBRw~y5KRMwiVHc)` zIBdliiY?Ai7*+k#NZf3MW5!hya~RZ6r7k)b?HF0e(n`ZX=iCpT7St`FDwL@SGgKlq zNnnU*3IcnYDzJg{7V$cb`xeb4(s(({&%f69XMTw-JQErS%?X_}?&y&tvHw@>1v{#R z4J@(=el^kRI+jGa;4)l#v%-jM^$~0ulxh6-{w*4Lsa>Tuc z>ElR3uM~GUChI)c{TW${73A3$vs<&iH;e?4HjW2MvSz9tp9@69+`_@x{Qte^eFo5IlAi&zw$=t6u8K%8JtjRI88PFNM7R>DaCO3rgngmk zI-RMOyt@kr-gVra=tl^@J#tI7M$dird(?aU!`&1xcm~2;dHN(RCxh4H((f|orQ!BS zu;(3Vn+^doXaqlhnjBJj-)w?5{;EEZTMx+?G>Rp4U^g<_yw_blAkdbj=5YrNhZB9@ zNmW=-!yFx5?5aF^+6*1XI|s3lIn_eyh`uv%?liNzSC#z&z^R(mqEYL@TdWzgkf>g1 zedzs*={eJavn{8vF%4nf@et<@wkOPR>NiVuYtESbFXQ;sDz_;|ITVeoW|me5>jN5P z5--{13JT{3ktkAf9M;Jty)yectg#{+9sK{C;2CvPU81tB3{8S5>hK{EXdVe?fR?sd8m`V zPM*$)g$HKp0~9Xf6#z!YJ&g!%VkCMxkt>ofE!62?#-&%|95^)JJ9 zk;GlJdoH0HwtDF(_aTv}mt$?EyRyE6@pm5DG~Gj-2%3HcZT13e)$)z99bdK_WCx|Q zQNza(R)Z>ZKTn8oIdcw%c^pFaMpFZ4HOds!BODgSBWJJYW3I_WJvoEm4xsfs%#LZ6 zdPCk{5XJ>2f7Hj-i*9lTW6BKCIuy)3L!b3(uPoSgW1WA+OEYYBRgSsJq7wjHh%c8ymMs3FU%~cprqL*084p*^T3{J%Gwq`jB30n(&y6- zII8-_r-s5&CVtsoNZ9%On?7yn;oZG03-$wx^uRk9>b*ufh15|HHk|%=MA^ioyb9CYU$7y$4R|M5HvpiCTxKSU`LUg$+ zB3IBl&{qO}agqF~BFM6&11wMeR-#Rkuh_(^j+P4{;X_w|siva$5P`dykyhfAUD%e8 z+{G0|7(Q`_U91sMKFO^rHoCWfXi0$^ev)-187G}klYv@+Rf%uZ&T4-Uhh=)pcU6O1 znXc^c5)!$X+39|4`yNHuCj0wkm+K1VN0G3_EL?-ZH$p5Y*v6ec4MV zS~1~}ZUhl&i^4`Fa|zyH4I%rXp;D6{&@*^TPEX2;4aI$}H@*ROEyFfe^RZI%;T>X> z>WVSUmx@2gGBxkV&nfyPK=JI$HxRKUv(-*xA_C;lDxT|PgX*&YYdkrd5-*3E1OSXBs>35DLsHHp%zm+n0N(Yu{lMo>_t&d1Xy zfCxl=(CNNx>ze+7w)60mp>(M``Qn$aUrVb$cJAb6=Do7VgW`Qn2;v5{9tB)jP$_mB zn{Hb_sMs4yxK|!`PI7+zO68}{Iv)dpu!+ZZl)xuoVU(oFsm<3gT{j2c*ORl|Lt+?dR^M?0 znW6rNA)cR*ci;z?BaG(f(XynY_y+kTjj~T$9{N{>ITQ4-DmZ6{cOkoea9*LpYL{Apo0hSpLqJu z9`tjP&ei;%pn9QY>-$9=<73M#X;qGb+%Bt0x>=u`eDtthI+LWB9CdAO=ulZo9&Ohs2X8GW>b7#&U|py28KTvPBl#Nqv^{AgkVXrOyS z@%3)}$I&mJOYWoG$BBb)Kb~0ptDmBxHNH^i6B8FA7NR2HfTnjP?eDnoY4NS_aYg4P zGGPw11sAf^^fTkY#j@T#6Ll*^GVaPo-1;aS6_a}{r{tWZilzse2m zc?LS=B|EWxCD|!O%|%t3C@Rd7=rKJRsteAWRoDu|*Kx-QwYZQeYpGrZ_1J%mFM;*S*u=0 z%1OC9>kmCGqBBu#-1jVPRVW*BTv%3uPI8fO?JOZD#P_W^V+K7&KVB>hzZ@PdY*%Ezo;}|5Mk`Mo2m*_K%no*jDJGp(s9j;&U`Z>z zO#SEe)k!p$VE-j2xDoX$!;Up5%8x$c`GH$l+gTA*YQaE0jwCOA<*__2NkV){z_u2=4NQ zSk$(oj$%ygio?3V8T3IyGMYvPs`t{im2IoHs7or+>>MYvG%Q?PwOLqe%73uGh6Wn; zo>e7qI$9?%cVVkvQLOLKcU5n*`~qn8pzkdu=Z4#2VnhUy>S*;kT=NqA!dQtnE?wVg zOKobxJ|QCjk`!(2*~5NQx{{=Lr=)ndyn{V|&PxUa=xQXVU?#M24F8H%C*uvs(#Va0 zSkp}0EFYq0#9xp&$O?gIInc#^^_6Ol88W%)S5A@HeE0(SR&!Yl>u=*5JEoUViDR@2 zJBjTsp=Y44W`Nb2+*CcZCkwP(QChX1s)b09DEIZCKt1$q2~;&DJ9!{bQ1Y6&T_9u1 zZM8^im8Wf#FUO6tZqc7#`z0cN_JA>#U_b7he%?cCnlV2&47y5Fc)Z7bp5xGe1zNq9 zl1VaV-tsm3fY=oIX^SPl!P;9$o?**0brq#ShM~3CXhh^SK0oOKB9O>;q3G@ z&4&h$mLSgohc^5IC|H>IGfZvVQFUT>T$|U7{znY`56<5d)07oiv*2R0+-BGPPkWJ! zIOzKF+<5o2YLWP|SGCx8w@<>u6K1o`++xJ+6kaJrt<&0Haq zyUccgxI$sR07Vo9-pF);heBva;?&NcAzC*gSSG9B3c?A;IH9J zl$j%F4*8;F0;H2Cjo*kWz4{kSh?nX}23&&KL+U(#nOAuR`wn@uwUNkWEgb*ZShKPy z`aXTJT4f*Um4`iv2KOfzf-~`#pOfH8>is*xnLBDTyx2Xuc8Y2Od6z((P2AZK@b_96 z#0V6jdw>sEDJ#uNGV|EshD1g&bYZCzCZTZ)286HLHc8Eyy_HPi;d#%;Wx}d6tUUxq z_VB$+898z_{9-A<*v6VI7?(dC04o!8$>DQ$OdbrA_@<6auiBNp{Dw$Hs@@gcybIQT zAU7Pc5YEX&&9IZ~iDo&V`&8K$-4o$)g?wF8xdv1I8-n}1bc7tviIBqt z#iIl1Hn;W?>2&#bU#VZ1wxq(7z=Q15#0yoz)#|r`KSPKI-{aN%l61^?B4RMDt?Vk` z)G#K6vUN?C!t{Q<@O4$0(qI>$U@@TI2FVF;AhSSb5}LtXx&=k&8%MWM3wv;Xq0p~W z#ZX;QFv5G9-i6=+d;R7Dwi)ciIZ1_V!aw;K^etau+g0fOA2HXpV#LQZGzf?h#@}(o z|3w!sZ|&mp$;tmDiO=zef5C|Alz+@@4u5#yZ7yNpP=&`432%a{K#{;nsS!jwk-$Qs zZRty}+N`Y~)c8|$&ra{bOQWM2K7qa}4Y{ndK%dKp&{ zFCvX{PAy_C{xzS_-`0>JlPP7&5!5 zBQ$NQz^z#2y-VeIxnfY|RzU`w+1t6vwQ|wM)LlpuaUzYehGII;>2DYyR|~wC@l97s zgX=f*1qtfDyco%BHmN+o<2qoi`D67R+RM$$NN5-moE4kx3MCFfuip*45nComOZKQf z3!(8tkSdhY5+A%@Y=eVEZkXU3S6B2V-R$ZuRIXWhsrJg3g)p4vXY@RV60bKuG zT6T!enE<;(A{*HPQhae*(@_!maV~AWD4EOwq10tkCXq+HPoe_Pu?d4Kg=2ypcs?&f zLa>mEmPF4ucJ%i~fEsNIa{QmQU27%Abh|w(`q)s~He5$5WYQ_wNJX6Qop<=7;I1jd zNZak`}0lVm+^O!i;|Lwo}ofXuJ)*UtH4xaPm*R7?YS*<&D__=@Kki>{f_Z-XqM;Tj195+~@d;rx zh5pj8oMuupWa#E(%85**I~1Zat-Sa^_R11-CiKdd`8m(DGuzOm9lX$Dd!DX!_Al}d zS!-|}dWG80S;`jSKDH%Uv;-OJNeBI0Bp$z->{_>1KU%h&Af7nns(L=xRN1 zLvOP=*UWIr)_5G2+fCsUV7mV|D>-~_VnvZ3_>=9 z_bL6`eK%W*9eJ34&Puz^@^ZIyoF@%DTun#OOEdUEn8>N9q(}?5*?`o?!_<(i%yc`k zf!xXD6SQscHgPgiHt>x6{n{+}%azrfV4VHi#umyi0;11c816`E??2`$;Rc`)qA2H( z5L|{o=ut7Te=^~@cR0_#cah0?w0Me$&>}ga8xxy=?DDl#}S~Y z4o2n`%IyGjQEP%8qS|v(kFK&RCJbF1gsRVJ>ceSjU`LuYJu%C>SRV#l`)ShD&KKzv ztD<9l0lcW0UQ8xjv|1NXRrCZhZh3JFX_BNT@V|u9$o~8M=cjOX|5iBS|9PAGPvQLc z6sA~BTM(~!c&V=5<}ZIx}O7A;|&bd7vR_y)t+ z?Vm7kb^gJ88g;!fRfMTSvKaPozQz4WcYD8l#0WxQ${P%0A$pwhjXzyA0ZzErH{1@M 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 OJOEd-=rxTO#Qy`$*1Hh^ literal 0 HcmV?d00001 diff --git a/ftgo-order-service-contracts/.mvn/wrapper/maven-wrapper.properties b/ftgo-order-service-contracts/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..c3150437 --- /dev/null +++ b/ftgo-order-service-contracts/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip diff --git a/ftgo-order-service-contracts/build.gradle b/ftgo-order-service-contracts/build.gradle new file mode 100644 index 00000000..ee99ddd9 --- /dev/null +++ b/ftgo-order-service-contracts/build.gradle @@ -0,0 +1,31 @@ +buildscript { + dependencies { + classpath "io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE" + // if using Stub Runner (consumer side) only remove this dependency + classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.2.1.RELEASE" + } + repositories { + mavenCentral() + } +} + +apply plugin: "io.spring.dependency-management" +apply plugin: 'spring-cloud-contract' +apply plugin: 'maven' + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:1.2.1.RELEASE' + } +} + +dependencies { + testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier' +} + +generateContractTests.enabled = false + diff --git a/ftgo-order-service-contracts/mvnw b/ftgo-order-service-contracts/mvnw new file mode 100755 index 00000000..a1ba1bf5 --- /dev/null +++ b/ftgo-order-service-contracts/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/ftgo-order-service-contracts/pom.xml b/ftgo-order-service-contracts/pom.xml new file mode 100644 index 00000000..46ccb679 --- /dev/null +++ b/ftgo-order-service-contracts/pom.xml @@ -0,0 +1,110 @@ + + + + 4.0.0 + + net.chrisrichardson.ftgo.contracts + ftgo-order-service-contracts + 1.0-SNAPSHOT + + POM used to install locally stubs for consumer side + + + org.springframework.boot + spring-boot-starter-parent + 1.5.4.RELEASE + + + + + UTF-8 + 1.8 + 1.2.0.RELEASE + Dalston.SR1 + + true + + true + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud-dependencies.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + true + + + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + + + ${project.basedir} + + + + + + + + spring + + true + + + + + spring-snapshots + Spring Snapshots + http://repo.spring.io/libs-snapshot-local + + true + + + + spring-milestones + Spring Milestones + http://repo.spring.io/libs-milestone-local + + false + + + + spring-plugin-snapshots + Spring Snapshots + http://repo.spring.io/plugins-snapshot-local + + true + + + + spring-plugin-milestones + Spring Milestones + http://repo.spring.io/plugins-release-local + + false + + + + + + + \ No newline at end of file diff --git a/ftgo-order-service-contracts/src/main/resources/contracts/http/GetNonExistentOrder.groovy b/ftgo-order-service-contracts/src/main/resources/contracts/http/GetNonExistentOrder.groovy new file mode 100644 index 00000000..647a4026 --- /dev/null +++ b/ftgo-order-service-contracts/src/main/resources/contracts/http/GetNonExistentOrder.groovy @@ -0,0 +1,11 @@ +package contracts.http; + +org.springframework.cloud.contract.spec.Contract.make { + request { + method 'GET' + url '/orders/555' + } + response { + status 404 + } +} \ No newline at end of file diff --git a/ftgo-order-service-contracts/src/main/resources/contracts/http/GetOrder.groovy b/ftgo-order-service-contracts/src/main/resources/contracts/http/GetOrder.groovy new file mode 100644 index 00000000..35d21c4c --- /dev/null +++ b/ftgo-order-service-contracts/src/main/resources/contracts/http/GetOrder.groovy @@ -0,0 +1,15 @@ +package contracts.http; + +org.springframework.cloud.contract.spec.Contract.make { + request { + method 'GET' + url '/orders/99' + } + response { + status 200 + headers { + header('Content-Type': 'application/json;charset=UTF-8') + } + body('''{"orderId" : "99", "state" : "CREATE_PENDING"}''') + } +} \ No newline at end of file diff --git a/ftgo-order-service-contracts/src/main/resources/contracts/messaging/OrderCreatedEvent.groovy b/ftgo-order-service-contracts/src/main/resources/contracts/messaging/OrderCreatedEvent.groovy new file mode 100644 index 00000000..45404c91 --- /dev/null +++ b/ftgo-order-service-contracts/src/main/resources/contracts/messaging/OrderCreatedEvent.groovy @@ -0,0 +1,18 @@ +package contracts.messaging; + +org.springframework.cloud.contract.spec.Contract.make { + label 'orderCreatedEvent' + input { + triggeredBy('orderCreated()') + } + + outputMessage { + sentTo('net.chrisrichardson.ftgo.orderservice.domain.Order') + body('''{"orderDetails":{"lineItems":[{"quantity":5,"menuItemId":"1","name":"Chicken Vindaloo","price":"12.34","total":"61.70"}],"orderTotal":"61.70","restaurantId":1, "consumerId":1511300065921}}''') + headers { + header('event-aggregate-type', 'net.chrisrichardson.ftgo.orderservice.domain.Order') + header('event-type', 'net.chrisrichardson.ftgo.orderservice.api.events.OrderCreatedEvent') + header('event-aggregate-id', '1') + } + } +} \ No newline at end of file diff --git a/ftgo-order-service/build.gradle b/ftgo-order-service/build.gradle index 41bb413a..1f47c062 100644 --- a/ftgo-order-service/build.gradle +++ b/ftgo-order-service/build.gradle @@ -1,12 +1,78 @@ -apply plugin: "spring-boot" +buildscript { + repositories { + mavenCentral() + jcenter() + } + dependencies { + classpath "io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE" + classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.2.0.RELEASE" + classpath "com.avast.gradle:gradle-docker-compose-plugin:$dockerComposePluginVersion" + } +} + +apply plugin: 'org.springframework.boot' +apply plugin: "io.spring.dependency-management" +apply plugin: 'spring-cloud-contract' +apply plugin: 'docker-compose' + +apply plugin: IntegrationTestsPlugin +apply plugin: ComponentTestsPlugin + +dependencyManagement { + imports { + mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:1.2.0.RELEASE' + } +} + +contracts { + contractsDslDir = new File("../ftgo-order-service-contracts/src/main/resources/contracts") + packageWithBaseClasses = 'net.chrisrichardson.ftgo.orderservice.contract' + generatedTestSourcesDir = project.file("${project.buildDir}/generated-integration-test-sources/contracts") +// baseClassForTests = 'net.chrisrichardson.ftgo.orderservice.contract.AbstractOrderConsumerContractTest' +// baseClassMappings { +// baseClassMapping('.*Order.groovy', 'net.chrisrichardson.ftgo.orderservice.contract.AbstractHttpOrderConsumerContractTest') +// baseClassMapping('.*/messaging/.*', 'net.chrisrichardson.ftgo.orderservice.contract.AbstractOrderConsumerContractTest') +// } +} + + +compileIntegrationTestGroovy { + source = project.file("${project.buildDir}/generated-integration-test-sources/contracts") +} +compileTestGroovy { + source = ['src/groovy'] +} + + +componentTest.dependsOn(assemble) + + +dockerCompose { + + startedServices = [ 'not-used'] + + componentTests { + startedServices = [ 'ftgo-order-service'] + stopContainers = false + } + integrationTests { + startedServices = [ 'mysql'] + stopContainers = false + } +} + +componentTest.dependsOn(componentTestsComposeUp) +integrationTest.dependsOn(integrationTestsComposeUp) dependencies { compile project(":common-swagger") + compile project(":ftgo-common-jpa") compile "io.eventuate.tram.core:eventuate-tram-jdbc-kafka:$eventuateTramVersion" + compile "io.eventuate.tram.core:eventuate-tram-commands:$eventuateTramVersion" compile "io.eventuate.tram.sagas:eventuate-tram-sagas-simple-dsl:$eventuateTramSagasVersion" compile project(":ftgo-accounting-service-api") @@ -24,6 +90,22 @@ dependencies { testCompile "io.eventuate.tram.core:eventuate-tram-test-util:$eventuateTramVersion" testCompile "io.eventuate.tram.sagas:eventuate-tram-sagas-in-memory:$eventuateTramSagasVersion" - testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" + + // Added this stuff + + testCompile "net.chrisrichardson.ftgo.contracts:common-contracts:1.0-SNAPSHOT:stubs" + testCompile "net.chrisrichardson.ftgo.contracts:ftgo-restaurant-order-service-contracts:1.0-SNAPSHOT:stubs" + testCompile "org.springframework.cloud:spring-cloud-contract-wiremock" + testCompile "org.springframework.cloud:spring-cloud-starter-contract-stub-runner" + testCompile 'io.rest-assured:rest-assured:3.0.6' + testCompile 'io.rest-assured:spring-mock-mvc:3.0.6' + testCompile "io.rest-assured:json-path:3.0.6" + testCompile project(":eventuate-tram-spring-cloud-contract-support") + + componentTestCompile 'info.cukes:cucumber-java:1.2.5' + componentTestCompile 'info.cukes:cucumber-junit:1.2.5' + componentTestCompile 'info.cukes:cucumber-spring:1.2.5' } + diff --git a/ftgo-order-service/docker-compose-component-test.yml b/ftgo-order-service/docker-compose-component-test.yml new file mode 100755 index 00000000..f4447f3f --- /dev/null +++ b/ftgo-order-service/docker-compose-component-test.yml @@ -0,0 +1,58 @@ +version: '3' +services: + zookeeper: + image: eventuateio/eventuateio-local-zookeeper:0.15.0 + ports: + - 2181:2181 + kafka: + image: eventuateio/eventuateio-local-kafka:0.15.0 + ports: + - 9092:9092 + depends_on: + - zookeeper + environment: + - ADVERTISED_HOST_NAME=${DOCKER_HOST_IP} + - KAFKA_HEAP_OPTS=-Xmx320m -Xms320m + - ZOOKEEPER_SERVERS=zookeeper:2181 + mysql: + image: eventuateio/eventuate-tram-sagas-mysql:0.3.0.RELEASE + ports: + - 3306:3306 + environment: + - MYSQL_ROOT_PASSWORD=rootpassword + - MYSQL_USER=mysqluser + - MYSQL_PASSWORD=mysqlpw + tram-cdc-service: + image: eventuateio/eventuate-tram-cdc-mysql-service:0.3.0.RELEASE + ports: + - "8099:8080" + depends_on: + - mysql + - kafka + environment: + SPRING_DATASOURCE_URL: jdbc:mysql://mysql/eventuate + SPRING_DATASOURCE_USERNAME: mysqluser + SPRING_DATASOURCE_PASSWORD: mysqlpw + SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.jdbc.Driver + EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:9092 + EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 + EVENTUATELOCAL_CDC_DB_USER_NAME: root + EVENTUATELOCAL_CDC_DB_PASSWORD: rootpassword + EVENTUATELOCAL_CDC_BINLOG_CLIENT_ID: 1234567890 + EVENTUATELOCAL_CDC_SOURCE_TABLE_NAME: message + ftgo-order-service: + build: . + ports: + - "8082:8080" + depends_on: + - mysql + - kafka + - tram-cdc-service + environment: + SPRING_DATASOURCE_URL: jdbc:mysql://mysql/eventuate + SPRING_DATASOURCE_USERNAME: mysqluser + SPRING_DATASOURCE_PASSWORD: mysqlpw + SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.jdbc.Driver + EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:9092 + EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 + JAVA_OPTS: -Xmx256m diff --git a/ftgo-order-service/src/attic/AbstractOrderServiceComponentTest.java b/ftgo-order-service/src/attic/AbstractOrderServiceComponentTest.java new file mode 100644 index 00000000..daf38128 --- /dev/null +++ b/ftgo-order-service/src/attic/AbstractOrderServiceComponentTest.java @@ -0,0 +1,126 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.tram.commands.producer.TramCommandProducerConfiguration; +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import io.eventuate.tram.messaging.consumer.MessageConsumer; +import io.eventuate.util.test.async.Eventually; +import net.chrisrichardson.ftgo.accountservice.api.AuthorizeCommand; +import net.chrisrichardson.ftgo.consumerservice.api.ValidateOrderByConsumer; +import net.chrisrichardson.ftgo.orderservice.api.web.CreateOrderRequest; +import net.chrisrichardson.ftgo.orderservice.domain.RestaurantRepository; +import net.chrisrichardson.ftgo.orderservice.messaging.OrderServiceMessagingConfiguration; +import net.chrisrichardson.ftgo.orderservice.sagaparticipants.ApproveOrderCommand; +import net.chrisrichardson.ftgo.orderservice.service.OrderCommandHandlersConfiguration; +import net.chrisrichardson.ftgo.orderservice.web.OrderWebConfiguration; +import net.chrisrichardson.ftgo.restaurantorderservice.api.ConfirmCreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrderReply; +import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantCreated; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.util.Collections; + +import static io.eventuate.tram.commands.consumer.CommandHandlerReplyBuilder.withSuccess; +import static io.restassured.RestAssured.given; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_RESTAURANT_MENU; +import static org.junit.Assert.assertNotNull; + +public abstract class AbstractOrderServiceComponentTest { + + protected abstract String baseUrl(String path); + + @Configuration + @Import({CommonMessagingStubConfiguration.class, OrderWebConfiguration.class, OrderServiceMessagingConfiguration.class, OrderCommandHandlersConfiguration.class, + TramCommandProducerConfiguration.class}) + public static class CommonTestConfiguration { + } + + @Configuration + @Import(SagaParticipantStubConfiguration.class) + public static class CommonMessagingStubConfiguration { + + @Bean + public MessagingStubConfiguration messagingStubConfiguration() { + return new MessagingStubConfiguration("consumerService", "restaurantOrderService", "accountingService", "orderService"); + } + + @Bean + public MessageTracker messageTracker(MessageConsumer messageConsumer) { + return new MessageTracker(new MessageTrackerConfiguration("orderService"), messageConsumer) ; + } + + } + + @Autowired + private SagaParticipantStubManager sagaParticipantStubManager; + + @Autowired + private DomainEventPublisher domainEventPublisher; + + @Autowired + private RestaurantRepository restaurantRepository; + + @Autowired + private MessageTracker messageTracker; + + @Before + public void setUp() throws Exception { + sagaParticipantStubManager.reset(); + } + + @Test + public void shouldCreateOrder() { + + // setup + + sagaParticipantStubManager. + forChannel("consumerService") + .when(ValidateOrderByConsumer.class).replyWith(cm -> withSuccess()) + .forChannel("restaurantOrderService") + .when(CreateRestaurantOrder.class).replyWith(cm -> withSuccess(new CreateRestaurantOrderReply(cm.getCommand().getOrderId()))) + .when(ConfirmCreateRestaurantOrder.class).replyWithSuccess() + .forChannel("accountingService") + .when(AuthorizeCommand.class).replyWithSuccess() + .forChannel("orderService") + .when(ApproveOrderCommand.class).replyWithSuccess() + ; + + + domainEventPublisher.publish("net.chrisrichardson.ftgo.restaurantservice.domain.Restaurant", RestaurantMother.AJANTA_ID, + Collections.singletonList(new RestaurantCreated(RestaurantMother.AJANTA_RESTAURANT_NAME, AJANTA_RESTAURANT_MENU))); + + + Eventually.eventually(() -> { + assertNotNull(restaurantRepository.findOne(RestaurantMother.AJANTA_ID)); + }); + + // make HTTP request + + Integer orderId = + given(). + body(new CreateOrderRequest(OrderDetailsMother.CONSUMER_ID, + RestaurantMother.AJANTA_ID, Collections.singletonList(new CreateOrderRequest.LineItem(RestaurantMother.CHICKEN_VINDALOO_MENU_ITEM_ID, + OrderDetailsMother.CHICKEN_VINDALOO_QUANTITY)))). + contentType("application/json"). + when(). + post(baseUrl("/orders")). + then(). + statusCode(200). + extract(). + path("orderId"); + + assertNotNull(orderId); + + // verify response + // verify Order state + // verify events published???? + + messageTracker.assertCommandMessageSent("orderService", ApproveOrderCommand.class); + + } +} diff --git a/ftgo-order-service/src/attic/OrderServiceExternalComponentTest.java b/ftgo-order-service/src/attic/OrderServiceExternalComponentTest.java new file mode 100644 index 00000000..995f0fb8 --- /dev/null +++ b/ftgo-order-service/src/attic/OrderServiceExternalComponentTest.java @@ -0,0 +1,41 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.jdbckafka.TramJdbcKafkaConfiguration; +import io.eventuate.tram.commands.producer.TramCommandProducerConfiguration; +import io.eventuate.tram.events.publisher.TramEventsPublisherConfiguration; +import net.chrisrichardson.ftgo.common.CommonJsonMapperInitializer; +import org.junit.runner.RunWith; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = OrderServiceExternalComponentTest.TestConfiguration.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE) +@TestPropertySource(properties = "debug=true") +public class OrderServiceExternalComponentTest extends AbstractOrderServiceComponentTest { + + + static { + CommonJsonMapperInitializer.registerMoneyModule(); + } + + private int port = 8082; + private String host = System.getenv("DOCKER_HOST_IP"); + + @Override + protected String baseUrl(String path) { + return String.format("http://%s:%s%s", host, port, path); + } + + @Configuration + @EnableAutoConfiguration + @Import({CommonMessagingStubConfiguration.class, + TramCommandProducerConfiguration.class, TramEventsPublisherConfiguration.class, + TramJdbcKafkaConfiguration.class}) + public static class TestConfiguration { + } +} diff --git a/ftgo-order-service/src/attic/OrderServiceInProcessComponentTest.java b/ftgo-order-service/src/attic/OrderServiceInProcessComponentTest.java new file mode 100644 index 00000000..6967f011 --- /dev/null +++ b/ftgo-order-service/src/attic/OrderServiceInProcessComponentTest.java @@ -0,0 +1,47 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.tram.inmemory.TramInMemoryConfiguration; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.sql.DataSource; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = OrderServiceInProcessComponentTest.TestConfiguration.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class OrderServiceInProcessComponentTest extends AbstractOrderServiceComponentTest { + + + @Value("${local.server.port}") + private int port; + + @Override + protected String baseUrl(String path) { + return "http://localhost:" + port + path; + } + + @Configuration + @EnableAutoConfiguration + @Import({CommonTestConfiguration.class, TramInMemoryConfiguration.class}) + public static class TestConfiguration { + + @Bean + public DataSource dataSource() { + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); + return builder.setType(EmbeddedDatabaseType.H2) + .addScript("embedded-schema.sql") + .addScript("eventuate-tram-sagas-embedded.sql") + .build(); + } + + + } +} diff --git a/ftgo-order-service/src/attic/OrderServiceOutOfProcessComponentTest.java b/ftgo-order-service/src/attic/OrderServiceOutOfProcessComponentTest.java new file mode 100644 index 00000000..12baf44f --- /dev/null +++ b/ftgo-order-service/src/attic/OrderServiceOutOfProcessComponentTest.java @@ -0,0 +1,33 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.jdbckafka.TramJdbcKafkaConfiguration; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = OrderServiceOutOfProcessComponentTest.TestConfiguration.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(properties = "debug=true") +public class OrderServiceOutOfProcessComponentTest extends AbstractOrderServiceComponentTest { + + + @Value("${local.server.port}") + private int port; + + @Override + protected String baseUrl(String path) { + return "http://localhost:" + port + path; + } + + @Configuration + @EnableAutoConfiguration + @Import({CommonTestConfiguration.class, TramJdbcKafkaConfiguration.class}) + public static class TestConfiguration { + } +} diff --git a/ftgo-order-service/src/attic/OrderServiceOutOfProcessComponentV0Test.java b/ftgo-order-service/src/attic/OrderServiceOutOfProcessComponentV0Test.java new file mode 100644 index 00000000..1f8b1a5e --- /dev/null +++ b/ftgo-order-service/src/attic/OrderServiceOutOfProcessComponentV0Test.java @@ -0,0 +1,128 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.tram.commands.common.ChannelMapping; +import io.eventuate.tram.commands.common.DefaultChannelMapping; +import io.eventuate.tram.commands.producer.TramCommandProducerConfiguration; +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import io.eventuate.tram.inmemory.TramInMemoryConfiguration; +import io.eventuate.util.test.async.Eventually; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderState; +import net.chrisrichardson.ftgo.orderservice.domain.Order; +import net.chrisrichardson.ftgo.orderservice.domain.OrderRepository; +import net.chrisrichardson.ftgo.orderservice.domain.OrderService; +import net.chrisrichardson.ftgo.orderservice.domain.RestaurantRepository; +import net.chrisrichardson.ftgo.orderservice.messaging.OrderServiceMessagingConfiguration; +import net.chrisrichardson.ftgo.orderservice.service.OrderCommandHandlersConfiguration; +import net.chrisrichardson.ftgo.orderservice.web.OrderWebConfiguration; +import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantCreated; +import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantMenu; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.stubrunner.BatchStubRunner; +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; +import org.springframework.cloud.contract.verifier.messaging.MessageVerifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.sql.DataSource; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes= OrderServiceOutOfProcessComponentV0Test.TestConfiguration.class, + webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "customer.service.url=http://localhost:8888/customers/{customerId}") +@AutoConfigureStubRunner(ids = + {"net.chrisrichardson.ftgo.contracts:common-contracts", + "net.chrisrichardson.ftgo.contracts:ftgo-restaurant-order-service-contracts"}, + workOffline = false) +@DirtiesContext +public class OrderServiceOutOfProcessComponentV0Test { + + @Configuration + @EnableAutoConfiguration + @Import({OrderWebConfiguration.class, OrderServiceMessagingConfiguration.class, OrderCommandHandlersConfiguration.class, + TramCommandProducerConfiguration.class, + TramInMemoryConfiguration.class}) + public static class TestConfiguration { + + @Bean + public ChannelMapping channelMapping() { + return new DefaultChannelMapping.DefaultChannelMappingBuilder().build(); + } + + + @Bean + public DataSource dataSource() { + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); + return builder.setType(EmbeddedDatabaseType.H2) + .addScript("embedded-schema.sql") + .addScript("eventuate-tram-sagas-embedded.sql") + .build(); + } + + + @Bean + public EventuateTramRoutesConfigurer eventuateTramRoutesConfigurer(BatchStubRunner batchStubRunner) { + return new EventuateTramRoutesConfigurer(batchStubRunner); + } + } + + @Value("${local.server.port}") + private int port; + + private String baseUrl(String path) { + return "http://localhost:" + port + path; + } + + @Autowired + private MessageVerifier verifier; + + @Autowired + private DomainEventPublisher domainEventPublisher; + + @Autowired + private RestaurantRepository restaurantRepository; + + @Autowired + private OrderService orderService; + + @Autowired + private OrderRepository orderRepository; + + + @Test + public void shouldCreateOrder() throws InterruptedException { + domainEventPublisher.publish("net.chrisrichardson.ftgo.restaurantservice.domain.Restaurant", RestaurantMother.AJANTA_ID, + Collections.singletonList(new RestaurantCreated(RestaurantMother.AJANTA_RESTAURANT_NAME, + new RestaurantMenu(Collections.singletonList(RestaurantMother.CHICKEN_VINDALOO_MENU_ITEM))))); + + Eventually.eventually(() -> { + assertNotNull(restaurantRepository.findOne(RestaurantMother.AJANTA_ID)); + }); + + + Order order = orderService.createOrder(OrderDetailsMother.CONSUMER_ID, + RestaurantMother.AJANTA_ID, + Collections.singletonList(OrderDetailsMother.CHICKEN_VINDALOO_MENU_ITEM_AND_QUANTITY)); + + + Eventually.eventually(() -> { + Order o = orderRepository.findOne(order.getId()); + assertNotNull(o); + assertEquals(OrderState.AUTHORIZED, o.getState()); + }); + } + +} diff --git a/ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTest.java b/ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTest.java new file mode 100644 index 00000000..8f5e7675 --- /dev/null +++ b/ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTest.java @@ -0,0 +1,10 @@ +package net.chrisrichardson.ftgo.orderservice.cucumber; + +import cucumber.api.CucumberOptions; +import cucumber.api.junit.Cucumber; +import org.junit.runner.RunWith; + +@RunWith(Cucumber.class) +@CucumberOptions(features = "src/component-test/resources/features") +public class OrderServiceComponentTest { +} \ No newline at end of file diff --git a/ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTestSpringContextConfiguration.java b/ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTestSpringContextConfiguration.java new file mode 100644 index 00000000..d4e1070f --- /dev/null +++ b/ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTestSpringContextConfiguration.java @@ -0,0 +1,71 @@ +package net.chrisrichardson.ftgo.orderservice.cucumber; + +import io.eventuate.jdbckafka.TramJdbcKafkaConfiguration; +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import net.chrisrichardson.ftgo.common.CommonJsonMapperInitializer; +import net.chrisrichardson.ftgo.orderservice.MessageTracker; +import net.chrisrichardson.ftgo.orderservice.MessageTrackerConfiguration; +import net.chrisrichardson.ftgo.orderservice.MessagingStubConfiguration; +import net.chrisrichardson.ftgo.orderservice.SagaParticipantStubConfiguration; +import net.chrisrichardson.ftgo.orderservice.SagaParticipantStubManager; +import net.chrisrichardson.ftgo.orderservice.domain.Order; +import net.chrisrichardson.ftgo.orderservice.domain.RestaurantRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@ContextConfiguration(classes = OrderServiceComponentTestSpringContextConfiguration.TestConfiguration.class) +public abstract class OrderServiceComponentTestSpringContextConfiguration { + + + static { + CommonJsonMapperInitializer.registerMoneyModule(); + } + + private int port = 8082; + private String host = System.getenv("DOCKER_HOST_IP"); + + protected String baseUrl(String path) { + return String.format("http://%s:%s%s", host, port, path); + } + + @Configuration + @EnableAutoConfiguration + @Import({TramJdbcKafkaConfiguration.class, SagaParticipantStubConfiguration.class}) + @EnableJpaRepositories(basePackageClasses = RestaurantRepository.class) // Need to verify that the restaurant has been created. Replace with verifyRestaurantCreatedInOrderService + @EntityScan(basePackageClasses = Order.class) + public static class TestConfiguration { + + @Bean + public MessagingStubConfiguration messagingStubConfiguration() { + return new MessagingStubConfiguration("consumerService", "restaurantOrderService", "accountingService", "orderService"); + } + + @Bean + public MessageTrackerConfiguration messageTrackerConfiguration() { + return new MessageTrackerConfiguration("net.chrisrichardson.ftgo.orderservice.domain.Order"); + } + } + + @Autowired + protected SagaParticipantStubManager sagaParticipantStubManager; + + @Autowired + protected MessageTracker messageTracker; + + @Autowired + protected DomainEventPublisher domainEventPublisher; + + @Autowired + protected RestaurantRepository restaurantRepository; + + + +} diff --git a/ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTestStepDefinitions.java b/ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTestStepDefinitions.java new file mode 100644 index 00000000..852f9cbf --- /dev/null +++ b/ftgo-order-service/src/component-test/java/net/chrisrichardson/ftgo/orderservice/cucumber/OrderServiceComponentTestStepDefinitions.java @@ -0,0 +1,199 @@ +package net.chrisrichardson.ftgo.orderservice.cucumber; + +import cucumber.api.java.Before; +import cucumber.api.java.en.And; +import cucumber.api.java.en.Given; +import cucumber.api.java.en.Then; +import cucumber.api.java.en.When; +import io.eventuate.jdbckafka.TramJdbcKafkaConfiguration; +import io.eventuate.tram.events.common.DomainEvent; +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import io.restassured.response.Response; +import net.chrisrichardson.ftgo.accountservice.api.AuthorizeCommand; +import net.chrisrichardson.ftgo.common.CommonJsonMapperInitializer; +import net.chrisrichardson.ftgo.consumerservice.api.ValidateOrderByConsumer; +import net.chrisrichardson.ftgo.orderservice.MessageTracker; +import net.chrisrichardson.ftgo.orderservice.MessageTrackerConfiguration; +import net.chrisrichardson.ftgo.orderservice.MessagingStubConfiguration; +import net.chrisrichardson.ftgo.orderservice.OrderDetailsMother; +import net.chrisrichardson.ftgo.orderservice.RestaurantMother; +import net.chrisrichardson.ftgo.orderservice.SagaParticipantStubConfiguration; +import net.chrisrichardson.ftgo.orderservice.SagaParticipantStubManager; +import net.chrisrichardson.ftgo.orderservice.api.web.CreateOrderRequest; +import net.chrisrichardson.ftgo.orderservice.domain.Order; +import net.chrisrichardson.ftgo.orderservice.domain.RestaurantRepository; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CancelCreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.ConfirmCreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrderReply; +import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantCreated; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ContextConfiguration; + +import java.util.Collections; + +import static io.eventuate.tram.commands.consumer.CommandHandlerReplyBuilder.withSuccess; +import static io.eventuate.util.test.async.Eventually.eventually; +import static io.restassured.RestAssured.given; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_RESTAURANT_MENU; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + + +//@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@ContextConfiguration(classes = OrderServiceComponentTestStepDefinitions.TestConfiguration.class) +public class OrderServiceComponentTestStepDefinitions /* extends OrderServiceComponentTestSpringContextConfiguration */ { + + + + private Response response; + private long consumerId; + + static { + CommonJsonMapperInitializer.registerMoneyModule(); + } + + private int port = 8082; + private String host = System.getenv("DOCKER_HOST_IP"); + + protected String baseUrl(String path) { + return String.format("http://%s:%s%s", host, port, path); + } + + @Configuration + @EnableAutoConfiguration + @Import({TramJdbcKafkaConfiguration.class, SagaParticipantStubConfiguration.class}) + @EnableJpaRepositories(basePackageClasses = RestaurantRepository.class) // Need to verify that the restaurant has been created. Replace with verifyRestaurantCreatedInOrderService + @EntityScan(basePackageClasses = Order.class) + public static class TestConfiguration { + + @Bean + public MessagingStubConfiguration messagingStubConfiguration() { + return new MessagingStubConfiguration("consumerService", "restaurantOrderService", "accountingService", "orderService"); + } + + @Bean + public MessageTrackerConfiguration messageTrackerConfiguration() { + return new MessageTrackerConfiguration("net.chrisrichardson.ftgo.orderservice.domain.Order"); + } + } + + @Autowired + protected SagaParticipantStubManager sagaParticipantStubManager; + + @Autowired + protected MessageTracker messageTracker; + + @Autowired + protected DomainEventPublisher domainEventPublisher; + + @Autowired + protected RestaurantRepository restaurantRepository; + + + @Before + public void setUp() throws Exception { + sagaParticipantStubManager.reset(); + } + + @Given("A valid consumer") + public void useConsumer() { + sagaParticipantStubManager. + forChannel("consumerService") + .when(ValidateOrderByConsumer.class).replyWith(cm -> withSuccess()); + } + + public enum CreditCardType { valid, expired} + + @Given("using a(.?) (.*) credit card") + public void useCreditCard(String ignore, CreditCardType creditCard) { + switch (creditCard) { + case valid : + sagaParticipantStubManager + .forChannel("accountingService") + .when(AuthorizeCommand.class).replyWithSuccess(); + break; + case expired: + sagaParticipantStubManager + .forChannel("accountingService") + .when(AuthorizeCommand.class).replyWithFailure(); + break; + default: + fail("Don't know what to do with this credit card"); + } + } + + @Given("the restaurant is accepting orders") + public void restaurantAcceptsOrder() { + sagaParticipantStubManager + .forChannel("restaurantOrderService") + .when(CreateRestaurantOrder.class).replyWith(cm -> withSuccess(new CreateRestaurantOrderReply(cm.getCommand().getOrderId()))) + .when(ConfirmCreateRestaurantOrder.class).replyWithSuccess() + .when(CancelCreateRestaurantOrder.class).replyWithSuccess(); + + domainEventPublisher.publish("net.chrisrichardson.ftgo.restaurantservice.domain.Restaurant", RestaurantMother.AJANTA_ID, + Collections.singletonList(new RestaurantCreated(RestaurantMother.AJANTA_RESTAURANT_NAME, AJANTA_RESTAURANT_MENU))); + + eventually(() -> { + assertNotNull(restaurantRepository.findOne(RestaurantMother.AJANTA_ID)); + }); + + } + + @When("I place an order for Chicken Vindaloo at Ajanta") + public void placeOrder() { + + response = given(). + body(new CreateOrderRequest(consumerId, + RestaurantMother.AJANTA_ID, Collections.singletonList(new CreateOrderRequest.LineItem(RestaurantMother.CHICKEN_VINDALOO_MENU_ITEM_ID, + OrderDetailsMother.CHICKEN_VINDALOO_QUANTITY)))). + contentType("application/json"). + when(). + post(baseUrl("/orders")); + } + + @Then("the order should be (.*)") + public void theOrderShouldBeInState(String desiredOrderState) { + + // TODO This doesn't make sense when the `OrderService` is live => duplicate replies + +// sagaParticipantStubManager +// .forChannel("orderService") +// .when(ApproveOrderCommand.class).replyWithSuccess(); +// + Integer orderId = + this.response. + then(). + statusCode(200). + extract(). + path("orderId"); + + assertNotNull(orderId); + + eventually(() -> { + String state = given(). + when(). + get(baseUrl("/orders/" + orderId)). + then(). + statusCode(200) + .extract(). + path("state"); + assertEquals(desiredOrderState, state); + }); + + } + + @And("an (.*) event should be published") + public void verifyEventPublished(String expectedEventClass) throws ClassNotFoundException { + messageTracker.assertDomainEventPublished("net.chrisrichardson.ftgo.orderservice.domain.Order", + (Class)Class.forName("net.chrisrichardson.ftgo.orderservice.domain." + expectedEventClass)); + } + +} diff --git a/ftgo-order-service/src/component-test/resources/features/createorder.feature b/ftgo-order-service/src/component-test/resources/features/createorder.feature new file mode 100644 index 00000000..b9f40c27 --- /dev/null +++ b/ftgo-order-service/src/component-test/resources/features/createorder.feature @@ -0,0 +1,20 @@ +Feature: Create order + + As a consumer of the Order Service + I should be able to create an order + + Scenario: Order authorized + Given A valid consumer + Given using a valid credit card + Given the restaurant is accepting orders + When I place an order for Chicken Vindaloo at Ajanta + Then the order should be AUTHORIZED + And an OrderAuthorized event should be published + + Scenario: Order rejected due to expired credit card + Given A valid consumer + Given using an expired credit card + Given the restaurant is accepting orders + When I place an order for Chicken Vindaloo at Ajanta + Then the order should be REJECTED + And an OrderRejected event should be published diff --git a/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/contract/HttpBase.java b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/contract/HttpBase.java new file mode 100644 index 00000000..1869fc59 --- /dev/null +++ b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/contract/HttpBase.java @@ -0,0 +1,37 @@ +package net.chrisrichardson.ftgo.orderservice.contract; + +import io.eventuate.javaclient.commonimpl.JSonMapper; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import net.chrisrichardson.ftgo.common.CommonJsonMapperInitializer; +import net.chrisrichardson.ftgo.orderservice.OrderDetailsMother; +import net.chrisrichardson.ftgo.orderservice.domain.OrderRepository; +import net.chrisrichardson.ftgo.orderservice.domain.OrderService; +import net.chrisrichardson.ftgo.orderservice.web.OrderController; +import org.junit.Before; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public abstract class HttpBase { + + private StandaloneMockMvcBuilder controllers(Object... controllers) { + CommonJsonMapperInitializer.registerMoneyModule(); + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(JSonMapper.objectMapper); + return MockMvcBuilders.standaloneSetup(controllers).setMessageConverters(converter); + } + + @Before + public void setup() { + OrderService orderService = mock(OrderService.class); + OrderRepository orderRepository = mock(OrderRepository.class); + OrderController orderController = new OrderController(orderService, orderRepository); + + when(orderRepository.findOne(OrderDetailsMother.ORDER_ID)).thenReturn(OrderDetailsMother.CHICKEN_VINDALOO_ORDER); + when(orderRepository.findOne(555L)).thenReturn(null); + RestAssuredMockMvc.standaloneSetup(controllers(orderController)); + + } +} \ No newline at end of file diff --git a/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/contract/MessagingBase.java b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/contract/MessagingBase.java new file mode 100644 index 00000000..98009f45 --- /dev/null +++ b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/contract/MessagingBase.java @@ -0,0 +1,56 @@ +package net.chrisrichardson.ftgo.orderservice.contract; + +import io.eventuate.tram.commands.common.ChannelMapping; +import io.eventuate.tram.commands.common.DefaultChannelMapping; +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import io.eventuate.tram.events.publisher.TramEventsPublisherConfiguration; +import io.eventuate.tram.inmemory.TramInMemoryConfiguration; +import io.eventuate.tram.springcloudcontractsupport.EventuateContractVerifierConfiguration; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderCreatedEvent; +import net.chrisrichardson.ftgo.orderservice.domain.OrderAggregateEventPublisher; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Collections; + +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_ORDER; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_ORDER_DETAILS; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = MessagingBase.TestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) +@AutoConfigureMessageVerifier +public abstract class MessagingBase { + + @Configuration + @EnableAutoConfiguration + @Import({EventuateContractVerifierConfiguration.class, TramEventsPublisherConfiguration.class, TramInMemoryConfiguration.class}) + public static class TestConfiguration { + + @Bean + public ChannelMapping channelMapping() { + return new DefaultChannelMapping.DefaultChannelMappingBuilder().build(); + } + + @Bean + public OrderAggregateEventPublisher orderAggregateEventPublisher(DomainEventPublisher eventPublisher) { + return new OrderAggregateEventPublisher(eventPublisher); + } + } + + + @Autowired + private OrderAggregateEventPublisher orderAggregateEventPublisher; + + protected void orderCreated() { + orderAggregateEventPublisher.publish(CHICKEN_VINDALOO_ORDER, + Collections.singletonList(new OrderCreatedEvent(CHICKEN_VINDALOO_ORDER_DETAILS))); + } + +} diff --git a/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderJpaTest.java b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderJpaTest.java new file mode 100644 index 00000000..a281ac5a --- /dev/null +++ b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderJpaTest.java @@ -0,0 +1,50 @@ +package net.chrisrichardson.ftgo.orderservice.domain; + +import net.chrisrichardson.ftgo.orderservice.api.events.OrderState; +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; +import org.springframework.transaction.support.TransactionTemplate; + +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_LINE_ITEMS; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CONSUMER_ID; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_ID; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = OrderJpaTestConfiguration.class) +public class OrderJpaTest { + + @Autowired + private OrderRepository orderRepository; + + @Autowired + private TransactionTemplate transactionTemplate; + + @Test + public void shouldSaveAndLoadOrder() { + + long orderId = transactionTemplate.execute((ts) -> { + Order order = new Order(CONSUMER_ID, AJANTA_ID, CHICKEN_VINDALOO_LINE_ITEMS); + orderRepository.save(order); + return order.getId(); + }); + + + transactionTemplate.execute((ts) -> { + Order order = orderRepository.findOne(orderId); + + assertNotNull(order); + assertEquals(OrderState.CREATE_PENDING, order.getState()); + assertEquals(AJANTA_ID, order.getRestaurantId()); + assertEquals(CONSUMER_ID, order.getConsumerId().longValue()); + assertEquals(CHICKEN_VINDALOO_LINE_ITEMS, order.getLineItems()); + return null; + }); + + } + +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderJpaTestConfiguration.java b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderJpaTestConfiguration.java similarity index 85% rename from ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderJpaTestConfiguration.java rename to ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderJpaTestConfiguration.java index fa469ee6..ab182663 100644 --- a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderJpaTestConfiguration.java +++ b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderJpaTestConfiguration.java @@ -1,4 +1,4 @@ -package net.chrisrichardson.ftgo.orderservice; +package net.chrisrichardson.ftgo.orderservice.domain; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Configuration; diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceIntegrationTest.java b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceIntegrationTest.java similarity index 65% rename from ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceIntegrationTest.java rename to ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceIntegrationTest.java index 52e7578e..32f26baa 100644 --- a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceIntegrationTest.java +++ b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceIntegrationTest.java @@ -1,12 +1,18 @@ package net.chrisrichardson.ftgo.orderservice.domain; +import com.jayway.jsonpath.JsonPath; import io.eventuate.tram.commands.common.ChannelMapping; +import io.eventuate.tram.commands.common.CommandMessageHeaders; import io.eventuate.tram.commands.common.DefaultChannelMapping; import io.eventuate.tram.commands.producer.TramCommandProducerConfiguration; import io.eventuate.tram.events.publisher.DomainEventPublisher; import io.eventuate.tram.inmemory.TramInMemoryConfiguration; +import io.eventuate.tram.messaging.common.Message; import io.eventuate.tram.testutil.TestMessageConsumerFactory; +import io.eventuate.util.test.async.Eventually; import net.chrisrichardson.ftgo.common.Money; +import net.chrisrichardson.ftgo.consumerservice.api.ConsumerServiceChannels; +import net.chrisrichardson.ftgo.consumerservice.api.ValidateOrderByConsumer; import net.chrisrichardson.ftgo.orderservice.messaging.OrderServiceMessagingConfiguration; import net.chrisrichardson.ftgo.orderservice.service.OrderCommandHandlersConfiguration; import net.chrisrichardson.ftgo.orderservice.web.MenuItemIdAndQuantity; @@ -14,10 +20,10 @@ import net.chrisrichardson.ftgo.restaurantservice.events.MenuItem; import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantCreated; import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantMenu; -import io.eventuate.util.test.async.Eventually; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; @@ -30,6 +36,7 @@ import javax.sql.DataSource; import java.util.Collections; +import java.util.function.Predicate; import static org.junit.Assert.assertNotNull; @@ -73,6 +80,12 @@ public DataSource dataSource() { .addScript("eventuate-tram-sagas-embedded.sql") .build(); } + + + @Bean + public TestMessageConsumer2 mockConsumerService() { + return new TestMessageConsumer2("mockConsumerService", ConsumerServiceChannels.consumerServiceChannel); + } } @Autowired @@ -84,7 +97,14 @@ public DataSource dataSource() { @Autowired private OrderService orderService; - public static final String CHICKED_VINDALOO_MENU_ITEM_ID = "1"; + private static final String CHICKED_VINDALOO_MENU_ITEM_ID = "1"; + + @Autowired + private OrderRepository orderRepository; + + @Autowired + @Qualifier("mockConsumerService") + private TestMessageConsumer2 mockConsumerService; @Test public void shouldCreateOrder() { @@ -96,9 +116,35 @@ public void shouldCreateOrder() { assertNotNull(restaurantRepository.findOne(Long.parseLong(RESTAURANT_ID))); }); - long consumerId = 10; + long consumerId = 1511300065921L; + + Order order = orderService.createOrder(consumerId, Long.parseLong(RESTAURANT_ID), Collections.singletonList(new MenuItemIdAndQuantity(CHICKED_VINDALOO_MENU_ITEM_ID, 5))); + + assertNotNull(orderRepository.findOne(order.getId())); + + String expectedPayload = "{\"consumerId\":1511300065921,\"orderId\":1,\"orderTotal\":\"61.70\"}"; + + Message message = mockConsumerService.assertMessageReceived( + commandMessageOfType(ValidateOrderByConsumer.class.getName()).and(withPayload(expectedPayload))); + + System.out.println("message=" + message); + + } + + private Predicate withPayload(String expectedPayload) { + return (m) -> expectedPayload.equals(m.getPayload()); + } + + private Predicate forConsumer(long consumerId) { + return (m) -> { + Object doc = com.jayway.jsonpath.Configuration.defaultConfiguration().jsonProvider().parse(m.getPayload()); + Object s = JsonPath.read(doc, "$.consumerId"); + return new Long(consumerId).equals(s); + }; + } - orderService.createOrder(consumerId, Long.parseLong(RESTAURANT_ID), Collections.singletonList(new MenuItemIdAndQuantity(CHICKED_VINDALOO_MENU_ITEM_ID, 5))); + private Predicate commandMessageOfType(String commandType) { + return (m) -> m.getRequiredHeader(CommandMessageHeaders.COMMAND_TYPE).equals(commandType); } } \ No newline at end of file diff --git a/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/RestaurantOrderServiceProxyIntegrationTest.java b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/RestaurantOrderServiceProxyIntegrationTest.java new file mode 100644 index 00000000..8d894352 --- /dev/null +++ b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/RestaurantOrderServiceProxyIntegrationTest.java @@ -0,0 +1,114 @@ +package net.chrisrichardson.ftgo.orderservice.sagaparticipants; + +import io.eventuate.tram.commands.common.ChannelMapping; +import io.eventuate.tram.commands.common.DefaultChannelMapping; +import io.eventuate.tram.commands.producer.TramCommandProducerConfiguration; +import io.eventuate.tram.inmemory.TramInMemoryConfiguration; +import io.eventuate.tram.sagas.orchestration.SagaCommandProducer; +import io.eventuate.tram.springcloudcontractsupport.EventuateContractVerifierConfiguration; +import net.chrisrichardson.ftgo.orderservice.EventuateTramRoutesConfigurer; +import net.chrisrichardson.ftgo.orderservice.OrderDetailsMother; +import net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrderReply; +import net.chrisrichardson.ftgo.restaurantorderservice.api.RestaurantOrderDetails; +import net.chrisrichardson.ftgo.restaurantorderservice.api.RestaurantOrderLineItem; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.stubrunner.BatchStubRunner; +import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.sql.DataSource; +import java.util.Collections; + +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_QUANTITY; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_ID; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.CHICKEN_VINDALOO; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.CHICKEN_VINDALOO_MENU_ITEM_ID; +import static org.junit.Assert.assertEquals; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes= RestaurantOrderServiceProxyIntegrationTest.TestConfiguration.class, + webEnvironment= SpringBootTest.WebEnvironment.NONE) +@AutoConfigureStubRunner(ids = + {"net.chrisrichardson.ftgo.contracts:ftgo-restaurant-order-service-contracts"}, + workOffline = false) +@DirtiesContext +public class RestaurantOrderServiceProxyIntegrationTest { + + + @Configuration + @EnableAutoConfiguration + @Import({TramCommandProducerConfiguration.class, + TramInMemoryConfiguration.class, EventuateContractVerifierConfiguration.class}) + public static class TestConfiguration { + + @Bean + public ChannelMapping channelMapping() { + return new DefaultChannelMapping.DefaultChannelMappingBuilder().build(); + } + + + /// TramSagaInMemoryConfiguration + + @Bean + public DataSource dataSource() { + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); + return builder.setType(EmbeddedDatabaseType.H2) + .addScript("embedded-schema.sql") + .addScript("eventuate-tram-sagas-embedded.sql") + .build(); + } + + + @Bean + public EventuateTramRoutesConfigurer eventuateTramRoutesConfigurer(BatchStubRunner batchStubRunner) { + return new EventuateTramRoutesConfigurer(batchStubRunner); + } + + @Bean + public SagaMessagingTestHelper sagaMessagingTestHelper() { + return new SagaMessagingTestHelper(); + } + + @Bean + public SagaCommandProducer sagaCommandProducer() { + return new SagaCommandProducer(); + } + + @Bean + public RestaurantOrderServiceProxy restaurantOrderServiceProxy() { + return new RestaurantOrderServiceProxy(); + } + } + + @Autowired + private SagaMessagingTestHelper sagaMessagingTestHelper; + + @Autowired + private RestaurantOrderServiceProxy restaurantOrderServiceProxy; + + @Test + public void shouldSuccessfullyCreateRestaurantOrder() { + CreateRestaurantOrder command = new CreateRestaurantOrder(AJANTA_ID, OrderDetailsMother.ORDER_ID, + new RestaurantOrderDetails(Collections.singletonList(new RestaurantOrderLineItem(CHICKEN_VINDALOO_MENU_ITEM_ID, CHICKEN_VINDALOO, CHICKEN_VINDALOO_QUANTITY)))); + CreateRestaurantOrderReply expectedReply = new CreateRestaurantOrderReply(OrderDetailsMother.ORDER_ID); + String sagaType = CreateOrderSaga.class.getName(); + + CreateRestaurantOrderReply reply = sagaMessagingTestHelper.sendAndReceiveCommand(restaurantOrderServiceProxy.create, command, CreateRestaurantOrderReply.class, sagaType); + + assertEquals(expectedReply, reply); + + } + +} \ No newline at end of file diff --git a/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/SagaMessagingTestHelper.java b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/SagaMessagingTestHelper.java new file mode 100644 index 00000000..475cd542 --- /dev/null +++ b/ftgo-order-service/src/integration-test/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/SagaMessagingTestHelper.java @@ -0,0 +1,39 @@ +package net.chrisrichardson.ftgo.orderservice.sagaparticipants; + +import io.eventuate.javaclient.commonimpl.JSonMapper; +import io.eventuate.javaclient.spring.jdbc.IdGenerator; +import io.eventuate.tram.commands.common.Command; +import io.eventuate.tram.sagas.orchestration.SagaCommandProducer; +import io.eventuate.tram.sagas.simpledsl.CommandEndpoint; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessage; +import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging; + +import javax.inject.Inject; + +public class SagaMessagingTestHelper { + + @Inject + ContractVerifierMessaging contractVerifierMessaging; + + @Autowired + private SagaCommandProducer sagaCommandProducer; + + @Autowired + private IdGenerator idGenerator; + + + public R sendAndReceiveCommand(CommandEndpoint commandEndpoint, C command, Class replyClass, String sagaType) { + // TODO verify that replyClass is allowed + + String sagaId = idGenerator.genId().asString(); + + String replyTo = sagaType + "-reply"; + sagaCommandProducer.sendCommand(sagaType, sagaId, commandEndpoint.getCommandChannel(), null, "XXX", command, replyTo); + + ContractVerifierMessage response = contractVerifierMessaging.receive(replyTo); + + String payload = (String) response.getPayload(); + return (R) JSonMapper.fromJson(payload, replyClass); + } +} diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/AbstractAggregateEventPublisher.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/AbstractAggregateEventPublisher.java new file mode 100644 index 00000000..45783035 --- /dev/null +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/AbstractAggregateEventPublisher.java @@ -0,0 +1,40 @@ +package net.chrisrichardson.ftgo.orderservice.domain; + +import io.eventuate.tram.events.common.DomainEvent; +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderCreatedEvent; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; + +import static java.util.Arrays.asList; + +public class AbstractAggregateEventPublisher { + private Function idSupplier; + private List> eventTypes; + private DomainEventPublisher eventPublisher; + private Class aggregateType; + + protected AbstractAggregateEventPublisher(DomainEventPublisher eventPublisher, + Class aggregateType, + Function idSupplier, + Class... eventTypes) { + this.eventPublisher = eventPublisher; + this.aggregateType = aggregateType; + this.idSupplier = idSupplier; + this.eventTypes = asList(eventTypes); + } + + public void publish(T aggregate, List events) { + verifyEvents(events); + eventPublisher.publish(aggregateType, idSupplier.apply(aggregate), events); + } + + protected void verifyEvents(List events) { + events.forEach(event -> { + if (eventTypes.stream().noneMatch(eventClass -> eventClass.isInstance(event))) + throw new RuntimeException(String.format("event %s is not one of the allowed types %s", event, eventTypes)); + }); + } +} diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/Order.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/Order.java index 8e668968..a4d86c30 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/Order.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/Order.java @@ -70,7 +70,7 @@ public void setId(Long id) { public static ResultWithEvents createOrder(long consumerId, long restaurantId, List orderLineItems) { Order order = new Order(consumerId, restaurantId, orderLineItems); - List events = singletonList(new OrderCreatedEvent(OrderState.CREATE_PENDING, + List events = singletonList(new OrderCreatedEvent( new OrderDetails(consumerId, restaurantId, orderLineItems, order.getOrderTotal()))); return new ResultWithEvents<>(order, events); } diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderAggregateEventPublisher.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderAggregateEventPublisher.java new file mode 100644 index 00000000..37e70aa1 --- /dev/null +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderAggregateEventPublisher.java @@ -0,0 +1,20 @@ +package net.chrisrichardson.ftgo.orderservice.domain; + +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderCreatedEvent; + +public class OrderAggregateEventPublisher extends AbstractAggregateEventPublisher { + + + public OrderAggregateEventPublisher(DomainEventPublisher eventPublisher) { + super(eventPublisher, Order.class, Order::getId, + OrderCreatedEvent.class, + OrderAuthorized.class, + OrderRejected.class, + OrderRevisionProposed.class, + OrderRevised.class, + OrderRevisionRejected.class, + OrderCancelled.class); + } + +} diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderAuthorized.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderAuthorized.java index 45087a55..aeae5072 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderAuthorized.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderAuthorized.java @@ -1,6 +1,19 @@ package net.chrisrichardson.ftgo.orderservice.domain; import io.eventuate.tram.events.common.DomainEvent; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; public class OrderAuthorized implements DomainEvent { + + @Override + public boolean equals(Object o) { + return EqualsBuilder.reflectionEquals(this, o); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + } diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderService.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderService.java index 6ed9fb12..77a1637f 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderService.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderService.java @@ -14,7 +14,6 @@ import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantMenu; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -27,39 +26,38 @@ public class OrderService { private Logger logger = LoggerFactory.getLogger(getClass()); - @Autowired private OrderRepository orderRepository; - @Autowired - private DomainEventPublisher eventPublisher; - - @Autowired private RestaurantRepository restaurantRepository; - @Autowired private SagaManager createOrderSagaManager; - @Autowired private SagaManager cancelOrderSagaManager; - - @Autowired private SagaManager reviseOrderSagaManager; + private OrderAggregateEventPublisher orderAggregateEventPublisher; + + public OrderService(OrderRepository orderRepository, DomainEventPublisher eventPublisher, RestaurantRepository restaurantRepository, SagaManager createOrderSagaManager, SagaManager cancelOrderSagaManager, SagaManager reviseOrderSagaManager, OrderAggregateEventPublisher orderAggregateEventPublisher) { + this.orderRepository = orderRepository; + this.restaurantRepository = restaurantRepository; + this.createOrderSagaManager = createOrderSagaManager; + this.cancelOrderSagaManager = cancelOrderSagaManager; + this.reviseOrderSagaManager = reviseOrderSagaManager; + this.orderAggregateEventPublisher = orderAggregateEventPublisher; + } public Order createOrder(long consumerId, long restaurantId, List lineItems) { Restaurant restaurant = restaurantRepository.findOne(restaurantId); if (restaurant == null) throw new RuntimeException("Restaurant not found: " + restaurantId); - System.out.println("restaurant=" + restaurant.getMenuItems()); - List orderLineItems = makeOrderLineItems(lineItems, restaurant); ResultWithEvents orderAndEvents = Order.createOrder(consumerId, restaurantId, orderLineItems); Order order = orderAndEvents.result; orderRepository.save(order); - eventPublisher.publish(Order.class, order.getId(), orderAndEvents.events); + orderAggregateEventPublisher.publish(order, orderAndEvents.events); OrderDetails orderDetails = new OrderDetails(consumerId, restaurantId, orderLineItems, order.getOrderTotal()); CreateOrderSagaData data = new CreateOrderSagaData(order.getId(), orderDetails); @@ -87,7 +85,7 @@ private List makeOrderLineItems(List lineI public Order confirmChangeLineItemQuantity(Long orderId, OrderRevision orderRevision) { Order order = orderRepository.findOne(orderId); List events = order.confirmRevision(orderRevision); - eventPublisher.publish(Order.class, Long.toString(order.getId()), events); + orderAggregateEventPublisher.publish(order, events); return order; } @@ -108,7 +106,7 @@ public Order cancel(Long orderId) { Order updateOrder(long orderId, Function> updater) { Order order = orderRepository.findOne(orderId); - eventPublisher.publish(Order.class, Long.toString(orderId), updater.apply(order)); + orderAggregateEventPublisher.publish(order, updater.apply(order)); return order; } @@ -144,10 +142,10 @@ public Order reviseOrder(long orderId, OrderRevision orderRevision) { } public RevisedOrder beginReviseOrder(long orderId, OrderRevision revision) { - Order order1 = orderRepository.findOne(orderId); - ResultWithEvents result = order1.revise(revision); - eventPublisher.publish(Order.class, Long.toString(orderId), result.events); - return new RevisedOrder(order1, result.result); + Order order = orderRepository.findOne(orderId); + ResultWithEvents result = order.revise(revision); + orderAggregateEventPublisher.publish(order, result.events); + return new RevisedOrder(order, result.result); } public void undoPendingRevision(long orderId) { diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceConfiguration.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceConfiguration.java index 910d2a3d..1351fdf2 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceConfiguration.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceConfiguration.java @@ -1,65 +1,79 @@ package net.chrisrichardson.ftgo.orderservice.domain; +import io.eventuate.tram.events.publisher.DomainEventPublisher; import io.eventuate.tram.events.publisher.TramEventsPublisherConfiguration; +import io.eventuate.tram.sagas.orchestration.SagaCommandProducer; import io.eventuate.tram.sagas.orchestration.SagaManager; import io.eventuate.tram.sagas.orchestration.SagaManagerImpl; import io.eventuate.tram.sagas.orchestration.SagaOrchestratorConfiguration; import net.chrisrichardson.ftgo.common.CommonConfiguration; +import net.chrisrichardson.ftgo.orderservice.sagaparticipants.RestaurantOrderServiceProxy; import net.chrisrichardson.ftgo.orderservice.sagas.cancelorder.CancelOrderSaga; import net.chrisrichardson.ftgo.orderservice.sagas.cancelorder.CancelOrderSagaData; import net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga; import net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSagaData; import net.chrisrichardson.ftgo.orderservice.sagas.reviseorder.ReviseOrderSaga; import net.chrisrichardson.ftgo.orderservice.sagas.reviseorder.ReviseOrderSagaData; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @Configuration -@EnableJpaRepositories -@EnableAutoConfiguration @Import({TramEventsPublisherConfiguration.class, SagaOrchestratorConfiguration.class, CommonConfiguration.class}) -@ComponentScan public class OrderServiceConfiguration { + // TODO move to framework - @Bean - public OrderService orderService() { - return new OrderService(); - } + @Bean + public SagaCommandProducer sagaCommandProducer() { + return new SagaCommandProducer(); + } - @Bean - public SagaManager createOrderSagaManager(CreateOrderSaga saga) { - return new SagaManagerImpl<>(saga); - } + @Bean + public OrderService orderService(RestaurantRepository restaurantRepository, OrderRepository orderRepository, DomainEventPublisher eventPublisher, + SagaManager createOrderSagaManager, + SagaManager cancelOrderSagaManager, SagaManager reviseOrderSagaManager, OrderAggregateEventPublisher orderAggregateEventPublisher) { + return new OrderService(orderRepository, eventPublisher, restaurantRepository, + createOrderSagaManager, cancelOrderSagaManager, reviseOrderSagaManager, orderAggregateEventPublisher); + } - @Bean - public CreateOrderSaga createOrderSaga() { - return new CreateOrderSaga(); - } + @Bean + public SagaManager createOrderSagaManager(CreateOrderSaga saga) { + return new SagaManagerImpl<>(saga); + } - @Bean - public SagaManager CancelOrderSagaManager(CancelOrderSaga saga) { - return new SagaManagerImpl<>(saga); - } + @Bean + public CreateOrderSaga createOrderSaga(RestaurantOrderServiceProxy restaurantOrderServiceProxy) { + return new CreateOrderSaga(restaurantOrderServiceProxy); + } - @Bean - public CancelOrderSaga cancelOrderSaga() { - return new CancelOrderSaga(); - } + @Bean + public SagaManager CancelOrderSagaManager(CancelOrderSaga saga) { + return new SagaManagerImpl<>(saga); + } - @Bean - public SagaManager reviseOrderSagaManager(ReviseOrderSaga saga) { - return new SagaManagerImpl<>(saga); - } + @Bean + public CancelOrderSaga cancelOrderSaga() { + return new CancelOrderSaga(); + } - @Bean - public ReviseOrderSaga reviseOrderSaga() { - return new ReviseOrderSaga(); - } + @Bean + public SagaManager reviseOrderSagaManager(ReviseOrderSaga saga) { + return new SagaManagerImpl<>(saga); + } + @Bean + public ReviseOrderSaga reviseOrderSaga() { + return new ReviseOrderSaga(); + } + @Bean + public RestaurantOrderServiceProxy restaurantOrderServiceProxy() { + return new RestaurantOrderServiceProxy(); + } + + @Bean + public OrderAggregateEventPublisher orderAggregateEventPublisher(DomainEventPublisher eventPublisher) { + return new OrderAggregateEventPublisher(eventPublisher); + } } diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceWithRepositoriesConfiguration.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceWithRepositoriesConfiguration.java new file mode 100644 index 00000000..b27dd93b --- /dev/null +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceWithRepositoriesConfiguration.java @@ -0,0 +1,15 @@ +package net.chrisrichardson.ftgo.orderservice.domain; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@EnableJpaRepositories +@EnableAutoConfiguration +@Import({OrderServiceConfiguration.class}) +public class OrderServiceWithRepositoriesConfiguration { + + +} diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderEventConsumer.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderEventConsumer.java index 389e8c8c..6d223cca 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderEventConsumer.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderEventConsumer.java @@ -7,14 +7,16 @@ import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantCreated; import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantMenu; import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantMenuRevised; -import org.springframework.beans.factory.annotation.Autowired; public class OrderEventConsumer { - @Autowired private OrderService orderService; + public OrderEventConsumer(OrderService orderService) { + this.orderService = orderService; + } + public DomainEventHandlers domainEventHandlers() { return DomainEventHandlersBuilder .forAggregateType("net.chrisrichardson.ftgo.restaurantservice.domain.Restaurant") diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderServiceMessagingConfiguration.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderServiceMessagingConfiguration.java index e87afc8d..85021c80 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderServiceMessagingConfiguration.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderServiceMessagingConfiguration.java @@ -2,18 +2,19 @@ import io.eventuate.tram.events.subscriber.DomainEventDispatcher; import io.eventuate.tram.messaging.consumer.MessageConsumer; -import net.chrisrichardson.ftgo.orderservice.domain.OrderServiceConfiguration; +import net.chrisrichardson.ftgo.orderservice.domain.OrderService; +import net.chrisrichardson.ftgo.orderservice.domain.OrderServiceWithRepositoriesConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration -@Import({OrderServiceConfiguration.class}) +@Import({OrderServiceWithRepositoriesConfiguration.class}) public class OrderServiceMessagingConfiguration { @Bean - public OrderEventConsumer orderEventConsumer() { - return new OrderEventConsumer(); + public OrderEventConsumer orderEventConsumer(OrderService orderService) { + return new OrderEventConsumer(orderService); } @Bean diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/OrderServiceProxy.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/OrderServiceProxy.java new file mode 100644 index 00000000..876943c2 --- /dev/null +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/OrderServiceProxy.java @@ -0,0 +1,22 @@ +package net.chrisrichardson.ftgo.orderservice.sagaparticipants; + +import io.eventuate.tram.commands.common.Success; +import io.eventuate.tram.sagas.simpledsl.CommandEndpoint; +import io.eventuate.tram.sagas.simpledsl.CommandEndpointBuilder; +import net.chrisrichardson.ftgo.orderservice.api.OrderServiceChannels; + +public class OrderServiceProxy { + + public final CommandEndpoint reject = CommandEndpointBuilder + .forCommand(RejectOrderCommand.class) + .withChannel(OrderServiceChannels.orderServiceChannel) + .withReply(Success.class) + .build(); + + public final CommandEndpoint approve = CommandEndpointBuilder + .forCommand(ApproveOrderCommand.class) + .withChannel(OrderServiceChannels.orderServiceChannel) + .withReply(Success.class) + .build(); + +} \ No newline at end of file diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/RestaurantOrderServiceProxy.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/RestaurantOrderServiceProxy.java new file mode 100644 index 00000000..69d8b914 --- /dev/null +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagaparticipants/RestaurantOrderServiceProxy.java @@ -0,0 +1,25 @@ +package net.chrisrichardson.ftgo.orderservice.sagaparticipants; + +import io.eventuate.tram.commands.common.Success; +import io.eventuate.tram.sagas.simpledsl.CommandEndpoint; +import io.eventuate.tram.sagas.simpledsl.CommandEndpointBuilder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.ConfirmCreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrderReply; +import net.chrisrichardson.ftgo.restaurantorderservice.api.RestaurantOrderServiceChannels; + +public class RestaurantOrderServiceProxy { + + public final CommandEndpoint create = CommandEndpointBuilder + .forCommand(CreateRestaurantOrder.class) + .withChannel(RestaurantOrderServiceChannels.restaurantOrderServiceChannel) + .withReply(CreateRestaurantOrderReply.class) + .build(); + + public final CommandEndpoint confirmCreate = CommandEndpointBuilder + .forCommand(ConfirmCreateRestaurantOrder.class) + .withChannel(RestaurantOrderServiceChannels.restaurantOrderServiceChannel) + .withReply(Success.class) + .build(); + +} \ No newline at end of file diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSaga.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSaga.java index e38a96a9..dcc0cdf3 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSaga.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSaga.java @@ -9,46 +9,59 @@ import net.chrisrichardson.ftgo.consumerservice.api.ValidateOrderByConsumer; import net.chrisrichardson.ftgo.orderservice.api.OrderServiceChannels; import net.chrisrichardson.ftgo.orderservice.api.events.OrderDetails; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderLineItem; import net.chrisrichardson.ftgo.orderservice.sagaparticipants.ApproveOrderCommand; import net.chrisrichardson.ftgo.orderservice.sagaparticipants.RejectOrderCommand; -import net.chrisrichardson.ftgo.restaurantorderservice.api.*; +import net.chrisrichardson.ftgo.orderservice.sagaparticipants.RestaurantOrderServiceProxy; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CancelCreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.ConfirmCreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrderReply; +import net.chrisrichardson.ftgo.restaurantorderservice.api.RestaurantOrderDetails; +import net.chrisrichardson.ftgo.restaurantorderservice.api.RestaurantOrderLineItem; +import net.chrisrichardson.ftgo.restaurantorderservice.api.RestaurantOrderServiceChannels; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + import static io.eventuate.tram.commands.consumer.CommandWithDestinationBuilder.send; +import static java.util.stream.Collectors.toList; public class CreateOrderSaga implements SimpleSaga { private Logger logger = LoggerFactory.getLogger(getClass()); - private SagaDefinition sagaDefinition = - step() - .withCompensation(this::rejectOrder) - .step() - .invokeParticipant(this::verifyConsumer) - .step() - .invokeParticipant(this::createRestaurantOrder) - .onReply(CreateRestaurantOrderReply.class, - this::handleCreateRestaurantOrderReply) - .withCompensation(this::rejectRestaurantOrder) - .step() - .invokeParticipant(this::authorizeCard) - .step() - .invokeParticipant(this::approveOrder) - .step() - .invokeParticipant(this::approveRestaurantOrder) - .build(); + private SagaDefinition sagaDefinition; + + public CreateOrderSaga(RestaurantOrderServiceProxy restaurantOrderService) { + this.sagaDefinition = step() + .withCompensation(this::rejectOrder) + .step() + .invokeParticipant(this::verifyConsumer) + .step() + .invokeParticipant(restaurantOrderService.create, this::makeCreateRestaurantOrderCommand) + .onReply(CreateRestaurantOrderReply.class, + this::handleCreateRestaurantOrderReply) + .withCompensation(this::rejectRestaurantOrder) + .step() + .invokeParticipant(this::authorizeCard) + .step() + .invokeParticipant(restaurantOrderService.confirmCreate, this::confirmCreateRestaurantOrder) + .step() + .invokeParticipant(this::approveOrder) + .build(); + + } @Override public SagaDefinition getSagaDefinition() { return sagaDefinition; } - private CommandWithDestination approveRestaurantOrder(CreateOrderSagaData data) { - return send(new ConfirmCreateRestaurantOrder(data.getRestaurantOrderId())) - .to(RestaurantOrderServiceChannels.restaurantOrderServiceChannel) - .build(); + private ConfirmCreateRestaurantOrder confirmCreateRestaurantOrder(CreateOrderSagaData data) { + return new ConfirmCreateRestaurantOrder(data.getRestaurantOrderId()); } @@ -78,15 +91,21 @@ private void handleCreateRestaurantOrderReply(CreateOrderSagaData data, data.setRestaurantOrderId(reply.getRestaurantOrderId()); } - private CommandWithDestination createRestaurantOrder(CreateOrderSagaData data) { - return send(new CreateRestaurantOrder(data.getOrderDetails().getRestaurantId(), data.getOrderId(), makeRestaurantOrderDetails(data.getOrderDetails()))) - .to(RestaurantOrderServiceChannels.restaurantOrderServiceChannel) - .build(); + private CreateRestaurantOrder makeCreateRestaurantOrderCommand(CreateOrderSagaData data) { + return new CreateRestaurantOrder(data.getOrderDetails().getRestaurantId(), data.getOrderId(), makeRestaurantOrderDetails(data.getOrderDetails())); } private RestaurantOrderDetails makeRestaurantOrderDetails(OrderDetails orderDetails) { // TODO FIXME - return new RestaurantOrderDetails(); + return new RestaurantOrderDetails(makeRestaurantOrderLineItems(orderDetails.getLineItems())); + } + + private List makeRestaurantOrderLineItems(List lineItems) { + return lineItems.stream().map(this::makeRestaurantOrderLineItem).collect(toList()); + } + + private RestaurantOrderLineItem makeRestaurantOrderLineItem(OrderLineItem orderLineItem) { + return new RestaurantOrderLineItem(orderLineItem.getMenuItemId(), orderLineItem.getName(), orderLineItem.getQuantity()); } private CommandWithDestination verifyConsumer(CreateOrderSagaData data) { diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSagaData.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSagaData.java index ced3ccee..5b1a07b2 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSagaData.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSagaData.java @@ -1,6 +1,8 @@ package net.chrisrichardson.ftgo.orderservice.sagas.createorder; import net.chrisrichardson.ftgo.orderservice.api.events.OrderDetails; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; public class CreateOrderSagaData { @@ -21,6 +23,16 @@ public CreateOrderSagaData(Long orderId, OrderDetails orderDetails) { this.orderDetails = orderDetails; } + @Override + public boolean equals(Object o) { + return EqualsBuilder.reflectionEquals(this, o); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + public OrderDetails getOrderDetails() { return orderDetails; } diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/web/OrderController.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/web/OrderController.java index b2ed0601..2dcc8c6d 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/web/OrderController.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/web/OrderController.java @@ -7,7 +7,6 @@ import net.chrisrichardson.ftgo.orderservice.domain.OrderRepository; import net.chrisrichardson.ftgo.orderservice.domain.OrderRevision; import net.chrisrichardson.ftgo.orderservice.domain.OrderService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; @@ -24,12 +23,16 @@ @RequestMapping(path = "/orders") public class OrderController { - @Autowired private OrderService orderService; - @Autowired private OrderRepository orderRepository; + + public OrderController(OrderService orderService, OrderRepository orderRepository) { + this.orderService = orderService; + this.orderRepository = orderRepository; + } + @RequestMapping(method = RequestMethod.POST) public CreateOrderResponse create(@RequestBody CreateOrderRequest request) { Order order = orderService.createOrder(request.getConsumerId(), diff --git a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/web/OrderWebConfiguration.java b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/web/OrderWebConfiguration.java index d0fd4afc..db39bdb7 100644 --- a/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/web/OrderWebConfiguration.java +++ b/ftgo-order-service/src/main/java/net/chrisrichardson/ftgo/orderservice/web/OrderWebConfiguration.java @@ -2,12 +2,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.eventuate.javaclient.commonimpl.JSonMapper; -import net.chrisrichardson.ftgo.orderservice.domain.OrderServiceConfiguration; +import net.chrisrichardson.ftgo.orderservice.domain.OrderServiceWithRepositoriesConfiguration; import org.springframework.context.annotation.*; @Configuration @ComponentScan -@Import(OrderServiceConfiguration.class) +@Import(OrderServiceWithRepositoriesConfiguration.class) public class OrderWebConfiguration { @Bean diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/EventuateTramRoutesConfigurer.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/EventuateTramRoutesConfigurer.java new file mode 100644 index 00000000..a74233a8 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/EventuateTramRoutesConfigurer.java @@ -0,0 +1,161 @@ +package net.chrisrichardson.ftgo.orderservice; + +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.toomuchcoding.jsonassert.JsonAssertion; +import io.eventuate.tram.commands.common.CommandMessageHeaders; +import io.eventuate.tram.commands.common.ReplyMessageHeaders; +import io.eventuate.tram.messaging.common.Message; +import io.eventuate.tram.messaging.consumer.MessageConsumer; +import io.eventuate.tram.messaging.producer.MessageBuilder; +import io.eventuate.tram.messaging.producer.MessageProducer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.contract.spec.Contract; +import org.springframework.cloud.contract.spec.internal.BodyMatcher; +import org.springframework.cloud.contract.spec.internal.BodyMatchers; +import org.springframework.cloud.contract.spec.internal.Header; +import org.springframework.cloud.contract.stubrunner.BatchStubRunner; +import org.springframework.cloud.contract.stubrunner.StubConfiguration; +import org.springframework.cloud.contract.verifier.util.BodyExtractor; +import org.springframework.cloud.contract.verifier.util.JsonPaths; +import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter; +import org.springframework.cloud.contract.verifier.util.MapConverter; +import org.springframework.cloud.contract.verifier.util.MethodBufferingJsonVerifiable; + +import javax.annotation.PostConstruct; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class EventuateTramRoutesConfigurer { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + private BatchStubRunner batchStubRunner; + + public EventuateTramRoutesConfigurer(BatchStubRunner batchStubRunner) { + this.batchStubRunner = batchStubRunner; + } + + @Autowired + private MessageConsumer messageConsumer; + + @Autowired + private MessageProducer messageProducer; + + private int idCounter; + + @PostConstruct + public void initialize() { + Map> contracts = batchStubRunner + .getContracts(); + for (Collection list : contracts.values()) { + for (Contract it : list) { + if (it.getInput() != null + && it.getInput().getMessageFrom() != null + && it.getOutputMessage() != null + && it.getOutputMessage().getSentTo() != null) { + String inputClientValue = it.getInput().getMessageFrom().getClientValue(); + String outputClientValue = it.getOutputMessage().getSentTo().getClientValue(); + + System.out.println("inputClientValue=" + inputClientValue); + System.out.println("outputClientValue=" + outputClientValue); + + messageConsumer.subscribe("Route-" + it.getLabel() + System.currentTimeMillis() + "." + idCounter++, + Collections.singleton(inputClientValue), (message) -> { + if (satisfies(message, it)) { + process(message, it).ifPresent(m -> messageProducer.send(outputClientValue, m)); + } + }); + +// from(inputClientValue) +// .filter(new StubRunnerCamelPredicate(it)) +// .process(new StubRunnerCamelProcessor(it)) +// .to(outputClientValue); + } + } + } + + } + + private Optional process(Message message, Contract groovyDsl) { + MessageBuilder messageBuilder = MessageBuilder + .withPayload(BodyExtractor + .extractStubValueFrom(groovyDsl.getOutputMessage().getBody())); + if (groovyDsl.getOutputMessage().getHeaders() != null) { + for (Header entry : groovyDsl.getOutputMessage().getHeaders().getEntries()) { + messageBuilder.withHeader(entry.getName(), entry.getClientValue().toString()); + } + } + messageBuilder.withExtraHeaders("", correlationHeaders(message.getHeaders())); + + return Optional.of(messageBuilder.build()); + } + + private Map correlationHeaders(Map headers) { + Map m = headers.entrySet() + .stream() + .filter(e -> e.getKey().startsWith(CommandMessageHeaders.COMMAND_HEADER_PREFIX)) + .collect(Collectors.toMap(e -> CommandMessageHeaders.inReply(e.getKey()), + Map.Entry::getValue)); + m.put(ReplyMessageHeaders.IN_REPLY_TO, headers.get(Message.ID)); + return m; + } + + private boolean satisfies(Message message, Contract groovyDsl) { + if (!headersMatch(message, groovyDsl)) { + logger.info("Headers don't match {} {} ", groovyDsl.getLabel(), message); + return false; + } + BodyMatchers matchers = groovyDsl.getInput().getMatchers(); + Object dslBody = MapConverter.getStubSideValues(groovyDsl.getInput().getMessageBody()); + Object matchingInputMessage = JsonToJsonPathsConverter + .removeMatchingJsonPaths(dslBody, matchers); + JsonPaths jsonPaths = JsonToJsonPathsConverter + .transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck( + matchingInputMessage); + DocumentContext parsedJson = JsonPath.parse(message.getPayload()); + boolean matches = true; + for (MethodBufferingJsonVerifiable path : jsonPaths) { + matches &= matchesJsonPath(parsedJson, path.jsonPath()); + } + logger.info("jsonPaths match {} {} {} ", groovyDsl.getLabel(), matches, message); + + if (matchers != null && matchers.hasMatchers()) { + for (BodyMatcher matcher : matchers.jsonPathMatchers()) { + String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher, dslBody); + matches &= matchesJsonPath(parsedJson, jsonPath); + } + } + logger.info("matchers {} {} {} ", groovyDsl.getLabel(), matches, message); + return matches; + } + + private boolean matchesJsonPath(DocumentContext parsedJson, String jsonPath) { + try { + JsonAssertion.assertThat(parsedJson).matchesJsonPath(jsonPath); + return true; + } catch (Exception e) { + return false; + } + } + + private boolean headersMatch(Message message, Contract groovyDsl) { + Map headers = message.getHeaders(); + boolean matches = true; + for (Header it : groovyDsl.getInput().getMessageHeaders().getEntries()) { + String name = it.getName(); + Object value = it.getClientValue(); + Object valueInHeader = headers.get(name); + matches &= value instanceof Pattern ? + ((Pattern) value).matcher(valueInHeader.toString()).matches() : + valueInHeader != null && valueInHeader.equals(value); + } + return matches; + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessageTracker.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessageTracker.java new file mode 100644 index 00000000..6b11e094 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessageTracker.java @@ -0,0 +1,63 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.tram.commands.common.Command; +import io.eventuate.tram.commands.common.CommandMessageHeaders; +import io.eventuate.tram.events.common.DomainEvent; +import io.eventuate.tram.events.common.EventMessageHeaders; +import io.eventuate.tram.messaging.common.Message; +import io.eventuate.tram.messaging.consumer.MessageConsumer; +import io.eventuate.util.test.async.Eventually; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; + +import static org.junit.Assert.fail; + +public class MessageTracker { + + private Set channels; + + private LinkedBlockingQueue messages = new LinkedBlockingQueue<>(); + + public MessageTracker(MessageTrackerConfiguration config, MessageConsumer messageConsumer) { + this.channels = config.getChannels(); + messageConsumer.subscribe("SagaParticipantStubManager-messages-" + System.currentTimeMillis(), channels, this::handleMessage); + } + + private void handleMessage(Message message) { + messages.add(message); + } + + private void validateChannel(String commandChannel) { + if (!channels.contains(commandChannel)) + throw new IllegalArgumentException(String.format("%s is not one of the specified channels: %s", commandChannel, channels)); + } + + public void reset() { + messages.clear(); + } + + public void assertCommandMessageSent(String channel, Class expectedCommandClass) { + validateChannel(channel); + Eventually.eventually(() -> { + List messages = Arrays.asList(this.messages.toArray(new Message[this.messages.size()])); + if (messages.stream() + .noneMatch(m -> m.getHeader(CommandMessageHeaders.COMMAND_TYPE).map(ct -> ct.equals(expectedCommandClass.getName())).orElse(false))) + fail(String.format("Cannot find command message of type %s in %s", expectedCommandClass.getName(), messages)); + }); + + } + + + public void assertDomainEventPublished(String channel, Class expectedDomainEventClass) { + validateChannel(channel); + Eventually.eventually(() -> { + List messages = Arrays.asList(this.messages.toArray(new Message[this.messages.size()])); + if (messages.stream() + .noneMatch(m -> m.getHeader(EventMessageHeaders.EVENT_TYPE).map(ct -> ct.equals(expectedDomainEventClass.getName())).orElse(false))) + fail(String.format("Cannot find domain eventmessage of type %s in %s", expectedDomainEventClass.getName(), messages)); + }); + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessageTrackerConfiguration.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessageTrackerConfiguration.java new file mode 100644 index 00000000..7cdf2bcb --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessageTrackerConfiguration.java @@ -0,0 +1,26 @@ +package net.chrisrichardson.ftgo.orderservice; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class MessageTrackerConfiguration { + private Set channels; + + public MessageTrackerConfiguration(String... channels) { + this.channels = new HashSet<>(Arrays.asList(channels)); + } + + public MessageTrackerConfiguration(Set channels) { + this.channels = channels; + } + + public void add(String channel) { + channels.add(channel); + } + + + public Set getChannels() { + return channels; + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessagingStubConfiguration.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessagingStubConfiguration.java new file mode 100644 index 00000000..12100967 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MessagingStubConfiguration.java @@ -0,0 +1,22 @@ +package net.chrisrichardson.ftgo.orderservice; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class MessagingStubConfiguration { + + private Set channels = new HashSet<>(); + + public MessagingStubConfiguration(String... channels) { + this.channels = new HashSet<>(Arrays.asList(channels)); + } + + public void add(String channel) { + channels.add(channel); + } + + public Set getChannels() { + return channels; + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MyCommandDispatcher.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MyCommandDispatcher.java new file mode 100644 index 00000000..7d0430b9 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MyCommandDispatcher.java @@ -0,0 +1,55 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.tram.commands.common.ChannelMapping; +import io.eventuate.tram.commands.consumer.CommandDispatcher; +import io.eventuate.tram.commands.consumer.CommandHandler; +import io.eventuate.tram.commands.consumer.CommandHandlers; +import io.eventuate.tram.messaging.common.Message; +import io.eventuate.tram.messaging.consumer.MessageConsumer; +import io.eventuate.tram.messaging.producer.MessageProducer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +import static java.util.stream.Collectors.toList; + +public class MyCommandDispatcher extends CommandDispatcher { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + private CommandHandlers commandHandlers; + private List unhandledMessages = new LinkedList<>(); + + public MyCommandDispatcher(String commandDispatcherId, CommandHandlers commandHandlers, ChannelMapping channelMapping, MessageConsumer messageConsumer, MessageProducer messageProducer) { + super(commandDispatcherId, commandHandlers, channelMapping, messageConsumer, messageProducer); + this.commandHandlers = commandHandlers; + } + + @Override + public void messageHandler(Message message) { + Optional possibleMethod = commandHandlers.findTargetMethod(message); + if (possibleMethod.isPresent()) + super.messageHandler(message); + else { + logger.info("unhandled message {}", message); + unhandledMessages.add(message); + + } + } + + + public void reset() { + unhandledMessages.clear(); + } + + public void noteNewCommandHandler(MyCommandHandler commandHandler) { + List handled = unhandledMessages.stream().filter(commandHandler::handles).collect(toList()); + if (!handled.isEmpty()) + logger.info("Processing unhandled messages {}", handled); + unhandledMessages.removeAll(handled); + handled.forEach(super::messageHandler); + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MyCommandHandler.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MyCommandHandler.java new file mode 100644 index 00000000..43837877 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/MyCommandHandler.java @@ -0,0 +1,28 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.tram.commands.consumer.CommandHandler; +import io.eventuate.tram.commands.consumer.CommandMessage; +import io.eventuate.tram.messaging.common.Message; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +import static java.util.Collections.singletonList; + +public class MyCommandHandler extends CommandHandler { + + private String commandChannel; + private final Predicate expectedCommand; + + public MyCommandHandler(String commandChannel, Class expectedCommandClass, Predicate expectedCommand, Function, Message> replyBuilder) { + super(commandChannel, Optional.empty(), expectedCommandClass, (cm, pv) -> singletonList(replyBuilder.apply(cm))); + this.commandChannel = commandChannel; + this.expectedCommand = expectedCommand; + } + + @Override + public boolean handles(Message message) { + return message.getRequiredHeader(Message.DESTINATION).equals(commandChannel) && super.handles(message) && expectedCommand.test(message); + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderDetailsMother.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderDetailsMother.java index 3c8f68b8..ee608a85 100644 --- a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderDetailsMother.java +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderDetailsMother.java @@ -3,11 +3,45 @@ import net.chrisrichardson.ftgo.common.Money; import net.chrisrichardson.ftgo.orderservice.api.events.OrderDetails; import net.chrisrichardson.ftgo.orderservice.api.events.OrderLineItem; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderState; +import net.chrisrichardson.ftgo.orderservice.domain.Order; +import net.chrisrichardson.ftgo.orderservice.web.MenuItemIdAndQuantity; import java.util.Collections; +import java.util.List; + +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_ID; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.CHICKEN_VINDALOO; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.CHICKEN_VINDALOO_PRICE; public class OrderDetailsMother { - static OrderDetails makeOrderDetails(long consumerId, long restaurantId, Money orderTotal) { - return new OrderDetails(consumerId, restaurantId, Collections.singletonList(new OrderLineItem("samosas", "Samosas", new Money("2.50"), 3)), orderTotal); + + public static long CONSUMER_ID = 1511300065921L; + + public static final int CHICKEN_VINDALOO_QUANTITY = 5; + public static final MenuItemIdAndQuantity CHICKEN_VINDALOO_MENU_ITEM_AND_QUANTITY = new MenuItemIdAndQuantity(RestaurantMother.CHICKEN_VINDALOO_MENU_ITEM_ID, CHICKEN_VINDALOO_QUANTITY); + public static final List CHICKEN_VINDALOO_MENU_ITEMS_AND_QUANTITIES = Collections.singletonList(CHICKEN_VINDALOO_MENU_ITEM_AND_QUANTITY); + + public static List chickenVindalooLineItems() { + return Collections.singletonList(new OrderLineItem(CHICKEN_VINDALOO_MENU_ITEM_AND_QUANTITY.getMenuItemId(), + CHICKEN_VINDALOO, + CHICKEN_VINDALOO_PRICE, + CHICKEN_VINDALOO_MENU_ITEM_AND_QUANTITY.getQuantity())); + } + + public static final Money CHICKEN_VINDALOO_ORDER_TOTAL = CHICKEN_VINDALOO_PRICE.multiply(5); + public static final OrderDetails CHICKEN_VINDALOO_ORDER_DETAILS = new OrderDetails(CONSUMER_ID, AJANTA_ID, + chickenVindalooLineItems(), CHICKEN_VINDALOO_ORDER_TOTAL); + + public static long ORDER_ID = 99L; + + public static Order CHICKEN_VINDALOO_ORDER = makeAjantaOrder(); + + public static final OrderState CHICKEN_VINDALOO_ORDER_STATE = OrderState.CREATE_PENDING; + + private static Order makeAjantaOrder() { + Order order = new Order(CONSUMER_ID, AJANTA_ID, chickenVindalooLineItems()); + order.setId(ORDER_ID); + return order; } } diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderJpaTest.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderJpaTest.java deleted file mode 100644 index 57577e3c..00000000 --- a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/OrderJpaTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.chrisrichardson.ftgo.orderservice; - -import net.chrisrichardson.ftgo.common.Money; -import net.chrisrichardson.ftgo.orderservice.api.events.OrderLineItem; -import net.chrisrichardson.ftgo.orderservice.domain.Order; -import net.chrisrichardson.ftgo.orderservice.domain.OrderRepository; -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; -import org.springframework.transaction.support.TransactionTemplate; - -import java.util.Collections; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -@RunWith(SpringRunner.class) -@SpringBootTest(classes = OrderJpaTestConfiguration.class) -public class OrderJpaTest { - - @Autowired - private OrderRepository orderRepository; - - @Autowired - private TransactionTemplate transactionTemplate; - - @Test - public void shouldDoSomething() { - - Long orderId = transactionTemplate.execute((ts) -> { - assertNotNull(orderRepository); - long consumerId = -1; - long restaurantId = -2; - Money orderTotal = Money.ZERO; - Order order = new Order(consumerId, restaurantId, Collections.singletonList(new OrderLineItem("samosas", "Samosas", new Money("2.50"), 3))); - orderRepository.save(order); - return order.getId(); - }); - - - transactionTemplate.execute((ts) -> { - Order loadedOrder = orderRepository.findOne(orderId); - - assertNotNull(loadedOrder); - assertEquals(1, loadedOrder.getLineItems().size()); - return null; - }); - - } - -} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/RestaurantMother.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/RestaurantMother.java new file mode 100644 index 00000000..bd6e04e0 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/RestaurantMother.java @@ -0,0 +1,25 @@ +package net.chrisrichardson.ftgo.orderservice; + +import net.chrisrichardson.ftgo.common.Money; +import net.chrisrichardson.ftgo.orderservice.domain.Restaurant; +import net.chrisrichardson.ftgo.restaurantservice.events.MenuItem; +import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantMenu; + +import java.util.Collections; +import java.util.List; + +public class RestaurantMother { + public static final String AJANTA_RESTAURANT_NAME = "Ajanta"; + public static final long AJANTA_ID = 1L; + + public static final String CHICKEN_VINDALOO = "Chicken Vindaloo"; + public static final String CHICKEN_VINDALOO_MENU_ITEM_ID = "1"; + public static final Money CHICKEN_VINDALOO_PRICE = new Money("12.34"); + + public static MenuItem CHICKEN_VINDALOO_MENU_ITEM = new MenuItem(CHICKEN_VINDALOO_MENU_ITEM_ID, CHICKEN_VINDALOO, CHICKEN_VINDALOO_PRICE); + + public static final List AJANTA_RESTAURANT_MENU_ITEMS = Collections.singletonList(new MenuItem(CHICKEN_VINDALOO_MENU_ITEM_ID, CHICKEN_VINDALOO, CHICKEN_VINDALOO_PRICE)); + public static final RestaurantMenu AJANTA_RESTAURANT_MENU = new RestaurantMenu(AJANTA_RESTAURANT_MENU_ITEMS); + public static final Restaurant AJANTA_RESTAURANT = + new Restaurant(AJANTA_ID, AJANTA_RESTAURANT_MENU_ITEMS); +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStub.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStub.java new file mode 100644 index 00000000..fb0e84da --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStub.java @@ -0,0 +1,20 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.tram.commands.common.Command; +import io.eventuate.tram.commands.consumer.CommandMessage; +import io.eventuate.tram.messaging.common.Message; + +import java.util.List; +import java.util.function.Function; + +public class SagaParticipantStub { + private Function, Message> replyBuilder; + + public SagaParticipantStub(Function, Message> replyBuilder) { + this.replyBuilder = replyBuilder; + } + + public List invoke(CommandMessage cm) { + throw new UnsupportedOperationException(); + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStubConfiguration.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStubConfiguration.java new file mode 100644 index 00000000..77c5f71b --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStubConfiguration.java @@ -0,0 +1,39 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.tram.commands.common.ChannelMapping; +import io.eventuate.tram.commands.common.DefaultChannelMapping; +import io.eventuate.tram.commands.producer.TramCommandProducerConfiguration; +import io.eventuate.tram.events.publisher.TramEventsPublisherConfiguration; +import io.eventuate.tram.messaging.consumer.MessageConsumer; +import io.eventuate.tram.messaging.producer.MessageProducer; +import io.eventuate.tram.sagas.orchestration.SagaCommandProducer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import({TramCommandProducerConfiguration.class, TramEventsPublisherConfiguration.class,}) +public class SagaParticipantStubConfiguration { + + @Bean + public SagaParticipantStubManager sagaParticipantStubManager(MessagingStubConfiguration messagingStubConfiguration, ChannelMapping channelMapping, MessageConsumer messageConsumer, MessageProducer messageProducer) { + return new SagaParticipantStubManager(messagingStubConfiguration, channelMapping, messageConsumer, messageProducer); + } + + @Bean + public MessageTracker messageTracker(MessageTrackerConfiguration configuration, MessageConsumer messageConsumer) { + return new MessageTracker(configuration, messageConsumer) ; + } + + @Bean + public SagaCommandProducer sagaCommandProducer() { + return new SagaCommandProducer(); + } + + @Bean + public ChannelMapping channelMapping() { + return new DefaultChannelMapping.DefaultChannelMappingBuilder().build(); + } + + +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStubManager.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStubManager.java new file mode 100644 index 00000000..8b37df50 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/SagaParticipantStubManager.java @@ -0,0 +1,140 @@ +package net.chrisrichardson.ftgo.orderservice; + +import io.eventuate.javaclient.commonimpl.JSonMapper; +import io.eventuate.tram.commands.common.ChannelMapping; +import io.eventuate.tram.commands.common.Command; +import io.eventuate.tram.commands.consumer.CommandExceptionHandler; +import io.eventuate.tram.commands.consumer.CommandHandler; +import io.eventuate.tram.commands.consumer.CommandHandlers; +import io.eventuate.tram.commands.consumer.CommandMessage; +import io.eventuate.tram.messaging.common.Message; +import io.eventuate.tram.messaging.consumer.MessageConsumer; +import io.eventuate.tram.messaging.producer.MessageProducer; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +import static io.eventuate.tram.commands.consumer.CommandHandlerReplyBuilder.withFailure; +import static io.eventuate.tram.commands.consumer.CommandHandlerReplyBuilder.withSuccess; + +public class SagaParticipantStubManager { + private final MyCommandHandlers commandHandlers; + private Set commandChannels; + private final MyCommandDispatcher commandDispatcher; + private String currentCommandChannel; + + + + + + class MyCommandHandlers extends CommandHandlers { + + private List myHandlers = new ArrayList<>(); + + public MyCommandHandlers() { + super(Collections.emptyList()); + } + + @Override + public Set getChannels() { + return commandChannels; + } + + public void add(CommandHandler commandHandler) { + this.myHandlers.add(commandHandler); + } + + @Override + public Optional findTargetMethod(Message message) { + return myHandlers.stream().filter(h -> h.handles(message)).findFirst(); + } + + @Override + public Optional findExceptionHandler(Throwable cause) { + return super.findExceptionHandler(cause); + } + + public void reset() { + myHandlers.clear(); + } + } + + public SagaParticipantStubManager(MessagingStubConfiguration messagingStubConfiguration, ChannelMapping channelMapping, MessageConsumer messageConsumer, MessageProducer messageProducer) { + this.commandChannels = messagingStubConfiguration.getChannels(); + this.commandHandlers = new MyCommandHandlers(); + this.commandDispatcher = new MyCommandDispatcher("SagaParticipantStubManager-command-dispatcher-" + System.currentTimeMillis(), + commandHandlers, + channelMapping, + messageConsumer, + messageProducer); + + /// TODO handle scenario where a command is recieved for which there is not a handler. + } + + + @PostConstruct + public void initialize() { + commandDispatcher.initialize(); + } + + public void reset() { + commandHandlers.reset(); + commandDispatcher.reset(); + + } + + public SagaParticipantStubManager forChannel(String commandChannel) { + validateChannel(commandChannel); + this.currentCommandChannel = commandChannel; + return this; + } + + private void validateChannel(String commandChannel) { + if (!commandChannels.contains(commandChannel)) + throw new IllegalArgumentException(String.format("%s is not one of the specified channels: %s", commandChannel, commandChannels)); + } + + public SagaParticipantStubManagerHelper when(C expectedCommand) { + return new SagaParticipantStubManagerHelper(this, (Class) expectedCommand.getClass(), + message -> JSonMapper.fromJson(message.getPayload(), expectedCommand.getClass()).equals(expectedCommand)); + } + + public SagaParticipantStubManagerHelper when(Class expectedCommandClass) { + return new SagaParticipantStubManagerHelper(this, expectedCommandClass, message -> true); + } + + + public class SagaParticipantStubManagerHelper { + private Class expectedCommandClass; + private final Predicate expectedCommand; + private SagaParticipantStubManager sagaParticipantStubManager; + + public SagaParticipantStubManagerHelper(SagaParticipantStubManager sagaParticipantStubManager, Class expectedCommandClass, Predicate expectedCommand) { + this.sagaParticipantStubManager = sagaParticipantStubManager; + this.expectedCommandClass = expectedCommandClass; + this.expectedCommand = expectedCommand; + } + + public SagaParticipantStubManager replyWith(Function, Message> replyBuilder) { + MyCommandHandler commandHandler = new MyCommandHandler<>(currentCommandChannel, expectedCommandClass, expectedCommand, replyBuilder); + sagaParticipantStubManager.commandHandlers.add(commandHandler); + commandDispatcher.noteNewCommandHandler(commandHandler); + return sagaParticipantStubManager; + } + + public SagaParticipantStubManager replyWithSuccess() { + return replyWith(cm -> withSuccess()); + } + + public SagaParticipantStubManager replyWithFailure() { + return replyWith(cm -> withFailure()); + } + } + +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceTest.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceTest.java new file mode 100644 index 00000000..20f2fb10 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderServiceTest.java @@ -0,0 +1,76 @@ +package net.chrisrichardson.ftgo.orderservice.domain; + +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import io.eventuate.tram.sagas.orchestration.SagaManager; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderCreatedEvent; +import net.chrisrichardson.ftgo.orderservice.sagas.cancelorder.CancelOrderSagaData; +import net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSagaData; +import net.chrisrichardson.ftgo.orderservice.sagas.reviseorder.ReviseOrderSagaData; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; + +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_MENU_ITEMS_AND_QUANTITIES; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_ORDER_DETAILS; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CONSUMER_ID; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.ORDER_ID; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_ID; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_RESTAURANT; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class OrderServiceTest { + + private OrderService orderService; + private OrderRepository orderRepository; + private DomainEventPublisher eventPublisher; + private RestaurantRepository restaurantRepository; + private SagaManager createOrderSagaManager; + private SagaManager cancelOrderSagaManager; + private SagaManager reviseOrderSagaManager; + private OrderAggregateEventPublisher orderAggregateEventPublisher; + + @Before + public void setup() { + orderRepository = mock(OrderRepository.class); + eventPublisher = mock(DomainEventPublisher.class); + restaurantRepository = mock(RestaurantRepository.class); + createOrderSagaManager = mock(SagaManager.class); + cancelOrderSagaManager = mock(SagaManager.class); + reviseOrderSagaManager = mock(SagaManager.class); + + // Mock DomainEventPublisher AND use the real OrderAggregateEventPublisher + + orderAggregateEventPublisher = mock(OrderAggregateEventPublisher.class); + + orderService = new OrderService(orderRepository, eventPublisher, restaurantRepository, + createOrderSagaManager, cancelOrderSagaManager, reviseOrderSagaManager, orderAggregateEventPublisher); + } + + + @Test + public void shouldCreateOrder() { + when(restaurantRepository.findOne(AJANTA_ID)).thenReturn(AJANTA_RESTAURANT); + when(orderRepository.save(any(Order.class))).then(invocation -> { + Order order = (Order) invocation.getArguments()[0]; + order.setId(ORDER_ID); + return order; + }); + + Order order = orderService.createOrder(CONSUMER_ID, AJANTA_ID, CHICKEN_VINDALOO_MENU_ITEMS_AND_QUANTITIES); + + verify(orderRepository).save(same(order)); + + verify(orderAggregateEventPublisher).publish(order, + Collections.singletonList(new OrderCreatedEvent(CHICKEN_VINDALOO_ORDER_DETAILS))); + + verify(createOrderSagaManager).create(new CreateOrderSagaData(ORDER_ID, CHICKEN_VINDALOO_ORDER_DETAILS), Order.class, ORDER_ID); + } + + // TODO write tests for other methods + +} \ No newline at end of file diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderTest.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderTest.java index 3931b40d..b3424024 100644 --- a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderTest.java +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/OrderTest.java @@ -1,20 +1,55 @@ package net.chrisrichardson.ftgo.orderservice.domain; import io.eventuate.tram.events.ResultWithEvents; -import net.chrisrichardson.ftgo.common.Money; -import net.chrisrichardson.ftgo.orderservice.api.events.OrderLineItem; +import io.eventuate.tram.events.common.DomainEvent; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderCreatedEvent; +import net.chrisrichardson.ftgo.orderservice.api.events.OrderState; +import org.junit.Before; import org.junit.Test; import java.util.Collections; +import java.util.List; import java.util.Optional; -import static org.junit.Assert.*; +import static java.util.Collections.singletonList; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.*; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_ID; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.CHICKEN_VINDALOO_PRICE; +import static org.junit.Assert.assertEquals; public class OrderTest { + private ResultWithEvents createResult; + private Order order; + + @Before + public void setUp() throws Exception { + createResult = Order.createOrder(CONSUMER_ID, AJANTA_ID, chickenVindalooLineItems()); + order = createResult.result; + } + + @Test + public void shouldCreateOrder() { + assertEquals(singletonList(new OrderCreatedEvent(CHICKEN_VINDALOO_ORDER_DETAILS)), createResult.events); + + assertEquals(OrderState.CREATE_PENDING, order.getState()); + // ... + } + + @Test + public void shouldCalculateTotal() { + assertEquals(CHICKEN_VINDALOO_PRICE.multiply(CHICKEN_VINDALOO_QUANTITY), order.getOrderTotal()); + } + + @Test + public void shouldAuthorize() { + List events = order.noteAuthorized(); + assertEquals(singletonList(new OrderAuthorized()), events); + assertEquals(OrderState.AUTHORIZED, order.getState()); + } + @Test public void shouldReviseOrder() { - Order order = Order.createOrder(101L, 102L, Collections.singletonList(new OrderLineItem("1", "Chicken Vindaloo", new Money(4), 5))).result; order.noteAuthorized(); @@ -22,11 +57,10 @@ public void shouldReviseOrder() { ResultWithEvents result = order.revise(orderRevision); - assertEquals(new Money(4).multiply(10), result.result.getNewOrderTotal()); - + assertEquals(CHICKEN_VINDALOO_PRICE.multiply(10), result.result.getNewOrderTotal()); order.confirmRevision(orderRevision); - assertEquals(new Money(4).multiply(10), order.getOrderTotal()); + assertEquals(CHICKEN_VINDALOO_PRICE.multiply(10), order.getOrderTotal()); } } \ No newline at end of file diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/TestMessageConsumer2.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/TestMessageConsumer2.java new file mode 100644 index 00000000..b7acf4f5 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/domain/TestMessageConsumer2.java @@ -0,0 +1,63 @@ +package net.chrisrichardson.ftgo.orderservice.domain; + +import io.eventuate.tram.messaging.common.Message; +import io.eventuate.tram.messaging.consumer.MessageConsumer; +import io.eventuate.util.test.async.Eventually; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PostConstruct; +import java.util.Collections; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class TestMessageConsumer2 { + + private Logger logger = LoggerFactory.getLogger(getClass()); + private LinkedBlockingDeque messages = new LinkedBlockingDeque<>(); + + private String subscriberId; + private String channel; + + @Autowired + private MessageConsumer messageConsumer; + + public TestMessageConsumer2(String subscriberId, String channel) { + this.subscriberId = subscriberId; + this.channel = channel; + } + + @PostConstruct + public void subscribe() { + messageConsumer.subscribe(subscriberId, Collections.singleton(channel), this::handle); + } + + private void handle(Message message) { + logger.debug("Got message: {}", message); + messages.add(message); + } + + public Message assertMessageReceived() { + return assertMessageReceived((m) -> true); + } + + public Message assertMessageReceived(Predicate predicate) { + return Eventually.eventuallyReturning(() -> { + Message m = null; + try { + m = messages.pollFirst(1, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + assertNotNull(m); + System.out.println("Testing message: " + m); + assertTrue("Failed predicate", predicate.test(m)); + return m; + }); + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/MockTramMessageSpecification.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/MockTramMessageSpecification.java new file mode 100644 index 00000000..d9811bcf --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/MockTramMessageSpecification.java @@ -0,0 +1,58 @@ +package net.chrisrichardson.ftgo.orderservice.messaging; + +import io.eventuate.javaclient.spring.jdbc.IdGenerator; +import io.eventuate.javaclient.spring.jdbc.IdGeneratorImpl; +import io.eventuate.tram.events.common.DomainEvent; +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import io.eventuate.tram.events.publisher.DomainEventPublisherImpl; +import io.eventuate.tram.events.subscriber.DomainEventDispatcher; +import io.eventuate.tram.events.subscriber.DomainEventHandlers; +import io.eventuate.tram.messaging.common.Message; +import io.eventuate.tram.messaging.consumer.MessageHandler; + +import java.util.Collections; + +public class MockTramMessageSpecification { + private MessageHandler handler; + private String aggregateType; + private Object aggregateId; + private DomainEventDispatcher dispatcher; + private IdGenerator idGenerator = new IdGeneratorImpl(); + + public MockTramMessageSpecification eventHandlers(DomainEventHandlers domainEventHandlers) { + this.dispatcher = new DomainEventDispatcher("MockId", domainEventHandlers, (subscriberId, channels, handler) -> MockTramMessageSpecification.this.handler = handler); + dispatcher.initialize(); + return this; + } + + public MockTramMessageSpecification when() { + return this; + } + + public MockTramMessageSpecification then() { + return this; + } + + public MockTramMessageSpecification aggregate(String aggregateType, Object aggregateId) { + this.aggregateType = aggregateType; + this.aggregateId = aggregateId; + return this; + } + + + public MockTramMessageSpecification publishes(DomainEvent event) { + DomainEventPublisher publisher = new DomainEventPublisherImpl((destination, message) -> { + String id = idGenerator.genId().asString(); + message.getHeaders().put(Message.ID, id); + handler.accept(message); + }); + + publisher.publish(aggregateType, aggregateId, Collections.singletonList(event)); + return this; + } + + public MockTramMessageSpecification verify(Runnable r) { + r.run(); + return this; + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/MockTramMessagingTestSupport.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/MockTramMessagingTestSupport.java new file mode 100644 index 00000000..abc17274 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/MockTramMessagingTestSupport.java @@ -0,0 +1,7 @@ +package net.chrisrichardson.ftgo.orderservice.messaging; + +public class MockTramMessagingTestSupport { + static MockTramMessageSpecification given() { + return new MockTramMessageSpecification(); + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderEventConsumerTest.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderEventConsumerTest.java new file mode 100644 index 00000000..896f81da --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/messaging/OrderEventConsumerTest.java @@ -0,0 +1,46 @@ +package net.chrisrichardson.ftgo.orderservice.messaging; + +import net.chrisrichardson.ftgo.common.CommonJsonMapperInitializer; +import net.chrisrichardson.ftgo.orderservice.RestaurantMother; +import net.chrisrichardson.ftgo.orderservice.domain.OrderService; +import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantCreated; +import net.chrisrichardson.ftgo.restaurantservice.events.RestaurantMenu; +import org.junit.Before; +import org.junit.Test; + +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_ID; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_RESTAURANT_NAME; +import static net.chrisrichardson.ftgo.orderservice.messaging.MockTramMessagingTestSupport.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class OrderEventConsumerTest { + + private OrderService orderService; + private OrderEventConsumer orderEventConsumer; + + @Before + public void setUp() throws Exception { + orderService = mock(OrderService.class); + orderEventConsumer = new OrderEventConsumer(orderService); + } + + @Test + public void shouldCreateMenu() { + + CommonJsonMapperInitializer.registerMoneyModule(); + + given(). + eventHandlers(orderEventConsumer.domainEventHandlers()). + when(). + aggregate("net.chrisrichardson.ftgo.restaurantservice.domain.Restaurant", AJANTA_ID). + publishes(new RestaurantCreated(AJANTA_RESTAURANT_NAME, RestaurantMother.AJANTA_RESTAURANT_MENU)). + then(). + verify(() -> { + verify(orderService).createMenu(AJANTA_ID, new RestaurantMenu(RestaurantMother.AJANTA_RESTAURANT_MENU_ITEMS)); + }) + ; + + } + +} \ No newline at end of file diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSagaTest.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSagaTest.java new file mode 100644 index 00000000..3790edd2 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/CreateOrderSagaTest.java @@ -0,0 +1,62 @@ +package net.chrisrichardson.ftgo.orderservice.sagas.createorder; + +import net.chrisrichardson.ftgo.common.CommonJsonMapperInitializer; +import net.chrisrichardson.ftgo.consumerservice.api.ConsumerServiceChannels; +import net.chrisrichardson.ftgo.consumerservice.api.ValidateOrderByConsumer; +import net.chrisrichardson.ftgo.orderservice.api.OrderServiceChannels; +import net.chrisrichardson.ftgo.orderservice.sagaparticipants.RejectOrderCommand; +import net.chrisrichardson.ftgo.orderservice.sagaparticipants.RestaurantOrderServiceProxy; +import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.api.RestaurantOrderServiceChannels; +import org.junit.BeforeClass; +import org.junit.Test; + +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_ORDER_DETAILS; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_ORDER_TOTAL; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CONSUMER_ID; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.ORDER_ID; +import static net.chrisrichardson.ftgo.orderservice.RestaurantMother.AJANTA_ID; +import static net.chrisrichardson.ftgo.orderservice.sagas.createorder.MockSagaTest.*; + +public class CreateOrderSagaTest { + + private RestaurantOrderServiceProxy restaurantOrderServiceProxy = new RestaurantOrderServiceProxy(); + + @BeforeClass + public static void initialize() { + CommonJsonMapperInitializer.registerMoneyModule(); + } + + @Test + public void shouldCreateOrder() { + given() + .saga(new CreateOrderSaga(restaurantOrderServiceProxy), + new CreateOrderSagaData(ORDER_ID, CHICKEN_VINDALOO_ORDER_DETAILS)). + expect(). + command(new ValidateOrderByConsumer(CONSUMER_ID, ORDER_ID, + CHICKEN_VINDALOO_ORDER_TOTAL)). + to(ConsumerServiceChannels.consumerServiceChannel). + andGiven(). + successReply(). + expect(). + command(new CreateRestaurantOrder(AJANTA_ID, ORDER_ID, null /* FIXME */)). + to(RestaurantOrderServiceChannels.restaurantOrderServiceChannel); + } + + @Test + public void shouldRejectOrderDueToConsumerVerificationFailed() { + given() + .saga(new CreateOrderSaga(restaurantOrderServiceProxy), + new CreateOrderSagaData(ORDER_ID, CHICKEN_VINDALOO_ORDER_DETAILS)). + expect(). + command(new ValidateOrderByConsumer(CONSUMER_ID, ORDER_ID, + CHICKEN_VINDALOO_ORDER_TOTAL)). + to(ConsumerServiceChannels.consumerServiceChannel). + andGiven(). + failureReply(). + expect(). + command(new RejectOrderCommand(ORDER_ID)). + to(OrderServiceChannels.orderServiceChannel); + } + +} \ No newline at end of file diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/MessageWithDestination.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/MessageWithDestination.java new file mode 100644 index 00000000..caef1e2b --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/MessageWithDestination.java @@ -0,0 +1,21 @@ +package net.chrisrichardson.ftgo.orderservice.sagas.createorder; + +import io.eventuate.tram.messaging.common.Message; + +public class MessageWithDestination { + private final String destination; + private final Message message; + + public MessageWithDestination(String destination, Message message) { + this.destination = destination; + this.message = message; + } + + public String getDestination() { + return destination; + } + + public Message getMessage() { + return message; + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/MockSagaTest.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/MockSagaTest.java new file mode 100644 index 00000000..65222b95 --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/sagas/createorder/MockSagaTest.java @@ -0,0 +1,150 @@ +package net.chrisrichardson.ftgo.orderservice.sagas.createorder; + +import io.eventuate.javaclient.commonimpl.JSonMapper; +import io.eventuate.javaclient.spring.jdbc.IdGeneratorImpl; +import io.eventuate.tram.commands.common.Command; +import io.eventuate.tram.commands.common.CommandMessageHeaders; +import io.eventuate.tram.commands.common.CommandReplyOutcome; +import io.eventuate.tram.commands.common.DefaultChannelMapping; +import io.eventuate.tram.commands.common.Failure; +import io.eventuate.tram.commands.common.ReplyMessageHeaders; +import io.eventuate.tram.commands.common.Success; +import io.eventuate.tram.commands.producer.CommandProducerImpl; +import io.eventuate.tram.messaging.common.Message; +import io.eventuate.tram.messaging.producer.MessageBuilder; +import io.eventuate.tram.sagas.orchestration.*; +import io.eventuate.tram.sagas.simpledsl.SimpleSaga; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +public class MockSagaTest { + + private final IdGeneratorImpl idGenerator = new IdGeneratorImpl(); + private SagaManagerImpl sagaManager; + private Command expectedCommand; + + private List sentCommands = new ArrayList<>(); + private MessageWithDestination sentCommand; + + static MockSagaTest given() { + return new MockSagaTest(); + } + + public MockSagaTest saga(SimpleSaga saga, T sagaData) { + sagaManager = new SagaManagerImpl<>(saga); + sagaManager.setSagaInstanceRepository(new SagaInstanceRepository() { + + private SagaInstance sagaInstance; + + @Override + public void save(SagaInstance sagaInstance) { + sagaInstance.setId(idGenerator.genId().asString()); + this.sagaInstance = sagaInstance; + } + + @Override + public SagaInstance find(String sagaType, String sagaId) { + return sagaInstance; + } + + @Override + public void update(SagaInstance sagaInstance) { + this.sagaInstance = sagaInstance; + } + + @Override + public SagaInstanceData findWithData(String sagaType, String sagaId) { + SagaInstance sagaInstance = find(sagaType, sagaId); + Data sagaData = SagaDataSerde.deserializeSagaData(sagaInstance.getSerializedSagaData()); + return new SagaInstanceData<>(sagaInstance, sagaData); + } + }); + sagaManager.setIdGenerator(idGenerator); + + CommandProducerImpl commandProducer = new CommandProducerImpl((destination, message) -> { + String id = idGenerator.genId().asString(); + message.getHeaders().put(Message.ID, id); + sentCommands.add(new MessageWithDestination(destination, message)); + }, new DefaultChannelMapping(Collections.emptyMap())); + + sagaManager.setCommandProducer(commandProducer); + sagaManager.setSagaCommandProducer(new SagaCommandProducer(commandProducer)); + + + sagaManager.setAggregateInstanceSubscriptionsDAO(mock(AggregateInstanceSubscriptionsDAO.class)); + + sagaManager.create(sagaData); + return this; + } + + public MockSagaTest expect() { + return this; + } + + public MockSagaTest command(Command command) { + expectedCommand = command; + return this; + } + + public MockSagaTest to(String commandChannel) { + assertEquals(1, sentCommands.size()); + sentCommand = sentCommands.get(0); + assertEquals(commandChannel, sentCommand.getDestination()); + assertEquals(expectedCommand.getClass().getName(), sentCommand.getMessage().getRequiredHeader(CommandMessageHeaders.COMMAND_TYPE)); + // TODO + sentCommands.clear(); + return this; + } + + public MockSagaTest andGiven() { + return this; + } + + // copy + private Map correlationHeaders(Map headers) { + Map m = headers.entrySet() + .stream() + .filter(e -> e.getKey().startsWith(CommandMessageHeaders.COMMAND_HEADER_PREFIX)) + .collect(Collectors.toMap(e -> CommandMessageHeaders.inReply(e.getKey()), + Map.Entry::getValue)); + m.put(ReplyMessageHeaders.IN_REPLY_TO, headers.get(Message.ID)); + return m; + } + + + public MockSagaTest successReply() { + Success reply = new Success(); + CommandReplyOutcome outcome = CommandReplyOutcome.SUCCESS; + Message message = replyMessage(reply, outcome); + String id = idGenerator.genId().asString(); + message.getHeaders().put(Message.ID, id); + sagaManager.handleMessage(message); + return this; + } + + public MockSagaTest failureReply() { + Failure reply = new Failure(); + CommandReplyOutcome outcome = CommandReplyOutcome.FAILURE; + Message message = replyMessage(reply, outcome); + String id = idGenerator.genId().asString(); + message.getHeaders().put(Message.ID, id); + sagaManager.handleMessage(message); + return this; + } + + private Message replyMessage(Object reply, CommandReplyOutcome outcome) { + return MessageBuilder + .withPayload(JSonMapper.toJson(reply)) + .withHeader(ReplyMessageHeaders.REPLY_OUTCOME, outcome.name()) + .withHeader(ReplyMessageHeaders.REPLY_TYPE, reply.getClass().getName()) + .withExtraHeaders("", correlationHeaders(sentCommand.getMessage().getHeaders())) + .build(); + } +} diff --git a/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/web/OrderControllerTest.java b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/web/OrderControllerTest.java new file mode 100644 index 00000000..21b39e8c --- /dev/null +++ b/ftgo-order-service/src/test/java/net/chrisrichardson/ftgo/orderservice/web/OrderControllerTest.java @@ -0,0 +1,71 @@ +package net.chrisrichardson.ftgo.orderservice.web; + +import io.eventuate.javaclient.commonimpl.JSonMapper; +import net.chrisrichardson.ftgo.common.CommonJsonMapperInitializer; +import net.chrisrichardson.ftgo.orderservice.OrderDetailsMother; +import net.chrisrichardson.ftgo.orderservice.domain.OrderRepository; +import net.chrisrichardson.ftgo.orderservice.domain.OrderService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder; + +import static io.restassured.module.mockmvc.RestAssuredMockMvc.given; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_ORDER; +import static net.chrisrichardson.ftgo.orderservice.OrderDetailsMother.CHICKEN_VINDALOO_ORDER_TOTAL; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class OrderControllerTest { + + private OrderService orderService; + private OrderRepository orderRepository; + private OrderController orderController; + + @Before + public void setUp() throws Exception { + orderService = mock(OrderService.class); + orderRepository = mock(OrderRepository.class); + orderController = new OrderController(orderService, orderRepository); + } + + + @Test + public void shouldFindOrder() { + + when(orderRepository.findOne(1L)).thenReturn(CHICKEN_VINDALOO_ORDER); + + given(). + standaloneSetup(configureControllers(orderController)). + when(). + get("/orders/1"). + then(). + statusCode(200). + body("orderId", equalTo(new Long(OrderDetailsMother.ORDER_ID).intValue())). + body("state", equalTo(OrderDetailsMother.CHICKEN_VINDALOO_ORDER_STATE.name())). + body("orderTotal", equalTo(CHICKEN_VINDALOO_ORDER_TOTAL.asString())) + ; + } + + @Test + public void shouldFindNotOrder() { + when(orderRepository.findOne(1L)).thenReturn(null); + + given(). + standaloneSetup(configureControllers(new OrderController(orderService, orderRepository))). + when(). + get("/orders/1"). + then(). + statusCode(404) + ; + } + + private StandaloneMockMvcBuilder configureControllers(Object... controllers) { + CommonJsonMapperInitializer.registerMoneyModule(); + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(JSonMapper.objectMapper); + return MockMvcBuilders.standaloneSetup(controllers).setMessageConverters(converter); + } + +} \ No newline at end of file diff --git a/ftgo-order-service/src/test/resources/application.properties b/ftgo-order-service/src/test/resources/application.properties index f3851b99..d0030836 100644 --- a/ftgo-order-service/src/test/resources/application.properties +++ b/ftgo-order-service/src/test/resources/application.properties @@ -1,3 +1,6 @@ logging.level.org.hibernate.SQL=DEBUG +logging.level.org.springframework.cloud.contract=DEBUG logging.level.io.eventuate=DEBUG spring.jpa.generate-ddl=true +stubrunner.stream.enabled=false +stubrunner.integration.enabled=false \ No newline at end of file diff --git a/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/CreateRestaurantOrder.java b/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/CreateRestaurantOrder.java index 04c41d14..861bf779 100644 --- a/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/CreateRestaurantOrder.java +++ b/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/CreateRestaurantOrder.java @@ -2,6 +2,7 @@ import io.eventuate.tram.commands.CommandDestination; import io.eventuate.tram.commands.common.Command; +import org.apache.commons.lang.builder.ToStringBuilder; @CommandDestination("restaurantService") public class CreateRestaurantOrder implements Command { @@ -10,6 +11,11 @@ public class CreateRestaurantOrder implements Command { private RestaurantOrderDetails orderDetails; private long restaurantId; + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + public Long getOrderId() { return orderId; } @@ -26,7 +32,7 @@ public void setRestaurantOrderDetails(RestaurantOrderDetails orderDetails) { this.orderDetails = orderDetails; } - public CreateRestaurantOrder() { + private CreateRestaurantOrder() { } diff --git a/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/CreateRestaurantOrderReply.java b/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/CreateRestaurantOrderReply.java index 8479ed0d..78538ded 100644 --- a/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/CreateRestaurantOrderReply.java +++ b/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/CreateRestaurantOrderReply.java @@ -1,17 +1,30 @@ package net.chrisrichardson.ftgo.restaurantorderservice.api; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; + public class CreateRestaurantOrderReply { private long restaurantOrderId; private CreateRestaurantOrderReply() { } - public void setRestaurantOrderId(long restaurantOrderId) { + public CreateRestaurantOrderReply(long restaurantOrderId) { + this.restaurantOrderId = restaurantOrderId; } - public CreateRestaurantOrderReply(long restaurantOrderId) { + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + + @Override + public boolean equals(Object o) { + return EqualsBuilder.reflectionEquals(this, o); + } + public void setRestaurantOrderId(long restaurantOrderId) { this.restaurantOrderId = restaurantOrderId; } diff --git a/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/RestaurantOrderDetails.java b/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/RestaurantOrderDetails.java index 3eb8951f..056f1231 100644 --- a/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/RestaurantOrderDetails.java +++ b/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/RestaurantOrderDetails.java @@ -1,10 +1,19 @@ package net.chrisrichardson.ftgo.restaurantorderservice.api; +import org.apache.commons.lang.builder.ToStringBuilder; + import java.util.List; public class RestaurantOrderDetails { private List lineItems; + public RestaurantOrderDetails() { + } + + public RestaurantOrderDetails(List lineItems) { + this.lineItems = lineItems; + } + public List getLineItems() { return lineItems; } @@ -12,4 +21,9 @@ public List getLineItems() { public void setLineItems(List lineItems) { this.lineItems = lineItems; } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } } diff --git a/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/RestaurantOrderLineItem.java b/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/RestaurantOrderLineItem.java index 39d5304a..161b545e 100644 --- a/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/RestaurantOrderLineItem.java +++ b/ftgo-restaurant-order-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantorderservice/api/RestaurantOrderLineItem.java @@ -13,4 +13,37 @@ public class RestaurantOrderLineItem { private String name; + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public String getMenuItemId() { + return menuItemId; + } + + public void setMenuItemId(String menuItemId) { + this.menuItemId = menuItemId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private RestaurantOrderLineItem() { + + } + + public RestaurantOrderLineItem(String menuItemId, String name, int quantity) { + this.menuItemId = menuItemId; + this.name = name; + this.quantity = quantity; + } } diff --git a/ftgo-restaurant-order-service-contracts/.mvn/wrapper/maven-wrapper.jar b/ftgo-restaurant-order-service-contracts/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5fd4d5023f1463b5ba3970e33c460c1eb26d748d GIT binary patch literal 49502 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%0A$pwhjXzyA0ZzErH{1@M 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 OJOEd-=rxTO#Qy`$*1Hh^ literal 0 HcmV?d00001 diff --git a/ftgo-restaurant-order-service-contracts/.mvn/wrapper/maven-wrapper.properties b/ftgo-restaurant-order-service-contracts/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..c3150437 --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip diff --git a/ftgo-restaurant-order-service-contracts/META-INF/MANIFEST.MF b/ftgo-restaurant-order-service-contracts/META-INF/MANIFEST.MF new file mode 100644 index 00000000..58630c02 --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/ftgo-restaurant-order-service-contracts/TODO.txt b/ftgo-restaurant-order-service-contracts/TODO.txt new file mode 100644 index 00000000..7cb0e8c2 --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/TODO.txt @@ -0,0 +1 @@ +This directory needs to be per-service \ No newline at end of file diff --git a/ftgo-restaurant-order-service-contracts/build.gradle b/ftgo-restaurant-order-service-contracts/build.gradle new file mode 100644 index 00000000..444c1eac --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/build.gradle @@ -0,0 +1,50 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath "io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE" + // if using Stub Runner (consumer side) only remove this dependency + classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.1.1.RELEASE" + } +} + +repositories { + mavenCentral() +} + +group = "net.chrisrichardson.ftgo.contracts" +version = "1.0-SNAPSHOT" +apply plugin: "io.spring.dependency-management" +apply plugin: 'java' +apply plugin: 'spring-cloud-contract' +apply plugin: 'maven' + +uploadArchives { + repositories { + mavenDeployer { + repository(url: deployUrl) + pom.withXml { + asNode().appendNode('classifier', 'stubs') + } + } + } +} + +dependencyManagement { + imports { + mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:1.1.4.RELEASE' + } +} + +dependencies { + // testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier' +} + +/* +// In this section you define all Spring Cloud Contract Verifier Gradle Plugin properties +contracts { + baseClassForTests = 'com.example.MvcTest' + // fully qualified name to a class that will be the base class for your generated test classes +} +*/ \ No newline at end of file diff --git a/ftgo-restaurant-order-service-contracts/gradle.properties b/ftgo-restaurant-order-service-contracts/gradle.properties new file mode 100644 index 00000000..9dc4d247 --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/gradle.properties @@ -0,0 +1,9 @@ + +org.gradle.jvmargs=-XX:MaxPermSize=512m + +deployUrl=file:///Users/cer/.m2/repository + +springBootVersion=1.5.4.RELEASE + +eventuateClientVersion=0.14.0.RELEASE +eventuateLocalVersion=0.9.0.RELEASE diff --git a/ftgo-restaurant-order-service-contracts/gradle/wrapper/gradle-wrapper.jar b/ftgo-restaurant-order-service-contracts/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..667288ad6c2b3b87c990ece1267e56f0bcbf3622 GIT binary patch literal 50514 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +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" ] ; 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"` + + # 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 + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/ftgo-restaurant-order-service-contracts/gradlew.bat b/ftgo-restaurant-order-service-contracts/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/gradlew.bat @@ -0,0 +1,90 @@ +@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 + +@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= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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 Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ftgo-restaurant-order-service-contracts/mvnw b/ftgo-restaurant-order-service-contracts/mvnw new file mode 100755 index 00000000..a1ba1bf5 --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/ftgo-restaurant-order-service-contracts/pom.xml b/ftgo-restaurant-order-service-contracts/pom.xml new file mode 100644 index 00000000..0e307737 --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/pom.xml @@ -0,0 +1,110 @@ + + + + 4.0.0 + + net.chrisrichardson.ftgo.contracts + ftgo-restaurant-order-service-contracts + 1.0-SNAPSHOT + + POM used to install locally stubs for consumer side + + + org.springframework.boot + spring-boot-starter-parent + 1.5.4.RELEASE + + + + + UTF-8 + 1.8 + 1.2.0.RELEASE + Dalston.SR1 + + true + + true + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud-dependencies.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + true + + + + org.springframework.cloud + spring-cloud-contract-maven-plugin + ${spring-cloud-contract.version} + true + + + ${project.basedir} + + + + + + + + spring + + true + + + + + spring-snapshots + Spring Snapshots + http://repo.spring.io/libs-snapshot-local + + true + + + + spring-milestones + Spring Milestones + http://repo.spring.io/libs-milestone-local + + false + + + + spring-plugin-snapshots + Spring Snapshots + http://repo.spring.io/plugins-snapshot-local + + true + + + + spring-plugin-milestones + Spring Milestones + http://repo.spring.io/plugins-release-local + + false + + + + + + + \ No newline at end of file diff --git a/ftgo-restaurant-order-service-contracts/src/main/resources/contracts/ConfirmCreateRestaurantOrder.groovy b/ftgo-restaurant-order-service-contracts/src/main/resources/contracts/ConfirmCreateRestaurantOrder.groovy new file mode 100644 index 00000000..70debc57 --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/src/main/resources/contracts/ConfirmCreateRestaurantOrder.groovy @@ -0,0 +1,22 @@ +package contracts; + +org.springframework.cloud.contract.spec.Contract.make { + label 'confirmCreateRestaurantOrder' + input { + messageFrom('restaurantOrderService') + messageBody('''{"restaurantOrderId":1}''') + messageHeaders { + header('command_type','net.chrisrichardson.ftgo.restaurantorderservice.api.ConfirmCreateRestaurantOrder') + header('command_saga_type','net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga') + header('command_saga_id',$(consumer(regex('[0-9a-f]{16}-[0-9a-f]{16}')))) + header('command_reply_to', 'net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga-reply') + } + } + outputMessage { + sentTo('net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga-reply') + headers { + header('reply_type', 'io.eventuate.tram.commands.common.Success') + header('reply_outcome-type', 'SUCCESS') + } + } +} \ No newline at end of file diff --git a/ftgo-restaurant-order-service-contracts/src/main/resources/contracts/CreateRestaurantOrder.groovy b/ftgo-restaurant-order-service-contracts/src/main/resources/contracts/CreateRestaurantOrder.groovy new file mode 100644 index 00000000..3c9cf41a --- /dev/null +++ b/ftgo-restaurant-order-service-contracts/src/main/resources/contracts/CreateRestaurantOrder.groovy @@ -0,0 +1,25 @@ +package contracts; + +org.springframework.cloud.contract.spec.Contract.make { + label 'createRestaurantOrder' + input { + messageFrom('restaurantOrderService') + messageBody('''{"orderId":99,"restaurantId":1,"restaurantOrderDetails":{"lineItems":[{"quantity":5,"menuItemId":"1","name":"Chicken Vindaloo"}]}}''') + messageHeaders { + header('command_type','net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrder') + header('command_saga_type','net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga') + header('command_saga_id',$(consumer(regex('[0-9a-f]{16}-[0-9a-f]{16}')))) + header('command_reply_to', 'net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga-reply') + } + } + outputMessage { + sentTo('net.chrisrichardson.ftgo.orderservice.sagas.createorder.CreateOrderSaga-reply') + body([ + restaurantOrderId: 99 + ]) + headers { + header('reply_type', 'net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrderReply') + header('reply_outcome-type', 'SUCCESS') + } + } +} \ No newline at end of file diff --git a/ftgo-restaurant-order-service/build.gradle b/ftgo-restaurant-order-service/build.gradle index 34a53cf4..b9fb50fc 100644 --- a/ftgo-restaurant-order-service/build.gradle +++ b/ftgo-restaurant-order-service/build.gradle @@ -1,5 +1,33 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath "io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE" + // if using Stub Runner (consumer side) only remove this dependency + classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.2.0.RELEASE" + } +} + +apply plugin: "io.spring.dependency-management" +apply plugin: 'spring-cloud-contract' + +dependencyManagement { + imports { + mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:1.2.0.RELEASE' + } +} + +dependencies { + testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier' +} + +contracts { + contractsDslDir = new File("../ftgo-restaurant-order-service-contracts/src/main/resources/contracts") + baseClassForTests = 'net.chrisrichardson.ftgo.restaurantorderservice.contract.AbstractRestaurantOrderConsumerContractTest' +} -apply plugin: "spring-boot" +apply plugin: 'org.springframework.boot' dependencies { @@ -10,6 +38,7 @@ dependencies { compile "io.eventuate.tram.sagas:eventuate-tram-sagas-simple-dsl:$eventuateTramSagasVersion" compile project(":common-swagger") + compile project(":ftgo-common-jpa") compile project(":ftgo-restaurant-order-service-api") compile project(":ftgo-restaurant-service-api") @@ -22,7 +51,8 @@ dependencies { testCompile "io.eventuate.tram.core:eventuate-tram-test-util:$eventuateTramVersion" testCompile "io.eventuate.tram.sagas:eventuate-tram-sagas-in-memory:$eventuateTramSagasVersion" - testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testCompile 'com.jayway.restassured:rest-assured:2.3.0' testCompile "com.jayway.jsonpath:json-path:2.3.0" + testCompile project(":eventuate-tram-spring-cloud-contract-support") } diff --git a/ftgo-restaurant-order-service/src/test/java/net/chrisrichardson/ftgo/restaurantorderservice/contract/AbstractRestaurantOrderConsumerContractTest.java b/ftgo-restaurant-order-service/src/test/java/net/chrisrichardson/ftgo/restaurantorderservice/contract/AbstractRestaurantOrderConsumerContractTest.java new file mode 100644 index 00000000..f10874ef --- /dev/null +++ b/ftgo-restaurant-order-service/src/test/java/net/chrisrichardson/ftgo/restaurantorderservice/contract/AbstractRestaurantOrderConsumerContractTest.java @@ -0,0 +1,52 @@ +package net.chrisrichardson.ftgo.restaurantorderservice.contract; + +import io.eventuate.tram.springcloudcontractsupport.EventuateContractVerifierConfiguration; +import net.chrisrichardson.ftgo.restaurantorderservice.api.RestaurantOrderDetails; +import net.chrisrichardson.ftgo.restaurantorderservice.domain.RestaurantOrder; +import net.chrisrichardson.ftgo.restaurantorderservice.domain.RestaurantOrderService; +import net.chrisrichardson.ftgo.restaurantorderservice.messagehandlers.RestaurantMessageHandlersConfiguration; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Collections; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = AbstractRestaurantOrderConsumerContractTest.TestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) +@AutoConfigureMessageVerifier +public abstract class AbstractRestaurantOrderConsumerContractTest { + + @Configuration + @Import({RestaurantMessageHandlersConfiguration.class, EventuateContractVerifierConfiguration.class}) + public static class TestConfiguration { + + @Bean + public RestaurantOrderService restaurantOrderService() { + return mock(RestaurantOrderService.class); + } + + } + + @Autowired + private RestaurantOrderService restaurantOrderService; + + @Before + public void setup() { + reset(restaurantOrderService); + when(restaurantOrderService.createRestaurantOrder(eq(1L), eq(99L), any(RestaurantOrderDetails.class))) + .thenReturn(new RestaurantOrder(1L, 99L, new RestaurantOrderDetails(Collections.emptyList()))); + } + +} diff --git a/ftgo-restaurant-order-service/src/test/java/restaurantorderservice/RestaurantOrderServiceInMemoryIntegrationTest.java b/ftgo-restaurant-order-service/src/test/java/net/chrisrichardson/ftgo/restaurantorderservice/domain/RestaurantOrderServiceInMemoryIntegrationTest.java similarity index 95% rename from ftgo-restaurant-order-service/src/test/java/restaurantorderservice/RestaurantOrderServiceInMemoryIntegrationTest.java rename to ftgo-restaurant-order-service/src/test/java/net/chrisrichardson/ftgo/restaurantorderservice/domain/RestaurantOrderServiceInMemoryIntegrationTest.java index 4bca8eae..ca07a839 100644 --- a/ftgo-restaurant-order-service/src/test/java/restaurantorderservice/RestaurantOrderServiceInMemoryIntegrationTest.java +++ b/ftgo-restaurant-order-service/src/test/java/net/chrisrichardson/ftgo/restaurantorderservice/domain/RestaurantOrderServiceInMemoryIntegrationTest.java @@ -1,4 +1,4 @@ -package restaurantorderservice; +package net.chrisrichardson.ftgo.restaurantorderservice.domain; import io.eventuate.tram.commands.common.ChannelMapping; @@ -12,8 +12,6 @@ import net.chrisrichardson.ftgo.common.Money; import net.chrisrichardson.ftgo.restaurantorderservice.api.CreateRestaurantOrder; import net.chrisrichardson.ftgo.restaurantorderservice.api.RestaurantOrderDetails; -import net.chrisrichardson.ftgo.restaurantorderservice.domain.Restaurant; -import net.chrisrichardson.ftgo.restaurantorderservice.domain.RestaurantRepository; import net.chrisrichardson.ftgo.restaurantorderservice.messagehandlers.RestaurantMessageHandlersConfiguration; import net.chrisrichardson.ftgo.restaurantorderservice.web.RestaurantOrderWebConfiguration; import org.junit.Test; diff --git a/ftgo-restaurant-order-service/src/test/resources/application.properties b/ftgo-restaurant-order-service/src/test/resources/application.properties new file mode 100644 index 00000000..d0030836 --- /dev/null +++ b/ftgo-restaurant-order-service/src/test/resources/application.properties @@ -0,0 +1,6 @@ +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.springframework.cloud.contract=DEBUG +logging.level.io.eventuate=DEBUG +spring.jpa.generate-ddl=true +stubrunner.stream.enabled=false +stubrunner.integration.enabled=false \ No newline at end of file diff --git a/ftgo-restaurant-service-api/build.gradle b/ftgo-restaurant-service-api/build.gradle index 00fce5de..d3d46579 100644 --- a/ftgo-restaurant-service-api/build.gradle +++ b/ftgo-restaurant-service-api/build.gradle @@ -19,7 +19,7 @@ dependencies { testCompile "io.eventuate.tram.core:eventuate-tram-test-util:$eventuateTramVersion" testCompile "io.eventuate.tram.sagas:eventuate-tram-sagas-in-memory:$eventuateTramSagasVersion" - testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testCompile 'com.jayway.restassured:rest-assured:2.3.0' testCompile "com.jayway.jsonpath:json-path:2.3.0" diff --git a/ftgo-restaurant-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantservice/events/MenuItem.java b/ftgo-restaurant-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantservice/events/MenuItem.java index 9cca7aa0..cf6c3e7d 100644 --- a/ftgo-restaurant-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantservice/events/MenuItem.java +++ b/ftgo-restaurant-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantservice/events/MenuItem.java @@ -1,6 +1,9 @@ package net.chrisrichardson.ftgo.restaurantservice.events; import net.chrisrichardson.ftgo.common.Money; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; import javax.persistence.Access; import javax.persistence.AccessType; @@ -23,6 +26,21 @@ public MenuItem(String id, String name, Money price) { this.price = price; } + @Override + public boolean equals(Object o) { + return EqualsBuilder.reflectionEquals(this, o); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + public String getId() { return id; } diff --git a/ftgo-restaurant-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantservice/events/RestaurantMenu.java b/ftgo-restaurant-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantservice/events/RestaurantMenu.java index 6e184551..e6adbd2c 100644 --- a/ftgo-restaurant-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantservice/events/RestaurantMenu.java +++ b/ftgo-restaurant-service-api/src/main/java/net/chrisrichardson/ftgo/restaurantservice/events/RestaurantMenu.java @@ -1,5 +1,9 @@ package net.chrisrichardson.ftgo.restaurantservice.events; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; + import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.ElementCollection; @@ -17,10 +21,25 @@ public class RestaurantMenu { private RestaurantMenu() { } + @Override + public boolean equals(Object o) { + return EqualsBuilder.reflectionEquals(this, o); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + public List getMenuItems() { return menuItems; } + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + public void setMenuItems(List menuItems) { this.menuItems = menuItems; } diff --git a/ftgo-restaurant-service/build.gradle b/ftgo-restaurant-service/build.gradle index 230901db..02b82f15 100644 --- a/ftgo-restaurant-service/build.gradle +++ b/ftgo-restaurant-service/build.gradle @@ -1,5 +1,5 @@ -apply plugin: "spring-boot" +apply plugin: 'org.springframework.boot' dependencies { @@ -11,6 +11,7 @@ dependencies { compile project(":common-swagger") compile project(":ftgo-restaurant-service-api") + compile project(":ftgo-common-jpa") compile "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion" @@ -22,8 +23,9 @@ dependencies { testCompile "io.eventuate.tram.core:eventuate-tram-test-util:$eventuateTramVersion" testCompile "io.eventuate.tram.sagas:eventuate-tram-sagas-in-memory:$eventuateTramSagasVersion" - testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testCompile 'com.jayway.restassured:rest-assured:2.3.0' testCompile "com.jayway.jsonpath:json-path:2.3.0" + testCompile project(":eventuate-tram-spring-cloud-contract-support") } diff --git a/gradle.properties b/gradle.properties index c01b4d17..bd69f2f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,12 +4,16 @@ deployUrl=file:///Users/cer/.m2/testdeploy eventuateMavenRepoUrl=https://dl.bintray.com/eventuateio-oss/eventuate-maven-release,file:///Users/cer/.m2/testdeploy springBootVersion=1.4.5.RELEASE +springBootVersion2=2.0.0.M3 + # springBootVersion = '1.5.6.RELEASE' eventuateClientVersion=0.20.1.RELEASE eventuateLocalVersion=0.16.0.RELEASE -eventuateTramVersion=0.4.0.RELEASE -eventuateTramSagasVersion=0.2.0.RELEASE +eventuateTramVersion=0.5.0.RELEASE +eventuateTramSagasVersion=0.4.0.RELEASE eventuateUtilVersion=0.1.0.RELEASE +#dockerComposePluginVersion=0.4.5 +dockerComposePluginVersion=0.6.6 diff --git a/mysql-cli.sh b/mysql-cli.sh index 3fbe0538..2b38dbe3 100755 --- a/mysql-cli.sh +++ b/mysql-cli.sh @@ -1,6 +1,6 @@ #! /bin/bash -e docker run $* \ - --network ftgoapplication_default \ + --network $(echo ${PWD##*/} | sed -e 's/-//g')_default \ --name mysqlterm --rm mysql:5.7.13 \ sh -c 'exec mysql -hmysql -P3306 -uroot -prootpassword eventuate' diff --git a/settings.gradle b/settings.gradle index ead4c5b5..12bc4d24 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,12 @@ include "common-swagger" include "ftgo-common" +include "ftgo-common-jpa" + include "ftgo-order-service" include "ftgo-order-service-api" +include "ftgo-order-service-contracts" + include "ftgo-restaurant-order-service-api" include "ftgo-restaurant-order-service" include "ftgo-accounting-service-api" @@ -14,4 +18,5 @@ include "ftgo-restaurant-service" include "ftgo-order-history-service" include "ftgo-api-gateway" include "ftgo-end-to-end-tests" +include "eventuate-tram-spring-cloud-contract-support" diff --git a/start-infrastructure-services.sh b/start-infrastructure-services.sh new file mode 100755 index 00000000..140634e2 --- /dev/null +++ b/start-infrastructure-services.sh @@ -0,0 +1,3 @@ +#!/bin/bash -e + +docker-compose up -d --build $* mysql tram-cdc-service eventuate-local-cdc-service