From 7a8fedb4791bb8809fe8f91f4d4f3bb110556d1a Mon Sep 17 00:00:00 2001 From: bfisher Date: Wed, 15 Apr 2020 09:43:42 +0100 Subject: [PATCH 1/3] Added zookeeper and kafka to docker-compose files, changed docker-compose gradle information to use -functional.yml --- README.md | 25 +++++++++++++++++++++++++ docker-compose-functional.yml | 23 +++++++++++++++++++++++ docker-compose.yml | 19 +++++++++++++------ gradle/docker-compose.gradle | 3 +-- 4 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 docker-compose-functional.yml diff --git a/README.md b/README.md index c86bc11..0963a81 100644 --- a/README.md +++ b/README.md @@ -73,3 +73,28 @@ The following dependencies are used within this project, # Stopping the docker container (projectRoot)$ docker-compose down ``` + +# Apache Kafka Integration + +``` +version: '2' +services: + zookeeper: + image: wurstmeister/zookeeper + ports: + - "2181:2181" + kafka: + build: . + ports: + - "9092" + environment: + KAFKA_ADVERTISED_HOST_NAME: 192.168.99.100 (DockerIP) + OR + HOSTNAME_COMMAND: "route -n | awk '/UG[ \t]/{print $$2}'" + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + volumes: + - /var/run/docker.sock:/var/run/docker.sock +``` +## Apache Zookeeper + +https://zookeeper.apache.org/ \ No newline at end of file diff --git a/docker-compose-functional.yml b/docker-compose-functional.yml new file mode 100644 index 0000000..536b428 --- /dev/null +++ b/docker-compose-functional.yml @@ -0,0 +1,23 @@ +version: "3.7" +services: + component: + image: docker.pkg.github.com/fishey2/java-component-template/component:0.1-SNAPSHOT + build: + context: . + dockerfile: docker/component/Dockerfile + ports: + - 8080:8080 + zookeeper: + image: wurstmeister/zookeeper + ports: + - "2181:2181" + kafka: + image: bitnami/kafka + ports: + - "9092" + environment: + HOSTNAME_COMMAND: "route -n | awk '/UG[ \t]/{print $$2}'" + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + ALLOW_PLAINTEXT_LISTENER: "yes" + volumes: + - /var/run/docker.sock:/var/run/docker.sock \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 2cbb33a..020e615 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,16 @@ version: "3.7" services: - component: - image: docker.pkg.github.com/fishey2/java-component-template/component:0.1-SNAPSHOT - build: - context: . - dockerfile: docker/component/Dockerfile + zookeeper: + image: wurstmeister/zookeeper ports: - - 8080:8080 \ No newline at end of file + - "2181:2181" + kafka: + image: bitnami/kafka + ports: + - "9092" + environment: + HOSTNAME_COMMAND: "route -n | awk '/UG[ \t]/{print $$2}'" + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + ALLOW_PLAINTEXT_LISTENER: "yes" + volumes: + - /var/run/docker.sock:/var/run/docker.sock \ No newline at end of file diff --git a/gradle/docker-compose.gradle b/gradle/docker-compose.gradle index 7b21101..549740b 100644 --- a/gradle/docker-compose.gradle +++ b/gradle/docker-compose.gradle @@ -1,7 +1,6 @@ dockerCompose { - functional { - useComposeFiles = ['docker-compose.yml'] + useComposeFiles = ['docker-compose-functional.yml'] startedServices = ['component'] buildBeforeUp = true From 5e6a546806d4629c3affd29dd68fa7b585b330ba Mon Sep 17 00:00:00 2001 From: bfisher Date: Wed, 15 Apr 2020 13:04:26 +0100 Subject: [PATCH 2/3] Externalised configuration for kafka, updated spring boot dependencies --- README.md | 41 +++++++++++++++++++++++++++++- build.gradle | 15 ++++++++--- gradle/docker-compose.gradle | 2 -- src/main/resources/application.yml | 9 +++++++ 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/application.yml diff --git a/README.md b/README.md index 0963a81..d799084 100644 --- a/README.md +++ b/README.md @@ -97,4 +97,43 @@ services: ``` ## Apache Zookeeper -https://zookeeper.apache.org/ \ No newline at end of file +https://zookeeper.apache.org/ + +Spring + Kafka: https://docs.spring.io/spring-kafka/reference/html/ + +### Docker Image (Kafka/Zookeeper) + +Finding the executables for kafka: + +```bash +$ docker ps + +$ docker exec -it /bin/bash + +I have no name!@$ cd opt/bitnami/kafka/bin +``` + +Creating new topic: +```bash +I have no name!@$ kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test + +I have no name!@$ kafka-topics.sh --list --bootstrap-server localhost:9092 +test +``` + +Producing messages on topic: +```bash +I have no name!@$ kafka-console-producer.sh --broker-list localhost:9092 --topic test +> Hello! +> Hello Again! + +# Consuming messages +I have no name!@$ kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning +Hello! +Hello Again! + +Processed a total of 2 messages +``` + +Configuring Spring Boot with gradle https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/gradle-plugin/reference/html/ + diff --git a/build.gradle b/build.gradle index f0ae78f..5175f0a 100644 --- a/build.gradle +++ b/build.gradle @@ -2,12 +2,15 @@ plugins { id 'java' id 'idea' id 'jacoco' - id 'org.springframework.boot' version '2.0.5.RELEASE' + id 'org.springframework.boot' version '2.2.6.RELEASE' id 'io.spring.dependency-management' version '1.0.7.RELEASE' id "com.avast.gradle.docker-compose" version "0.10.7" id "org.sonarqube" version "2.8" } +apply plugin: 'java' +apply plugin: 'io.spring.dependency-management' + group 'com.roboautomator' version '1.0-SNAPSHOT' @@ -18,14 +21,17 @@ repositories { } dependencies { - implementation('org.springframework.boot:spring-boot-dependencies:2.2.2.RELEASE') - implementation('org.springframework.boot:spring-boot-starter-web:2.0.5.RELEASE') +// implementation('org.springframework.boot:spring-boot-dependencies') + implementation('org.springframework.boot:spring-boot-starter-web') + implementation('org.springframework.kafka:spring-kafka') implementation('ch.qos.logback:logback-classic:1.2.3') implementation('io.springfox:springfox-swagger2:2.9.2') implementation('io.springfox:springfox-swagger-ui:2.9.2') + implementation('org.yaml:snakeyaml:1.26') testImplementation('org.junit.jupiter:junit-jupiter:5.5.2') - testImplementation('org.springframework.boot:spring-boot-starter-test:2.0.5.RELEASE') + testImplementation('org.springframework.boot:spring-boot-starter-test') + testImplementation('org.springframework.kafka:spring-kafka-test') testImplementation('org.mockito:mockito-core:2.22.0') testImplementation('io.rest-assured:rest-assured:4.1.2') testImplementation('io.rest-assured:json-path:4.1.2') @@ -46,6 +52,7 @@ bootJar { configurations.all { exclude group:'junit', module:'junit' + exclude group: 'org.junit.vintage', module:'junit-vintage-engine' exclude group:'org.mockito', module:'mockito-all' exclude group:'org.slf4j', module:'slf4j-log4j12' exclude group: 'org.springframework.boot', module:'spring-boot-starter-logging' diff --git a/gradle/docker-compose.gradle b/gradle/docker-compose.gradle index 549740b..6e2fd39 100644 --- a/gradle/docker-compose.gradle +++ b/gradle/docker-compose.gradle @@ -11,5 +11,3 @@ dockerCompose { functionalComposeBuild.dependsOn(bootJar) functionalComposeUp.dependsOn(bootJar) functionalTest.dependsOn(functionalComposeUp) - -//test.dependsOn(composeUp) \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..264e2ca --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,9 @@ +server: + port: 9000 + +spring: + kafka: + consumer: + group-id: tpd-loggers + auto-offset-reset: earliest + bootstrap-servers: 192.168.176.3:9092 \ No newline at end of file From c52aad6b9fb6f190fbe06237624a468ca232b362 Mon Sep 17 00:00:00 2001 From: bfisher Date: Sun, 20 Sep 2020 15:32:38 +0100 Subject: [PATCH 3/3] [#16] Adds kafka instead of ActiveMQ --- Makefile | 4 +- build.gradle | 5 +- docker-compose-functional.yml | 23 ---- docker-compose-test.yml | 24 +++- docker-compose.yml | 31 +++++- scripts/wait-for-url.sh | 2 +- .../health/HealthActuatorFuncTest.java | 11 -- .../component/QueueConsumer.java | 27 ----- .../component/QueueProducer.java | 11 -- .../config/queue/ActiveMQConfig.java | 78 ------------- .../component/config/queue/QueueConfig.java | 34 ------ .../component/message/ActiveMQConsumer.java | 74 ------------- .../component/message/ActiveMQProducer.java | 47 -------- .../component/message/KafkaConsumer.java | 45 ++++++++ .../component/message/KafkaProducer.java | 32 ++++++ .../component/message/MessageController.java | 11 +- src/main/resources/application.yml | 50 ++------- .../component/MainApplicationTestIT.java | 2 + .../config/logging/LoggingConfigTest.java | 8 +- .../config/queue/ActiveMQConfigTestIT.java | 70 ------------ .../config/swagger/SwaggerConfigTest.java | 8 +- .../swagger/SwaggerControllerTestIT.java | 14 +-- .../message/ActiveMQConsumerTest.java | 104 ------------------ .../message/ActiveMQProducerTest.java | 59 ---------- .../component/message/KafkaConsumerTest.java | 79 +++++++++++++ .../component/message/KafkaProducerTest.java | 66 +++++++++++ .../message/MessageControllerTest.java | 96 +++------------- .../message/MessageControllerTestIT.java | 14 ++- .../message/MessageRepositoryTestIT.java | 2 + .../CorrelationMiddlewareTestIT.java | 2 + .../middleware/LoggingMiddlewareTestIT.java | 2 + .../patient/PatientControllerTestIT.java | 2 + .../patient/PatientRepositoryTestIT.java | 2 + .../component/util/AbstractMockMvcTest.java | 4 +- src/test/resources/application-test.yml | 39 +++++++ src/test/resources/application.yml | 67 ----------- 36 files changed, 381 insertions(+), 768 deletions(-) delete mode 100644 docker-compose-functional.yml delete mode 100644 src/main/java/com/roboautomator/component/QueueConsumer.java delete mode 100644 src/main/java/com/roboautomator/component/QueueProducer.java delete mode 100644 src/main/java/com/roboautomator/component/config/queue/ActiveMQConfig.java delete mode 100644 src/main/java/com/roboautomator/component/config/queue/QueueConfig.java delete mode 100644 src/main/java/com/roboautomator/component/message/ActiveMQConsumer.java delete mode 100644 src/main/java/com/roboautomator/component/message/ActiveMQProducer.java create mode 100644 src/main/java/com/roboautomator/component/message/KafkaConsumer.java create mode 100644 src/main/java/com/roboautomator/component/message/KafkaProducer.java delete mode 100644 src/test/java/com/roboautomator/component/config/queue/ActiveMQConfigTestIT.java delete mode 100644 src/test/java/com/roboautomator/component/message/ActiveMQConsumerTest.java delete mode 100644 src/test/java/com/roboautomator/component/message/ActiveMQProducerTest.java create mode 100644 src/test/java/com/roboautomator/component/message/KafkaConsumerTest.java create mode 100644 src/test/java/com/roboautomator/component/message/KafkaProducerTest.java create mode 100644 src/test/resources/application-test.yml delete mode 100644 src/test/resources/application.yml diff --git a/Makefile b/Makefile index c538335..48180b8 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ testIntegration: _setUpGradle _testComposeUp testFunctional: _setUpGradle _functionalComposeUp make _waitForLocalService echo "Running Functional tests" - ./gradlew testFunctional + ./gradlew testFunctional --stacktrace make _functionalComposeDown analyseWithJacoco: _setUpGradle @@ -56,4 +56,4 @@ analyseWithSonar: _gitFetchUnshallow _setUpGradle package: _setUpGradle echo "Packaging service" - ./gradlew bootJar \ No newline at end of file + ./gradlew clean bootJar \ No newline at end of file diff --git a/build.gradle b/build.gradle index 00eacf9..3f7dd0b 100644 --- a/build.gradle +++ b/build.gradle @@ -21,8 +21,8 @@ dependencies { implementation('org.springframework.boot:spring-boot-starter-validation') implementation('org.projectlombok:lombok:1.18.12') - // For Message Queue - implementation('org.springframework.boot:spring-boot-starter-activemq') + // For Kafka + implementation('org.springframework.kafka:spring-kafka') // For Database implementation('org.springframework.boot:spring-boot-starter-data-jpa') @@ -49,6 +49,7 @@ dependencies { testImplementation('io.rest-assured:rest-assured:4.2.0') testImplementation('io.rest-assured:json-path:4.2.0') testImplementation('io.rest-assured:xml-path:4.2.0') + testImplementation('org.awaitility:awaitility:3.1.6') // Annotations annotationProcessor('org.springframework.boot:spring-boot-configuration-processor') diff --git a/docker-compose-functional.yml b/docker-compose-functional.yml deleted file mode 100644 index 536b428..0000000 --- a/docker-compose-functional.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: "3.7" -services: - component: - image: docker.pkg.github.com/fishey2/java-component-template/component:0.1-SNAPSHOT - build: - context: . - dockerfile: docker/component/Dockerfile - ports: - - 8080:8080 - zookeeper: - image: wurstmeister/zookeeper - ports: - - "2181:2181" - kafka: - image: bitnami/kafka - ports: - - "9092" - environment: - HOSTNAME_COMMAND: "route -n | awk '/UG[ \t]/{print $$2}'" - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - ALLOW_PLAINTEXT_LISTENER: "yes" - volumes: - - /var/run/docker.sock:/var/run/docker.sock \ No newline at end of file diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 9341967..00da344 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -12,11 +12,27 @@ services: - 8081:8081 depends_on: - database - activemq: - image: rmohr/activemq:5.15.9-alpine + - kafka + links: + - kafka + - database + + kafka: + image: obsidiandynamics/kafka + restart: "no" ports: - - 61616:61616 - - 8161:8161 + - "2181:2181" + - "9092:9092" + environment: + KAFKA_LISTENERS: "INTERNAL://:29092,EXTERNAL://:9092" + KAFKA_ADVERTISED_LISTENERS: "INTERNAL://kafka:29092,EXTERNAL://kafka:9092" + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT" + KAFKA_INTER_BROKER_LISTENER_NAME: "INTERNAL" + KAFKA_ZOOKEEPER_SESSION_TIMEOUT: "6000" + KAFKA_RESTART_ATTEMPTS: "10" + KAFKA_RESTART_DELAY: "5" + ZOOKEEPER_AUTOPURGE_PURGE_INTERVAL: "0" + database: image: postgres:12 ports: diff --git a/docker-compose.yml b/docker-compose.yml index d88a1e9..9ac0e02 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,5 @@ version: "3.7" services: - activemq: - image: rmohr/activemq:5.15.9-alpine - ports: - - 61616:61616 - - 8161:8161 database: image: postgres:12 ports: @@ -14,3 +9,29 @@ services: POSTGRES_PASSWORD: postgres POSTGRES_DB: java_component + kafka: + image: obsidiandynamics/kafka + restart: "no" + ports: + - "2181:2181" + - "9092:9092" + environment: + KAFKA_LISTENERS: "INTERNAL://:29092,EXTERNAL://:9092" + KAFKA_ADVERTISED_LISTENERS: "INTERNAL://kafka:29092,EXTERNAL://localhost:9092" + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT" + KAFKA_INTER_BROKER_LISTENER_NAME: "INTERNAL" + KAFKA_ZOOKEEPER_SESSION_TIMEOUT: "6000" + KAFKA_RESTART_ATTEMPTS: "10" + KAFKA_RESTART_DELAY: "5" + ZOOKEEPER_AUTOPURGE_PURGE_INTERVAL: "0" + + kafdrop: + image: obsidiandynamics/kafdrop + restart: "no" + ports: + - "9000:9000" + environment: + KAFKA_BROKERCONNECT: "kafka:29092" + JVM_OPTS: "-Xms16M -Xmx48M -Xss180K -XX:-TieredCompilation -XX:+UseStringDeduplication -noverify" + depends_on: + - "kafka" \ No newline at end of file diff --git a/scripts/wait-for-url.sh b/scripts/wait-for-url.sh index 6ed457f..42e6035 100755 --- a/scripts/wait-for-url.sh +++ b/scripts/wait-for-url.sh @@ -9,7 +9,7 @@ fi echo "Waiting for $1" -MAX_ATTEMPTS=5 +MAX_ATTEMPTS=10 CURRENT_ATTEMPT=0 # Check if the provided URL can be reached diff --git a/src/functional-test/java/com/roboautomator/component/health/HealthActuatorFuncTest.java b/src/functional-test/java/com/roboautomator/component/health/HealthActuatorFuncTest.java index fe21c4f..251e7df 100644 --- a/src/functional-test/java/com/roboautomator/component/health/HealthActuatorFuncTest.java +++ b/src/functional-test/java/com/roboautomator/component/health/HealthActuatorFuncTest.java @@ -53,17 +53,6 @@ void shouldReturnDiskSpaceStatusWhenRunning() { .body("components.diskSpace.details.threshold", any(Number.class)); } - @Test - void shouldReturnJMSStatusWhenRunning() { - given() - .when() - .get(HEALTH_PROBE_ENDPOINT) - .then() - .statusCode(200) - .body("components.jms.status", equalTo("UP")) - .body("components.jms.details.provider", equalTo("ActiveMQ")); - } - @Test void shouldReturnPingStatusWhenRunning() { given() diff --git a/src/main/java/com/roboautomator/component/QueueConsumer.java b/src/main/java/com/roboautomator/component/QueueConsumer.java deleted file mode 100644 index b59b7ac..0000000 --- a/src/main/java/com/roboautomator/component/QueueConsumer.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.roboautomator.component; - -import javax.jms.JMSException; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.handler.annotation.Headers; -import org.springframework.messaging.handler.annotation.Payload; - -import javax.jms.Message; -import javax.jms.Session; - -public interface QueueConsumer { - - /** - *

