diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityAttrNode.java index 0a650bf3d9..c11e6b8c2a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityAttrNode.java @@ -49,8 +49,8 @@ public abstract class TbAbstractGetEntityAttrNode extends Tb protected abstract ListenableFuture findEntityAsync(TbContext ctx, EntityId originator); - protected void checkIfMappingIsNotEmptyOrThrow(TbGetEntityAttrNodeConfiguration config) throws TbNodeException { - if (config.getAttrMapping().isEmpty()) { + protected void checkIfMappingIsNotEmptyOrElseThrow(Map attrMapping) throws TbNodeException { + if (attrMapping == null || attrMapping.isEmpty()) { throw new TbNodeException("At least one attribute mapping should be specified!"); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java index 0016234a91..e91bfd8410 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java @@ -22,11 +22,14 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.EntityDetails; import org.thingsboard.server.common.data.ContactBased; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import java.util.List; + import static org.thingsboard.common.util.DonAsynchron.withCallback; @Slf4j @@ -45,8 +48,8 @@ public abstract class TbAbstractGetEntityDetailsNode> getContactBasedFuture(TbContext ctx, TbMsg msg); - protected void checkIfDetailsListIsNotEmptyOrThrow(C configuration) throws TbNodeException { - if (configuration.getDetailsList().isEmpty()) { + protected void checkIfDetailsListIsNotEmptyOrElseThrow(List detailsList) throws TbNodeException { + if (detailsList == null || detailsList.isEmpty()) { throw new TbNodeException("No entity details selected!"); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index 5f5f73c01c..dc878ab8ad 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -45,7 +45,7 @@ public class TbGetCustomerAttributeNode extends TbAbstractGetEntityAttrNode> getContactBasedFuture(TbContext ctx, TbMsg msg) { + protected ListenableFuture getContactBasedFuture(TbContext ctx, TbMsg msg) { switch (msg.getOriginator().getEntityType()) { case DEVICE: return Futures.transformAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId())), diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index b535d050f4..50cd2a53d3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -46,7 +46,7 @@ public class TbGetRelatedAttributeNode extends TbAbstractGetEntityAttrNode> getContactBasedFuture(TbContext ctx, TbMsg msg) { + protected ListenableFuture getContactBasedFuture(TbContext ctx, TbMsg msg) { return ctx.getTenantService().findTenantByIdAsync(ctx.getTenantId(), ctx.getTenantId()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java index 35cdc42a54..8ab7088442 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java @@ -18,8 +18,9 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.EntityDetails; -import java.util.Collections; +import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @@ -28,7 +29,7 @@ public class TbGetTenantDetailsNodeConfiguration extends TbAbstractGetEntityDeta @Override public TbGetTenantDetailsNodeConfiguration defaultConfiguration() { var configuration = new TbGetTenantDetailsNodeConfiguration(); - configuration.setDetailsList(Collections.emptyList()); + configuration.setDetailsList(List.of(EntityDetails.STATE, EntityDetails.TITLE)); configuration.setFetchTo(FetchTo.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 0693a12526..3f45ae8d6d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -17,11 +17,13 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; 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.ArgumentMatcher; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; @@ -64,7 +66,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; @@ -77,8 +79,9 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @ExtendWith(MockitoExtension.class) public class TbGetCustomerAttributeNodeTest { - private static final EntityId DUMMY_ENTITY_ID = new DeviceId(UUID.randomUUID()); + private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final CustomerId CUSTOMER_ID = new CustomerId(UUID.randomUUID()); private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { @Override public ListenableFuture executeAsync(Callable task) { @@ -172,10 +175,9 @@ public class TbGetCustomerAttributeNodeTest { @Test public void givenEmptyAttributesMapping_whenInit_thenException() { - // SETUP + // GIVEN var expectedExceptionMessage = "At least one attribute mapping should be specified!"; - // GIVEN config.setAttrMapping(Collections.emptyMap()); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -191,7 +193,7 @@ public class TbGetCustomerAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -203,13 +205,12 @@ public class TbGetCustomerAttributeNodeTest { @Test public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { - // SETUP - var expectedExceptionMessage = "Entity with id: '" + DUMMY_ENTITY_ID + + // GIVEN + var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + "' specified in the configuration doesn't belong to the current tenant."; - // GIVEN - doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_ENTITY_ID); - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "{}"); + doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_DEVICE_ORIGINATOR); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); // WHEN var exception = assertThrows(RuntimeException.class, () -> node.onMsg(ctxMock, msg)); @@ -226,9 +227,12 @@ public class TbGetCustomerAttributeNodeTest { msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", userId, new TbMsgMetaData(), "{}"); - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getUserService()).thenReturn(userServiceMock); - doReturn(Futures.immediateFuture(null)).when(userServiceMock).findUserByIdAsync(any(), any()); + doReturn(Futures.immediateFuture(null)).when(userServiceMock).findUserByIdAsync(eq(TENANT_ID), eq(userId)); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN node.onMsg(ctxMock, msg); @@ -257,22 +261,26 @@ public class TbGetCustomerAttributeNodeTest { public void givenFetchAttributesToData_whenOnMsg_thenShouldFetchAttributesToData() { // GIVEN var device = new Device(new DeviceId(UUID.randomUUID())); - device.setCustomerId(new CustomerId(UUID.randomUUID())); + device.setCustomerId(CUSTOMER_ID); prepareMsgAndConfig(FetchTo.DATA, false, device.getId()); - when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); - doReturn(Futures.immediateFuture(device)).when(deviceServiceMock).findDeviceByIdAsync(any(), any()); - - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - List attributes = List.of( + List attributesList = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L) ); - when(attributesServiceMock.find(eq(TENANT_ID), eq(device.getCustomerId()), eq(SERVER_SCOPE), anyList())) - .thenReturn(Futures.immediateFuture(attributes)); + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + doReturn(Futures.immediateFuture(device)).when(deviceServiceMock).findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId())); + + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + .thenReturn(Futures.immediateFuture(attributesList)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -280,6 +288,7 @@ public class TbGetCustomerAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -299,22 +308,26 @@ public class TbGetCustomerAttributeNodeTest { public void givenFetchAttributesToMetaData_whenOnMsg_thenShouldFetchAttributesToMetaData() { // GIVEN var user = new User(new UserId(UUID.randomUUID())); - user.setCustomerId(new CustomerId(UUID.randomUUID())); + user.setCustomerId(CUSTOMER_ID); prepareMsgAndConfig(FetchTo.METADATA, false, user.getId()); - when(ctxMock.getUserService()).thenReturn(userServiceMock); - doReturn(Futures.immediateFuture(user)).when(userServiceMock).findUserByIdAsync(any(), any()); - - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - List attributes = List.of( + List attributesList = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L) ); - when(attributesServiceMock.find(eq(TENANT_ID), eq(user.getCustomerId()), eq(SERVER_SCOPE), anyList())) - .thenReturn(Futures.immediateFuture(attributes)); + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getUserService()).thenReturn(userServiceMock); + doReturn(Futures.immediateFuture(user)).when(userServiceMock).findUserByIdAsync(eq(TENANT_ID), eq(user.getId())); + + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + .thenReturn(Futures.immediateFuture(attributesList)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -322,6 +335,7 @@ public class TbGetCustomerAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -344,15 +358,19 @@ public class TbGetCustomerAttributeNodeTest { prepareMsgAndConfig(FetchTo.DATA, true, customer.getId()); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); - List timeseries = List.of( + List timeseriesList = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3")) ); - when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(customer.getId()), anyList())) - .thenReturn(Futures.immediateFuture(timeseries)); + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(customer.getId()), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + .thenReturn(Futures.immediateFuture(timeseriesList)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -360,6 +378,7 @@ public class TbGetCustomerAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -383,18 +402,22 @@ public class TbGetCustomerAttributeNodeTest { prepareMsgAndConfig(FetchTo.METADATA, true, asset.getId()); - when(ctxMock.getAssetService()).thenReturn(assetServiceMock); - doReturn(Futures.immediateFuture(asset)).when(assetServiceMock).findAssetByIdAsync(any(), any()); - - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); - List timeseries = List.of( + List timeseriesList = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3")) ); - when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(asset.getCustomerId()), anyList())) - .thenReturn(Futures.immediateFuture(timeseries)); + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getAssetService()).thenReturn(assetServiceMock); + doReturn(Futures.immediateFuture(asset)).when(assetServiceMock).findAssetByIdAsync(eq(TENANT_ID), eq(asset.getId())); + + when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(asset.getCustomerId()), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + .thenReturn(Futures.immediateFuture(timeseriesList)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -402,6 +425,7 @@ public class TbGetCustomerAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -417,7 +441,7 @@ public class TbGetCustomerAttributeNodeTest { assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } - private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId entityId) { + private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId originator) { config.setAttrMapping(Map.of( "sourceKey1", "targetKey1", "${metaDataPattern1}", "$[messageBodyPattern1]", @@ -427,11 +451,32 @@ public class TbGetCustomerAttributeNodeTest { node.config = config; node.fetchTo = fetchTo; + var msgMetaData = new TbMsgMetaData(); msgMetaData.putValue("metaDataPattern1", "sourceKey2"); msgMetaData.putValue("metaDataPattern2", "targetKey3"); + var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", entityId, msgMetaData, msgData); + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + } + + @RequiredArgsConstructor + private static class ListMatcher implements ArgumentMatcher> { + + private final List expectedList; + + @Override + public boolean matches(List actualList) { + if (actualList == expectedList) { + return true; + } + if (actualList.size() != expectedList.size()) { + return false; + } + return actualList.containsAll(expectedList); + } + } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index 58e3431af6..8428165ab9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -32,7 +32,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.device.DeviceService; @@ -55,7 +55,8 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class TbGetOriginatorFieldsNodeTest { - private static final EntityId DUMMY_ENTITY_ID = new DeviceId(UUID.randomUUID()); + private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); + private static final TenantId DUMMY_TENANT_ID = new TenantId(UUID.randomUUID()); private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { @Override public ListenableFuture executeAsync(Callable task) { @@ -74,7 +75,7 @@ public class TbGetOriginatorFieldsNodeTest { @Mock private TbContext ctxMock; @Mock - private DeviceService deviceService; + private DeviceService deviceServiceMock; private TbGetOriginatorFieldsNode node; private TbGetOriginatorFieldsConfiguration config; private TbNodeConfiguration nodeConfiguration; @@ -114,6 +115,7 @@ public class TbGetOriginatorFieldsNodeTest { "name", "originatorName", "type", "originatorType")); assertThat(config.isIgnoreNullStrings()).isEqualTo(false); + assertThat(config.getFetchTo()).isEqualTo(FetchTo.METADATA); assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); } @@ -138,6 +140,7 @@ public class TbGetOriginatorFieldsNodeTest { "sourceField2", "targetKey2", "sourceField3", "targetKey3")); assertThat(config.isIgnoreNullStrings()).isEqualTo(true); + assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA); assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); } @@ -145,7 +148,7 @@ public class TbGetOriginatorFieldsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_ENTITY_ID, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -158,12 +161,12 @@ public class TbGetOriginatorFieldsNodeTest { @Test public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { // SETUP - var expectedExceptionMessage = "Entity with id: '" + DUMMY_ENTITY_ID + + var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + "' specified in the configuration doesn't belong to the current tenant."; // GIVEN - doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_ENTITY_ID); - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_ENTITY_ID, new TbMsgMetaData(), "{}"); + doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_DEVICE_ORIGINATOR); + msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); // WHEN var exception = assertThrows(RuntimeException.class, () -> node.onMsg(ctxMock, msg)); @@ -177,7 +180,7 @@ public class TbGetOriginatorFieldsNodeTest { public void givenValidMsgAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchToData() { // GIVEN var device = new Device(); - device.setId((DeviceId) DUMMY_ENTITY_ID); + device.setId(DUMMY_DEVICE_ORIGINATOR); device.setName("Test device"); device.setType("Test device type"); @@ -192,10 +195,11 @@ public class TbGetOriginatorFieldsNodeTest { node.fetchTo = FetchTo.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, msgMetaData, msgData); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); - when(ctxMock.getDeviceService()).thenReturn(deviceService); - when(deviceService.findDeviceByIdAsync(any(), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); + when(deviceServiceMock.findDeviceByIdAsync(eq(DUMMY_TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -217,7 +221,7 @@ public class TbGetOriginatorFieldsNodeTest { public void givenValidMsgAndFetchToMetaData_whenOnMsg_thenShouldTellSuccessAndFetchToMetaData() { // GIVEN var device = new Device(); - device.setId((DeviceId) DUMMY_ENTITY_ID); + device.setId(DUMMY_DEVICE_ORIGINATOR); device.setName("Test device"); device.setType("Test device type"); @@ -234,10 +238,11 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, msgMetaData, msgData); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); - when(ctxMock.getDeviceService()).thenReturn(deviceService); - when(deviceService.findDeviceByIdAsync(any(), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); + when(deviceServiceMock.findDeviceByIdAsync(eq(DUMMY_TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -264,7 +269,7 @@ public class TbGetOriginatorFieldsNodeTest { public void givenNullEntityFieldsAndIgnoreNullStringsFalse_whenOnMsg_thenShouldTellSuccessAndFetchNullField() { // GIVEN var device = new Device(); - device.setId((DeviceId) DUMMY_ENTITY_ID); + device.setId(DUMMY_DEVICE_ORIGINATOR); device.setName("Test device"); device.setType("Test device type"); @@ -281,10 +286,11 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, msgMetaData, msgData); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); - when(ctxMock.getDeviceService()).thenReturn(deviceService); - when(deviceService.findDeviceByIdAsync(any(), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); + when(deviceServiceMock.findDeviceByIdAsync(eq(DUMMY_TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -311,7 +317,6 @@ public class TbGetOriginatorFieldsNodeTest { @Test public void givenEmptyFieldsMapping_whenInit_thenException() { // GIVEN - config = config.defaultConfiguration(); config.setFieldsMapping(Collections.emptyMap()); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index 0fa3b6b367..07b25b15e8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -17,11 +17,13 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; 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.ArgumentMatcher; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; @@ -31,13 +33,16 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -66,7 +71,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; @@ -79,7 +84,7 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @ExtendWith(MockitoExtension.class) public class TbGetRelatedAttributeNodeTest { - private static final EntityId DUMMY_ENTITY_ID = new DeviceId(UUID.randomUUID()); + private static final EntityId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { @Override @@ -192,10 +197,9 @@ public class TbGetRelatedAttributeNodeTest { @Test public void givenEmptyAttributesMapping_whenInit_thenException() { - // SETUP + // GIVEN var expectedExceptionMessage = "At least one attribute mapping should be specified!"; - // GIVEN config.setAttrMapping(Collections.emptyMap()); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -211,7 +215,7 @@ public class TbGetRelatedAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -223,13 +227,12 @@ public class TbGetRelatedAttributeNodeTest { @Test public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { - // SETUP - var expectedExceptionMessage = "Entity with id: '" + DUMMY_ENTITY_ID + + // GIVEN + var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + "' specified in the configuration doesn't belong to the current tenant."; - // GIVEN - doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_ENTITY_ID); - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "{}"); + doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_DEVICE_ORIGINATOR); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); // WHEN var exception = assertThrows(RuntimeException.class, () -> node.onMsg(ctxMock, msg)); @@ -242,11 +245,14 @@ public class TbGetRelatedAttributeNodeTest { @Test public void givenDidNotFindEntity_whenOnMsg_thenShouldTellFailure() { // GIVEN - prepareMsgAndConfig(FetchTo.METADATA, false, DUMMY_ENTITY_ID); + prepareMsgAndConfig(FetchTo.METADATA, false, DUMMY_DEVICE_ORIGINATOR); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getRelationService()).thenReturn(relationServiceMock); - doReturn(Futures.immediateFuture(null)).when(relationServiceMock).findByQuery(any(), any()); + doReturn(Futures.immediateFuture(null)).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN node.onMsg(ctxMock, msg); @@ -275,25 +281,28 @@ public class TbGetRelatedAttributeNodeTest { var customer = new Customer(new CustomerId(UUID.randomUUID())); var user = new User(new UserId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.DATA, false, user.getId()); + prepareMsgAndConfig(FetchTo.DATA, false, customer.getId()); - entityRelation.setFrom(user.getId()); - entityRelation.setTo(customer.getId()); + entityRelation.setFrom(customer.getId()); + entityRelation.setTo(user.getId()); entityRelation.setType(EntityRelation.CONTAINS_TYPE); - when(ctxMock.getRelationService()).thenReturn(relationServiceMock); - doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); - - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - List attributes = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L) ); - when(attributesServiceMock.find(eq(TENANT_ID), eq(customer.getId()), eq(SERVER_SCOPE), anyList())) + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); + + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(eq(TENANT_ID), eq(user.getId()), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributes)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -301,6 +310,7 @@ public class TbGetRelatedAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -319,26 +329,31 @@ public class TbGetRelatedAttributeNodeTest { @Test public void givenFetchAttributesToMetaData_whenOnMsg_thenShouldFetchAttributesToMetaData() { // GIVEN - var customer = new Customer(new CustomerId(UUID.randomUUID())); + var firstCustomer = new Customer(new CustomerId(UUID.randomUUID())); + var secondCustomer = new Customer(new CustomerId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.METADATA, false, customer.getId()); + prepareMsgAndConfig(FetchTo.METADATA, false, firstCustomer.getId()); - entityRelation.setFrom(customer.getId()); - entityRelation.setTo(customer.getId()); - entityRelation.setType(EntityRelation.CONTAINS_TYPE); - - when(ctxMock.getRelationService()).thenReturn(relationServiceMock); - doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); + entityRelation.setFrom(firstCustomer.getId()); + entityRelation.setTo(secondCustomer.getId()); + entityRelation.setType(EntityRelation.MANAGES_TYPE); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); List attributes = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L) ); - when(attributesServiceMock.find(eq(TENANT_ID), eq(customer.getId()), eq(SERVER_SCOPE), anyList())) + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); + + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(eq(TENANT_ID), eq(secondCustomer.getId()), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributes)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -346,6 +361,7 @@ public class TbGetRelatedAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -364,27 +380,31 @@ public class TbGetRelatedAttributeNodeTest { @Test public void givenFetchTelemetryToData_whenOnMsg_thenShouldFetchTelemetryToData() { // GIVEN - var customer = new Customer(new CustomerId(UUID.randomUUID())); - var asset = new Asset(new AssetId(UUID.randomUUID())); + var dashboard = new Dashboard(new DashboardId(UUID.randomUUID())); + var entityView = new EntityView(new EntityViewId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.DATA, true, asset.getId()); + prepareMsgAndConfig(FetchTo.DATA, true, dashboard.getId()); - entityRelation.setFrom(asset.getId()); - entityRelation.setTo(customer.getId()); + entityRelation.setFrom(dashboard.getId()); + entityRelation.setTo(entityView.getId()); entityRelation.setType(EntityRelation.CONTAINS_TYPE); - when(ctxMock.getRelationService()).thenReturn(relationServiceMock); - doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); - - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); List timeseries = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3")) ); - when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(customer.getId()), anyList())) + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); + + when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(entityView.getId()), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(timeseries)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -392,6 +412,7 @@ public class TbGetRelatedAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -410,27 +431,31 @@ public class TbGetRelatedAttributeNodeTest { @Test public void givenFetchTelemetryToMetaData_whenOnMsg_thenShouldFetchTelemetryToMetaData() { // GIVEN - var customer = new Customer(new CustomerId(UUID.randomUUID())); + var tenant = new Tenant(new TenantId(UUID.randomUUID())); var device = new Device(new DeviceId(UUID.randomUUID())); - prepareMsgAndConfig(FetchTo.METADATA, true, device.getId()); + prepareMsgAndConfig(FetchTo.METADATA, true, tenant.getId()); - entityRelation.setFrom(device.getId()); - entityRelation.setTo(customer.getId()); + entityRelation.setFrom(tenant.getId()); + entityRelation.setTo(device.getId()); entityRelation.setType(EntityRelation.CONTAINS_TYPE); - when(ctxMock.getRelationService()).thenReturn(relationServiceMock); - doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); - - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); List timeseries = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3")) ); - when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(customer.getId()), anyList())) + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(tenant.getId()); + + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(tenant.getId()), any()); + + when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + when(timeseriesServiceMock.findLatest(eq(tenant.getId()), eq(device.getId()), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(timeseries)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -438,6 +463,7 @@ public class TbGetRelatedAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -453,7 +479,7 @@ public class TbGetRelatedAttributeNodeTest { assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } - private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId entityId) { + private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId originator) { config.setAttrMapping(Map.of( "sourceKey1", "targetKey1", "${metaDataPattern1}", "$[messageBodyPattern1]", @@ -463,11 +489,32 @@ public class TbGetRelatedAttributeNodeTest { node.config = config; node.fetchTo = fetchTo; + var msgMetaData = new TbMsgMetaData(); msgMetaData.putValue("metaDataPattern1", "sourceKey2"); msgMetaData.putValue("metaDataPattern2", "targetKey3"); + var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", entityId, msgMetaData, msgData); + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + } + + @RequiredArgsConstructor + private static class ListMatcher implements ArgumentMatcher> { + + private final List expectedList; + + @Override + public boolean matches(List actualList) { + if (actualList == expectedList) { + return true; + } + if (actualList.size() != expectedList.size()) { + return false; + } + return actualList.containsAll(expectedList); + } + } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index 6b7509d0d0..76af5fabb4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -17,11 +17,13 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; 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.ArgumentMatcher; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; @@ -53,7 +55,7 @@ import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -65,7 +67,7 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @ExtendWith(MockitoExtension.class) public class TbGetTenantAttributeNodeTest { - private static final EntityId DUMMY_ENTITY_ID = new DeviceId(UUID.randomUUID()); + private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { @Override @@ -154,10 +156,9 @@ public class TbGetTenantAttributeNodeTest { @Test public void givenEmptyAttributesMapping_whenInit_thenException() { - // SETUP + // GIVEN var expectedExceptionMessage = "At least one attribute mapping should be specified!"; - // GIVEN config.setAttrMapping(Collections.emptyMap()); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -173,7 +174,7 @@ public class TbGetTenantAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -185,13 +186,12 @@ public class TbGetTenantAttributeNodeTest { @Test public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { - // SETUP - var expectedExceptionMessage = "Entity with id: '" + DUMMY_ENTITY_ID + + // GIVEN + var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + "' specified in the configuration doesn't belong to the current tenant."; - // GIVEN - doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_ENTITY_ID); - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "{}"); + doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_DEVICE_ORIGINATOR); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); // WHEN var exception = assertThrows(RuntimeException.class, () -> node.onMsg(ctxMock, msg)); @@ -208,15 +208,19 @@ public class TbGetTenantAttributeNodeTest { prepareMsgAndConfig(FetchTo.DATA, false, deviceId); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - List attributes = List.of( + List attributesList = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L) ); - when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), anyList())) - .thenReturn(Futures.immediateFuture(attributes)); + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + .thenReturn(Futures.immediateFuture(attributesList)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -224,6 +228,7 @@ public class TbGetTenantAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -244,15 +249,19 @@ public class TbGetTenantAttributeNodeTest { // GIVEN prepareMsgAndConfig(FetchTo.METADATA, false, TENANT_ID); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - List attributes = List.of( + List attributesList = List.of( new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L), new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L) ); - when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), anyList())) - .thenReturn(Futures.immediateFuture(attributes)); + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + .thenReturn(Futures.immediateFuture(attributesList)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -260,6 +269,7 @@ public class TbGetTenantAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -282,15 +292,19 @@ public class TbGetTenantAttributeNodeTest { prepareMsgAndConfig(FetchTo.DATA, true, customerId); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); List timeseries = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3")) ); - when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(TENANT_ID), anyList())) + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(TENANT_ID), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(timeseries)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -298,6 +312,7 @@ public class TbGetTenantAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -320,15 +335,19 @@ public class TbGetTenantAttributeNodeTest { prepareMsgAndConfig(FetchTo.METADATA, true, ruleChainId); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); List timeseries = List.of( new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")), new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3")) ); - when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(TENANT_ID), anyList())) + var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3"); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(TENANT_ID), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(timeseries)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN @@ -336,6 +355,7 @@ public class TbGetTenantAttributeNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); @@ -351,7 +371,7 @@ public class TbGetTenantAttributeNodeTest { assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } - private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId entityId) { + private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId originator) { config.setAttrMapping(Map.of( "sourceKey1", "targetKey1", "${metaDataPattern1}", "$[messageBodyPattern1]", @@ -361,11 +381,32 @@ public class TbGetTenantAttributeNodeTest { node.config = config; node.fetchTo = fetchTo; + var msgMetaData = new TbMsgMetaData(); msgMetaData.putValue("metaDataPattern1", "sourceKey2"); msgMetaData.putValue("metaDataPattern2", "targetKey3"); + var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", entityId, msgMetaData, msgData); + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + } + + @RequiredArgsConstructor + private static class ListMatcher implements ArgumentMatcher> { + + private final List expectedList; + + @Override + public boolean matches(List actualList) { + if (actualList == expectedList) { + return true; + } + if (actualList.size() != expectedList.size()) { + return false; + } + return actualList.containsAll(expectedList); + } + } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java new file mode 100644 index 0000000000..5611e36d4b --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -0,0 +1,482 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.metadata; + +import com.google.common.util.concurrent.Futures; +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.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.EntityDetails; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.tenant.TenantService; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class TbGetTenantDetailsNodeTest { + + private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); + @Mock + private TbContext ctxMock; + @Mock + private TenantService tenantServiceMock; + private TbGetTenantDetailsNode node; + private TbGetTenantDetailsNodeConfiguration config; + private TbNodeConfiguration nodeConfiguration; + private TbMsg msg; + + @BeforeEach + public void setUp() { + node = new TbGetTenantDetailsNode(); + config = new TbGetTenantDetailsNodeConfiguration().defaultConfiguration(); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + } + + @Test + public void givenConfigWithNullFetchTo_whenInit_thenException() { + // GIVEN + config.setFetchTo(null); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); + + // THEN + assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { + // GIVEN + + // WHEN + node.init(ctxMock, nodeConfiguration); + + // THEN + assertThat(node.config).isEqualTo(config); + assertThat(config.getDetailsList()).isEqualTo(List.of(EntityDetails.STATE, EntityDetails.TITLE)); + assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA); + assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); + } + + @Test + public void givenCustomConfig_whenInit_thenOK() throws TbNodeException { + // GIVEN + config.setDetailsList(List.of(EntityDetails.ID, EntityDetails.PHONE)); + config.setFetchTo(FetchTo.METADATA); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + // WHEN + node.init(ctxMock, nodeConfiguration); + + // THEN + assertThat(node.config).isEqualTo(config); + assertThat(config.getDetailsList()).isEqualTo(List.of(EntityDetails.ID, EntityDetails.PHONE)); + assertThat(config.getFetchTo()).isEqualTo(FetchTo.METADATA); + assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); + } + + @Test + public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { + // GIVEN + node.fetchTo = FetchTo.DATA; + msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + + // WHEN + var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { + // SETUP + var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + + "' specified in the configuration doesn't belong to the current tenant."; + + // GIVEN + doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_DEVICE_ORIGINATOR); + msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); + + // WHEN + var exception = assertThrows(RuntimeException.class, () -> node.onMsg(ctxMock, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void givenAllEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchAllToData() { + // GIVEN + var tenant = new Tenant(); + tenant.setId(new TenantId(UUID.randomUUID())); + tenant.setTitle("Tenant title"); + tenant.setCountry("Tenant country"); + tenant.setCity("Tenant city"); + tenant.setState("Tenant state"); + tenant.setZip("123456"); + tenant.setAddress("Tenant address 1"); + tenant.setAddress2("Tenant address 2"); + tenant.setPhone("+123456789"); + tenant.setEmail("email@tenant.com"); + tenant.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":\"Tenant description\"}")); + + var device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + device.setTenantId(tenant.getId()); + device.setName("Test device"); + device.setType("Test device type"); + + config.setDetailsList(List.of(EntityDetails.values())); + config.setFetchTo(FetchTo.DATA); + + node.config = config; + node.fetchTo = FetchTo.DATA; + + var msgMetaData = new TbMsgMetaData(); + msgMetaData.putValue("metaKey1", "metaValue1"); + msgMetaData.putValue("metaKey2", "metaValue2"); + + var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", device.getId(), msgMetaData, msgData); + + when(ctxMock.getTenantId()).thenReturn(tenant.getId()); + + when(ctxMock.getTenantService()).thenReturn(tenantServiceMock); + when(tenantServiceMock.findTenantByIdAsync(eq(tenant.getId()), eq(tenant.getId()))).thenReturn(Futures.immediateFuture(tenant)); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + + var expectedMsgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"," + + "\"tenant_id\":\"" + tenant.getId() + "\"," + + "\"tenant_title\":\"" + tenant.getTitle() + "\"," + + "\"tenant_country\":\"" + tenant.getCountry() + "\"," + + "\"tenant_city\":\"" + tenant.getCity() + "\"," + + "\"tenant_state\":\"" + tenant.getState() + "\"," + + "\"tenant_zip\":\"" + tenant.getZip() + "\"," + + "\"tenant_address\":\"" + tenant.getAddress() + "\"," + + "\"tenant_address2\":\"" + tenant.getAddress2() + "\"," + + "\"tenant_phone\":\"" + tenant.getPhone() + "\"," + + "\"tenant_email\":\"" + tenant.getEmail() + "\"," + + "\"tenant_additionalInfo\":\"" + tenant.getAdditionalInfo().get("description").asText() + "\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); + } + + @Test + public void givenSomeEntityDetailsAndFetchToMetadata_whenOnMsg_thenShouldTellSuccessAndFetchSomeToMetaData() { + // GIVEN + var tenant = new Tenant(); + tenant.setId(new TenantId(UUID.randomUUID())); + tenant.setTitle("Tenant title"); + tenant.setCountry("Tenant country"); + tenant.setCity("Tenant city"); + tenant.setState("Tenant state"); + tenant.setZip("123456"); + tenant.setAddress("Tenant address 1"); + tenant.setAddress2("Tenant address 2"); + tenant.setPhone("+123456789"); + tenant.setEmail("email@tenant.com"); + tenant.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":\"Tenant description\"}")); + + var device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + device.setTenantId(tenant.getId()); + device.setName("Test device"); + device.setType("Test device type"); + + config.setDetailsList(List.of(EntityDetails.ID, EntityDetails.TITLE, EntityDetails.PHONE)); + config.setFetchTo(FetchTo.METADATA); + + node.config = config; + node.fetchTo = FetchTo.METADATA; + + var msgMetaData = new TbMsgMetaData(); + msgMetaData.putValue("metaKey1", "metaValue1"); + msgMetaData.putValue("metaKey2", "metaValue2"); + + var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", device.getId(), msgMetaData, msgData); + + when(ctxMock.getTenantId()).thenReturn(tenant.getId()); + + when(ctxMock.getTenantService()).thenReturn(tenantServiceMock); + when(tenantServiceMock.findTenantByIdAsync(eq(tenant.getId()), eq(tenant.getId()))).thenReturn(Futures.immediateFuture(tenant)); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + + var expectedMsgMetaData = new TbMsgMetaData(msgMetaData.getData()); + expectedMsgMetaData.putValue("tenant_id", tenant.getId().getId().toString()); + expectedMsgMetaData.putValue("tenant_title", tenant.getTitle()); + expectedMsgMetaData.putValue("tenant_phone", tenant.getPhone()); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); + } + + @Test + public void givenNoEntityDetailsAndFetchToMetadata_whenOnMsg_thenShouldTellSuccessAndFetchNothingToMetaData() { + // GIVEN + var tenant = new Tenant(); + tenant.setId(new TenantId(UUID.randomUUID())); + tenant.setTitle("Tenant title"); + tenant.setCountry("Tenant country"); + tenant.setCity("Tenant city"); + tenant.setState("Tenant state"); + tenant.setZip("123456"); + tenant.setAddress("Tenant address 1"); + tenant.setAddress2("Tenant address 2"); + tenant.setPhone("+123456789"); + tenant.setEmail("email@tenant.com"); + tenant.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":\"Tenant description\"}")); + + var device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + device.setTenantId(tenant.getId()); + device.setName("Test device"); + device.setType("Test device type"); + + config.setDetailsList(Collections.emptyList()); + config.setFetchTo(FetchTo.METADATA); + + node.config = config; + node.fetchTo = FetchTo.METADATA; + + var msgMetaData = new TbMsgMetaData(); + msgMetaData.putValue("metaKey1", "metaValue1"); + msgMetaData.putValue("metaKey2", "metaValue2"); + + var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", device.getId(), msgMetaData, msgData); + + when(ctxMock.getTenantId()).thenReturn(tenant.getId()); + + when(ctxMock.getTenantService()).thenReturn(tenantServiceMock); + when(tenantServiceMock.findTenantByIdAsync(eq(tenant.getId()), eq(tenant.getId()))).thenReturn(Futures.immediateFuture(tenant)); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); + } + + @Test + public void givenNotPresentEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { + // GIVEN + var tenant = new Tenant(); + tenant.setId(new TenantId(UUID.randomUUID())); + tenant.setTitle("Tenant title"); + tenant.setCountry("Tenant country"); + tenant.setCity("Tenant city"); + tenant.setState("Tenant state"); + tenant.setPhone("+123456789"); + tenant.setEmail("email@tenant.com"); + tenant.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":\"Tenant description\"}")); + + var device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + device.setTenantId(tenant.getId()); + device.setName("Test device"); + device.setType("Test device type"); + + config.setDetailsList(List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2)); + config.setFetchTo(FetchTo.DATA); + + node.config = config; + node.fetchTo = FetchTo.DATA; + + var msgMetaData = new TbMsgMetaData(); + msgMetaData.putValue("metaKey1", "metaValue1"); + msgMetaData.putValue("metaKey2", "metaValue2"); + + var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", device.getId(), msgMetaData, msgData); + + when(ctxMock.getTenantId()).thenReturn(tenant.getId()); + + when(ctxMock.getTenantService()).thenReturn(tenantServiceMock); + when(tenantServiceMock.findTenantByIdAsync(eq(tenant.getId()), eq(tenant.getId()))).thenReturn(Futures.immediateFuture(tenant)); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); + } + + @Test + public void givenDidNotFindTenant_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { + // GIVEN + var tenant = new Tenant(); + tenant.setId(new TenantId(UUID.randomUUID())); + tenant.setTitle("Tenant title"); + tenant.setCountry("Tenant country"); + tenant.setCity("Tenant city"); + tenant.setState("Tenant state"); + tenant.setPhone("+123456789"); + tenant.setEmail("email@tenant.com"); + tenant.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":\"Tenant description\"}")); + + var device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + device.setTenantId(tenant.getId()); + device.setName("Test device"); + device.setType("Test device type"); + + config.setDetailsList(List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2)); + config.setFetchTo(FetchTo.DATA); + + node.config = config; + node.fetchTo = FetchTo.DATA; + + var msgMetaData = new TbMsgMetaData(); + msgMetaData.putValue("metaKey1", "metaValue1"); + msgMetaData.putValue("metaKey2", "metaValue2"); + + var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", device.getId(), msgMetaData, msgData); + + when(ctxMock.getTenantId()).thenReturn(tenant.getId()); + + when(ctxMock.getTenantService()).thenReturn(tenantServiceMock); + when(tenantServiceMock.findTenantByIdAsync(eq(tenant.getId()), eq(tenant.getId()))).thenReturn(Futures.immediateFuture(null)); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); + } + + @Test + public void givenNullDescription_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { + // GIVEN + var tenant = new Tenant(); + tenant.setId(new TenantId(UUID.randomUUID())); + tenant.setTitle("Tenant title"); + tenant.setCountry("Tenant country"); + tenant.setCity("Tenant city"); + tenant.setState("Tenant state"); + tenant.setPhone("+123456789"); + tenant.setEmail("email@tenant.com"); + tenant.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":null}")); + + var device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + device.setTenantId(tenant.getId()); + device.setName("Test device"); + device.setType("Test device type"); + + config.setDetailsList(List.of(EntityDetails.ADDITIONAL_INFO)); + config.setFetchTo(FetchTo.DATA); + + node.config = config; + node.fetchTo = FetchTo.DATA; + + var msgMetaData = new TbMsgMetaData(); + msgMetaData.putValue("metaKey1", "metaValue1"); + msgMetaData.putValue("metaKey2", "metaValue2"); + + var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", device.getId(), msgMetaData, msgData); + + when(ctxMock.getTenantId()).thenReturn(tenant.getId()); + + when(ctxMock.getTenantService()).thenReturn(tenantServiceMock); + when(tenantServiceMock.findTenantByIdAsync(eq(tenant.getId()), eq(tenant.getId()))).thenReturn(Futures.immediateFuture(tenant)); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); + } + +}