From 99e512596a0411647e83bf1ec54262a663f5a38f Mon Sep 17 00:00:00 2001 From: Mali Bayhan Date: Fri, 10 Dec 2021 09:48:02 -0800 Subject: [PATCH] master- Sync Repo --- README.md | 1 + jenkins/cd/Jenkinsfile | 44 ++++++++ jenkins/without-cd/Jenkinsfile-build | 27 +++++ jenkins/without-cd/Jenkinsfile-deploy | 38 +++++++ lombok.config | 2 + pom.xml | 88 +++++++++++++++ .../kreuzwerker/cdc/messagingapp/Friend.java | 11 ++ .../messagingapp/MessagingApplication.java | 15 +++ .../de/kreuzwerker/cdc/messagingapp/User.java | 17 +++ .../cdc/messagingapp/UserServiceClient.java | 24 ++++ src/main/resources/application.yml | 2 + .../MessagingApplicationTests.java | 13 +++ .../messagingapp/UserServiceContractTest.java | 103 ++++++++++++++++++ .../UserServiceContractTestV1.java | 50 +++++++++ ...GenericStateWithParameterContractTest.java | 100 +++++++++++++++++ 15 files changed, 535 insertions(+) create mode 100644 jenkins/cd/Jenkinsfile create mode 100644 jenkins/without-cd/Jenkinsfile-build create mode 100644 jenkins/without-cd/Jenkinsfile-deploy create mode 100644 lombok.config create mode 100644 pom.xml create mode 100644 src/main/java/de/kreuzwerker/cdc/messagingapp/Friend.java create mode 100644 src/main/java/de/kreuzwerker/cdc/messagingapp/MessagingApplication.java create mode 100644 src/main/java/de/kreuzwerker/cdc/messagingapp/User.java create mode 100644 src/main/java/de/kreuzwerker/cdc/messagingapp/UserServiceClient.java create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/de/kreuzwerker/cdc/messagingapp/MessagingApplicationTests.java create mode 100644 src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTest.java create mode 100644 src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTestV1.java create mode 100644 src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceGenericStateWithParameterContractTest.java diff --git a/README.md b/README.md index e69de29..162ad6a 100644 --- a/README.md +++ b/README.md @@ -0,0 +1 @@ +pact-messaging-app diff --git a/jenkins/cd/Jenkinsfile b/jenkins/cd/Jenkinsfile new file mode 100644 index 0000000..af03390 --- /dev/null +++ b/jenkins/cd/Jenkinsfile @@ -0,0 +1,44 @@ +#!groovy +pipeline { + + agent any + + environment { + BRANCH_NAME=env.GIT_BRANCH.replace("origin/", "") + } + + stages { + stage('Build') { + steps { + dir('messaging-app') { + sh '../mvnw clean verify' + } + } + } + stage('Publish Pacts') { + steps { + dir('messaging-app') { + sh '../mvnw pact:publish -Dpact.consumer.version=${GIT_COMMIT} -Dpact.tag=${BRANCH_NAME}' + } + } + } + stage('Check Pact Verifications') { + steps { + sh 'curl -LO https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v1.88.3/pact-1.88.3-linux-x86_64.tar.gz' + sh 'tar xzf pact-1.88.3-linux-x86_64.tar.gz' + dir('pact/bin') { + sh "./pact-broker can-i-deploy --retry-while-unknown=12 --retry-interval=10 -a messaging-app -b http://pact_broker -e ${GIT_COMMIT}" + } + } + } + stage('Deploy') { + when { + branch 'junit5' + } + steps { + echo 'Deploying to prod now...' + } + } + } + +} \ No newline at end of file diff --git a/jenkins/without-cd/Jenkinsfile-build b/jenkins/without-cd/Jenkinsfile-build new file mode 100644 index 0000000..6a97182 --- /dev/null +++ b/jenkins/without-cd/Jenkinsfile-build @@ -0,0 +1,27 @@ +#!groovy +pipeline { + + agent any + + environment { + BRANCH_NAME=env.GIT_BRANCH.replace("origin/", "") + } + + stages { + stage('Build') { + steps { + dir('messaging-app') { + sh '../mvnw clean verify' + } + } + } + stage('Publish Pacts') { + steps { + dir('messaging-app') { + sh '../mvnw pact:publish -Dpact.consumer.version=${GIT_COMMIT} -Dpact.tag=${BRANCH_NAME}' + } + } + } + } + +} \ No newline at end of file diff --git a/jenkins/without-cd/Jenkinsfile-deploy b/jenkins/without-cd/Jenkinsfile-deploy new file mode 100644 index 0000000..c1ed3ca --- /dev/null +++ b/jenkins/without-cd/Jenkinsfile-deploy @@ -0,0 +1,38 @@ +#!groovy +pipeline { + + agent any + + parameters { + string(name: 'GIT_COMMIT', defaultValue: '', description: 'Version (a.k.a. git commit) to deploy') + } + + options { + skipDefaultCheckout() + } + + stages { + stage('Check Pact Verifications') { + steps { + sh 'curl -LO https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v1.88.3/pact-1.88.3-linux-x86_64.tar.gz' + sh 'tar xzf pact-1.88.3-linux-x86_64.tar.gz' + dir('pact/bin') { + sh "./pact-broker can-i-deploy -a messaging-app -b http://pact_broker -e ${GIT_COMMIT} --to prod" + } + } + } + stage('Deploy') { + steps { + echo 'Deploying to prod now...' + } + } + stage('Tag Pact') { + steps { + dir('pact/bin') { + sh "./pact-broker create-version-tag -a messaging-app -b http://pact_broker -e ${GIT_COMMIT} -t prod" + } + } + } + } + +} \ No newline at end of file diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..15d5dd7 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.noArgsConstructor.extraPrivate = true \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0525d98 --- /dev/null +++ b/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + de.kreuzwerker.cdc + messaging-app + 1.0.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.4.RELEASE + + + + UTF-8 + UTF-8 + 1.8 + 4.0.7 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + + au.com.dius + pact-jvm-consumer-java8 + ${pact.version} + test + + + au.com.dius + pact-jvm-consumer-junit5 + ${pact.version} + test + + + + + + + au.com.dius + pact-jvm-provider-maven + ${pact.version} + + + http://pact_broker + ${pact.consumer.version} + + ${pact.tag} + + + + + + + + diff --git a/src/main/java/de/kreuzwerker/cdc/messagingapp/Friend.java b/src/main/java/de/kreuzwerker/cdc/messagingapp/Friend.java new file mode 100644 index 0000000..0e76c8e --- /dev/null +++ b/src/main/java/de/kreuzwerker/cdc/messagingapp/Friend.java @@ -0,0 +1,11 @@ +package de.kreuzwerker.cdc.messagingapp; + +import lombok.Data; + +@Data +public class Friend { + + private String id; + private String name; + +} diff --git a/src/main/java/de/kreuzwerker/cdc/messagingapp/MessagingApplication.java b/src/main/java/de/kreuzwerker/cdc/messagingapp/MessagingApplication.java new file mode 100644 index 0000000..9b6f654 --- /dev/null +++ b/src/main/java/de/kreuzwerker/cdc/messagingapp/MessagingApplication.java @@ -0,0 +1,15 @@ +package de.kreuzwerker.cdc.messagingapp; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@Slf4j +public class MessagingApplication { + + public static void main(String[] args) { + SpringApplication.run(MessagingApplication.class, args); + } + +} diff --git a/src/main/java/de/kreuzwerker/cdc/messagingapp/User.java b/src/main/java/de/kreuzwerker/cdc/messagingapp/User.java new file mode 100644 index 0000000..592a28d --- /dev/null +++ b/src/main/java/de/kreuzwerker/cdc/messagingapp/User.java @@ -0,0 +1,17 @@ +package de.kreuzwerker.cdc.messagingapp; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class User { + + private String name; + private String role; + private LocalDateTime lastLogin; + private List friends; + + +} diff --git a/src/main/java/de/kreuzwerker/cdc/messagingapp/UserServiceClient.java b/src/main/java/de/kreuzwerker/cdc/messagingapp/UserServiceClient.java new file mode 100644 index 0000000..df7b67c --- /dev/null +++ b/src/main/java/de/kreuzwerker/cdc/messagingapp/UserServiceClient.java @@ -0,0 +1,24 @@ +package de.kreuzwerker.cdc.messagingapp; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.web.client.RestTemplate; + +@Component +public class UserServiceClient { + + private final RestTemplate restTemplate; + + public UserServiceClient(@Value("${user-service.base-url}") String baseUrl) { + this.restTemplate = new RestTemplateBuilder().rootUri(baseUrl).build(); + } + + public User getUser(String id) { + final User user = restTemplate.getForObject("/users/" + id, User.class); + Assert.hasText(user.getName(), "Name is blank."); + return user; + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..4d30c5d --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,2 @@ +user-service: + base-url: "http://localhost:8090" \ No newline at end of file diff --git a/src/test/java/de/kreuzwerker/cdc/messagingapp/MessagingApplicationTests.java b/src/test/java/de/kreuzwerker/cdc/messagingapp/MessagingApplicationTests.java new file mode 100644 index 0000000..ef7bbcc --- /dev/null +++ b/src/test/java/de/kreuzwerker/cdc/messagingapp/MessagingApplicationTests.java @@ -0,0 +1,13 @@ +package de.kreuzwerker.cdc.messagingapp; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class MessagingApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTest.java b/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTest.java new file mode 100644 index 0000000..d60a9f2 --- /dev/null +++ b/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTest.java @@ -0,0 +1,103 @@ +package de.kreuzwerker.cdc.messagingapp; + +import au.com.dius.pact.consumer.dsl.DslPart; +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.consumer.junit5.PactTestFor; +import au.com.dius.pact.core.model.RequestResponsePact; +import au.com.dius.pact.core.model.annotations.Pact; +import io.pactfoundation.consumer.dsl.LambdaDsl; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.client.HttpClientErrorException; + +import java.sql.Date; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = "user-service.base-url:http://localhost:8080", + classes = UserServiceClient.class) +@Disabled +@ExtendWith(PactConsumerTestExt.class) +@PactTestFor(providerName = "user-service", port = "8080") +public class UserServiceContractTest { + + private static final String NAME = "user name for CDC"; + private static final LocalDateTime LAST_LOGIN = LocalDateTime.of(2018, 10, 16, 12, 34, 12); + + @Autowired + private UserServiceClient userServiceClient; + + @Pact(consumer = "messaging-app") + public RequestResponsePact pactUserExists(PactDslWithProvider builder) { + + // See https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-consumer-junit#dsl-matching-methods + DslPart body = LambdaDsl.newJsonBody((o) -> o + .stringType("name", NAME) + .timestamp("lastLogin", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + Date.from(LAST_LOGIN.atZone(ZoneId.systemDefault()).toInstant())) + .stringMatcher("role", "ADMIN|USER", "ADMIN") + .minArrayLike("friends", 0, 2, friend -> friend + .stringType("id", "2") + .stringType("name", "a friend") + )).build(); + + return builder.given( + "User 1 exists") + .uponReceiving("A request to /users/1") + .path("/users/1") + .method("GET") + .willRespondWith() + .status(200) + .body(body) + .toPact(); + + } + + @Pact(consumer = "messaging-app") + public RequestResponsePact pactUserDoesNotExist(PactDslWithProvider builder) { + + return builder.given( + "User 2 does not exist") + .uponReceiving("A request to /users/2") + .path("/users/2") + .method("GET") + .willRespondWith() + .status(404) + .toPact(); + } + + @PactTestFor(pactMethod = "pactUserExists") + @Test + public void userExists() { + final User user = userServiceClient.getUser("1"); + + assertThat(user.getName()).isEqualTo(NAME); + assertThat(user.getLastLogin()).isEqualTo(LAST_LOGIN); + assertThat(user.getRole()).isEqualTo("ADMIN"); + assertThat(user.getFriends()).hasSize(2) + // currently not possible to define multiple values, s. https://github.com/DiUS/pact-jvm/issues/379 + .extracting(Friend::getId, Friend::getName) + .containsExactly(Tuple.tuple("2", "a friend"), Tuple.tuple("2", "a friend")); + } + + @PactTestFor(pactMethod = "pactUserDoesNotExist") + @Test + public void userDoesNotExist() { + HttpClientErrorException exception = assertThrows(HttpClientErrorException.class, () -> + userServiceClient.getUser("2")); + assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode()); + } +} \ No newline at end of file diff --git a/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTestV1.java b/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTestV1.java new file mode 100644 index 0000000..39b6443 --- /dev/null +++ b/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTestV1.java @@ -0,0 +1,50 @@ +package de.kreuzwerker.cdc.messagingapp; + +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.consumer.junit5.PactTestFor; +import au.com.dius.pact.core.model.RequestResponsePact; +import au.com.dius.pact.core.model.annotations.Pact; +import io.pactfoundation.consumer.dsl.LambdaDsl; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = "user-service.base-url:http://localhost:8080", + classes = UserServiceClient.class) +@Disabled +@ExtendWith(PactConsumerTestExt.class) +@PactTestFor(providerName = "user-service", port = "8080") +public class UserServiceContractTestV1 { + + @Autowired + private UserServiceClient userServiceClient; + + + @Pact(consumer = "messaging-app") + public RequestResponsePact pactUserExists(PactDslWithProvider builder) { + return builder.given( + "User 1 exists") + .uponReceiving("A request to /users/1") + .path("/users/1") + .method("GET") + .willRespondWith() + .status(200) + .body(LambdaDsl.newJsonBody((o) -> + o.stringType("name", "user name for CDC") + ).build()).toPact(); + } + + @PactTestFor(pactMethod = "pactUserExists") + @Test + public void userExists() { + User user = userServiceClient.getUser("1"); + + assertThat(user.getName()).isEqualTo("user name for CDC"); + } +} \ No newline at end of file diff --git a/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceGenericStateWithParameterContractTest.java b/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceGenericStateWithParameterContractTest.java new file mode 100644 index 0000000..985bee1 --- /dev/null +++ b/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceGenericStateWithParameterContractTest.java @@ -0,0 +1,100 @@ +package de.kreuzwerker.cdc.messagingapp; + +import au.com.dius.pact.consumer.dsl.DslPart; +import au.com.dius.pact.consumer.dsl.PactDslWithProvider; +import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; +import au.com.dius.pact.consumer.junit5.PactTestFor; +import au.com.dius.pact.core.model.RequestResponsePact; +import au.com.dius.pact.core.model.annotations.Pact; +import io.pactfoundation.consumer.dsl.LambdaDsl; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.web.client.HttpClientErrorException; + +import java.sql.Date; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = "user-service.base-url:http://localhost:9000", + classes = UserServiceClient.class) +@ExtendWith(PactConsumerTestExt.class) +@PactTestFor(providerName = "user-service", port = "9000") +public class UserServiceGenericStateWithParameterContractTest { + + private static final String NAME = "user name for CDC"; + private static final LocalDateTime LAST_LOGIN = LocalDateTime.of(2018, 10, 16, 12, 34, 12); + + @Autowired + private UserServiceClient userServiceClient; + + + @Pact(consumer = "messaging-app") + public RequestResponsePact pactUserExists(PactDslWithProvider builder) { + + // See https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-consumer-junit#dsl-matching-methods + DslPart body = LambdaDsl.newJsonBody((o) -> o + .stringType("id", "1") + .stringType("name", NAME) + .timestamp("lastLogin", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + Date.from(LAST_LOGIN.atZone(ZoneId.systemDefault()).toInstant())) + .stringMatcher("role", "ADMIN|USER", "ADMIN") + .minArrayLike("friends", 0, 2, friend -> friend + .stringType("id", "2") + .stringType("name", "a friend") + )).build(); + + return builder.given("default", Collections.singletonMap("userExists", true)) + .uponReceiving("A request for an existing user") + .path("/users/1") + .method("GET") + .willRespondWith() + .status(200) + .body(body) + .toPact(); + + } + + @Pact(consumer = "messaging-app") + public RequestResponsePact pactUserDoesNotExist(PactDslWithProvider builder) { + + return builder.given("default", Collections.singletonMap("userExists", false)) + .uponReceiving("A request for a non-existing user") + .path("/users/1") + .method("GET") + .willRespondWith() + .status(404) + .toPact(); + } + + @PactTestFor(pactMethod = "pactUserExists") + @Test + public void userExists() { + final User user = userServiceClient.getUser("1"); + + assertThat(user.getName()).isEqualTo(NAME); + assertThat(user.getLastLogin()).isEqualTo(LAST_LOGIN); + assertThat(user.getRole()).isEqualTo("ADMIN"); + assertThat(user.getFriends()).hasSize(2) + // currently not possible to define multiple values, s. https://github.com/DiUS/pact-jvm/issues/379 + .extracting(Friend::getId, Friend::getName) + .containsExactly(Tuple.tuple("2", "a friend"), Tuple.tuple("2", "a friend")); + } + + @PactTestFor(pactMethod = "pactUserDoesNotExist") + @Test + public void userDoesNotExist() { + HttpClientErrorException exception = assertThrows(HttpClientErrorException.class, () -> + userServiceClient.getUser("1")); + assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode()); + } +} \ No newline at end of file