From 3cd244ec11df4c9f0ee5cbddde58ce7418f02787 Mon Sep 17 00:00:00 2001 From: Timon Back Date: Mon, 20 Nov 2023 23:05:13 +0100 Subject: [PATCH] test(jms): add controller and binding tests --- .../springwolf-jms-example/build.gradle | 13 +- ...rIntegrationWithDockerIntegrationTest.java | 5 +- .../springwolf-jms-plugin/build.gradle | 18 +- .../controller/SpringwolfJmsController.java | 2 +- .../producer/SpringwolfJmsProducer.java | 18 +- ...pringwolfJmsControllerIntegrationTest.java | 195 ++++++++++++++++++ .../JmsMessageBindingProcessorTest.java | 40 ++++ .../JmsOperationBindingProcessorTest.java | 28 +++ .../producer/SpringwolfJmsProducerTest.java | 6 +- 9 files changed, 305 insertions(+), 20 deletions(-) create mode 100644 springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java create mode 100644 springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessorTest.java create mode 100644 springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessorTest.java diff --git a/springwolf-examples/springwolf-jms-example/build.gradle b/springwolf-examples/springwolf-jms-example/build.gradle index 7953f9244..de8db1a78 100644 --- a/springwolf-examples/springwolf-jms-example/build.gradle +++ b/springwolf-examples/springwolf-jms-example/build.gradle @@ -16,21 +16,20 @@ dependencies { annotationProcessor project(":springwolf-plugins:springwolf-jms") runtimeOnly project(":springwolf-ui") - implementation 'org.springframework.boot:spring-boot-starter-activemq' - - runtimeOnly "org.springframework.boot:spring-boot-starter-web" + compileOnly "jakarta.jms:jakarta.jms-api" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" + implementation "io.swagger.core.v3:swagger-annotations:${swaggerVersion}" - implementation "org.springframework.boot:spring-boot-autoconfigure" - implementation "org.springframework.boot:spring-boot" implementation "org.springframework:spring-context" + implementation "org.springframework:spring-jms" + implementation "org.springframework.boot:spring-boot" + implementation "org.springframework.boot:spring-boot-autoconfigure" + runtimeOnly "org.springframework.boot:spring-boot-starter-web" testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}" - testImplementation "com.vaadin.external.google:android-json:${androidJsonVersion}" - testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}" diff --git a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ProducerIntegrationWithDockerIntegrationTest.java b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ProducerIntegrationWithDockerIntegrationTest.java index 700340021..1f5d41277 100644 --- a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ProducerIntegrationWithDockerIntegrationTest.java +++ b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ProducerIntegrationWithDockerIntegrationTest.java @@ -18,6 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.io.File; +import java.util.Map; import static io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto.ExampleEnum.FOO1; import static org.mockito.Mockito.timeout; @@ -49,7 +50,7 @@ public class ProducerIntegrationWithDockerIntegrationTest { @Test @Order(2) - void producerCanUseSpringwolfConfigurationToSendMessage() throws InterruptedException { + void producerCanUseSpringwolfConfigurationToSendMessage() { // given ExamplePayloadDto payload = new ExamplePayloadDto(); payload.setSomeString("foo"); @@ -57,7 +58,7 @@ void producerCanUseSpringwolfConfigurationToSendMessage() throws InterruptedExce payload.setSomeEnum(FOO1); // when - springwolfJmsProducer.send("example-queue", payload); + springwolfJmsProducer.send("example-queue", Map.of(), payload); // then verify(exampleConsumer, timeout(10000)).receiveExamplePayload(payload); diff --git a/springwolf-plugins/springwolf-jms-plugin/build.gradle b/springwolf-plugins/springwolf-jms-plugin/build.gradle index 673f506b6..06343fd23 100644 --- a/springwolf-plugins/springwolf-jms-plugin/build.gradle +++ b/springwolf-plugins/springwolf-jms-plugin/build.gradle @@ -9,15 +9,18 @@ plugins { dependencies { api project(":springwolf-core") + implementation "jakarta.jms:jakarta.jms-api" + implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" - implementation 'org.springframework:spring-jms' - compileOnly 'org.apache.activemq:activemq-broker' + runtimeOnly "org.apache.activemq:activemq-broker" implementation "org.springframework:spring-context" implementation "org.springframework:spring-core" + implementation "org.springframework:spring-jms" implementation "org.springframework:spring-web" + implementation "org.springframework.boot:spring-boot" implementation "org.springframework.boot:spring-boot-autoconfigure" @@ -27,18 +30,21 @@ dependencies { annotationProcessor "org.projectlombok:lombok:${lombokVersion}" annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" - testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" + testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}" + testRuntimeOnly "org.springframework.boot:spring-boot-starter-web" testImplementation "org.assertj:assertj-core:${assertjCoreVersion}" + testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" + testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}" - testImplementation "org.springframework.boot:spring-boot-test" testImplementation "org.springframework:spring-beans" testImplementation "org.springframework:spring-test" - testImplementation 'org.apache.activemq:activemq-broker' - testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}" + testImplementation "org.springframework.boot:spring-boot-test" + testImplementation "org.springframework.boot:spring-boot-test-autoconfigure" + testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" } diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsController.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsController.java index b14844d7a..be56a8d2d 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsController.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsController.java @@ -27,6 +27,6 @@ protected boolean isEnabled() { @Override protected void publishMessage(String topic, MessageDto message, Object payload) { log.debug("Publishing to JMS queue {}: {}", topic, message); - producer.send(topic, payload); + producer.send(topic, message.getHeaders(), payload); } } diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducer.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducer.java index cc3a091d0..41acc5c92 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducer.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducer.java @@ -1,10 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.producer; +import jakarta.jms.JMSException; import lombok.extern.slf4j.Slf4j; import org.springframework.jms.core.JmsTemplate; import java.util.List; +import java.util.Map; import java.util.Optional; @Slf4j @@ -20,9 +22,21 @@ public boolean isEnabled() { return template.isPresent(); } - public void send(String channelName, Object payload) { + public void send(String channelName, Map headers, Object payload) { if (template.isPresent()) { - template.get().convertAndSend(channelName, payload); + template.get().convertAndSend(channelName, payload, message -> { + if (headers != null) { + headers.forEach((name, value) -> { + try { + message.setStringProperty(name, value); + } catch (JMSException ex) { + log.warn("Unable to set JMS Header key=%s value=%s".formatted(name, value), ex); + } + }); + } + return message; + }); + } else { log.warn("JMS producer is not configured"); } diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java new file mode 100644 index 000000000..f72a1208a --- /dev/null +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; +import io.github.stavshamir.springwolf.producer.SpringwolfJmsProducer; +import io.github.stavshamir.springwolf.schemas.DefaultSchemasService; +import io.github.stavshamir.springwolf.schemas.SchemasService; +import io.github.stavshamir.springwolf.schemas.example.ExampleJsonGenerator; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Map; + +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(SpringwolfJmsController.class) +@ContextConfiguration( + classes = { + SpringwolfJmsController.class, + PublishingPayloadCreator.class, + SpringwolfJmsProducer.class, + DefaultSchemasService.class, + ExampleJsonGenerator.class, + SpringwolfConfigProperties.class, + }) +@TestPropertySource( + properties = { + "springwolf.docket.base-package=io.github.stavshamir.springwolf.asyncapi", + "springwolf.docket.info.title=Title", + "springwolf.docket.info.version=1.0", + "springwolf.docket.servers.jms.protocol=jms", + "springwolf.docket.servers.jms.url=127.0.0.1", + "springwolf.plugin.jms.publishing.enabled=true", + "springwolf.use-fqn=true" + }) +class SpringwolfJmsControllerIntegrationTest { + + @Autowired + private MockMvc mvc; + + @Autowired + private SchemasService schemasService; + + @MockBean + private SpringwolfJmsProducer springwolfJmsProducer; + + @Captor + private ArgumentCaptor payloadCaptor; + + @Captor + private ArgumentCaptor> headerCaptor; + + @BeforeEach + void setup() { + when(springwolfJmsProducer.isEnabled()).thenReturn(true); + + schemasService.register(PayloadDto.class); + } + + @Test + void testControllerShouldReturnBadRequestIfPayloadIsEmpty() { + try { + String content = + """ + { + "bindings": null, + "headers": null, + "payload": "" + }"""; + mvc.perform(post("/springwolf/jms/publish?topic=test-topic") + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } catch (Exception e) { + verifyNoInteractions(springwolfJmsProducer); + } + } + + @Test + void testControllerShouldReturnBadRequestIfPayloadIsNotSet() { + try { + String content = + """ + { + "bindings": null, + "headers": null + }"""; + mvc.perform(post("/springwolf/jms/publish?topic=test-topic") + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isBadRequest()); + } catch (Exception e) { + verifyNoInteractions(springwolfJmsProducer); + } + } + + @Test + void testControllerShouldReturnNotFoundIfNoJmsProducerIsEnabled() throws Exception { + when(springwolfJmsProducer.isEnabled()).thenReturn(false); + + String content = + """ + { + "bindings": null, + "headers": null, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }" + }"""; + mvc.perform(post("/springwolf/jms/publish?topic=test-topic") + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isNotFound()); + } + + @Test + void testControllerShouldCallJmsProducerIfOnlyPayloadIsSend() throws Exception { + when(springwolfJmsProducer.isEnabled()).thenReturn(true); + + String content = + """ + { + "bindings": null, + "headers": null, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", + "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfJmsControllerIntegrationTest$PayloadDto" + }"""; + + mvc.perform(post("/springwolf/jms/publish") + .param("topic", "test-topic") + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isOk()); + + verify(springwolfJmsProducer).send(eq("test-topic"), isNull(), payloadCaptor.capture()); + + assertThat(payloadCaptor.getValue()).isEqualTo(new PayloadDto("some-payload-value")); + } + + @Test + void testControllerShouldCallJmsProducerIfPayloadAndHeadersAreSend() throws Exception { + when(springwolfJmsProducer.isEnabled()).thenReturn(true); + + String content = + """ + { + "bindings": null, + "headers": { + "some-header-key": "some-header-value" + }, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", + "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfJmsControllerIntegrationTest$PayloadDto" + } + """; + + mvc.perform(post("/springwolf/jms/publish?topic=test-topic") + .contentType(MediaType.APPLICATION_JSON) + .content(content)) + .andExpect(status().isOk()); + + verify(springwolfJmsProducer).send(eq("test-topic"), headerCaptor.capture(), payloadCaptor.capture()); + + assertThat(headerCaptor.getValue()).isEqualTo(singletonMap("some-header-key", "some-header-value")); + assertThat(payloadCaptor.getValue()).isEqualTo(new PayloadDto("some-payload-value")); + } + + @Data + @AllArgsConstructor + @Jacksonized + @Builder + public static class PayloadDto { + @JsonProperty("some-payload-key") + private String field; + } +} diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessorTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessorTest.java new file mode 100644 index 000000000..53765e63a --- /dev/null +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessorTest.java @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; + +import com.asyncapi.v2.binding.message.jms.JMSMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.JmsAsyncOperationBinding; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +class JmsMessageBindingProcessorTest { + private final JmsMessageBindingProcessor processor = new JmsMessageBindingProcessor(); + + @Test + void processTest() throws NoSuchMethodException { + Method method = JmsMessageBindingProcessorTest.class.getMethod("methodWithAnnotation"); + + ProcessedMessageBinding binding = processor.process(method).get(); + + assertThat(binding.getType()).isEqualTo("jms"); + assertThat(binding.getBinding()).isEqualTo(new JMSMessageBinding()); + } + + @Test + void processWithoutAnnotationTest() throws NoSuchMethodException { + Method method = JmsMessageBindingProcessorTest.class.getMethod("methodWithoutAnnotation"); + + Optional binding = processor.process(method); + + assertThat(binding).isNotPresent(); + } + + @JmsAsyncOperationBinding + public void methodWithAnnotation() {} + + public void methodWithoutAnnotation() {} +} diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessorTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessorTest.java new file mode 100644 index 000000000..99879973b --- /dev/null +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessorTest.java @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; + +import com.asyncapi.v2.binding.operation.jms.JMSOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.JmsAsyncOperationBinding; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class JmsOperationBindingProcessorTest { + private final JmsOperationBindingProcessor processor = new JmsOperationBindingProcessor(); + + @Test + void mapToOperationBindingTest() throws NoSuchMethodException { + JmsAsyncOperationBinding annotation = JmsOperationBindingProcessorTest.class + .getMethod("methodWithAnnotation") + .getAnnotation(JmsAsyncOperationBinding.class); + + ProcessedOperationBinding binding = processor.mapToOperationBinding(annotation); + + assertThat(binding.getType()).isEqualTo("jms"); + assertThat(binding.getBinding()).isEqualTo(new JMSOperationBinding()); + } + + @JmsAsyncOperationBinding + public void methodWithAnnotation() {} +} diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducerTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducerTest.java index 22a16dae4..493323d65 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducerTest.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/producer/SpringwolfJmsProducerTest.java @@ -4,11 +4,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.jms.core.JmsTemplate; +import org.springframework.jms.core.MessagePostProcessor; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; @@ -29,8 +31,8 @@ void setUp() { @Test void send_defaultExchangeAndChannelNameAsRoutingKey() { Map payload = new HashMap<>(); - springwolfJmsProducer.send("channel-name", payload); + springwolfJmsProducer.send("channel-name", Map.of(), payload); - verify(template).convertAndSend(eq("channel-name"), same(payload)); + verify(template).convertAndSend(eq("channel-name"), same(payload), any(MessagePostProcessor.class)); } }