diff --git a/sh_dependencies.json b/sh_dependencies.json index 9d63a354..edfcf261 100644 --- a/sh_dependencies.json +++ b/sh_dependencies.json @@ -59,6 +59,10 @@ { "name": "JACOCO_VERSION", "packageName": "org.jacoco:jacoco-maven-plugin" + }, + { + "name": "CLOUD_EVENTS_VERSION", + "packageName": "io.cloudevents:cloudevents-json-jackson" } ], "gradle": [ diff --git a/src/main/java/co/com/bancolombia/Constants.java b/src/main/java/co/com/bancolombia/Constants.java index 4f9beea9..38670d8c 100644 --- a/src/main/java/co/com/bancolombia/Constants.java +++ b/src/main/java/co/com/bancolombia/Constants.java @@ -24,6 +24,7 @@ public final class Constants { public static final String RESILIENCE_4J_VERSION = "2.2.0"; public static final String BIN_STASH_VERSION = "1.2.6"; public static final String SPRING_DOC_OPENAPI_VERSION = "2.7.0"; + public static final String CLOUD_EVENTS_VERSION = "4.0.1"; // gradle plugins public static final String JACOCO_VERSION = "0.8.12"; public static final String SONAR_VERSION = "6.0.1.5171"; diff --git a/src/main/resources/driven-adapter/async-event-bus/build.gradle.mustache b/src/main/resources/driven-adapter/async-event-bus/build.gradle.mustache index ec64e70f..e7af559e 100644 --- a/src/main/resources/driven-adapter/async-event-bus/build.gradle.mustache +++ b/src/main/resources/driven-adapter/async-event-bus/build.gradle.mustache @@ -1,5 +1,8 @@ dependencies { implementation project(':model') + {{#eda}} + implementation 'io.cloudevents:cloudevents-json-jackson:{{CLOUD_EVENTS_VERSION}}' + {{/eda}} {{#rabbitmq}} implementation 'org.reactivecommons:async-commons-rabbit-starter:{{REACTIVE_COMMONS_VERSION}}' {{/rabbitmq}} diff --git a/src/main/resources/driven-adapter/async-event-bus/definition.json b/src/main/resources/driven-adapter/async-event-bus/definition.json index 1e815b00..de02bdb6 100644 --- a/src/main/resources/driven-adapter/async-event-bus/definition.json +++ b/src/main/resources/driven-adapter/async-event-bus/definition.json @@ -6,6 +6,8 @@ "driven-adapter/async-event-bus/events-gateway.java.mustache": "domain/model/src/main/java/{{packagePath}}/model/events/gateways/EventsGateway.java", "driven-adapter/async-event-bus/reactive-events-gateway.java.mustache": "infrastructure/driven-adapters/async-event-bus/src/main/java/{{packagePath}}/events/ReactiveEventsGateway.java", "driven-adapter/async-event-bus/reactive-direct-async-gateway.java.mustache": "infrastructure/driven-adapters/async-event-bus/src/main/java/{{packagePath}}/events/ReactiveDirectAsyncGateway.java", + "driven-adapter/async-event-bus/reactive-direct-async-gateway.unit.test.java.mustache": "infrastructure/driven-adapters/async-event-bus/src/test/java/{{packagePath}}/events/ReactiveDirectAsyncGatewayTest.java", + "driven-adapter/async-event-bus/reactive-events-gateway.unit.test.java.mustache": "infrastructure/driven-adapters/async-event-bus/src/test/java/{{packagePath}}/events/ReactiveEventsGatewayTest.java", "driven-adapter/async-event-bus/build.gradle.mustache": "infrastructure/driven-adapters/async-event-bus/build.gradle" } } diff --git a/src/main/resources/driven-adapter/async-event-bus/reactive-direct-async-gateway.unit.test.java.mustache b/src/main/resources/driven-adapter/async-event-bus/reactive-direct-async-gateway.unit.test.java.mustache new file mode 100644 index 00000000..fb49da2b --- /dev/null +++ b/src/main/resources/driven-adapter/async-event-bus/reactive-direct-async-gateway.unit.test.java.mustache @@ -0,0 +1,118 @@ +package {{package}}.events; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.reactivecommons.async.api.DirectAsyncGateway; +import reactor.core.publisher.Mono; +{{#eda}} +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.jackson.JsonCloudEventData; + +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.UUID; +{{/eda}} +{{^eda}} +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.AsyncQuery; +{{/eda}} + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class ReactiveDirectAsyncGatewayTest { + + @Mock + private DirectAsyncGateway directAsyncGateway; + {{#eda}} + @Mock + private ObjectMapper objectMapper; + {{/eda}} + + private ReactiveDirectAsyncGateway gateway; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + gateway = new ReactiveDirectAsyncGateway(directAsyncGateway{{#eda}}, objectMapper{{/eda}}); + } + + @Test + public void testRunRemoteJobSendsCommand() {{#eda}}throws Exception{{/eda}} { + Object command = new Object() { + public String toString() { + return "testCommand"; + } + }; + {{#eda}} + when(objectMapper.valueToTree(command)).thenReturn(mock(ObjectNode.class)); + when(directAsyncGateway.sendCommand(any(CloudEvent.class), any(String.class))).thenReturn(Mono.empty()); + {{/eda}} + {{^eda}} + when(directAsyncGateway.sendCommand(any(Command.class), any(String.class))).thenReturn(Mono.empty()); + {{/eda}} + + gateway.runRemoteJob(command).block(); + + {{#eda}} + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(CloudEvent.class); + verify(directAsyncGateway, times(1)).sendCommand(eventCaptor.capture(), eq(ReactiveDirectAsyncGateway.TARGET_NAME)); + CloudEvent cloudEvent = eventCaptor.getValue(); + {{/eda}} + {{^eda}} + ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(Command.class); + verify(directAsyncGateway, times(1)).sendCommand(commandCaptor.capture(), eq(ReactiveDirectAsyncGateway.TARGET_NAME)); + Command capturedCommand = commandCaptor.getValue(); + assertEquals(ReactiveDirectAsyncGateway.SOME_COMMAND_NAME, capturedCommand.getName()); + {{/eda}} + } + + @Test + public void testRequestForRemoteDataSendsQuery() {{#eda}}throws JsonProcessingException{{/eda}} { + Object query = new Object() { + public String toString() { + return "testQuery"; + } + }; + {{#eda}} + ObjectNode mockNode = mock(ObjectNode.class); + when(objectMapper.valueToTree(query)).thenReturn(mockNode); + when(objectMapper.createObjectNode()).thenReturn(new ObjectMapper().createObjectNode()); + + CloudEvent mockCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://spring.io/foos")) + .withType(ReactiveDirectAsyncGateway.SOME_QUERY_NAME) + .withTime(OffsetDateTime.now()) + .withData("application/json", JsonCloudEventData.wrap(objectMapper.createObjectNode().put("key", "value"))) + .build(); + + when(directAsyncGateway.requestReply(any(CloudEvent.class), any(String.class), eq(CloudEvent.class))) + .thenReturn(Mono.just(mockCloudEvent)); + + gateway.requestForRemoteData(query).block(); + + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(CloudEvent.class); + verify(directAsyncGateway, times(1)).requestReply(eventCaptor.capture(), eq(ReactiveDirectAsyncGateway.TARGET_NAME), eq(CloudEvent.class)); + CloudEvent cloudEvent = eventCaptor.getValue(); + {{/eda}} + {{^eda}} + when(directAsyncGateway.requestReply(any(AsyncQuery.class), any(String.class), eq(Object.class))).thenReturn(Mono.just(new Object())); + + gateway.requestForRemoteData(query).block(); + + ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(AsyncQuery.class); + verify(directAsyncGateway, times(1)).requestReply(queryCaptor.capture(), eq(ReactiveDirectAsyncGateway.TARGET_NAME), eq(Object.class)); + AsyncQuery capturedQuery = queryCaptor.getValue(); + assertEquals(ReactiveDirectAsyncGateway.SOME_QUERY_NAME, capturedQuery.getResource()); + {{/eda}} + } +} diff --git a/src/main/resources/driven-adapter/async-event-bus/reactive-events-gateway.unit.test.java.mustache b/src/main/resources/driven-adapter/async-event-bus/reactive-events-gateway.unit.test.java.mustache new file mode 100644 index 00000000..a9c6e487 --- /dev/null +++ b/src/main/resources/driven-adapter/async-event-bus/reactive-events-gateway.unit.test.java.mustache @@ -0,0 +1,124 @@ +package {{package}}.events; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import reactor.core.publisher.Mono; +import org.reactivecommons.api.domain.DomainEventBus; + +{{#eda}} +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.cloudevents.CloudEvent; +{{/eda}} +{{^eda}} +import org.reactivecommons.api.domain.DomainEvent; +{{/eda}} +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ReactiveEventsGatewayTest { + + {{#eda}} + @Mock + private DomainEventBus domainEventBus; + + @Mock + private ObjectMapper objectMapper; + + private ReactiveEventsGateway gateway; + {{/eda}} + {{^eda}} + @Mock + private DomainEventBus domainEventBus; + + private ReactiveEventsGateway reactiveEventsGateway; + {{/eda}} + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + {{#eda}} + gateway = new ReactiveEventsGateway(domainEventBus, objectMapper); + when(domainEventBus.emit(any(CloudEvent.class))).thenReturn(Mono.empty()); + {{/eda}} + {{^eda}} + reactiveEventsGateway = new ReactiveEventsGateway(domainEventBus); + {{/eda}} + } + + @Test + public void testEmitLogsEvent() { + Object event = new Object() { + @Override + public String toString() { + return "testEvent"; + } + }; + + {{#eda}} + when(objectMapper.valueToTree(event)).thenReturn(mock(ObjectNode.class)); + + gateway.emit(event).block(); + + verify(domainEventBus, times(1)).emit(any(CloudEvent.class)); + {{/eda}} + {{^eda}} + when(domainEventBus.emit(any(DomainEvent.class))).thenReturn(Mono.empty()); + + reactiveEventsGateway.emit(event).block(); + + verify(domainEventBus, times(1)).emit(any(DomainEvent.class)); + {{/eda}} + } + + {{#eda}} + @Test + public void testEmitConstructsCloudEvent() { + Object event = new Object() { + public String toString() { return "testEvent"; } + }; + + when(objectMapper.valueToTree(event)).thenReturn(mock(ObjectNode.class)); + + gateway.emit(event).block(); + + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(CloudEvent.class); + verify(domainEventBus, times(1)).emit(eventCaptor.capture()); + + CloudEvent cloudEvent = eventCaptor.getValue(); + assertEquals(ReactiveEventsGateway.SOME_EVENT_NAME, cloudEvent.getType()); + assertEquals("https://reactive-commons.org/foos", cloudEvent.getSource().toString()); + } + {{/eda}} + + {{^eda}} + @Test + public void testEmitConstructsDomainEvent() { + Object event = new Object() { + @Override + public String toString() { + return "testEvent"; + } + }; + + when(domainEventBus.emit(any(DomainEvent.class))).thenReturn(Mono.empty()); + + reactiveEventsGateway.emit(event).block(); + + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(DomainEvent.class); + verify(domainEventBus, times(1)).emit(eventCaptor.capture()); + + DomainEvent capturedEvent = eventCaptor.getValue(); + assertEquals(ReactiveEventsGateway.SOME_EVENT_NAME, capturedEvent.getName()); + assertEquals(event.toString(), capturedEvent.getData().toString()); + } + {{/eda}} + + + + +} diff --git a/src/main/resources/entry-point/async-event-handler/build.gradle.mustache b/src/main/resources/entry-point/async-event-handler/build.gradle.mustache index be182923..4e5dddca 100644 --- a/src/main/resources/entry-point/async-event-handler/build.gradle.mustache +++ b/src/main/resources/entry-point/async-event-handler/build.gradle.mustache @@ -1,6 +1,9 @@ dependencies { implementation project(':model') implementation project(':usecase') + {{#eda}} + implementation 'io.cloudevents:cloudevents-json-jackson:{{CLOUD_EVENTS_VERSION}}' + {{/eda}} {{#rabbitmq}} implementation 'org.reactivecommons:async-commons-rabbit-starter:{{REACTIVE_COMMONS_VERSION}}' {{/rabbitmq}}