diff --git a/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/data/ProtocolAdapterPublisherJsonPayload.java b/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/data/ProtocolAdapterPublisherJsonPayload.java index f60a9da4ea..e53d5fe3f2 100644 --- a/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/data/ProtocolAdapterPublisherJsonPayload.java +++ b/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/data/ProtocolAdapterPublisherJsonPayload.java @@ -16,6 +16,7 @@ package com.hivemq.edge.modules.adapters.data; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import com.hivemq.extension.sdk.api.annotations.NotNull; import com.hivemq.extension.sdk.api.annotations.Nullable; @@ -25,14 +26,27 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class ProtocolAdapterPublisherJsonPayload extends AbstractProtocolAdapterJsonPayload { - private @NotNull TagSample sample; + @JsonProperty("value") + private @NotNull Object value; + + @JsonProperty("tagName") + private @Nullable String tagName; public ProtocolAdapterPublisherJsonPayload(final @Nullable Long timestamp, final @NotNull TagSample sample) { super(timestamp); - this.sample = sample; + this.value = sample.getTagValue(); + this.tagName = sample.getTagName(); + } + + @NotNull + public Object getValue() { + return value; } - public TagSample getSample() { - return sample; + @Nullable + public String getTagName() { + return tagName; } + + } diff --git a/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/impl/AbstractPollingProtocolAdapter.java b/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/impl/AbstractPollingProtocolAdapter.java index 4eebd5657c..79dd68a1d8 100644 --- a/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/impl/AbstractPollingProtocolAdapter.java +++ b/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/impl/AbstractPollingProtocolAdapter.java @@ -16,6 +16,7 @@ package com.hivemq.edge.modules.adapters.impl; import com.codahale.metrics.MetricRegistry; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.hivemq.edge.modules.adapters.data.AbstractProtocolAdapterJsonPayload; @@ -55,7 +56,8 @@ public AbstractPollingProtocolAdapter( super(adapterInformation, adapterConfig, metricRegistry); } - protected void bindServices(final @NotNull ModuleServices moduleServices){ + @VisibleForTesting + public void bindServices(final @NotNull ModuleServices moduleServices){ Preconditions.checkNotNull(moduleServices); super.bindServices(moduleServices); if(protocolAdapterPollingService == null){ diff --git a/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/impl/AbstractProtocolAdapter.java b/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/impl/AbstractProtocolAdapter.java index 651aa32101..8ac935ff2b 100644 --- a/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/impl/AbstractProtocolAdapter.java +++ b/hivemq-edge/src/main/java/com/hivemq/edge/modules/adapters/impl/AbstractProtocolAdapter.java @@ -19,6 +19,7 @@ import com.codahale.metrics.MetricRegistry; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.hivemq.edge.model.TypeIdentifier; import com.hivemq.edge.modules.adapters.ProtocolAdapterException; @@ -41,6 +42,7 @@ import com.hivemq.edge.modules.api.events.EventUtils; import com.hivemq.edge.modules.api.events.model.Event; import com.hivemq.edge.modules.config.impl.AbstractProtocolAdapterConfig; +import com.hivemq.edge.modules.config.impl.AbstractProtocolAdapterConfig.Subscription.MessageHandlingOptions; import com.hivemq.extension.sdk.api.annotations.NotNull; import com.hivemq.extension.sdk.api.annotations.Nullable; import org.slf4j.Logger; @@ -117,7 +119,7 @@ public List convertAdapterSampleToPublishes( Long timestamp = data.getSubscription().getIncludeTimestamp() ? data.getTimestamp() : null; if(data.getDataPoints().size() > 1 && data.getSubscription().getMessageHandlingOptions() == - AbstractProtocolAdapterConfig.Subscription.MessageHandlingOptions.MQTTMessagePerSubscription){ + MessageHandlingOptions.MQTTMessagePerSubscription){ //-- Put all derived samples into a single MQTT message list.add(createMultiPublishPayload(timestamp, data.getDataPoints(), data.getSubscription().getIncludeTagNames())); } else { @@ -156,7 +158,8 @@ public byte[] convertToJson(final @NotNull AbstractProtocolAdapterJsonPayload da } } - protected void bindServices(final @NotNull ModuleServices moduleServices){ + @VisibleForTesting + public void bindServices(final @NotNull ModuleServices moduleServices){ Preconditions.checkNotNull(moduleServices); if(adapterPublishService == null){ adapterPublishService = moduleServices.adapterPublishService(); diff --git a/modules/hivemq-edge-module-http/build.gradle.kts b/modules/hivemq-edge-module-http/build.gradle.kts index 9bde653db2..e67570931c 100644 --- a/modules/hivemq-edge-module-http/build.gradle.kts +++ b/modules/hivemq-edge-module-http/build.gradle.kts @@ -57,6 +57,7 @@ dependencies { compileOnly("com.hivemq:hivemq-extension-sdk") testImplementation("org.apache.commons:commons-lang3:${property("commons-lang.version")}") testImplementation("commons-io:commons-io:${property("commons-io.version")}") + testImplementation("com.google.guava:guava:${property("guava.version")}") testImplementation("org.mockito:mockito-core:${property("mockito.version")}") testImplementation("org.junit.jupiter:junit-jupiter-api:${property("junit.jupiter.version")}") testImplementation("org.junit.jupiter:junit-jupiter-params:${property("junit.jupiter.version")}") @@ -64,6 +65,7 @@ dependencies { testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${property("junit.jupiter.version")}") testImplementation("org.mockito:mockito-core:${property("mockito.version")}") testImplementation("org.mockito:mockito-junit-jupiter:${property("mockito.version")}") + testImplementation("net.javacrumbs.json-unit:json-unit-assertj:${property("jsonUnit")}") } tasks.test { diff --git a/modules/hivemq-edge-module-http/gradle.properties b/modules/hivemq-edge-module-http/gradle.properties index eab0425207..516a9df70e 100644 --- a/modules/hivemq-edge-module-http/gradle.properties +++ b/modules/hivemq-edge-module-http/gradle.properties @@ -14,6 +14,7 @@ junit.jupiter.platform.version=1.7.1 mockito.version=4.7.0 commons-lang.version=3.11 commons-io.version=2.8.0 +jsonUnit = 2.38.0 # logging slf4j.version=1.7.30 diff --git a/modules/hivemq-edge-module-http/src/main/java/com/hivemq/edge/adapters/http/HttpAdapterConfig.java b/modules/hivemq-edge-module-http/src/main/java/com/hivemq/edge/adapters/http/HttpAdapterConfig.java index 90616799fa..2d06f68fbf 100644 --- a/modules/hivemq-edge-module-http/src/main/java/com/hivemq/edge/adapters/http/HttpAdapterConfig.java +++ b/modules/hivemq-edge-module-http/src/main/java/com/hivemq/edge/adapters/http/HttpAdapterConfig.java @@ -26,19 +26,22 @@ import java.util.ArrayList; import java.util.List; -@JsonPropertyOrder({"url", - "destination", - "qos", - "httpRequestMethod", - "httpConnectTimeout", - "httpRequestBodyContentType", - "httpRequestBody", - "httpPublishSuccessStatusCodeOnly", - "httpHeaders"}) +@JsonPropertyOrder({ + "url", + "destination", + "qos", + "httpRequestMethod", + "httpConnectTimeout", + "httpRequestBodyContentType", + "httpRequestBody", + "httpPublishSuccessStatusCodeOnly", + "httpHeaders"}) public class HttpAdapterConfig extends AbstractPollingProtocolAdapterConfig { public enum HttpMethod { - GET, POST, PUT + GET, + POST, + PUT } public enum HttpContentType { @@ -48,7 +51,7 @@ public enum HttpContentType { XML("application/xml"), YAML("application/yaml"); - HttpContentType(String contentType){ + HttpContentType(String contentType) { this.contentType = contentType; } @@ -60,11 +63,9 @@ public String getContentType() { } @JsonProperty("url") - @ModuleConfigField(title = "URL", - description = "The url of the http request you would like to make", + @ModuleConfigField(title = "URL", description = "The url of the http request you would like to make", // stringPattern = HttpConstants.HTTP_URL_REGEX, - format = ModuleConfigField.FieldType.URI, - required = true) + format = ModuleConfigField.FieldType.URI, required = true) private @NotNull String url; @JsonProperty(value = "destination", required = true) @@ -96,18 +97,18 @@ public String getContentType() { private @NotNull HttpAdapterConfig.HttpContentType httpRequestBodyContentType = HttpContentType.JSON; @JsonProperty("httpRequestBody") - @ModuleConfigField(title = "Http Request Body", - description = "The body to include in the HTTP request") + @ModuleConfigField(title = "Http Request Body", description = "The body to include in the HTTP request") private @NotNull String httpRequestBody; @JsonProperty("httpConnectTimeout") @ModuleConfigField(title = "Http Connection Timeout", - description = "Timeout (in second) to wait for the HTTP Request to complete", required = true, defaultValue = HttpAdapterConstants.DEFAULT_TIMEOUT_SECONDS + "") + description = "Timeout (in second) to wait for the HTTP Request to complete", + required = true, + defaultValue = HttpAdapterConstants.DEFAULT_TIMEOUT_SECONDS + "") private @NotNull Integer httpConnectTimeout = HttpAdapterConstants.DEFAULT_TIMEOUT_SECONDS; @JsonProperty("httpHeaders") - @ModuleConfigField(title = "HTTP Headers", - description = "HTTP headers to be added to your requests") + @ModuleConfigField(title = "HTTP Headers", description = "HTTP headers to be added to your requests") private @NotNull List httpHeaders = new ArrayList<>(); @JsonProperty("httpPublishSuccessStatusCodeOnly") @@ -125,9 +126,14 @@ public String getContentType() { public HttpAdapterConfig() { } + public HttpAdapterConfig(final @NotNull String adapterId) { + this.id = adapterId; + } + public boolean isHttpPublishSuccessStatusCodeOnly() { return httpPublishSuccessStatusCodeOnly; } + public @NotNull HttpMethod getHttpRequestMethod() { return httpRequestMethod; } @@ -167,13 +173,11 @@ public boolean isAllowUntrustedCertificates() { public static class HttpHeader { @JsonProperty("name") - @ModuleConfigField(title = "Http Header Name", - description = "The name of the HTTP header") + @ModuleConfigField(title = "Http Header Name", description = "The name of the HTTP header") private String name; @JsonProperty("value") - @ModuleConfigField(title = "Http Header Value", - description = "The value of the HTTP header") + @ModuleConfigField(title = "Http Header Value", description = "The value of the HTTP header") private String value; public HttpHeader() { diff --git a/modules/hivemq-edge-module-http/src/main/java/com/hivemq/edge/adapters/http/HttpProtocolAdapter.java b/modules/hivemq-edge-module-http/src/main/java/com/hivemq/edge/adapters/http/HttpProtocolAdapter.java index 197de6fbc8..1ff1e56ddf 100644 --- a/modules/hivemq-edge-module-http/src/main/java/com/hivemq/edge/adapters/http/HttpProtocolAdapter.java +++ b/modules/hivemq-edge-module-http/src/main/java/com/hivemq/edge/adapters/http/HttpProtocolAdapter.java @@ -52,7 +52,7 @@ */ public class HttpProtocolAdapter extends AbstractPollingProtocolAdapter { - private static final String RESPONSE_DATA = "httpResponseData"; + static final String RESPONSE_DATA = "httpResponseData"; private static final Logger log = LoggerFactory.getLogger(HttpProtocolAdapter.class); private HttpClient httpClient = null; diff --git a/modules/hivemq-edge-module-http/src/test/java/com/hivemq/edge/adapters/http/HttpProtocolAdapterTest.java b/modules/hivemq-edge-module-http/src/test/java/com/hivemq/edge/adapters/http/HttpProtocolAdapterTest.java new file mode 100644 index 0000000000..925921aba8 --- /dev/null +++ b/modules/hivemq-edge-module-http/src/test/java/com/hivemq/edge/adapters/http/HttpProtocolAdapterTest.java @@ -0,0 +1,68 @@ +package com.hivemq.edge.adapters.http; + +import com.codahale.metrics.MetricRegistry; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableMap; +import com.hivemq.edge.adapters.http.model.HttpData; +import com.hivemq.edge.modules.adapters.impl.ProtocolAdapterPublishBuilderImpl; +import com.hivemq.edge.modules.api.adapters.ModuleServices; +import com.hivemq.edge.modules.api.adapters.ProtocolAdapterPublishService; +import com.hivemq.edge.modules.api.events.EventService; +import com.hivemq.edge.modules.config.impl.AbstractProtocolAdapterConfig; +import com.hivemq.extension.sdk.api.annotations.NotNull; +import com.hivemq.mqtt.handler.publish.PublishReturnCode; +import com.hivemq.mqtt.message.publish.PUBLISH; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static com.hivemq.edge.adapters.http.HttpProtocolAdapter.RESPONSE_DATA; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class HttpProtocolAdapterTest { + + private final @NotNull MetricRegistry metricRegistry = new MetricRegistry(); + private final @NotNull HttpAdapterConfig httpAdapterConfig = new HttpAdapterConfig("adapterId"); + private final @NotNull HttpProtocolAdapter httpProtocolAdapter = + new HttpProtocolAdapter(HttpProtocolAdapterInformation.INSTANCE, httpAdapterConfig, metricRegistry); + private final @NotNull ProtocolAdapterPublishService publishService = mock(ProtocolAdapterPublishService.class); + private final @NotNull ModuleServices moduleServices = mock(ModuleServices.class); + private final @NotNull ProtocolAdapterPublishBuilderImpl.SendCallback sendCallback = + mock(ProtocolAdapterPublishBuilderImpl.SendCallback.class); + private final @NotNull ArgumentCaptor publishArgumentCaptor = ArgumentCaptor.forClass(PUBLISH.class); + + @BeforeEach + void setUp() { + when(moduleServices.adapterPublishService()).thenReturn(publishService); + when(moduleServices.eventService()).thenReturn(mock(EventService.class)); + httpProtocolAdapter.bindServices(moduleServices); + //noinspection unchecked + when(sendCallback.onPublishSend(publishArgumentCaptor.capture(), any(), any(ImmutableMap.class))).thenReturn( + CompletableFuture.completedFuture(PublishReturnCode.DELIVERED)); + final ProtocolAdapterPublishBuilderImpl protocolAdapterPublishBuilder = + new ProtocolAdapterPublishBuilderImpl("hivemq", sendCallback); + protocolAdapterPublishBuilder.withAdapter(httpProtocolAdapter); + when(publishService.publish()).thenReturn(protocolAdapterPublishBuilder); + } + + @Test + void test_captureDataSample_expectedPayloadPresent() + throws ExecutionException, InterruptedException, JsonProcessingException { + final AbstractProtocolAdapterConfig.Subscription subscription = + new AbstractProtocolAdapterConfig.Subscription("topic", 2); + final HttpData httpData = new HttpData(subscription, "http://localhost:8080", 200, "text/plain"); + httpData.addDataPoint(RESPONSE_DATA, "hello world"); + + httpProtocolAdapter.captureDataSample(httpData).get(); + + final String payloadAsString = new String(publishArgumentCaptor.getValue().getPayload()); + assertThatJson(payloadAsString).node("timestamp").isIntegralNumber(); + assertThatJson(payloadAsString).node("value").isString().isEqualTo("hello world"); + } +} diff --git a/modules/hivemq-edge-module-modbus/build.gradle.kts b/modules/hivemq-edge-module-modbus/build.gradle.kts index de904dbf5a..e5ad2e6667 100644 --- a/modules/hivemq-edge-module-modbus/build.gradle.kts +++ b/modules/hivemq-edge-module-modbus/build.gradle.kts @@ -54,12 +54,17 @@ configurations { } dependencies { + testImplementation("com.hivemq:hivemq-edge") + compileOnly("com.hivemq:hivemq-extension-sdk") + testImplementation("com.google.guava:guava:${property("guava.version")}") + testImplementation("org.mockito:mockito-core:${property("mockito.version")}") testImplementation("org.junit.jupiter:junit-jupiter-api:${property("junit.jupiter.version")}") testImplementation("org.junit.jupiter:junit-jupiter-params:${property("junit.jupiter.version")}") testImplementation("org.junit.platform:junit-platform-launcher:${property("junit.jupiter.platform.version")}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${property("junit.jupiter.version")}") testImplementation("org.mockito:mockito-core:${property("mockito.version")}") testImplementation("org.mockito:mockito-junit-jupiter:${property("mockito.version")}") + testImplementation("net.javacrumbs.json-unit:json-unit-assertj:${property("jsonUnit")}") } tasks.test { diff --git a/modules/hivemq-edge-module-modbus/gradle.properties b/modules/hivemq-edge-module-modbus/gradle.properties index ffaf7b9d71..9099a5d005 100644 --- a/modules/hivemq-edge-module-modbus/gradle.properties +++ b/modules/hivemq-edge-module-modbus/gradle.properties @@ -12,6 +12,7 @@ guava.version=32.0.1-jre junit.jupiter.version=5.7.1 junit.jupiter.platform.version=1.7.1 mockito.version=4.7.0 +jsonUnit = 2.38.0 # logging slf4j.version=1.7.30 logback.version=1.2.3 diff --git a/modules/hivemq-edge-module-modbus/src/main/java/com/hivemq/edge/adapters/modbus/ModbusAdapterConfig.java b/modules/hivemq-edge-module-modbus/src/main/java/com/hivemq/edge/adapters/modbus/ModbusAdapterConfig.java index 0c39ff6357..8a7cee0389 100644 --- a/modules/hivemq-edge-module-modbus/src/main/java/com/hivemq/edge/adapters/modbus/ModbusAdapterConfig.java +++ b/modules/hivemq-edge-module-modbus/src/main/java/com/hivemq/edge/adapters/modbus/ModbusAdapterConfig.java @@ -69,6 +69,10 @@ public class ModbusAdapterConfig extends AbstractPollingProtocolAdapterConfig { public ModbusAdapterConfig() { } + public ModbusAdapterConfig(final @NotNull String adapterId) { + this.id = adapterId; + } + public boolean getPublishChangedDataOnly() { return publishChangedDataOnly; } diff --git a/modules/hivemq-edge-module-modbus/src/test/java/com/hivemq/edge/adapters/modbus/ModbusProtocolAdapterTest.java b/modules/hivemq-edge-module-modbus/src/test/java/com/hivemq/edge/adapters/modbus/ModbusProtocolAdapterTest.java new file mode 100644 index 0000000000..87fc7edece --- /dev/null +++ b/modules/hivemq-edge-module-modbus/src/test/java/com/hivemq/edge/adapters/modbus/ModbusProtocolAdapterTest.java @@ -0,0 +1,68 @@ +package com.hivemq.edge.adapters.modbus; + +import com.codahale.metrics.MetricRegistry; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableMap; +import com.hivemq.edge.adapters.modbus.model.ModBusData; +import com.hivemq.edge.modules.adapters.impl.ProtocolAdapterPublishBuilderImpl; +import com.hivemq.edge.modules.api.adapters.ModuleServices; +import com.hivemq.edge.modules.api.adapters.ProtocolAdapterPublishService; +import com.hivemq.edge.modules.api.events.EventService; +import com.hivemq.edge.modules.config.impl.AbstractProtocolAdapterConfig; +import com.hivemq.extension.sdk.api.annotations.NotNull; +import com.hivemq.mqtt.handler.publish.PublishReturnCode; +import com.hivemq.mqtt.message.publish.PUBLISH; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ModbusProtocolAdapterTest { + + private final @NotNull MetricRegistry metricRegistry = new MetricRegistry(); + private final @NotNull ModbusAdapterConfig adapterConfig = new ModbusAdapterConfig("adapterId"); + private final @NotNull ModbusProtocolAdapter adapter = + new ModbusProtocolAdapter(ModbusProtocolAdapterInformation.INSTANCE, adapterConfig, metricRegistry); + private final @NotNull ProtocolAdapterPublishService publishService = mock(ProtocolAdapterPublishService.class); + private final @NotNull ModuleServices moduleServices = mock(ModuleServices.class); + private final @NotNull ProtocolAdapterPublishBuilderImpl.SendCallback sendCallback = + mock(ProtocolAdapterPublishBuilderImpl.SendCallback.class); + private final @NotNull ArgumentCaptor publishArgumentCaptor = ArgumentCaptor.forClass(PUBLISH.class); + + @BeforeEach + void setUp() { + when(moduleServices.adapterPublishService()).thenReturn(publishService); + when(moduleServices.eventService()).thenReturn(mock(EventService.class)); + adapter.bindServices(moduleServices); + //noinspection unchecked + when(sendCallback.onPublishSend(publishArgumentCaptor.capture(), any(), any(ImmutableMap.class))).thenReturn( + CompletableFuture.completedFuture(PublishReturnCode.DELIVERED)); + final ProtocolAdapterPublishBuilderImpl protocolAdapterPublishBuilder = + new ProtocolAdapterPublishBuilderImpl("hivemq", sendCallback); + protocolAdapterPublishBuilder.withAdapter(adapter); + when(publishService.publish()).thenReturn(protocolAdapterPublishBuilder); + } + + @Test + void test_captureDataSample_expectedPayloadPresent() + throws ExecutionException, InterruptedException, JsonProcessingException { + final AbstractProtocolAdapterConfig.Subscription subscription = + new AbstractProtocolAdapterConfig.Subscription("topic", 2); + final ModBusData data = new ModBusData(subscription, ModBusData.TYPE.INPUT_REGISTERS); + data.addDataPoint("register", "hello world"); + + adapter.captureDataSample(data).get(); + + final String payloadAsString = new String(publishArgumentCaptor.getValue().getPayload()); + assertThatJson(payloadAsString).node("timestamp").isIntegralNumber(); + assertThatJson(payloadAsString).node("value").isString().isEqualTo("hello world"); + } + +}