A listener that listens to the queueName for messages. When a onMessage event is - * emitted. It will only log the {@link Payload}, {@link Headers}, {@link Message} and {@link Session}.

- * - *

Requires the annotation {@link org.springframework.jms.annotation.JmsListener} to be configured with the - * queue name

- * - * @param message - The de-serialized message of type {@link T} - * @param headers - The message {@link Headers} from the message queue. - * @param rawMessage - The raw message ({@link Message}), unprocessed from the queue. - * @param session - The session information ({@link Session}) - */ - void handleMessage(@Payload T message, @Headers MessageHeaders headers, - Message rawMessage, Session session) throws JMSException; -} diff --git a/src/main/java/com/roboautomator/component/QueueProducer.java b/src/main/java/com/roboautomator/component/QueueProducer.java deleted file mode 100644 index 5916dbe..0000000 --- a/src/main/java/com/roboautomator/component/QueueProducer.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.roboautomator.component; - -public interface QueueProducer { - - /** - *

Simplest working solution to be able to send a message to a message queue.

- * - * @param message the message object (type {@link T}) to send to the message queue. - */ - void sendMessage(T message); -} diff --git a/src/main/java/com/roboautomator/component/config/queue/ActiveMQConfig.java b/src/main/java/com/roboautomator/component/config/queue/ActiveMQConfig.java deleted file mode 100644 index 6ae7d64..0000000 --- a/src/main/java/com/roboautomator/component/config/queue/ActiveMQConfig.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.roboautomator.component.config.queue; - -import java.util.List; -import org.apache.activemq.ActiveMQConnectionFactory; -import org.apache.activemq.ActiveMQSslConnectionFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.jms.config.DefaultJmsListenerContainerFactory; -import org.springframework.jms.connection.CachingConnectionFactory; -import org.springframework.jms.core.JmsTemplate; - -@Configuration -public class ActiveMQConfig implements QueueConfig { - - @Value("${spring.activemq.broker-url}") - private String brokerUrl; - - @Value("${spring.activemq.user}") - private String user; - - @Value("${spring.activemq.password}") - private String password; - - private ActiveMQSslConnectionFactory getSslConnectionFactory() { - return new ActiveMQSslConnectionFactory(); - } - - /** - *

Strategy: If URL contains ssl then it will use {@link ActiveMQSslConnectionFactory} instead of - * {@link ActiveMQConnectionFactory}.

- * - *

Trusted Packages: - *

    - *
  • com.roboautomator.component.message
  • - *
  • com.roboautomator.component.patient
  • - *

- * - * @return the {@link ActiveMQConnectionFactory} to be used. - */ - public ActiveMQConnectionFactory configureActiveMQConnectionFactory() { - - var activeMQConnectionFactory = brokerUrl.contains("ssl") - ? getSslConnectionFactory() - : new ActiveMQConnectionFactory(); - - activeMQConnectionFactory.setBrokerURL(brokerUrl); - activeMQConnectionFactory.setUserName(user); - activeMQConnectionFactory.setPassword(password); - - activeMQConnectionFactory.setTrustedPackages(List.of( - "com.roboautomator.component.message", - "com.roboautomator.component.patient" - )); - - return activeMQConnectionFactory; - } - - @Override - public CachingConnectionFactory getConnectionFactory() { - return new CachingConnectionFactory( - configureActiveMQConnectionFactory()); - } - - @Bean - @Override - public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { - var defaultJmsListenerContainerFactory = new DefaultJmsListenerContainerFactory(); - defaultJmsListenerContainerFactory.setConnectionFactory(getConnectionFactory()); - defaultJmsListenerContainerFactory.setConcurrency("1-1"); - return defaultJmsListenerContainerFactory; - } - - @Override - public JmsTemplate jmsTemplate() { - return new JmsTemplate(getConnectionFactory()); - } -} \ No newline at end of file diff --git a/src/main/java/com/roboautomator/component/config/queue/QueueConfig.java b/src/main/java/com/roboautomator/component/config/queue/QueueConfig.java deleted file mode 100644 index 550a426..0000000 --- a/src/main/java/com/roboautomator/component/config/queue/QueueConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.roboautomator.component.config.queue; - -import org.springframework.jms.annotation.EnableJms; -import org.springframework.jms.config.DefaultJmsListenerContainerFactory; -import org.springframework.jms.connection.CachingConnectionFactory; -import org.springframework.jms.core.JmsTemplate; - -@EnableJms -public interface QueueConfig { - - /** - * Required by JmsListener annotation (Assumed to have either DefaultJmsListnenerCobtainerFactory - * as jmsListenerContainerFactory or JmsListenerContainerFactory) - * - * @return {@link DefaultJmsListenerContainerFactory} - */ - DefaultJmsListenerContainerFactory jmsListenerContainerFactory(); - - /** - * not required to be bean {@link CachingConnectionFactory}. - * - * @return the configured {@link CachingConnectionFactory} - */ - CachingConnectionFactory getConnectionFactory(); - - /** - * Configures the {@link JmsTemplate} to include the {@link CachingConnectionFactory}. - * - * @return the configured {@link JmsTemplate} - */ - JmsTemplate jmsTemplate(); - -} - diff --git a/src/main/java/com/roboautomator/component/message/ActiveMQConsumer.java b/src/main/java/com/roboautomator/component/message/ActiveMQConsumer.java deleted file mode 100644 index 1e04dc4..0000000 --- a/src/main/java/com/roboautomator/component/message/ActiveMQConsumer.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.roboautomator.component.message; - -import static com.roboautomator.component.util.StringHelper.cleanString; -import com.roboautomator.component.QueueConsumer; -import java.util.UUID; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.Session; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.slf4j.MDC; -import org.springframework.jms.annotation.JmsListener; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.handler.annotation.Headers; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.stereotype.Component; - -/** - *

Simple consumer for ActiveMQ

- * - * See: {@link org.springframework.jms.config.AbstractJmsListenerContainerFactory} - * for event emitter configuration. - * - * Required in config (application.yml): - *
    - *
  • spring.activemq.broker-url
  • - *
  • spring.activemq.user
  • - *
  • spring.activemq.password
  • - *
- */ -@Slf4j -@Component -@RequiredArgsConstructor -public class ActiveMQConsumer implements QueueConsumer { - - private static final String QUEUE_NAME = "testQueue"; - - private static final String UUID_PATTERN = "[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}"; - - private final MessageRepository messageRepository; - - @Override - @JmsListener(destination = QUEUE_NAME) - public void handleMessage(@Payload String message, @Headers MessageHeaders headers, - Message rawMessage, Session session) { - - var cleanMessage = cleanString(message); - - var correlationId = extractCorrelationId(rawMessage); - - MDC.put("correlationId", correlationId.toString()); - - var messageEntity = MessageEntity.builder() - .message(cleanMessage) - .correlationId(correlationId) - .build(); - - messageRepository.save(messageEntity); - } - - private UUID extractCorrelationId(Message jmsMessage) { - try { - var correlationId = jmsMessage.getJMSCorrelationID(); - - return correlationId != null && correlationId.matches(UUID_PATTERN) - ? UUID.fromString(correlationId) - : UUID.randomUUID(); - - } catch (JMSException exception) { - log.info("Could not extract JMSCorrelationID, generated new UUID"); - return UUID.randomUUID(); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/roboautomator/component/message/ActiveMQProducer.java b/src/main/java/com/roboautomator/component/message/ActiveMQProducer.java deleted file mode 100644 index b1eca2f..0000000 --- a/src/main/java/com/roboautomator/component/message/ActiveMQProducer.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.roboautomator.component.message; - -import com.roboautomator.component.QueueProducer; -import com.roboautomator.component.config.queue.ActiveMQConfig; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.slf4j.MDC; -import org.springframework.jms.core.JmsTemplate; -import org.springframework.stereotype.Component; - -/** - *

Producer for ActiveMQ implementation

- * - * Required in config (application.yml): - *
    - *
  • mq.queueName
  • - *
  • spring.activemq.broker-url
  • - *
  • spring.activemq.user
  • - *
  • spring.activemq.password
  • - *
- * - * Usage: - *

The ActiveMQ configuration is setup in - * {@link ActiveMQConfig} and access to - * {@link #sendMessage(String)} will be through instantiating this class.

- * - *

An example implementation of usage can be found in - * {@link MessageController}

- */ -@Slf4j -@Component -@AllArgsConstructor -public class ActiveMQProducer implements QueueProducer { - - private static final String QUEUE_NAME = "testQueue"; - - private final JmsTemplate jmsTemplate; - - @Override - public void sendMessage(String message) { - log.info("Sending message \"{}\" to the \"{}\" queue", message, QUEUE_NAME); - jmsTemplate.convertAndSend(QUEUE_NAME, message, msg -> { - msg.setJMSCorrelationID(MDC.get("correlationId")); - return msg; - }); - } -} \ No newline at end of file diff --git a/src/main/java/com/roboautomator/component/message/KafkaConsumer.java b/src/main/java/com/roboautomator/component/message/KafkaConsumer.java new file mode 100644 index 0000000..32d7147 --- /dev/null +++ b/src/main/java/com/roboautomator/component/message/KafkaConsumer.java @@ -0,0 +1,45 @@ +package com.roboautomator.component.message; + +import static com.roboautomator.component.util.StringHelper.cleanString; +import java.util.Map; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.handler.annotation.Headers; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class KafkaConsumer { + + private static final String TOPIC_NAME = "Test.Topic"; + + private final MessageRepository messageRepository; + + @KafkaListener(topics = TOPIC_NAME) + public void onMessage(@Payload String payload, @Headers Map headers) { + var cleanString = cleanString(payload); + log.info("New message on topic {}: {}", TOPIC_NAME, cleanString); + + var correlationId = extractCorrelationId(headers); + MDC.put("correlationId", correlationId.toString()); + + var messageEntity = MessageEntity.builder() + .message(cleanString) + .correlationId(correlationId) + .build(); + messageRepository.save(messageEntity); + } + + private UUID extractCorrelationId(Map headers) { + var correlationId = headers.get(KafkaHeaders.CORRELATION_ID); + return (correlationId == null) + ? UUID.randomUUID() + : UUID.fromString((String) correlationId); + } +} diff --git a/src/main/java/com/roboautomator/component/message/KafkaProducer.java b/src/main/java/com/roboautomator/component/message/KafkaProducer.java new file mode 100644 index 0000000..727c1c8 --- /dev/null +++ b/src/main/java/com/roboautomator/component/message/KafkaProducer.java @@ -0,0 +1,32 @@ +package com.roboautomator.component.message; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class KafkaProducer { + + private static final String TOPIC_NAME = "Test.Topic"; + + private final KafkaTemplate producer; + + public void send(String message) { + log.info("Sending message to Topic {}: {}", TOPIC_NAME, message); + + var record = MessageBuilder + .withPayload(message) + .setHeader(KafkaHeaders.TOPIC, TOPIC_NAME) + .setHeader(KafkaHeaders.MESSAGE_KEY, MDC.get("correlationId")) + .setHeader(KafkaHeaders.CORRELATION_ID, MDC.get("correlationId")) + .build(); + + producer.send(record); + } +} diff --git a/src/main/java/com/roboautomator/component/message/MessageController.java b/src/main/java/com/roboautomator/component/message/MessageController.java index ed29d06..5f603fe 100644 --- a/src/main/java/com/roboautomator/component/message/MessageController.java +++ b/src/main/java/com/roboautomator/component/message/MessageController.java @@ -4,9 +4,7 @@ import java.util.UUID; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.jms.JmsException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -21,7 +19,7 @@ public class MessageController { private final MessageRepository messageRepository; - private final ActiveMQProducer producer; + private final KafkaProducer producer; @PostMapping public ResponseEntity sendMessageToQueue(@RequestBody UserMessage userMessage) { @@ -30,12 +28,7 @@ public ResponseEntity sendMessageToQueue(@RequestBody UserMessage userMe log.info("Received request to send the message \"{}\" to queue", message); - try { - producer.sendMessage(message); - } catch (JmsException e) { - log.error("Could not process the message \"{}\", returning", message); - return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(e.toString()); - } + producer.send(message); return ResponseEntity.ok(message); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e47c4ce..f4a79e6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,8 +12,16 @@ management: spring: main: banner-mode: off - profiles: - active: "dev" + kafka: + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:kafka:9092} + properties: + security.protocol: "PLAINTEXT" + consumer: + group-id: component + auto-offset-reset: earliest + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer swagger: endpoint: /swagger-ui.html jpa: @@ -26,42 +34,6 @@ spring: connection-timeout: 20000 maximum-pool-size: 5 driver-class-name: org.postgresql.Driver - ---- - -spring: - profiles: dev - activemq: - broker-url: tcp://localhost:61616 - user: admin - password: admin - datasource: - url: jdbc:postgresql://localhost:5432/java_component + url: ${DATABASE_URL:jdbc:postgresql://database:5432/java_component} username: postgres password: postgres - ---- - -spring: - profiles: test - activemq: - broker-url: tcp://localhost:61616 - user: admin - password: admin - datasource: - url: jdbc:postgresql://localhost:5432/java_component - username: postgres - password: postgres - ---- - -spring: - profiles: prod - activemq: - broker-url: tcp://activemq:61616 - user: admin - password: admin - datasource: - url: jdbc:postgresql://database:5432/java_component - username: postgres - password: postgres \ No newline at end of file diff --git a/src/test/java/com/roboautomator/component/MainApplicationTestIT.java b/src/test/java/com/roboautomator/component/MainApplicationTestIT.java index 02d0082..a766f47 100644 --- a/src/test/java/com/roboautomator/component/MainApplicationTestIT.java +++ b/src/test/java/com/roboautomator/component/MainApplicationTestIT.java @@ -7,9 +7,11 @@ import org.springframework.boot.actuate.autoconfigure.web.server.LocalManagementPort; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +@ActiveProfiles("test") @ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class MainApplicationTestIT { diff --git a/src/test/java/com/roboautomator/component/config/logging/LoggingConfigTest.java b/src/test/java/com/roboautomator/component/config/logging/LoggingConfigTest.java index 368dcf4..7bdfcb7 100644 --- a/src/test/java/com/roboautomator/component/config/logging/LoggingConfigTest.java +++ b/src/test/java/com/roboautomator/component/config/logging/LoggingConfigTest.java @@ -1,6 +1,7 @@ package com.roboautomator.component.config.logging; -import com.roboautomator.component.config.logging.LoggingConfig; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import com.roboautomator.component.middleware.LoggingMiddleware; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -9,9 +10,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - @ExtendWith(MockitoExtension.class) class LoggingConfigTest { @@ -34,6 +32,6 @@ void shouldAddLoggingMiddlewareToInteceptorRegistry() { loggingConfig.addInterceptors(interceptorRegistry); verify(interceptorRegistry, times(1)) - .addInterceptor(loggingMiddleware); + .addInterceptor(loggingMiddleware); } } diff --git a/src/test/java/com/roboautomator/component/config/queue/ActiveMQConfigTestIT.java b/src/test/java/com/roboautomator/component/config/queue/ActiveMQConfigTestIT.java deleted file mode 100644 index b77f5a1..0000000 --- a/src/test/java/com/roboautomator/component/config/queue/ActiveMQConfigTestIT.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.roboautomator.component.config.queue; - -import org.apache.activemq.ActiveMQConnectionFactory; -import org.apache.activemq.ActiveMQSslConnectionFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.jms.core.JmsTemplate; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -@ExtendWith(SpringExtension.class) -@SpringBootTest -class ActiveMQConfigTestIT { - - @Value("${spring.activemq.broker-url}") - private String brokerUrl; - - @Value("${spring.activemq.user}") - private String user; - - @Value("${spring.activemq.password}") - private String password; - - @Autowired - private ActiveMQConfig activeMQConfig; - - @Test - void testJmsTemplateReturnsJmsTemplate() { - assertThat(activeMQConfig.jmsTemplate().getClass()) - .isEqualTo(JmsTemplate.class); - } - - @Test - void testConnectionFactoryReturnsObject() { - assertThat(activeMQConfig.configureActiveMQConnectionFactory().getClass()) - .isEqualTo(ActiveMQConnectionFactory.class); - } - - @Test - void testConnectionFactoryCallsActiveMQWithBrokerUrl() { - assertThat(activeMQConfig.configureActiveMQConnectionFactory().getBrokerURL()) - .isEqualTo(brokerUrl); - } - - @Test - void testConnectionFactoryCallsActiveMQWithUser() { - assertThat(activeMQConfig.configureActiveMQConnectionFactory().getUserName()) - .isEqualTo(user); - } - - @Test - void testConnectionFactoryCallsActiveMQWithPassword() { - assertThat(activeMQConfig.configureActiveMQConnectionFactory().getPassword()) - .isEqualTo(password); - } - - @Test - void testSSLConnectionFactoryUsedWhenURLContainsSSL() { - org.springframework.test.util.ReflectionTestUtils - .setField(activeMQConfig, "brokerUrl", "tcp+ssl://127.0.0.1:61616"); - - assertThat(activeMQConfig.configureActiveMQConnectionFactory().getClass()).isEqualTo(ActiveMQSslConnectionFactory.class); - } -} diff --git a/src/test/java/com/roboautomator/component/config/swagger/SwaggerConfigTest.java b/src/test/java/com/roboautomator/component/config/swagger/SwaggerConfigTest.java index 8bde366..95949d7 100644 --- a/src/test/java/com/roboautomator/component/config/swagger/SwaggerConfigTest.java +++ b/src/test/java/com/roboautomator/component/config/swagger/SwaggerConfigTest.java @@ -1,13 +1,13 @@ package com.roboautomator.component.config.swagger; -import com.roboautomator.component.config.swagger.SwaggerConfig; +import static com.roboautomator.component.config.swagger.SwaggerConfig.APPLICATION_DESCRIPTION; +import static com.roboautomator.component.config.swagger.SwaggerConfig.APPLICATION_NAME; +import static com.roboautomator.component.config.swagger.SwaggerConfig.APPLICATION_VERSION; +import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import springfox.documentation.spi.DocumentationType; -import static com.roboautomator.component.config.swagger.SwaggerConfig.*; -import static org.assertj.core.api.Assertions.assertThat; - class SwaggerConfigTest { private SwaggerConfig testConfig; diff --git a/src/test/java/com/roboautomator/component/config/swagger/SwaggerControllerTestIT.java b/src/test/java/com/roboautomator/component/config/swagger/SwaggerControllerTestIT.java index b170e14..948fbe7 100644 --- a/src/test/java/com/roboautomator/component/config/swagger/SwaggerControllerTestIT.java +++ b/src/test/java/com/roboautomator/component/config/swagger/SwaggerControllerTestIT.java @@ -1,24 +1,22 @@ package com.roboautomator.component.config.swagger; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; -import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - +@ActiveProfiles("test") @ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @EnableAutoConfiguration @@ -37,7 +35,7 @@ void setUp() { @Test void testThatUserIsRedirectedToSwaggerDocsFromRoot() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/swagger-ui.html")); + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/swagger-ui.html")); } } \ No newline at end of file diff --git a/src/test/java/com/roboautomator/component/message/ActiveMQConsumerTest.java b/src/test/java/com/roboautomator/component/message/ActiveMQConsumerTest.java deleted file mode 100644 index 3461bb1..0000000 --- a/src/test/java/com/roboautomator/component/message/ActiveMQConsumerTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.roboautomator.component.message; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.verify; -import ch.qos.logback.classic.spi.ILoggingEvent; -import com.roboautomator.component.util.AbstractLoggingTest; -import java.util.UUID; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.Session; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentMatchers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.slf4j.MDC; -import org.springframework.messaging.MessageHeaders; - -@ExtendWith(MockitoExtension.class) -class ActiveMQConsumerTest extends AbstractLoggingTest { - - private static final String UUID_PATTERN = "[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}"; - @Mock - private MessageRepository messageRepository; - - @Mock - private MessageHeaders mockHeaders; - - @Mock - private Message mockMessage; - - @Mock - private Session mockSession; - - @InjectMocks - private ActiveMQConsumer activeMQConsumer; - - @BeforeEach - void setUpLoggingTestConfig() { - setupLoggingAppender(activeMQConsumer); - } - - @Test - void shouldSetMDCCorrelationIdWhenSetInMessageHeader() throws JMSException { - var correlationId = UUID.randomUUID(); - - willReturn(correlationId.toString()) - .given(mockMessage) - .getJMSCorrelationID(); - - activeMQConsumer.handleMessage("Some Message", mockHeaders, mockMessage, mockSession); - - assertThat(MDC.get("correlationId")).isEqualTo(correlationId.toString()); - } - - @Test - void shouldSaveMessageToMessageRepository() { - willReturn(MessageEntity.builder().build()) - .given(messageRepository) - .save(any(MessageEntity.class)); - - activeMQConsumer.handleMessage("Some Message", mockHeaders, mockMessage, mockSession); - - verify(messageRepository).save(ArgumentMatchers.any(MessageEntity.class)); - } - - @Test - void shouldLogErrorWhenFailedToGetJmsCorrelationId() throws JMSException { - willThrow(new JMSException("Failed")) - .given(mockMessage) - .getJMSCorrelationID(); - - activeMQConsumer.handleMessage("Some Message", mockHeaders, mockMessage, mockSession); - - assertThat(getLoggingEventListAppender().list) - .flatExtracting(ILoggingEvent::getFormattedMessage) - .contains("Could not extract JMSCorrelationID, generated new UUID"); - } - - @Test - void shouldGenerateRandomUUIDForCorrelationIdWhenCorrelationIdIsWrong() throws JMSException { - var correlationId = "not-a-uuid"; - - willReturn(correlationId) - .given(mockMessage) - .getJMSCorrelationID(); - - activeMQConsumer.handleMessage("Some Message", mockHeaders, mockMessage, mockSession); - - assertThat(MDC.get("correlationId")).isNotEqualTo(correlationId); - assertThat(MDC.get("correlationId")).matches(UUID_PATTERN); - } - @Test - void shouldGenerateCorrelationIdWhenOneIsNotProvided() { - activeMQConsumer.handleMessage("Some Message", mockHeaders, mockMessage, mockSession); - - assertThat(MDC.get("correlationId")).matches(UUID_PATTERN); - } -} diff --git a/src/test/java/com/roboautomator/component/message/ActiveMQProducerTest.java b/src/test/java/com/roboautomator/component/message/ActiveMQProducerTest.java deleted file mode 100644 index 7c85f31..0000000 --- a/src/test/java/com/roboautomator/component/message/ActiveMQProducerTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.roboautomator.component.message; - -import ch.qos.logback.classic.spi.ILoggingEvent; -import com.roboautomator.component.util.AbstractLoggingTest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.jms.core.JmsTemplate; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -@ExtendWith(SpringExtension.class) -@SpringBootTest(classes = ActiveMQProducer.class) -class ActiveMQProducerTest extends AbstractLoggingTest { - - @MockBean - private JmsTemplate jmsTemplate; - - private ActiveMQProducer activeMQProducer; - - @BeforeEach - void setupProducer() { - activeMQProducer = new ActiveMQProducer(jmsTemplate); - setupLoggingAppender(activeMQProducer); - } - - @Test - void shouldCallJmsTemplateWithMessageToBeSent() { - activeMQProducer.sendMessage("Message"); - - verify(jmsTemplate, times(1)) - .convertAndSend(anyString(), eq("Message"), any()); - } - - @Test - void shouldCallJmsTemplateWithCorrectQueueName() { - activeMQProducer.sendMessage("Any"); - - verify(jmsTemplate, times(1)) - .convertAndSend(eq("testQueue"), anyString(), any()); - } - - @Test - void shouldCallLogWithMessageInformation() { - activeMQProducer.sendMessage("Message"); - - assertThat(getLoggingEventListAppender().list) - .extracting(ILoggingEvent::getFormattedMessage) - .contains("Sending message \"Message\" to the \"testQueue\" queue"); - } -} diff --git a/src/test/java/com/roboautomator/component/message/KafkaConsumerTest.java b/src/test/java/com/roboautomator/component/message/KafkaConsumerTest.java new file mode 100644 index 0000000..d637c73 --- /dev/null +++ b/src/test/java/com/roboautomator/component/message/KafkaConsumerTest.java @@ -0,0 +1,79 @@ +package com.roboautomator.component.message; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static org.mockito.Mockito.verify; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import com.roboautomator.component.util.AbstractLoggingTest; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.MDC; +import org.springframework.kafka.support.KafkaHeaders; + +@ExtendWith(MockitoExtension.class) +class KafkaConsumerTest extends AbstractLoggingTest { + + @Mock + private MessageRepository messageRepository; + + @Captor + private ArgumentCaptor argumentCaptor; + + private KafkaConsumer kafkaConsumer; + + @BeforeEach + void setup() { + kafkaConsumer = new KafkaConsumer(messageRepository); + setupLoggingAppender(kafkaConsumer); + } + + @Test + void shouldSaveMessageToMessageRepository() { + var payload = "Hello Payload"; + var correlationId = UUID.randomUUID(); + + kafkaConsumer.onMessage(payload, Map.of(KafkaHeaders.CORRELATION_ID, correlationId.toString())); + + verify(messageRepository).save(argumentCaptor.capture()); + var messageEntity = argumentCaptor.getValue(); + assertThat(messageEntity).isNotNull(); + assertThat(messageEntity.getMessage()).isEqualTo(payload); + assertThat(messageEntity.getCorrelationId()).isEqualTo(correlationId); + } + + @Test + void shouldCreateUUIDValueIfReceived() { + var payload = "Hello Payload"; + + kafkaConsumer.onMessage(payload, Collections.emptyMap()); + + verify(messageRepository).save(argumentCaptor.capture()); + + var messageEntity = argumentCaptor.getValue(); + assertThat(messageEntity).isNotNull(); + assertThat(messageEntity.getMessage()).isEqualTo(payload); + assertThat(messageEntity.getCorrelationId()).isNotNull(); + } + + @Test + void shouldLogWhenMessageReceived() { + var payload = "Hello Payload"; + var correlationId = UUID.randomUUID(); + + kafkaConsumer.onMessage(payload, Map.of(KafkaHeaders.CORRELATION_ID, correlationId.toString())); + + assertThat(MDC.get("correlationId")).isEqualTo(correlationId.toString()); + assertThat(getLoggingEventListAppender().list) + .extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel) + .containsExactly(tuple("New message on topic {}: {}", Level.INFO)); + } +} diff --git a/src/test/java/com/roboautomator/component/message/KafkaProducerTest.java b/src/test/java/com/roboautomator/component/message/KafkaProducerTest.java new file mode 100644 index 0000000..0fa4259 --- /dev/null +++ b/src/test/java/com/roboautomator/component/message/KafkaProducerTest.java @@ -0,0 +1,66 @@ +package com.roboautomator.component.message; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static org.mockito.Mockito.verify; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import com.roboautomator.component.util.AbstractLoggingTest; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.MDC; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.Message; + +@ExtendWith(MockitoExtension.class) +class KafkaProducerTest extends AbstractLoggingTest { + + @Mock + private KafkaTemplate kafkaTemplate; + + @Captor + private ArgumentCaptor> argumentCaptor; + + private KafkaProducer kafkaProducer; + + @BeforeEach + void setup() { + kafkaProducer = new KafkaProducer(kafkaTemplate); + setupLoggingAppender(kafkaProducer); + } + + @Test + void shouldSentMessageToKafka() { + var message = "some message"; + var correlationId = UUID.randomUUID().toString(); + MDC.put("correlationId", correlationId); + + kafkaProducer.send(message); + + verify(kafkaTemplate).send(argumentCaptor.capture()); + var sentMessage = argumentCaptor.getValue(); + assertThat(sentMessage).isNotNull(); + assertThat(sentMessage.getPayload()).isEqualTo(message); + assertThat(sentMessage.getHeaders()).isNotEmpty(); + assertThat(sentMessage.getHeaders().get(KafkaHeaders.MESSAGE_KEY)).isEqualTo(correlationId); + assertThat(sentMessage.getHeaders().get(KafkaHeaders.CORRELATION_ID)).isEqualTo(correlationId); + } + + @Test + void shouldLogWhenMessageReceived() { + var payload = "Hello Payload"; + + kafkaProducer.send(payload); + + assertThat(getLoggingEventListAppender().list) + .extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel) + .containsExactly(tuple("Sending message to Topic {}: {}", Level.INFO)); + } +} diff --git a/src/test/java/com/roboautomator/component/message/MessageControllerTest.java b/src/test/java/com/roboautomator/component/message/MessageControllerTest.java index 711e0f3..6a9b809 100644 --- a/src/test/java/com/roboautomator/component/message/MessageControllerTest.java +++ b/src/test/java/com/roboautomator/component/message/MessageControllerTest.java @@ -1,8 +1,13 @@ package com.roboautomator.component.message; -import ch.qos.logback.classic.Level; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.BDDMockito.willReturn; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import ch.qos.logback.classic.spi.ILoggingEvent; - import com.roboautomator.component.util.AbstractLoggingTest; import java.util.Optional; import java.util.UUID; @@ -12,23 +17,11 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; - import org.springframework.http.MediaType; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import javax.jms.MessageNotWriteableException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.willDoNothing; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.BDDMockito.willThrow; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @ExtendWith(SpringExtension.class) @SpringBootTest(classes = MessageController.class) @AutoConfigureMockMvc @@ -40,12 +33,8 @@ class MessageControllerTest extends AbstractLoggingTest { "\"messageBody\": \"Hello Message\"" + "}"; - private static final String ERROR_JSON = "{" + - "\"messageBody\": \"error\"" + - "}"; - @MockBean - private ActiveMQProducer activeMQProducer; + private KafkaProducer kafkaProducer; @MockBean private MessageRepository messageRepository; @@ -54,14 +43,14 @@ class MessageControllerTest extends AbstractLoggingTest { @BeforeEach void setupMockMvc() { - var messageController = new MessageController(messageRepository, activeMQProducer); + var messageController = new MessageController(messageRepository, kafkaProducer); setupLoggingAppender(messageController); mockMvc = MockMvcBuilders.standaloneSetup(messageController).build(); } @Test void shouldCallLogWithMessage() throws Exception { - willDoNothing().given(activeMQProducer).sendMessage(any()); + willDoNothing().given(kafkaProducer).send(any()); mockMvc .perform(post(TEST_ENDPOINT) @@ -70,19 +59,19 @@ void shouldCallLogWithMessage() throws Exception { .andExpect(status().isOk()); assertThat(getLoggingEventListAppender().list) - .extracting(ILoggingEvent::getMessage) - .contains("Received request to send the message \"{}\" to queue"); + .extracting(ILoggingEvent::getMessage) + .contains("Received request to send the message \"{}\" to queue"); - var listOfArgs = new String[]{"Hello Message"}; + var listOfArgs = new String[] {"Hello Message"}; assertThat(getLoggingEventListAppender().list) - .extracting(ILoggingEvent::getArgumentArray) - .contains(listOfArgs); + .extracting(ILoggingEvent::getArgumentArray) + .contains(listOfArgs); } @Test void shouldReturnOkWhenMessageSent() throws Exception { - willDoNothing().given(activeMQProducer).sendMessage(any()); + willDoNothing().given(kafkaProducer).send(any()); mockMvc .perform(post(TEST_ENDPOINT) @@ -93,7 +82,7 @@ void shouldReturnOkWhenMessageSent() throws Exception { @Test void shouldReturnMessageInResponseBody() throws Exception { - willDoNothing().given(activeMQProducer).sendMessage(any()); + willDoNothing().given(kafkaProducer).send(any()); var result = mockMvc .perform(post(TEST_ENDPOINT) @@ -104,57 +93,6 @@ void shouldReturnMessageInResponseBody() throws Exception { assertThat(result.getResponse().getContentAsString()).isEqualTo("Hello Message"); } - @Test - void shouldReturn503WhenMessageNotSent() throws Exception { - willThrow(new org.springframework.jms.MessageNotWriteableException(new MessageNotWriteableException("Error Occurred"))) - .given(activeMQProducer) - .sendMessage("error"); - - mockMvc - .perform(post(TEST_ENDPOINT) - .contentType(MediaType.APPLICATION_JSON) - .content(ERROR_JSON)) - .andExpect(status().is5xxServerError()); - } - - @Test - void shouldReturnErrorMessageWhenMessageHasNotBeenSent() throws Exception { - var exception = new org.springframework.jms.MessageNotWriteableException(new MessageNotWriteableException("Error Occurred")); - willThrow(exception) - .given(activeMQProducer) - .sendMessage("error"); - - var result = mockMvc - .perform(post(TEST_ENDPOINT) - .contentType(MediaType.APPLICATION_JSON) - .content(ERROR_JSON)) - .andReturn(); - - assertThat(result.getResponse().getContentAsString()) - .isEqualTo(exception.toString()); - } - - @Test - void shouldLogAnErrorWhenMessageCanNotBeProcessed() throws Exception { - var exception = new org.springframework.jms.MessageNotWriteableException(new MessageNotWriteableException("Error Occurred")); - willThrow(exception) - .given(activeMQProducer) - .sendMessage("error"); - - mockMvc - .perform(post(TEST_ENDPOINT) - .contentType(MediaType.APPLICATION_JSON) - .content(ERROR_JSON)); - - assertThat(getLoggingEventListAppender().list) - .extracting(ILoggingEvent::getLevel) - .contains(Level.ERROR); - - assertThat(getLoggingEventListAppender().list) - .extracting(ILoggingEvent::getFormattedMessage) - .contains("Could not process the message \"error\", returning"); - } - @Test void shouldReturn404NotFoundIfCorrelationIdNotFound() throws Exception { var correlationId = UUID.randomUUID(); diff --git a/src/test/java/com/roboautomator/component/message/MessageControllerTestIT.java b/src/test/java/com/roboautomator/component/message/MessageControllerTestIT.java index 92b9ce6..12d7c17 100644 --- a/src/test/java/com/roboautomator/component/message/MessageControllerTestIT.java +++ b/src/test/java/com/roboautomator/component/message/MessageControllerTestIT.java @@ -1,11 +1,13 @@ package com.roboautomator.component.message; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import com.roboautomator.component.message.MessageEntity; import com.roboautomator.component.message.MessageRepository; import com.roboautomator.component.message.Message; import java.net.URL; import java.util.UUID; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpSessionEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,8 +20,10 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +@ActiveProfiles("test") @ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class MessageControllerTestIT { @@ -80,9 +84,13 @@ void shouldGetMessageFromDatabaseWithCorrectCorrelationId() { var responseBody = template.getForEntity(uri, Message.class).getBody(); System.out.println(responseBody); - assertThat(responseBody).isNotNull(); - assertThat(responseBody.getCorrelationId()).isEqualTo(correlationId); - assertThat(responseBody.getMessageBody()).isEqualTo("Hello World!"); + await() + .atMost(20, TimeUnit.SECONDS) + .untilAsserted(() -> { + assertThat(responseBody).isNotNull(); + assertThat(responseBody.getCorrelationId()).isEqualTo(correlationId); + assertThat(responseBody.getMessageBody()).isEqualTo("Hello World!"); + }); } @Test diff --git a/src/test/java/com/roboautomator/component/message/MessageRepositoryTestIT.java b/src/test/java/com/roboautomator/component/message/MessageRepositoryTestIT.java index 68caec6..5542257 100644 --- a/src/test/java/com/roboautomator/component/message/MessageRepositoryTestIT.java +++ b/src/test/java/com/roboautomator/component/message/MessageRepositoryTestIT.java @@ -10,6 +10,7 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import javax.persistence.EntityManager; @@ -17,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@ActiveProfiles("test") @ExtendWith(SpringExtension.class) @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) diff --git a/src/test/java/com/roboautomator/component/middleware/CorrelationMiddlewareTestIT.java b/src/test/java/com/roboautomator/component/middleware/CorrelationMiddlewareTestIT.java index 70df239..b16ef6c 100644 --- a/src/test/java/com/roboautomator/component/middleware/CorrelationMiddlewareTestIT.java +++ b/src/test/java/com/roboautomator/component/middleware/CorrelationMiddlewareTestIT.java @@ -6,6 +6,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import javax.servlet.http.HttpServletRequest; @@ -16,6 +17,7 @@ import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +@ActiveProfiles("test") @ExtendWith(SpringExtension.class) @SpringBootTest @AutoConfigureMockMvc diff --git a/src/test/java/com/roboautomator/component/middleware/LoggingMiddlewareTestIT.java b/src/test/java/com/roboautomator/component/middleware/LoggingMiddlewareTestIT.java index 2ab2304..a977663 100644 --- a/src/test/java/com/roboautomator/component/middleware/LoggingMiddlewareTestIT.java +++ b/src/test/java/com/roboautomator/component/middleware/LoggingMiddlewareTestIT.java @@ -6,6 +6,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; @@ -15,6 +16,7 @@ import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +@ActiveProfiles("test") @ExtendWith(SpringExtension.class) @SpringBootTest @AutoConfigureMockMvc diff --git a/src/test/java/com/roboautomator/component/patient/PatientControllerTestIT.java b/src/test/java/com/roboautomator/component/patient/PatientControllerTestIT.java index aeeef30..1c52289 100644 --- a/src/test/java/com/roboautomator/component/patient/PatientControllerTestIT.java +++ b/src/test/java/com/roboautomator/component/patient/PatientControllerTestIT.java @@ -15,8 +15,10 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +@ActiveProfiles("test") @ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class PatientControllerTestIT { diff --git a/src/test/java/com/roboautomator/component/patient/PatientRepositoryTestIT.java b/src/test/java/com/roboautomator/component/patient/PatientRepositoryTestIT.java index 3e747a4..549d787 100644 --- a/src/test/java/com/roboautomator/component/patient/PatientRepositoryTestIT.java +++ b/src/test/java/com/roboautomator/component/patient/PatientRepositoryTestIT.java @@ -11,8 +11,10 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +@ActiveProfiles("test") @ExtendWith(SpringExtension.class) @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) diff --git a/src/test/java/com/roboautomator/component/util/AbstractMockMvcTest.java b/src/test/java/com/roboautomator/component/util/AbstractMockMvcTest.java index fbeb445..24e290c 100644 --- a/src/test/java/com/roboautomator/component/util/AbstractMockMvcTest.java +++ b/src/test/java/com/roboautomator/component/util/AbstractMockMvcTest.java @@ -1,17 +1,17 @@ package com.roboautomator.component.util; -import com.roboautomator.component.message.ActiveMQConsumer; import com.roboautomator.component.message.MessageRepository; import com.roboautomator.component.patient.PatientRepository; import javax.persistence.EntityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.kafka.core.KafkaTemplate; import org.springframework.test.web.servlet.MockMvc; public abstract class AbstractMockMvcTest { @MockBean - private ActiveMQConsumer activeMQConsumer; + private KafkaTemplate kafkaTemplate; @MockBean private MessageRepository messageRepository; diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 0000000..1e27b1d --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,39 @@ +server: + port: 8080 +management: + server: + port: 8081 + endpoint: + info: + enabled: false + health: + show-details: always + +spring: + main: + banner-mode: off + kafka: + bootstrap-servers: localhost:9092 + properties: + security.protocol: "PLAINTEXT" + consumer: + group-id: component + auto-offset-reset: earliest + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer + swagger: + endpoint: /swagger-ui.html + jpa: + hibernate: + ddl-auto: create + open-in-view: true + database: postgresql + datasource: + hikari: + connection-timeout: 20000 + maximum-pool-size: 5 + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/java_component + username: postgres + password: postgres diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml deleted file mode 100644 index e47c4ce..0000000 --- a/src/test/resources/application.yml +++ /dev/null @@ -1,67 +0,0 @@ -server: - port: 8080 -management: - server: - port: 8081 - endpoint: - info: - enabled: false - health: - show-details: always - -spring: - main: - banner-mode: off - profiles: - active: "dev" - swagger: - endpoint: /swagger-ui.html - jpa: - hibernate: - ddl-auto: create - open-in-view: true - database: postgresql - datasource: - hikari: - connection-timeout: 20000 - maximum-pool-size: 5 - driver-class-name: org.postgresql.Driver - ---- - -spring: - profiles: dev - activemq: - broker-url: tcp://localhost:61616 - user: admin - password: admin - datasource: - url: jdbc:postgresql://localhost:5432/java_component - username: postgres - password: postgres - ---- - -spring: - profiles: test - activemq: - broker-url: tcp://localhost:61616 - user: admin - password: admin - datasource: - url: jdbc:postgresql://localhost:5432/java_component - username: postgres - password: postgres - ---- - -spring: - profiles: prod - activemq: - broker-url: tcp://activemq:61616 - user: admin - password: admin - datasource: - url: jdbc:postgresql://database:5432/java_component - username: postgres - password: postgres \ No newline at end of file