From 58f51455646297a7a41c756ac09738b5f86e026d Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 5 Apr 2023 11:55:29 +0300 Subject: [PATCH 01/29] Refactor: rewrite old tests and add some new --- .../engine/metadata/CalculateDeltaNode.java | 13 +- .../CalculateDeltaNodeConfiguration.java | 12 +- .../rule/engine/metadata/FetchTo.java | 2 + .../metadata/TbAbstractGetAttributesNode.java | 2 + .../metadata/TbAbstractNodeWithFetchTo.java | 2 +- .../TbFetchDeviceCredentialsNode.java | 1 - ...tchDeviceCredentialsNodeConfiguration.java | 2 + .../engine/metadata/TbGetAttributesNode.java | 2 + .../metadata/TbGetOriginatorFieldsNode.java | 3 +- .../TbGetTenantDetailsNodeConfiguration.java | 2 +- .../util/EntitiesCustomerIdAsyncLoader.java | 25 +- .../EntitiesRelatedDeviceIdAsyncLoader.java | 49 +- .../EntitiesRelatedEntityIdAsyncLoader.java | 39 +- ...va => OldTbAbstractAttributeNodeTest.java} | 90 ++-- .../OldTbGetCustomerAttributeNodeTest.java | 184 +++++++ .../OldTbGetRelatedAttributeNodeTest.java | 235 +++++++++ .../OldTbGetTenantAttributeNodeTest.java | 177 +++++++ .../TbFetchDeviceCredentialsNodeTest.java | 115 ++-- ...Test.java => TbGetAttributesNodeTest.java} | 281 +++++----- .../TbGetCustomerAttributeNodeTest.java | 421 ++++++++++++--- .../TbGetOriginatorFieldsNodeTest.java | 52 +- .../TbGetRelatedAttributeNodeTest.java | 490 +++++++++++++----- .../TbGetTenantAttributeNodeTest.java | 370 +++++++++---- .../EntitiesCustomerIdAsyncLoaderTest.java | 134 +++++ .../util/EntitiesFieldsAsyncLoaderTest.java | 44 +- .../rule/engine/util/TenantIdLoaderTest.java | 12 +- 26 files changed, 2108 insertions(+), 651 deletions(-) rename rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/{TbAbstractAttributeNodeTest.java => OldTbAbstractAttributeNodeTest.java} (71%) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetRelatedAttributeNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetTenantAttributeNodeTest.java rename rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/{TbAbstractGetAttributesNodeTest.java => TbGetAttributesNodeTest.java} (53%) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 55100f0e8d..080344c8d3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; @@ -34,7 +35,6 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.common.util.JacksonUtil; import java.math.BigDecimal; import java.math.RoundingMode; @@ -54,6 +54,7 @@ import java.util.concurrent.ConcurrentHashMap; uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeCalculateDeltaConfig") public class CalculateDeltaNode implements TbNode { + private Map cache; private CalculateDeltaNodeConfiguration config; private TbContext ctx; @@ -94,7 +95,6 @@ public class CalculateDeltaNode implements TbNode { return; } - if (config.getRound() != null) { delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP); } @@ -181,4 +181,5 @@ public class CalculateDeltaNode implements TbNode { this.value = value; } } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java index 6e7b77ad93..2dd66ed5ae 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.NodeConfiguration; @Data @JsonIgnoreProperties(ignoreUnknown = true) public class CalculateDeltaNodeConfiguration implements NodeConfiguration { + private String inputValueKey; private String outputValueKey; private boolean useCache; @@ -32,13 +33,14 @@ public class CalculateDeltaNodeConfiguration implements NodeConfiguration extends TbAbstractNodeWithFetchTo { + private static final String VALUE = "value"; private static final String TS = "ts"; private boolean isTellFailureIfAbsent; @@ -188,4 +189,5 @@ public abstract class TbAbstractGetAttributesNode { + @Override public TbFetchDeviceCredentialsNodeConfiguration defaultConfiguration() { var configuration = new TbFetchDeviceCredentialsNodeConfiguration(); configuration.setFetchTo(FetchTo.METADATA); return configuration; } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index ee588fa123..4d306e93ed 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -42,6 +42,7 @@ import org.thingsboard.server.common.msg.TbMsg; uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") public class TbGetAttributesNode extends TbAbstractGetAttributesNode { + @Override protected TbGetAttributesNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { return TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class); @@ -51,4 +52,5 @@ public class TbGetAttributesNode extends TbAbstractGetAttributesNode findEntityIdAsync(TbContext ctx, TbMsg msg) { return Futures.immediateFuture(msg.getOriginator()); } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java index f520425cbc..691ca232a8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java @@ -50,7 +50,7 @@ public class TbGetOriginatorFieldsNode extends TbAbstractNodeWithFetchTo * 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 - * + *

+ * 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. @@ -29,23 +29,24 @@ import org.thingsboard.server.common.data.id.UserId; public class EntitiesCustomerIdAsyncLoader { - public static ListenableFuture findEntityIdAsync(TbContext ctx, EntityId original) { - switch (original.getEntityType()) { + public static ListenableFuture findEntityIdAsync(TbContext ctx, EntityId originator) { + switch (originator.getEntityType()) { case CUSTOMER: - return Futures.immediateFuture((CustomerId) original); + return Futures.immediateFuture((CustomerId) originator); case USER: - return getCustomerAsync(ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), (UserId) original)); + return toCustomerIdAsync(ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), (UserId) originator)); case ASSET: - return getCustomerAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), (AssetId) original)); + return toCustomerIdAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), (AssetId) originator)); case DEVICE: - return getCustomerAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), (DeviceId) original)); + return toCustomerIdAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), (DeviceId) originator)); default: - return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType())); + return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType: " + originator.getEntityType())); } } - private static ListenableFuture getCustomerAsync(ListenableFuture future) { + private static ListenableFuture toCustomerIdAsync(ListenableFuture future) { return Futures.transformAsync(future, in -> in != null ? Futures.immediateFuture(in.getCustomerId()) : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java index 0ea43026d1..efb4e856fb 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -21,35 +21,42 @@ import com.google.common.util.concurrent.MoreExecutors; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.DeviceRelationsQuery; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; -import org.thingsboard.server.dao.device.DeviceService; - -import java.util.List; public class EntitiesRelatedDeviceIdAsyncLoader { - public static ListenableFuture findDeviceAsync(TbContext ctx, EntityId originator, - DeviceRelationsQuery deviceRelationsQuery) { - DeviceService deviceService = ctx.getDeviceService(); - DeviceSearchQuery query = buildQuery(originator, deviceRelationsQuery); - - ListenableFuture> asyncDevices = deviceService.findDevicesByQuery(ctx.getTenantId(), query); - - return Futures.transformAsync(asyncDevices, d -> CollectionUtils.isNotEmpty(d) ? Futures.immediateFuture(d.get(0).getId()) - : Futures.immediateFuture(null), MoreExecutors.directExecutor()); + public static ListenableFuture findDeviceAsync( + TbContext ctx, + EntityId originator, + DeviceRelationsQuery deviceRelationsQuery + ) { + var deviceService = ctx.getDeviceService(); + var query = buildQuery(originator, deviceRelationsQuery); + var devicesListFuture = deviceService.findDevicesByQuery(ctx.getTenantId(), query); + return Futures.transformAsync(devicesListFuture, + deviceList -> CollectionUtils.isNotEmpty(deviceList) ? + Futures.immediateFuture(deviceList.get(0).getId()) + : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } - private static DeviceSearchQuery buildQuery(EntityId originator, DeviceRelationsQuery deviceRelationsQuery) { - DeviceSearchQuery query = new DeviceSearchQuery(); - RelationsSearchParameters parameters = new RelationsSearchParameters(originator, - deviceRelationsQuery.getDirection(), deviceRelationsQuery.getMaxLevel(), deviceRelationsQuery.isFetchLastLevelOnly()); + private static DeviceSearchQuery buildQuery( + EntityId originator, + DeviceRelationsQuery deviceRelationsQuery + ) { + var query = new DeviceSearchQuery(); + var parameters = new RelationsSearchParameters( + originator, + deviceRelationsQuery.getDirection(), + deviceRelationsQuery.getMaxLevel(), + deviceRelationsQuery.isFetchLastLevelOnly() + ); query.setParameters(parameters); query.setRelationType(deviceRelationsQuery.getRelationType()); query.setDeviceTypes(deviceRelationsQuery.getDeviceTypes()); return query; } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java index c80e709072..be44c85f56 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java @@ -22,37 +22,44 @@ import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; -import org.thingsboard.server.dao.relation.RelationService; - -import java.util.List; public class EntitiesRelatedEntityIdAsyncLoader { - public static ListenableFuture findEntityAsync(TbContext ctx, EntityId originator, - RelationsQuery relationsQuery) { - RelationService relationService = ctx.getRelationService(); - EntityRelationsQuery query = buildQuery(originator, relationsQuery); - ListenableFuture> asyncRelation = relationService.findByQuery(ctx.getTenantId(), query); + public static ListenableFuture findEntityAsync( + TbContext ctx, + EntityId originator, + RelationsQuery relationsQuery + ) { + var relationService = ctx.getRelationService(); + var query = buildQuery(originator, relationsQuery); + var relationListFuture = relationService.findByQuery(ctx.getTenantId(), query); if (relationsQuery.getDirection() == EntitySearchDirection.FROM) { - return Futures.transformAsync(asyncRelation, r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) - : Futures.immediateFuture(null), MoreExecutors.directExecutor()); + return Futures.transformAsync(relationListFuture, + relationList -> CollectionUtils.isNotEmpty(relationList) ? + Futures.immediateFuture(relationList.get(0).getTo()) + : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) { - return Futures.transformAsync(asyncRelation, r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) - : Futures.immediateFuture(null), MoreExecutors.directExecutor()); + return Futures.transformAsync(relationListFuture, + relationList -> CollectionUtils.isNotEmpty(relationList) ? + Futures.immediateFuture(relationList.get(0).getFrom()) + : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); } private static EntityRelationsQuery buildQuery(EntityId originator, RelationsQuery relationsQuery) { - EntityRelationsQuery query = new EntityRelationsQuery(); - RelationsSearchParameters parameters = new RelationsSearchParameters(originator, - relationsQuery.getDirection(), relationsQuery.getMaxLevel(), relationsQuery.isFetchLastLevelOnly()); + var query = new EntityRelationsQuery(); + var parameters = new RelationsSearchParameters( + originator, relationsQuery.getDirection(), + relationsQuery.getMaxLevel(), + relationsQuery.isFetchLastLevelOnly() + ); query.setParameters(parameters); query.setFilters(relationsQuery.getFilters()); return query; } + } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbAbstractAttributeNodeTest.java similarity index 71% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractAttributeNodeTest.java rename to rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbAbstractAttributeNodeTest.java index 7dab0a675d..3d1b362ad0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbAbstractAttributeNodeTest.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.metadata; import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import org.junit.runner.RunWith; @@ -66,7 +65,7 @@ import static org.mockito.Mockito.when; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @RunWith(MockitoJUnitRunner.class) -public abstract class TbAbstractAttributeNodeTest { +public abstract class OldTbAbstractAttributeNodeTest { final CustomerId customerId = new CustomerId(Uuids.timeBased()); final TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); @@ -74,24 +73,23 @@ public abstract class TbAbstractAttributeNodeTest { final String keyAttrConf = "${word}"; final String valueAttrConf = "${result}"; @Mock - TbContext ctx; + protected TbContext ctxMock; @Mock - AttributesService attributesService; + protected AttributesService attributesServiceMock; @Mock - TimeseriesService timeseriesService; + protected TimeseriesService timeseriesServiceMock; @Mock - UserService userService; + protected UserService userServiceMock; @Mock - AssetService assetService; + protected AssetService assetServiceMock; @Mock - DeviceService deviceService; + protected DeviceService deviceServiceMock; TbMsg msg; Map metaData; - TbAbstractGetEntityAttrNode node; + TbAbstractGetEntityAttrNode node; - void init(TbAbstractGetEntityAttrNode node) throws TbNodeException { - ObjectMapper mapper = JacksonUtil.OBJECT_MAPPER; - TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(getTbNodeConfig())); + protected void init(TbAbstractGetEntityAttrNode node) throws TbNodeException { + var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(getTbNodeConfig())); metaData = new HashMap<>(); metaData.putIfAbsent("word", "temperature"); @@ -104,13 +102,13 @@ public abstract class TbAbstractAttributeNodeTest { void errorThrownIfCannotLoadAttributes(User user) { msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - when(ctx.getAttributesService()).thenReturn(attributesService); - when(attributesService.find(any(), eq(getEntityId()), eq(SERVER_SCOPE), anyCollection())) + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(any(), eq(getEntityId()), eq(SERVER_SCOPE), anyCollection())) .thenThrow(new IllegalStateException("something wrong")); - node.onMsg(ctx, msg); + node.onMsg(ctxMock, msg); final ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); - verify(ctx).tellFailure(same(msg), captor.capture()); + verify(ctxMock).tellFailure(same(msg), captor.capture()); Throwable value = captor.getValue(); assertEquals("something wrong", value.getMessage()); @@ -120,13 +118,13 @@ public abstract class TbAbstractAttributeNodeTest { void errorThrownIfCannotLoadAttributesAsync(User user) { msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - when(ctx.getAttributesService()).thenReturn(attributesService); - when(attributesService.find(any(), eq(getEntityId()), eq(SERVER_SCOPE), anyCollection())) + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(any(), eq(getEntityId()), eq(SERVER_SCOPE), anyCollection())) .thenReturn(Futures.immediateFailedFuture(new IllegalStateException("something wrong"))); - node.onMsg(ctx, msg); + node.onMsg(ctxMock, msg); final ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); - verify(ctx).tellFailure(same(msg), captor.capture()); + verify(ctxMock).tellFailure(same(msg), captor.capture()); Throwable value = captor.getValue(); assertEquals("something wrong", value.getMessage()); @@ -136,9 +134,9 @@ public abstract class TbAbstractAttributeNodeTest { void failedChainUsedIfCustomerCannotBeFound(User user) { msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - node.onMsg(ctx, msg); + node.onMsg(ctxMock, msg); var exceptionCaptor = ArgumentCaptor.forClass(NoSuchElementException.class); - verify(ctx).tellFailure(eq(msg), exceptionCaptor.capture()); + verify(ctxMock).tellFailure(eq(msg), exceptionCaptor.capture()); assertThat(exceptionCaptor.getValue().getMessage()).contains("Did not find entity! Msg ID: "); assertTrue(msg.getMetaData().getData().isEmpty()); @@ -146,30 +144,29 @@ public abstract class TbAbstractAttributeNodeTest { void entityAttributeAddedInMetadata(EntityId entityId, String type) { msg = TbMsg.newMsg(type, entityId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - entityAttributeFetched(getEntityId()); + entityAttributesFetched(getEntityId()); } void usersCustomerAttributesFetched(User user) { msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - entityAttributeFetched(getEntityId()); + entityAttributesFetched(getEntityId()); } void assetsCustomerAttributesFetched(Asset asset) { msg = TbMsg.newMsg("ASSET", asset.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - entityAttributeFetched(getEntityId()); + entityAttributesFetched(getEntityId()); } void deviceCustomerAttributesFetched(Device device) { msg = TbMsg.newMsg("DEVICE", device.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - entityAttributeFetched(getEntityId()); + entityAttributesFetched(getEntityId()); } void deviceCustomerTelemetryFetched(Device device) throws TbNodeException { - ObjectMapper mapper = JacksonUtil.OBJECT_MAPPER; - TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(getTbNodeConfigForTelemetry())); + var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(getTbNodeConfigForTelemetry())); TbAbstractGetEntityAttrNode node = getEmptyNode(); node.init(null, nodeConfiguration); @@ -178,32 +175,33 @@ public abstract class TbAbstractAttributeNodeTest { List timeseries = Lists.newArrayList(new BasicTsKvEntry(1L, new StringDataEntry("temperature", "highest"))); - when(ctx.getTimeseriesService()).thenReturn(timeseriesService); - when(timeseriesService.findLatest(any(), eq(getEntityId()), anyCollection())) + when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + when(timeseriesServiceMock.findLatest(any(), eq(getEntityId()), anyCollection())) .thenReturn(Futures.immediateFuture(timeseries)); - node.onMsg(ctx, msg); - verify(ctx).tellSuccess(msg); + node.onMsg(ctxMock, msg); + verify(ctxMock).tellSuccess(msg); assertEquals(msg.getMetaData().getValue("answer"), "highest"); } - void entityAttributeFetched(EntityId entityId) { - List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); + protected void entityAttributesFetched(EntityId entityId) { + List attributes = List.of(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); - when(ctx.getAttributesService()).thenReturn(attributesService); - when(attributesService.find(any(), eq(entityId), eq(SERVER_SCOPE), anyCollection())) + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(any(), eq(entityId), eq(SERVER_SCOPE), anyCollection())) .thenReturn(Futures.immediateFuture(attributes)); - node.onMsg(ctx, msg); - verify(ctx).tellSuccess(msg); + node.onMsg(ctxMock, msg); + + verify(ctxMock).tellSuccess(msg); assertEquals(msg.getMetaData().getValue("answer"), "high"); } - TbGetEntityAttrNodeConfiguration getTbNodeConfig() { + protected TbGetEntityAttrNodeConfiguration getTbNodeConfig() { return getConfig(false); } - TbGetEntityAttrNodeConfiguration getTbNodeConfigForTelemetry() { + protected TbGetEntityAttrNodeConfiguration getTbNodeConfigForTelemetry() { return getConfig(true); } @@ -222,17 +220,17 @@ public abstract class TbAbstractAttributeNodeTest { abstract EntityId getEntityId(); void mockFindDevice(Device device) { - when(ctx.getDeviceService()).thenReturn(deviceService); - when(deviceService.findDeviceByIdAsync(any(), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(deviceServiceMock.findDeviceByIdAsync(any(), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); } void mockFindAsset(Asset asset) { - when(ctx.getAssetService()).thenReturn(assetService); - when(assetService.findAssetByIdAsync(any(), eq(asset.getId()))).thenReturn(Futures.immediateFuture(asset)); + when(ctxMock.getAssetService()).thenReturn(assetServiceMock); + when(assetServiceMock.findAssetByIdAsync(any(), eq(asset.getId()))).thenReturn(Futures.immediateFuture(asset)); } void mockFindUser(User user) { - when(ctx.getUserService()).thenReturn(userService); - when(userService.findUserByIdAsync(any(), eq(user.getId()))).thenReturn(Futures.immediateFuture(user)); + when(ctxMock.getUserService()).thenReturn(userServiceMock); + when(userServiceMock.findUserByIdAsync(any(), eq(user.getId()))).thenReturn(Futures.immediateFuture(user)); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java new file mode 100644 index 0000000000..54a1c54ff4 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java @@ -0,0 +1,184 @@ +/** + * 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.datastax.oss.driver.api.core.uuid.Uuids; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.Device; +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.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +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.anyCollection; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; + +@RunWith(MockitoJUnitRunner.class) +public class OldTbGetCustomerAttributeNodeTest extends OldTbAbstractAttributeNodeTest { + + User user = new User(); + Asset asset = new Asset(); + Device device = new Device(); + + @Before + public void initDataForTests() throws TbNodeException { + init(new TbGetCustomerAttributeNode()); + user.setCustomerId(customerId); + user.setId(new UserId(UUID.randomUUID())); + + asset.setCustomerId(customerId); + asset.setId(new AssetId(UUID.randomUUID())); + + device.setCustomerId(customerId); + device.setId(new DeviceId(Uuids.timeBased())); + } + + @Override + protected TbAbstractGetEntityAttrNode getEmptyNode() { + return new TbGetCustomerAttributeNode(); + } + + @Override + protected EntityId getEntityId() { + return customerId; + } + + @Test + public void errorThrownIfFetchToIsNull() { + var node = new TbGetCustomerAttributeNode(); + var config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration(); + config.setFetchTo(null); + var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); + + assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { + node.fetchTo = FetchTo.DATA; + node.config.setFetchTo(FetchTo.DATA); + msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new CustomerId(UUID.randomUUID()), new TbMsgMetaData(), "[]"); + + var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); + + assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void errorThrownIfCannotLoadAttributes() { + mockFindUser(user); + errorThrownIfCannotLoadAttributes(user); + } + + @Test + public void errorThrownIfCannotLoadAttributesAsync() { + mockFindUser(user); + errorThrownIfCannotLoadAttributesAsync(user); + } + + @Test + public void failedChainUsedIfCustomerCannotBeFound() { + when(ctxMock.getUserService()).thenReturn(userServiceMock); + when(userServiceMock.findUserByIdAsync(any(), eq(user.getId()))).thenReturn(Futures.immediateFuture(null)); + failedChainUsedIfCustomerCannotBeFound(user); + } + + @Test + public void customerAttributeAddedInMetadata() { + entityAttributeAddedInMetadata(customerId, "CUSTOMER"); + } + + @Test + public void customerAttributeAddedInData() { + node.fetchTo = FetchTo.DATA; + node.config.setFetchTo(FetchTo.DATA); + + msg = TbMsg.newMsg("CUSTOMER", customerId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + + List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); + + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(any(), eq(customerId), eq(SERVER_SCOPE), anyCollection())) + .thenReturn(Futures.immediateFuture(attributes)); + + node.onMsg(ctxMock, msg); + + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + + var expectedMsgData = "{\"answer\":\"high\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + } + + @Test + public void usersCustomerAttributesFetched() { + mockFindUser(user); + usersCustomerAttributesFetched(user); + } + + @Test + public void assetsCustomerAttributesFetched() { + mockFindAsset(asset); + assetsCustomerAttributesFetched(asset); + } + + @Test + public void deviceCustomerAttributesFetched() { + mockFindDevice(device); + deviceCustomerAttributesFetched(device); + } + + @Test + public void deviceCustomerTelemetryFetched() throws TbNodeException { + mockFindDevice(device); + deviceCustomerTelemetryFetched(device); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetRelatedAttributeNodeTest.java new file mode 100644 index 0000000000..91700bb4ea --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetRelatedAttributeNodeTest.java @@ -0,0 +1,235 @@ +/** + * 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.collect.Lists; +import com.google.common.util.concurrent.Futures; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.Device; +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.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.relation.RelationService; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +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.anyCollection; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; + +@RunWith(MockitoJUnitRunner.class) +public class OldTbGetRelatedAttributeNodeTest extends OldTbAbstractAttributeNodeTest { + User user = new User(); + Asset asset = new Asset(); + Device device = new Device(); + @Mock + private RelationService relationService; + private EntityRelation entityRelation; + + @Before + public void initDataForTests() throws TbNodeException { + init(new TbGetRelatedAttributeNode()); + entityRelation = new EntityRelation(); + entityRelation.setTo(customerId); + entityRelation.setType(EntityRelation.CONTAINS_TYPE); + when(ctxMock.getRelationService()).thenReturn(relationService); + + user.setCustomerId(customerId); + user.setId(new UserId(UUID.randomUUID())); + entityRelation.setFrom(user.getId()); + + asset.setCustomerId(customerId); + asset.setId(new AssetId(UUID.randomUUID())); + + device.setCustomerId(customerId); + device.setId(new DeviceId(UUID.randomUUID())); + } + + @Override + protected TbAbstractGetEntityAttrNode getEmptyNode() { + return new TbGetRelatedAttributeNode(); + } + + @Override + protected TbGetEntityAttrNodeConfiguration getTbNodeConfig() { + return getConfig(false); + } + + @Override + protected TbGetEntityAttrNodeConfiguration getTbNodeConfigForTelemetry() { + return getConfig(true); + } + + private TbGetEntityAttrNodeConfiguration getConfig(boolean isTelemetry) { + TbGetRelatedAttrNodeConfiguration config = new TbGetRelatedAttrNodeConfiguration(); + config = config.defaultConfiguration(); + Map conf = new HashMap<>(); + conf.put(keyAttrConf, valueAttrConf); + config.setAttrMapping(conf); + config.setTelemetry(isTelemetry); + config.setFetchTo(FetchTo.METADATA); + return config; + } + + @Override + EntityId getEntityId() { + return customerId; + } + + @Test + public void errorThrownIfFetchToIsNull() { + var node = new TbGetRelatedAttributeNode(); + var config = new TbGetRelatedAttrNodeConfiguration().defaultConfiguration(); + config.setFetchTo(null); + var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); + + assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { + node.fetchTo = FetchTo.DATA; + node.config.setFetchTo(FetchTo.DATA); + msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new DeviceId(UUID.randomUUID()), new TbMsgMetaData(), "[]"); + + var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); + + assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void errorThrownIfCannotLoadAttributes() { + entityRelation.setFrom(user.getId()); + entityRelation.setTo(customerId); + when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + errorThrownIfCannotLoadAttributes(user); + } + + @Test + public void errorThrownIfCannotLoadAttributesAsync() { + entityRelation.setFrom(user.getId()); + entityRelation.setTo(customerId); + when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + errorThrownIfCannotLoadAttributesAsync(user); + } + + @Test + public void failedChainUsedIfCustomerCannotBeFound() { + entityRelation.setFrom(customerId); + entityRelation.setTo(null); + when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + failedChainUsedIfCustomerCannotBeFound(user); + } + + @Test + public void customerAttributeAddedInMetadata() { + entityRelation.setFrom(customerId); + entityRelation.setTo(customerId); + when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + entityAttributeAddedInMetadata(customerId, "CUSTOMER"); + } + + @Test + public void customerAttributeAddedInData() { + node.fetchTo = FetchTo.DATA; + node.config.setFetchTo(FetchTo.DATA); + + entityRelation.setFrom(customerId); + entityRelation.setTo(customerId); + when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + + msg = TbMsg.newMsg("CUSTOMER", customerId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + + List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); + + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(any(), eq(customerId), eq(SERVER_SCOPE), anyCollection())) + .thenReturn(Futures.immediateFuture(attributes)); + + node.onMsg(ctxMock, msg); + + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + + var expectedMsgData = "{\"answer\":\"high\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + } + + @Test + public void usersCustomerAttributesFetched() { + entityRelation.setFrom(user.getId()); + entityRelation.setTo(customerId); + when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + usersCustomerAttributesFetched(user); + } + + @Test + public void assetsCustomerAttributesFetched() { + entityRelation.setFrom(asset.getId()); + entityRelation.setTo(customerId); + when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + assetsCustomerAttributesFetched(asset); + } + + @Test + public void deviceCustomerAttributesFetched() { + entityRelation.setFrom(device.getId()); + entityRelation.setTo(customerId); + when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + deviceCustomerAttributesFetched(device); + } + + @Test + public void deviceCustomerTelemetryFetched() throws TbNodeException { + entityRelation.setFrom(device.getId()); + entityRelation.setTo(customerId); + when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + deviceCustomerTelemetryFetched(device); + } +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetTenantAttributeNodeTest.java new file mode 100644 index 0000000000..0263a1b523 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetTenantAttributeNodeTest.java @@ -0,0 +1,177 @@ +/** + * 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.collect.Lists; +import com.google.common.util.concurrent.Futures; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.Device; +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.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +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.anyCollection; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; + +@RunWith(MockitoJUnitRunner.class) +public class OldTbGetTenantAttributeNodeTest extends OldTbAbstractAttributeNodeTest { + User user = new User(); + Asset asset = new Asset(); + Device device = new Device(); + + @Before + public void initDataForTests() throws TbNodeException { + init(new TbGetTenantAttributeNode()); + + user.setTenantId(tenantId); + user.setId(new UserId(UUID.randomUUID())); + + asset.setTenantId(tenantId); + asset.setId(new AssetId(UUID.randomUUID())); + + device.setTenantId(tenantId); + device.setId(new DeviceId(UUID.randomUUID())); + + when(ctxMock.getTenantId()).thenReturn(tenantId); + } + + @Override + protected TbAbstractGetEntityAttrNode getEmptyNode() { + return new TbGetTenantAttributeNode(); + } + + @Override + EntityId getEntityId() { + return tenantId; + } + + @Test + public void errorThrownIfFetchToIsNull() { + var node = new TbGetTenantAttributeNode(); + var config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration(); + config.setFetchTo(null); + var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); + + assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { + node.fetchTo = FetchTo.DATA; + node.config.setFetchTo(FetchTo.DATA); + msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new TenantId(UUID.randomUUID()), new TbMsgMetaData(), "[]"); + + var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); + + assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void errorThrownIfCannotLoadAttributes() { + errorThrownIfCannotLoadAttributes(user); + } + + @Test + public void errorThrownIfCannotLoadAttributesAsync() { + errorThrownIfCannotLoadAttributesAsync(user); + } + + @Test + public void failedChainUsedIfTenantIdFromCtxCannotBeFound() { + when(ctxMock.getTenantId()).thenReturn(null); + failedChainUsedIfCustomerCannotBeFound(user); + } + + @Test + public void customerAttributeAddedInMetadata() { + entityAttributeAddedInMetadata(tenantId, "TENANT"); + } + + @Test + public void customerAttributeAddedInData() { + node.fetchTo = FetchTo.DATA; + node.config.setFetchTo(FetchTo.DATA); + + msg = TbMsg.newMsg("TENANT", tenantId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + + List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); + + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(attributesServiceMock.find(any(), eq(tenantId), eq(SERVER_SCOPE), anyCollection())) + .thenReturn(Futures.immediateFuture(attributes)); + + node.onMsg(ctxMock, msg); + + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + + var expectedMsgData = "{\"answer\":\"high\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + } + + @Test + public void usersCustomerAttributesFetched() { + usersCustomerAttributesFetched(user); + } + + @Test + public void assetsCustomerAttributesFetched() { + assetsCustomerAttributesFetched(asset); + } + + @Test + public void deviceCustomerAttributesFetched() { + deviceCustomerAttributesFetched(device); + } + + @Test + public void deviceCustomerTelemetryFetched() throws TbNodeException { + deviceCustomerTelemetryFetched(device); + } +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java index 344ce20c20..6b442efab1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,11 +15,14 @@ */ package org.thingsboard.rule.engine.metadata; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; 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.Spy; +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; @@ -39,46 +42,40 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.thingsboard.server.common.data.security.DeviceCredentialsType.ACCESS_TOKEN; +@ExtendWith(MockitoExtension.class) public class TbFetchDeviceCredentialsNodeTest { - final ObjectMapper mapper = new ObjectMapper(); - DeviceId deviceId; - TbFetchDeviceCredentialsNode node; - TbFetchDeviceCredentialsNodeConfiguration config; - TbNodeConfiguration nodeConfiguration; - TbContext ctx; - TbMsgCallback callback; - DeviceCredentialsService deviceCredentialsService; + @Mock + private TbContext ctxMock; + @Mock + private TbMsgCallback callbackMock; + @Mock + private DeviceCredentialsService deviceCredentialsServiceMock; + @Spy + private TbFetchDeviceCredentialsNode node; + private DeviceId deviceId; + private TbFetchDeviceCredentialsNodeConfiguration config; @BeforeEach void setUp() throws TbNodeException { deviceId = new DeviceId(UUID.randomUUID()); - callback = mock(TbMsgCallback.class); - ctx = mock(TbContext.class); config = new TbFetchDeviceCredentialsNodeConfiguration().defaultConfiguration(); config.setFetchTo(FetchTo.METADATA); - nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - node = spy(new TbFetchDeviceCredentialsNode()); - node.init(ctx, nodeConfiguration); - deviceCredentialsService = mock(DeviceCredentialsService.class); + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); - willReturn(deviceCredentialsService).given(ctx).getDeviceCredentialsService(); - willAnswer(invocation -> { + lenient().doReturn(deviceCredentialsServiceMock).when(ctxMock).getDeviceCredentialsService(); + lenient().doAnswer(invocation -> { DeviceCredentials deviceCredentials = new DeviceCredentials(); deviceCredentials.setCredentialsType(ACCESS_TOKEN); return deviceCredentials; - }).given(deviceCredentialsService).findDeviceCredentialsByDeviceId(any(), any()); - willAnswer(invocation -> { - return JacksonUtil.newObjectNode(); - }).given(deviceCredentialsService).toCredentialsInfo(any()); + }).when(deviceCredentialsServiceMock).findDeviceCredentialsByDeviceId(any(), any()); + lenient().doAnswer(invocation -> JacksonUtil.newObjectNode()).when(deviceCredentialsServiceMock).toCredentialsInfo(any()); } @AfterEach @@ -94,20 +91,24 @@ public class TbFetchDeviceCredentialsNodeTest { @Test void givenDefaultConfig_whenVerify_thenOK() { - TbFetchDeviceCredentialsNodeConfiguration defaultConfig = new TbFetchDeviceCredentialsNodeConfiguration().defaultConfiguration(); + var defaultConfig = new TbFetchDeviceCredentialsNodeConfiguration().defaultConfiguration(); assertThat(defaultConfig.getFetchTo()).isEqualTo(FetchTo.METADATA); } @Test - void givenMsg_whenOnMsg_thenVerifyOutput() throws Exception { - node.onMsg(ctx, getTbMsg(deviceId)); + void givenValidMsg_whenOnMsg_thenVerifyOutput() throws Exception { + // GIVEN - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); - verify(ctx, never()).tellFailure(any(), any()); - verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any()); + // WHEN + node.onMsg(ctxMock, getTbMsg(deviceId)); - TbMsg newMsg = newMsgCaptor.getValue(); + // THEN + var newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(newMsgCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + verify(deviceCredentialsServiceMock, times(1)).findDeviceCredentialsByDeviceId(any(), any()); + + var newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg.getMetaData().getData().containsKey("credentials")).isEqualTo(true); @@ -115,29 +116,36 @@ public class TbFetchDeviceCredentialsNodeTest { } @Test - void givenUnsupportedOriginatorType_whenOnMsg_thenTellFailure() throws Exception { - node.onMsg(ctx, getTbMsg(new CustomerId(UUID.randomUUID()))); + void givenUnsupportedOriginatorType_whenOnMsg_thenShouldTellFailure() throws Exception { + // GIVEN + var randomCustomerId = new CustomerId(UUID.randomUUID()); + + // WHEN + node.onMsg(ctxMock, getTbMsg(randomCustomerId)); - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(Exception.class); - verify(ctx, never()).tellSuccess(any()); - verify(ctx, times(1)).tellFailure(newMsgCaptor.capture(), exceptionCaptor.capture()); + // THEN + var newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + var exceptionCaptor = ArgumentCaptor.forClass(Exception.class); + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, times(1)).tellFailure(newMsgCaptor.capture(), exceptionCaptor.capture()); assertThat(exceptionCaptor.getValue()).isInstanceOf(RuntimeException.class); } @Test - void givenGetDeviceCredentials_whenOnMsg_thenTellFailure() throws Exception { - willAnswer(invocation -> { - return null; - }).given(deviceCredentialsService).findDeviceCredentialsByDeviceId(any(), any()); + void givenGetDeviceCredentials_whenOnMsg_thenShouldTellFailure() throws Exception { + // GIVEN + willAnswer(invocation -> null) + .given(deviceCredentialsServiceMock).findDeviceCredentialsByDeviceId(any(), any()); - node.onMsg(ctx, getTbMsg(deviceId)); + // WHEN + node.onMsg(ctxMock, getTbMsg(deviceId)); - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(Exception.class); - verify(ctx, never()).tellSuccess(any()); - verify(ctx, times(1)).tellFailure(newMsgCaptor.capture(), exceptionCaptor.capture()); + // THEN + var newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + var exceptionCaptor = ArgumentCaptor.forClass(Exception.class); + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, times(1)).tellFailure(newMsgCaptor.capture(), exceptionCaptor.capture()); assertThat(exceptionCaptor.getValue()).isInstanceOf(RuntimeException.class); } @@ -148,9 +156,10 @@ public class TbFetchDeviceCredentialsNodeTest { "city", "NY" ); - final TbMsgMetaData metaData = new TbMsgMetaData(mdMap); + final var metaData = new TbMsgMetaData(mdMap); final String data = "{\"TestAttribute_1\": \"humidity\", \"TestAttribute_2\": \"voltage\"}"; - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, metaData, data, callback); + return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, metaData, data, callbackMock); } + } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java similarity index 53% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNodeTest.java rename to rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index 853818f226..da6060e0a9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -16,18 +16,13 @@ package org.thingsboard.rule.engine.metadata; import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.AbstractListeningExecutor; import org.thingsboard.common.util.JacksonUtil; @@ -53,31 +48,37 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) -public class TbAbstractGetAttributesNodeTest { +public class TbGetAttributesNodeTest { - final ObjectMapper mapper = new ObjectMapper(); - - private EntityId originator = new DeviceId(Uuids.timeBased()); - private TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); + private static final EntityId ORIGINATOR = new DeviceId(Uuids.timeBased()); + private static final TenantId TENANT_ID = TenantId.fromUUID(Uuids.timeBased()); + private AbstractListeningExecutor dbExecutor; @Mock - private TbContext ctx; + private TbContext ctxMock; @Mock - private AttributesService attributesService; + private AttributesService attributesServiceMock; @Mock - private TimeseriesService tsService; - private AbstractListeningExecutor dbExecutor; + private TimeseriesService timeseriesServiceMock; private List clientAttributes; private List serverAttributes; private List sharedAttributes; private List tsKeys; private long ts; + private TbGetAttributesNode node; @Before public void before() throws TbNodeException { @@ -89,18 +90,10 @@ public class TbAbstractGetAttributesNodeTest { }; dbExecutor.init(); - Mockito.reset(ctx); - Mockito.reset(attributesService); - Mockito.reset(tsService); - - Mockito.reset(ctx); - Mockito.reset(attributesService); - Mockito.reset(tsService); - - lenient().when(ctx.getAttributesService()).thenReturn(attributesService); - lenient().when(ctx.getTimeseriesService()).thenReturn(tsService); - lenient().when(ctx.getTenantId()).thenReturn(tenantId); - lenient().when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); + when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getDbCallbackExecutor()).thenReturn(dbExecutor); clientAttributes = getAttributeNames("client"); serverAttributes = getAttributeNames("server"); @@ -108,18 +101,16 @@ public class TbAbstractGetAttributesNodeTest { tsKeys = List.of("temperature", "humidity", "unknown"); ts = System.currentTimeMillis(); - Mockito.when(attributesService.find(tenantId, originator, DataConstants.CLIENT_SCOPE, clientAttributes)) + when(attributesServiceMock.find(TENANT_ID, ORIGINATOR, DataConstants.CLIENT_SCOPE, clientAttributes)) .thenReturn(Futures.immediateFuture(getListAttributeKvEntry(clientAttributes, ts))); - - Mockito.when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, serverAttributes)) + when(attributesServiceMock.find(TENANT_ID, ORIGINATOR, DataConstants.SERVER_SCOPE, serverAttributes)) .thenReturn(Futures.immediateFuture(getListAttributeKvEntry(serverAttributes, ts))); - - Mockito.when(attributesService.find(tenantId, originator, DataConstants.SHARED_SCOPE, sharedAttributes)) + when(attributesServiceMock.find(TENANT_ID, ORIGINATOR, DataConstants.SHARED_SCOPE, sharedAttributes)) .thenReturn(Futures.immediateFuture(getListAttributeKvEntry(sharedAttributes, ts))); - Mockito.when(tsService.findLatest(tenantId, originator, tsKeys)) + when(timeseriesServiceMock.findLatest(TENANT_ID, ORIGINATOR, tsKeys)) .thenReturn(Futures.immediateFuture(getListTsKvEntry(tsKeys, ts))); } @@ -129,192 +120,197 @@ public class TbAbstractGetAttributesNodeTest { } @Test - public void fetchToMetadata_whenOnMsg_then_success() throws Exception { - TbGetAttributesNode node = initNode(FetchTo.METADATA, false, false); - TbMsg msg = getTbMsg(originator); - node.onMsg(ctx, msg); + public void givenFetchAttributesToMetadata_whenOnMsg_thenShouldTellSuccess() throws Exception { + // GIVEN + node = initNode(FetchTo.METADATA, false, false); + var msg = getTbMsg(ORIGINATOR); - // check msg - TbMsg resultMsg = checkMsg(true); + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var resultMsg = checkMsg(true); - //check attributes checkAttributes(resultMsg, FetchTo.METADATA, "cs_", clientAttributes); checkAttributes(resultMsg, FetchTo.METADATA, "ss_", serverAttributes); checkAttributes(resultMsg, FetchTo.METADATA, "shared_", sharedAttributes); - //check timeseries - checkTs(resultMsg, false, false, tsKeys); + checkTs(resultMsg, FetchTo.METADATA, false, tsKeys); } @Test - public void fetchToMetadata_latestWithTs_whenOnMsg_then_success() throws Exception { - TbGetAttributesNode node = initNode(FetchTo.METADATA, true, false); - TbMsg msg = getTbMsg(originator); - node.onMsg(ctx, msg); + public void givenFetchLatestTimeseriesToMetadata_whenOnMsg_thenShouldTellSuccess() throws Exception { + // GIVEN + node = initNode(FetchTo.METADATA, true, false); + var msg = getTbMsg(ORIGINATOR); + + // WHEN + node.onMsg(ctxMock, msg); - // check msg - TbMsg resultMsg = checkMsg(true); + // THEN + var resultMsg = checkMsg(true); - //check attributes checkAttributes(resultMsg, FetchTo.METADATA, "cs_", clientAttributes); checkAttributes(resultMsg, FetchTo.METADATA, "ss_", serverAttributes); checkAttributes(resultMsg, FetchTo.METADATA, "shared_", sharedAttributes); - //check timeseries with ts - checkTs(resultMsg, false, true, tsKeys); + checkTs(resultMsg, FetchTo.METADATA, true, tsKeys); } @Test - public void fetchToData_whenOnMsg_then_success() throws Exception { - TbGetAttributesNode node = initNode(FetchTo.DATA, false, false); - TbMsg msg = getTbMsg(originator); - node.onMsg(ctx, msg); + public void givenFetchAttributesToData_whenOnMsg_thenShouldTellSuccess() throws Exception { + // GIVEN + node = initNode(FetchTo.DATA, false, false); + var msg = getTbMsg(ORIGINATOR); - // check msg - TbMsg resultMsg = checkMsg(true); + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var resultMsg = checkMsg(true); - //check attributes checkAttributes(resultMsg, FetchTo.DATA, "cs_", clientAttributes); checkAttributes(resultMsg, FetchTo.DATA, "ss_", serverAttributes); checkAttributes(resultMsg, FetchTo.DATA, "shared_", sharedAttributes); - //check timeseries - checkTs(resultMsg, true, false, tsKeys); + checkTs(resultMsg, FetchTo.DATA, false, tsKeys); } @Test - public void fetchToData_latestWithTs_whenOnMsg_then_success() throws Exception { - TbGetAttributesNode node = initNode(FetchTo.DATA, true, false); - TbMsg msg = getTbMsg(originator); - node.onMsg(ctx, msg); + public void givenFetchLatestTimeseriesToData_whenOnMsg_thenShouldTellSuccess() throws Exception { + // GIVEN + node = initNode(FetchTo.DATA, true, false); + var msg = getTbMsg(ORIGINATOR); + + // WHEN + node.onMsg(ctxMock, msg); - // check msg - TbMsg resultMsg = checkMsg(true); + // THEN + var resultMsg = checkMsg(true); - //check attributes checkAttributes(resultMsg, FetchTo.DATA, "cs_", clientAttributes); checkAttributes(resultMsg, FetchTo.DATA, "ss_", serverAttributes); checkAttributes(resultMsg, FetchTo.DATA, "shared_", sharedAttributes); - //check timeseries with ts - checkTs(resultMsg, true, true, tsKeys); + checkTs(resultMsg, FetchTo.DATA, true, tsKeys); } @Test - public void fetchToMetadata_whenOnMsg_then_failure() throws Exception { - TbGetAttributesNode node = initNode(FetchTo.METADATA, false, true); - TbMsg msg = getTbMsg(originator); - node.onMsg(ctx, msg); + public void givenFetchAttributesToMetadata_whenOnMsg_thenShouldTellFailure() throws Exception { + // GIVEN + node = initNode(FetchTo.METADATA, false, true); + var msg = getTbMsg(ORIGINATOR); - // check msg - TbMsg actualMsg = checkMsg(false); + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsg = checkMsg(false); - //check attributes checkAttributes(actualMsg, FetchTo.METADATA, "cs_", clientAttributes); checkAttributes(actualMsg, FetchTo.METADATA, "ss_", serverAttributes); checkAttributes(actualMsg, FetchTo.METADATA, "shared_", sharedAttributes); - //check timeseries with ts - checkTs(actualMsg, false, false, tsKeys); + checkTs(actualMsg, FetchTo.METADATA, false, tsKeys); } @Test - public void fetchToData_whenOnMsg_then_failure() throws Exception { - TbGetAttributesNode node = initNode(FetchTo.DATA, true, true); - TbMsg msg = getTbMsg(originator); - node.onMsg(ctx, msg); + public void givenFetchLatestTimeseriesToData_whenOnMsg_thenShouldTellFailure() throws Exception { + // GIVEN + node = initNode(FetchTo.DATA, true, true); + var msg = getTbMsg(ORIGINATOR); + + // WHEN + node.onMsg(ctxMock, msg); - // check msg - TbMsg actualMsg = checkMsg(false); + // THEN + var actualMsg = checkMsg(false); - //check attributes checkAttributes(actualMsg, FetchTo.DATA, "cs_", clientAttributes); checkAttributes(actualMsg, FetchTo.DATA, "ss_", serverAttributes); checkAttributes(actualMsg, FetchTo.DATA, "shared_", sharedAttributes); - //check timeseries with ts - checkTs(actualMsg, true, true, tsKeys); + checkTs(actualMsg, FetchTo.DATA, true, tsKeys); } @Test - public void fetchToData_whenOnMsg_and_data_is_not_object_then_failure() throws Exception { - TbGetAttributesNode node = initNode(FetchTo.DATA, true, true); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), "[]"); - node.onMsg(ctx, msg); - - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(IllegalArgumentException.class); - Mockito.verify(ctx, never()).tellSuccess(any()); - Mockito.verify(ctx, Mockito.timeout(5000)).tellFailure(newMsgCaptor.capture(), exceptionCaptor.capture()); - - Assert.assertSame(msg, newMsgCaptor.getValue()); - Assert.assertNotNull(exceptionCaptor.getValue()); + public void givenFetchLatestTimeseriesToDataAndDataIsNotJsonObject_whenOnMsg_thenException() throws Exception { + // GIVEN + node = initNode(FetchTo.DATA, true, true); + var msg = TbMsg.newMsg("TEST", ORIGINATOR, new TbMsgMetaData(), "[]"); + + // WHEN + var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); + + // THEN + verify(ctxMock, never()).tellSuccess(any()); + assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); } private TbMsg checkMsg(boolean checkSuccess) { - ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); if (checkSuccess) { - Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); + verify(ctxMock, timeout(5000)).tellSuccess(msgCaptor.capture()); } else { - ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(RuntimeException.class); - Mockito.verify(ctx, never()).tellSuccess(any()); - Mockito.verify(ctx, Mockito.timeout(5000)).tellFailure(msgCaptor.capture(), exceptionCaptor.capture()); - RuntimeException exception = exceptionCaptor.getValue(); - Assert.assertNotNull(exception); - Assert.assertNotNull(exception.getMessage()); - Assert.assertTrue(exception.getMessage().startsWith("The following attribute/telemetry keys is not present in the DB:")); + var exceptionCaptor = ArgumentCaptor.forClass(RuntimeException.class); + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, timeout(5000)).tellFailure(msgCaptor.capture(), exceptionCaptor.capture()); + var exception = exceptionCaptor.getValue(); + assertNotNull(exception); + assertNotNull(exception.getMessage()); + assertTrue(exception.getMessage().startsWith("The following attribute/telemetry keys is not present in the DB:")); } - TbMsg resultMsg = msgCaptor.getValue(); - Assert.assertNotNull(resultMsg); - Assert.assertNotNull(resultMsg.getMetaData()); - Assert.assertNotNull(resultMsg.getData()); + var resultMsg = msgCaptor.getValue(); + assertNotNull(resultMsg); + assertNotNull(resultMsg.getMetaData()); + assertNotNull(resultMsg.getData()); return resultMsg; } private void checkAttributes(TbMsg actualMsg, FetchTo fetchTo, String prefix, List attributes) { - JsonNode msgData = JacksonUtil.toJsonNode(actualMsg.getData()); + var msgData = JacksonUtil.toJsonNode(actualMsg.getData()); attributes.stream() .filter(attribute -> !attribute.equals("unknown")) .forEach(attribute -> { - String result; + String result = null; if (FetchTo.DATA.equals(fetchTo)) { result = msgData.get(prefix + attribute).asText(); - } else { + } else if (FetchTo.METADATA.equals(fetchTo)) { result = actualMsg.getMetaData().getValue(prefix + attribute); } - Assert.assertNotNull(result); - Assert.assertEquals(attribute + "_value", result); + assertNotNull(result); + assertEquals(attribute + "_value", result); }); } - private void checkTs(TbMsg actualMsg, boolean fetchToData, boolean getLatestValueWithTs, List tsKeys) { - JsonNode msgData = JacksonUtil.toJsonNode(actualMsg.getData()); + private void checkTs(TbMsg actualMsg, FetchTo fetchTo, boolean getLatestValueWithTs, List tsKeys) { + var msgData = JacksonUtil.toJsonNode(actualMsg.getData()); long value = 1L; - for (String key : tsKeys) { + for (var key : tsKeys) { if (key.equals("unknown")) { continue; } - String actualValue; + String actualValue = null; String expectedValue; if (getLatestValueWithTs) { expectedValue = "{\"ts\":" + ts + ",\"value\":{\"data\":" + value + "}}"; } else { expectedValue = "{\"data\":" + value + "}"; } - if (fetchToData) { + if (FetchTo.DATA.equals(fetchTo)) { actualValue = JacksonUtil.toString(msgData.get(key)); - } else { + } else if (FetchTo.METADATA.equals(fetchTo)) { actualValue = actualMsg.getMetaData().getValue(key); } - Assert.assertNotNull(actualValue); - Assert.assertEquals(expectedValue, actualValue); + assertNotNull(actualValue); + assertEquals(expectedValue, actualValue); value++; } } private TbGetAttributesNode initNode(FetchTo fetchTo, boolean getLatestValueWithTs, boolean isTellFailureIfAbsent) throws TbNodeException { - TbGetAttributesNodeConfiguration config = new TbGetAttributesNodeConfiguration(); + var config = new TbGetAttributesNodeConfiguration(); config.setClientAttributeNames(List.of("client_attr_1", "client_attr_2", "${client_attr_metadata}", "unknown")); config.setServerAttributeNames(List.of("server_attr_1", "server_attr_2", "${server_attr_metadata}", "unknown")); config.setSharedAttributeNames(List.of("shared_attr_1", "shared_attr_2", "$[shared_attr_data]", "unknown")); @@ -322,17 +318,18 @@ public class TbAbstractGetAttributesNodeTest { config.setFetchTo(fetchTo); config.setGetLatestValueWithTs(getLatestValueWithTs); config.setTellFailureIfAbsent(isTellFailureIfAbsent); - TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - TbGetAttributesNode node = new TbGetAttributesNode(); - node.init(ctx, nodeConfiguration); + + var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + var node = new TbGetAttributesNode(); + node.init(ctxMock, nodeConfiguration); return node; } private TbMsg getTbMsg(EntityId entityId) { - ObjectNode msgData = JacksonUtil.newObjectNode(); + var msgData = JacksonUtil.newObjectNode(); msgData.put("shared_attr_data", "shared_attr_3"); - TbMsgMetaData msgMetaData = new TbMsgMetaData(); + var msgMetaData = new TbMsgMetaData(); msgMetaData.putValue("client_attr_metadata", "client_attr_3"); msgMetaData.putValue("server_attr_metadata", "server_attr_3"); @@ -343,8 +340,8 @@ public class TbAbstractGetAttributesNodeTest { return List.of(prefix + "_attr_1", prefix + "_attr_2", prefix + "_attr_3", "unknown"); } - private List getListAttributeKvEntry(List attributes, long ts) { - return attributes.stream() + private List getListAttributeKvEntry(List attributesList, long ts) { + return attributesList.stream() .filter(attribute -> !attribute.equals("unknown")) .map(attribute -> toAttributeKvEntry(ts, attribute)) .collect(Collectors.toList()); @@ -354,18 +351,18 @@ public class TbAbstractGetAttributesNodeTest { return new BaseAttributeKvEntry(ts, new StringDataEntry(attribute, attribute + "_value")); } - private List getListTsKvEntry(List keys, long ts) { + private List getListTsKvEntry(List keysList, long ts) { long value = 1L; - List kvEntries = new ArrayList<>(); - for (String key : keys) { + var kvEntriesList = new ArrayList(); + for (var key : keysList) { if (key.equals("unknown")) { continue; } String dataValue = "{\"data\":" + value + "}"; - kvEntries.add(new BasicTsKvEntry(ts, new JsonDataEntry(key, dataValue))); + kvEntriesList.add(new BasicTsKvEntry(ts, new JsonDataEntry(key, dataValue))); value++; } - return kvEntries; + return kvEntriesList; } } 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 16c3219c6e..658dbe8b78 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 @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,17 +15,21 @@ */ package org.thingsboard.rule.engine.metadata; -import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import com.google.common.util.concurrent.ListenableFuture; +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.junit.MockitoJUnitRunner; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; @@ -33,150 +37,401 @@ import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; 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.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.user.UserService; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; import java.util.UUID; +import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; +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.anyCollection; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +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; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -@RunWith(MockitoJUnitRunner.class) -public class TbGetCustomerAttributeNodeTest extends TbAbstractAttributeNodeTest { - User user = new User(); - Asset asset = new Asset(); - Device device = new Device(); +@ExtendWith(MockitoExtension.class) +public class TbGetCustomerAttributeNodeTest { - @Before - public void initDataForTests() throws TbNodeException { - init(new TbGetCustomerAttributeNode()); - user.setCustomerId(customerId); - user.setId(new UserId(UUID.randomUUID())); + private static final EntityId DUMMY_ENTITY_ID = new DeviceId(UUID.randomUUID()); + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - asset.setCustomerId(customerId); - asset.setId(new AssetId(UUID.randomUUID())); + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + }; + @Mock + private TbContext ctxMock; + @Mock + private AttributesService attributesServiceMock; + @Mock + private TimeseriesService timeseriesServiceMock; + @Mock + private UserService userServiceMock; + @Mock + private AssetService assetServiceMock; + @Mock + private DeviceService deviceServiceMock; + private TbGetCustomerAttributeNode node; + private TbGetEntityAttrNodeConfiguration config; + private TbNodeConfiguration nodeConfiguration; + private TbMsg msg; - device.setCustomerId(customerId); - device.setId(new DeviceId(Uuids.timeBased())); + @BeforeEach + public void setUp() { + node = new TbGetCustomerAttributeNode(); + config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration(); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); } - @Override - protected TbAbstractGetEntityAttrNode getEmptyNode() { - return new TbGetCustomerAttributeNode(); - } + @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)); - @Override - EntityId getEntityId() { - return customerId; + // THEN + assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + verify(ctxMock, never()).tellSuccess(any()); } @Test - public void errorThrownIfFetchToIsNull() { - var node = new TbGetCustomerAttributeNode(); - var config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration(); - config.setFetchTo(null); - var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { + // GIVEN - var exception = assertThrows(TbNodeException.class, () -> node.init(ctx, nodeConfiguration)); + // WHEN + node.init(ctxMock, nodeConfiguration); - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); - verify(ctx, never()).tellSuccess(any()); + // THEN + assertThat(node.config).isEqualTo(config); + assertThat(config.getAttrMapping()).isEqualTo(Map.of("alarmThreshold", "threshold")); + assertThat(config.isTelemetry()).isEqualTo(false); + assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); } @Test - public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new CustomerId(UUID.randomUUID()), new TbMsgMetaData(), "[]"); + public void givenCustomConfig_whenInit_thenOK() throws TbNodeException { + // GIVEN + config.setAttrMapping(Map.of( + "sourceAttr1", "targetKey1", + "sourceAttr2", "targetKey2", + "sourceAttr3", "targetKey3")); + config.setTelemetry(true); + config.setFetchTo(FetchTo.DATA); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctx, msg)); + // WHEN + node.init(ctxMock, nodeConfiguration); - assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); - verify(ctx, never()).tellSuccess(any()); + // THEN + assertThat(node.config).isEqualTo(config); + assertThat(config.getAttrMapping()).isEqualTo(Map.of( + "sourceAttr1", "targetKey1", + "sourceAttr2", "targetKey2", + "sourceAttr3", "targetKey3")); + assertThat(config.isTelemetry()).isEqualTo(true); + assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); } @Test - public void errorThrownIfCannotLoadAttributes() { - mockFindUser(user); - errorThrownIfCannotLoadAttributes(user); + public void givenEmptyAttributesMapping_whenInit_thenException() { + // SETUP + var expectedExceptionMessage = "At least one attribute mapping should be specified!"; + + // GIVEN + config.setAttrMapping(Collections.emptyMap()); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); + + // THEN + assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); + verify(ctxMock, never()).tellSuccess(any()); } @Test - public void errorThrownIfCannotLoadAttributesAsync() { - mockFindUser(user); - errorThrownIfCannotLoadAttributesAsync(user); + public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { + // GIVEN + node.fetchTo = FetchTo.DATA; + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, 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 failedChainUsedIfCustomerCannotBeFound() { - when(ctx.getUserService()).thenReturn(userService); - when(userService.findUserByIdAsync(any(), eq(user.getId()))).thenReturn(Futures.immediateFuture(null)); - failedChainUsedIfCustomerCannotBeFound(user); + public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { + // SETUP + var expectedExceptionMessage = "Entity with id: '" + DUMMY_ENTITY_ID + + "' 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(), "{}"); + + // WHEN + var exception = assertThrows(RuntimeException.class, () -> node.onMsg(ctxMock, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); + verify(ctxMock, never()).tellSuccess(any()); } @Test - public void customerAttributeAddedInMetadata() { - entityAttributeAddedInMetadata(customerId, "CUSTOMER"); + public void givenDidNotFindEntity_whenOnMsg_thenShouldTellFailure() { + // GIVEN + var userId = new UserId(UUID.randomUUID()); + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", userId, new TbMsgMetaData(), "{}"); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getUserService()).thenReturn(userServiceMock); + doReturn(Futures.immediateFuture(null)).when(userServiceMock).findUserByIdAsync(any(), any()); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + var actualExceptionCaptor = ArgumentCaptor.forClass(Throwable.class); + + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, times(1)) + .tellFailure(actualMessageCaptor.capture(), actualExceptionCaptor.capture()); + + var actualMessage = actualMessageCaptor.getValue(); + var actualException = actualExceptionCaptor.getValue(); + + var expectedExceptionMessage = String.format( + "Failed to find customer for entity with id %s and type %s", + userId.getId(), userId.getEntityType().getNormalName()); + + assertEquals(msg, actualMessage); + assertEquals(expectedExceptionMessage, actualException.getMessage()); + assertInstanceOf(NoSuchElementException.class, actualException); } @Test - public void customerAttributeAddedInData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); + public void givenFetchAttributesToData_whenOnMsg_thenShouldFetchAttributesToData() { + // GIVEN + var device = new Device(new DeviceId(UUID.randomUUID())); + device.setCustomerId(new CustomerId(UUID.randomUUID())); - msg = TbMsg.newMsg("CUSTOMER", customerId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + prepareMsgAndConfig(FetchTo.DATA, false, device.getId()); - List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + doReturn(Futures.immediateFuture(device)).when(deviceServiceMock).findDeviceByIdAsync(any(), any()); - when(ctx.getAttributesService()).thenReturn(attributesService); - when(attributesService.find(any(), eq(customerId), eq(SERVER_SCOPE), anyCollection())) + 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(device.getCustomerId()), eq(SERVER_SCOPE), anyList())) .thenReturn(Futures.immediateFuture(attributes)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - node.onMsg(ctx, msg); + // WHEN + node.onMsg(ctxMock, msg); + // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); - var expectedMsgData = "{\"answer\":\"high\"}"; + var expectedMsgData = "{\"temp\":42," + + "\"humidity\":77," + + "\"messageBodyPattern1\":\"targetKey2\"," + + "\"messageBodyPattern2\":\"sourceKey3\"," + + "\"targetKey1\":\"sourceValue1\"," + + "\"targetKey2\":\"sourceValue2\"," + + "\"targetKey3\":\"sourceValue3\"}"; assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @Test - public void usersCustomerAttributesFetched() { - mockFindUser(user); - usersCustomerAttributesFetched(user); + public void givenFetchAttributesToMetaData_whenOnMsg_thenShouldFetchAttributesToMetaData() { + // GIVEN + var user = new User(new UserId(UUID.randomUUID())); + user.setCustomerId(new CustomerId(UUID.randomUUID())); + + 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( + 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)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // 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(Map.of( + "metaDataPattern1", "sourceKey2", + "metaDataPattern2", "targetKey3", + "targetKey1", "sourceValue1", + "targetKey2", "sourceValue2", + "targetKey3", "sourceValue3" + )); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } @Test - public void assetsCustomerAttributesFetched() { - mockFindAsset(asset); - assetsCustomerAttributesFetched(asset); + public void givenFetchTelemetryToData_whenOnMsg_thenShouldFetchTelemetryToData() { + // GIVEN + var customer = new Customer(new CustomerId(UUID.randomUUID())); + + prepareMsgAndConfig(FetchTo.DATA, true, customer.getId()); + + 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())) + .thenReturn(Futures.immediateFuture(timeseries)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // 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 = "{\"temp\":42," + + "\"humidity\":77," + + "\"messageBodyPattern1\":\"targetKey2\"," + + "\"messageBodyPattern2\":\"sourceKey3\"," + + "\"targetKey1\":\"sourceValue1\"," + + "\"targetKey2\":\"sourceValue2\"," + + "\"targetKey3\":\"sourceValue3\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @Test - public void deviceCustomerAttributesFetched() { - mockFindDevice(device); - deviceCustomerAttributesFetched(device); + public void givenFetchTelemetryToMetaData_whenOnMsg_thenShouldFetchTelemetryToMetaData() { + // GIVEN + var asset = new Asset(new AssetId(UUID.randomUUID())); + asset.setCustomerId(new CustomerId(UUID.randomUUID())); + + 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( + 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)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // 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(Map.of( + "metaDataPattern1", "sourceKey2", + "metaDataPattern2", "targetKey3", + "targetKey1", "sourceValue1", + "targetKey2", "sourceValue2", + "targetKey3", "sourceValue3" + )); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } - @Test - public void deviceCustomerTelemetryFetched() throws TbNodeException { - mockFindDevice(device); - deviceCustomerTelemetryFetched(device); + private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId entityId) { + config.setAttrMapping(Map.of( + "sourceKey1", "targetKey1", + "${metaDataPattern1}", "$[messageBodyPattern1]", + "$[messageBodyPattern2]", "${metaDataPattern2}")); + config.setTelemetry(isTelemetry); + config.setFetchTo(fetchTo); + + 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); } + } 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 a258cf4c94..9bd03b4f0b 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 @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -54,8 +54,9 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class TbGetOriginatorFieldsNodeTest { + private static final EntityId DUMMY_ENTITY_ID = new DeviceId(UUID.randomUUID()); - public static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { + private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { @Override public ListenableFuture executeAsync(Callable task) { try { @@ -81,15 +82,14 @@ public class TbGetOriginatorFieldsNodeTest { @BeforeEach public void setUp() { - config = new TbGetOriginatorFieldsConfiguration(); node = new TbGetOriginatorFieldsNode(); + config = new TbGetOriginatorFieldsConfiguration().defaultConfiguration(); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); } @Test - public void givenConfigWithNullFetchTo_whenOnInit_thenException() { + public void givenConfigWithNullFetchTo_whenInit_thenException() { // GIVEN - config = config.defaultConfiguration(); config.setFetchTo(null); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -97,15 +97,13 @@ public class TbGetOriginatorFieldsNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); verify(ctxMock, never()).tellSuccess(any()); } @Test public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { // GIVEN - config = config.defaultConfiguration(); - nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); // WHEN node.init(ctxMock, nodeConfiguration); @@ -311,35 +309,18 @@ public class TbGetOriginatorFieldsNodeTest { } @Test - public void givenEmptyFieldsMapping_whenOnMsg_thenShouldTellSuccessWithSameMsg() { + public void givenEmptyFieldsMapping_whenInit_thenException() { // GIVEN + config = config.defaultConfiguration(); config.setFieldsMapping(Collections.emptyMap()); - config.setIgnoreNullStrings(false); - config.setFetchTo(FetchTo.METADATA); - - node.config = config; - node.fetchTo = FetchTo.METADATA; - var msgMetaData = new TbMsgMetaData(Map.of( - "testKey1", "testValue1", - "testKey2", "123")); - var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, msgMetaData, msgData); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); // WHEN - node.onMsg(ctxMock, msg); + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); - verify(ctxMock, never()).tellFailure(any(), any()); - - var expectedMsgMetaData = new TbMsgMetaData(Map.of( - "testKey1", "testValue1", - "testKey2", "123" - )); - - assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msgData); - assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); + assertThat(exception.getMessage()).isEqualTo("At least one field mapping should be specified!"); + verify(ctxMock, never()).tellSuccess(any()); } @Test @@ -373,4 +354,5 @@ public class TbGetOriginatorFieldsNodeTest { assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msgData); assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); } -} \ No newline at end of file + +} 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 0ed1296d18..3725f24dc4 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 @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,221 +15,459 @@ */ package org.thingsboard.rule.engine.metadata; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import com.google.common.util.concurrent.ListenableFuture; +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.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ListeningExecutor; +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.data.RelationsQuery; +import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; 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.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.UUID; +import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; +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.anyCollection; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +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; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -@RunWith(MockitoJUnitRunner.class) -public class TbGetRelatedAttributeNodeTest extends TbAbstractAttributeNodeTest { - User user = new User(); - Asset asset = new Asset(); - Device device = new Device(); +@ExtendWith(MockitoExtension.class) +public class TbGetRelatedAttributeNodeTest { + + private static final EntityId DUMMY_ENTITY_ID = new DeviceId(UUID.randomUUID()); + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + }; @Mock - private RelationService relationService; + private TbContext ctxMock; + @Mock + private AttributesService attributesServiceMock; + @Mock + private TimeseriesService timeseriesServiceMock; + @Mock + private RelationService relationServiceMock; + private TbGetRelatedAttributeNode node; + private TbGetRelatedAttrNodeConfiguration config; + private TbNodeConfiguration nodeConfiguration; private EntityRelation entityRelation; + private TbMsg msg; - @Before - public void initDataForTests() throws TbNodeException { - init(new TbGetRelatedAttributeNode()); + @BeforeEach + public void setUp() { + node = new TbGetRelatedAttributeNode(); + config = new TbGetRelatedAttrNodeConfiguration().defaultConfiguration(); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); entityRelation = new EntityRelation(); - entityRelation.setTo(customerId); - entityRelation.setType(EntityRelation.CONTAINS_TYPE); - when(ctx.getRelationService()).thenReturn(relationService); - - user.setCustomerId(customerId); - user.setId(new UserId(UUID.randomUUID())); - entityRelation.setFrom(user.getId()); - - asset.setCustomerId(customerId); - asset.setId(new AssetId(UUID.randomUUID())); - - device.setCustomerId(customerId); - device.setId(new DeviceId(UUID.randomUUID())); } - @Override - protected TbAbstractGetEntityAttrNode getEmptyNode() { - return new TbGetRelatedAttributeNode(); - } + @Test + public void givenConfigWithNullFetchTo_whenInit_thenException() { + // GIVEN + config.setFetchTo(null); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - @Override - TbGetEntityAttrNodeConfiguration getTbNodeConfig() { - return getConfig(false); - } + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); - @Override - TbGetEntityAttrNodeConfiguration getTbNodeConfigForTelemetry() { - return getConfig(true); + // THEN + assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + verify(ctxMock, never()).tellSuccess(any()); } - private TbGetEntityAttrNodeConfiguration getConfig(boolean isTelemetry) { - TbGetRelatedAttrNodeConfiguration config = new TbGetRelatedAttrNodeConfiguration(); - config = config.defaultConfiguration(); - Map conf = new HashMap<>(); - conf.put(keyAttrConf, valueAttrConf); - config.setAttrMapping(conf); - config.setTelemetry(isTelemetry); - config.setFetchTo(FetchTo.METADATA); - return config; + @Test + public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { + // GIVEN + + // WHEN + node.init(ctxMock, nodeConfiguration); + + // THEN + var nodeConfig = (TbGetRelatedAttrNodeConfiguration) node.config; + assertThat(nodeConfig).isEqualTo(config); + assertThat(nodeConfig.getAttrMapping()).isEqualTo(Map.of("serialNumber", "sn")); + assertThat(nodeConfig.isTelemetry()).isEqualTo(false); + assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); + + var relationsQuery = new RelationsQuery(); + var relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); + relationsQuery.setDirection(EntitySearchDirection.FROM); + relationsQuery.setMaxLevel(1); + relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter)); + + assertThat(nodeConfig.getRelationsQuery()).isEqualTo(relationsQuery); } - @Override - EntityId getEntityId() { - return customerId; + @Test + public void givenCustomConfig_whenInit_thenOK() throws TbNodeException { + // GIVEN + config.setAttrMapping(Map.of( + "sourceAttr1", "targetKey1", + "sourceAttr2", "targetKey2", + "sourceAttr3", "targetKey3")); + config.setTelemetry(true); + config.setFetchTo(FetchTo.DATA); + + var relationsQuery = new RelationsQuery(); + var relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); + relationsQuery.setDirection(EntitySearchDirection.FROM); + relationsQuery.setMaxLevel(1); + relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter)); + + config.setRelationsQuery(relationsQuery); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + // WHEN + node.init(ctxMock, nodeConfiguration); + + // THEN + var nodeConfig = (TbGetRelatedAttrNodeConfiguration) node.config; + assertThat(nodeConfig).isEqualTo(config); + assertThat(nodeConfig.getAttrMapping()).isEqualTo(Map.of( + "sourceAttr1", "targetKey1", + "sourceAttr2", "targetKey2", + "sourceAttr3", "targetKey3" + )); + assertThat(nodeConfig.isTelemetry()).isEqualTo(true); + assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); + assertThat(nodeConfig.getRelationsQuery()).isEqualTo(relationsQuery); } @Test - public void errorThrownIfFetchToIsNull() { - var node = new TbGetRelatedAttributeNode(); - var config = new TbGetRelatedAttrNodeConfiguration().defaultConfiguration(); - config.setFetchTo(null); - var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + public void givenEmptyAttributesMapping_whenInit_thenException() { + // SETUP + var expectedExceptionMessage = "At least one attribute mapping should be specified!"; + + // GIVEN + config.setAttrMapping(Collections.emptyMap()); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - var exception = assertThrows(TbNodeException.class, () -> node.init(ctx, nodeConfiguration)); + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); - verify(ctx, never()).tellSuccess(any()); + // THEN + assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); + verify(ctxMock, never()).tellSuccess(any()); } @Test - public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { + public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { + // GIVEN node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new DeviceId(UUID.randomUUID()), new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "[]"); - var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctx, msg)); + // WHEN + var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); + // THEN assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); - verify(ctx, never()).tellSuccess(any()); + verify(ctxMock, never()).tellSuccess(any()); } @Test - public void errorThrownIfCannotLoadAttributes() { - entityRelation.setFrom(user.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - errorThrownIfCannotLoadAttributes(user); - } + public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { + // SETUP + var expectedExceptionMessage = "Entity with id: '" + DUMMY_ENTITY_ID + + "' specified in the configuration doesn't belong to the current tenant."; - @Test - public void errorThrownIfCannotLoadAttributesAsync() { - entityRelation.setFrom(user.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - errorThrownIfCannotLoadAttributesAsync(user); - } + // GIVEN + doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_ENTITY_ID); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "{}"); - @Test - public void failedChainUsedIfCustomerCannotBeFound() { - entityRelation.setFrom(customerId); - entityRelation.setTo(null); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - failedChainUsedIfCustomerCannotBeFound(user); + // WHEN + var exception = assertThrows(RuntimeException.class, () -> node.onMsg(ctxMock, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); + verify(ctxMock, never()).tellSuccess(any()); } @Test - public void customerAttributeAddedInMetadata() { - entityRelation.setFrom(customerId); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - entityAttributeAddedInMetadata(customerId, "CUSTOMER"); + public void givenDidNotFindEntity_whenOnMsg_thenShouldTellFailure() { + // GIVEN + prepareMsgAndConfig(FetchTo.METADATA, false, DUMMY_ENTITY_ID); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + doReturn(Futures.immediateFuture(null)).when(relationServiceMock).findByQuery(any(), any()); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + var actualExceptionCaptor = ArgumentCaptor.forClass(Throwable.class); + + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, times(1)) + .tellFailure(actualMessageCaptor.capture(), actualExceptionCaptor.capture()); + + var actualMessage = actualMessageCaptor.getValue(); + var actualException = actualExceptionCaptor.getValue(); + + var expectedExceptionMessage = "Failed to find related entity to message originator using relation query specified in the configuration!"; + + assertEquals(msg, actualMessage); + assertEquals(expectedExceptionMessage, actualException.getMessage()); + assertInstanceOf(NoSuchElementException.class, actualException); } @Test - public void customerAttributeAddedInData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); + public void givenFetchAttributesToData_whenOnMsg_thenShouldFetchAttributesToData() { + // GIVEN + var customer = new Customer(new CustomerId(UUID.randomUUID())); + var user = new User(new UserId(UUID.randomUUID())); + + prepareMsgAndConfig(FetchTo.DATA, false, user.getId()); - entityRelation.setFrom(customerId); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + entityRelation.setFrom(user.getId()); + entityRelation.setTo(customer.getId()); + entityRelation.setType(EntityRelation.CONTAINS_TYPE); - msg = TbMsg.newMsg("CUSTOMER", customerId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); - List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(ctx.getAttributesService()).thenReturn(attributesService); - when(attributesService.find(any(), eq(customerId), eq(SERVER_SCOPE), anyCollection())) + 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())) .thenReturn(Futures.immediateFuture(attributes)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - node.onMsg(ctx, msg); + // WHEN + node.onMsg(ctxMock, msg); + // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); - var expectedMsgData = "{\"answer\":\"high\"}"; + var expectedMsgData = "{\"temp\":42," + + "\"humidity\":77," + + "\"messageBodyPattern1\":\"targetKey2\"," + + "\"messageBodyPattern2\":\"sourceKey3\"," + + "\"targetKey1\":\"sourceValue1\"," + + "\"targetKey2\":\"sourceValue2\"," + + "\"targetKey3\":\"sourceValue3\"}"; assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @Test - public void usersCustomerAttributesFetched() { - entityRelation.setFrom(user.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - usersCustomerAttributesFetched(user); + public void givenFetchAttributesToMetaData_whenOnMsg_thenShouldFetchAttributesToMetaData() { + // GIVEN + var customer = new Customer(new CustomerId(UUID.randomUUID())); + + prepareMsgAndConfig(FetchTo.METADATA, false, customer.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()); + + 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())) + .thenReturn(Futures.immediateFuture(attributes)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // 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(Map.of( + "metaDataPattern1", "sourceKey2", + "metaDataPattern2", "targetKey3", + "targetKey1", "sourceValue1", + "targetKey2", "sourceValue2", + "targetKey3", "sourceValue3" + )); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } @Test - public void assetsCustomerAttributesFetched() { + public void givenFetchTelemetryToData_whenOnMsg_thenShouldFetchTelemetryToData() { + // GIVEN + var customer = new Customer(new CustomerId(UUID.randomUUID())); + var asset = new Asset(new AssetId(UUID.randomUUID())); + + prepareMsgAndConfig(FetchTo.DATA, true, asset.getId()); + entityRelation.setFrom(asset.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - assetsCustomerAttributesFetched(asset); + 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()); + + 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())) + .thenReturn(Futures.immediateFuture(timeseries)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // 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 = "{\"temp\":42," + + "\"humidity\":77," + + "\"messageBodyPattern1\":\"targetKey2\"," + + "\"messageBodyPattern2\":\"sourceKey3\"," + + "\"targetKey1\":\"sourceValue1\"," + + "\"targetKey2\":\"sourceValue2\"," + + "\"targetKey3\":\"sourceValue3\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @Test - public void deviceCustomerAttributesFetched() { + public void givenFetchTelemetryToMetaData_whenOnMsg_thenShouldFetchTelemetryToMetaData() { + // GIVEN + var customer = new Customer(new CustomerId(UUID.randomUUID())); + var device = new Device(new DeviceId(UUID.randomUUID())); + + prepareMsgAndConfig(FetchTo.METADATA, true, device.getId()); + entityRelation.setFrom(device.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - deviceCustomerAttributesFetched(device); + 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()); + + 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())) + .thenReturn(Futures.immediateFuture(timeseries)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // 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(Map.of( + "metaDataPattern1", "sourceKey2", + "metaDataPattern2", "targetKey3", + "targetKey1", "sourceValue1", + "targetKey2", "sourceValue2", + "targetKey3", "sourceValue3" + )); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } - @Test - public void deviceCustomerTelemetryFetched() throws TbNodeException { - entityRelation.setFrom(device.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - deviceCustomerTelemetryFetched(device); + private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId entityId) { + config.setAttrMapping(Map.of( + "sourceKey1", "targetKey1", + "${metaDataPattern1}", "$[messageBodyPattern1]", + "$[messageBodyPattern2]", "${metaDataPattern2}")); + config.setTelemetry(isTelemetry); + config.setFetchTo(fetchTo); + + 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); } + } 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 6537657a7f..d4f30f986b 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 @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,163 +15,357 @@ */ package org.thingsboard.rule.engine.metadata; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import com.google.common.util.concurrent.ListenableFuture; +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.junit.MockitoJUnitRunner; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.Device; -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.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.UUID; +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.anyCollection; +import static org.mockito.ArgumentMatchers.anyList; 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; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -@RunWith(MockitoJUnitRunner.class) -public class TbGetTenantAttributeNodeTest extends TbAbstractAttributeNodeTest { - User user = new User(); - Asset asset = new Asset(); - Device device = new Device(); +@ExtendWith(MockitoExtension.class) +public class TbGetTenantAttributeNodeTest { + + private static final EntityId DUMMY_ENTITY_ID = new DeviceId(UUID.randomUUID()); + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + }; + @Mock + private TbContext ctxMock; + @Mock + private AttributesService attributesServiceMock; + @Mock + private TimeseriesService timeseriesServiceMock; + private TbGetTenantAttributeNode node; + private TbGetEntityAttrNodeConfiguration config; + private TbNodeConfiguration nodeConfiguration; + private TbMsg msg; + + @BeforeEach + public void setUp() { + node = new TbGetTenantAttributeNode(); + config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration(); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + } - @Before - public void initDataForTests() throws TbNodeException { - init(new TbGetTenantAttributeNode()); + @Test + public void givenConfigWithNullFetchTo_whenInit_thenException() { + // GIVEN + config.setFetchTo(null); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - user.setTenantId(tenantId); - user.setId(new UserId(UUID.randomUUID())); + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); - asset.setTenantId(tenantId); - asset.setId(new AssetId(UUID.randomUUID())); + // THEN + assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + verify(ctxMock, never()).tellSuccess(any()); + } - device.setTenantId(tenantId); - device.setId(new DeviceId(UUID.randomUUID())); + @Test + public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { + // GIVEN - when(ctx.getTenantId()).thenReturn(tenantId); - } + // WHEN + node.init(ctxMock, nodeConfiguration); - @Override - protected TbAbstractGetEntityAttrNode getEmptyNode() { - return new TbGetTenantAttributeNode(); + // THEN + assertThat(node.config).isEqualTo(config); + assertThat(config.getAttrMapping()).isEqualTo(Map.of("alarmThreshold", "threshold")); + assertThat(config.isTelemetry()).isEqualTo(false); + assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); } - @Override - EntityId getEntityId() { - return tenantId; + @Test + public void givenCustomConfig_whenInit_thenOK() throws TbNodeException { + // GIVEN + config.setAttrMapping(Map.of( + "sourceAttr1", "targetKey1", + "sourceAttr2", "targetKey2", + "sourceAttr3", "targetKey3")); + config.setTelemetry(true); + config.setFetchTo(FetchTo.DATA); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + // WHEN + node.init(ctxMock, nodeConfiguration); + + // THEN + assertThat(node.config).isEqualTo(config); + assertThat(config.getAttrMapping()).isEqualTo(Map.of( + "sourceAttr1", "targetKey1", + "sourceAttr2", "targetKey2", + "sourceAttr3", "targetKey3")); + assertThat(config.isTelemetry()).isEqualTo(true); + assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); } @Test - public void errorThrownIfFetchToIsNull() { - var node = new TbGetTenantAttributeNode(); - var config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration(); - config.setFetchTo(null); - var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + public void givenEmptyAttributesMapping_whenInit_thenException() { + // SETUP + var expectedExceptionMessage = "At least one attribute mapping should be specified!"; - var exception = assertThrows(TbNodeException.class, () -> node.init(ctx, nodeConfiguration)); + // GIVEN + config.setAttrMapping(Collections.emptyMap()); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); - verify(ctx, never()).tellSuccess(any()); + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); + + // THEN + assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); + verify(ctxMock, never()).tellSuccess(any()); } @Test - public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { + public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { + // GIVEN node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new TenantId(UUID.randomUUID()), new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "[]"); - var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctx, msg)); + // WHEN + var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); + // THEN assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); - verify(ctx, never()).tellSuccess(any()); + verify(ctxMock, never()).tellSuccess(any()); } @Test - public void errorThrownIfCannotLoadAttributes() { - errorThrownIfCannotLoadAttributes(user); - } + public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { + // SETUP + var expectedExceptionMessage = "Entity with id: '" + DUMMY_ENTITY_ID + + "' specified in the configuration doesn't belong to the current tenant."; - @Test - public void errorThrownIfCannotLoadAttributesAsync() { - errorThrownIfCannotLoadAttributesAsync(user); - } + // GIVEN + doThrow(new RuntimeException(expectedExceptionMessage)).when(ctxMock).checkTenantEntity(DUMMY_ENTITY_ID); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, new TbMsgMetaData(), "{}"); - @Test - public void failedChainUsedIfTenantIdFromCtxCannotBeFound() { - when(ctx.getTenantId()).thenReturn(null); - failedChainUsedIfCustomerCannotBeFound(user); - } + // WHEN + var exception = assertThrows(RuntimeException.class, () -> node.onMsg(ctxMock, msg)); - @Test - public void customerAttributeAddedInMetadata() { - entityAttributeAddedInMetadata(tenantId, "TENANT"); + // THEN + assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); + verify(ctxMock, never()).tellSuccess(any()); } @Test - public void customerAttributeAddedInData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - - msg = TbMsg.newMsg("TENANT", tenantId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); - - when(ctx.getAttributesService()).thenReturn(attributesService); - when(attributesService.find(any(), eq(tenantId), eq(SERVER_SCOPE), anyCollection())) + public void givenFetchAttributesToData_whenOnMsg_thenShouldFetchAttributesToData() { + // GIVEN + var deviceId = new DeviceId(UUID.randomUUID()); + + prepareMsgAndConfig(FetchTo.DATA, false, deviceId); + + 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(TENANT_ID), eq(SERVER_SCOPE), anyList())) .thenReturn(Futures.immediateFuture(attributes)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - node.onMsg(ctx, msg); + // WHEN + node.onMsg(ctxMock, msg); + // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); - var expectedMsgData = "{\"answer\":\"high\"}"; + var expectedMsgData = "{\"temp\":42," + + "\"humidity\":77," + + "\"messageBodyPattern1\":\"targetKey2\"," + + "\"messageBodyPattern2\":\"sourceKey3\"," + + "\"targetKey1\":\"sourceValue1\"," + + "\"targetKey2\":\"sourceValue2\"," + + "\"targetKey3\":\"sourceValue3\"}"; assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @Test - public void usersCustomerAttributesFetched() { - usersCustomerAttributesFetched(user); + public void givenFetchAttributesToMetaData_whenOnMsg_thenShouldFetchAttributesToMetaData() { + // GIVEN + prepareMsgAndConfig(FetchTo.METADATA, false, TENANT_ID); + + 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(TENANT_ID), eq(SERVER_SCOPE), anyList())) + .thenReturn(Futures.immediateFuture(attributes)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // 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(Map.of( + "metaDataPattern1", "sourceKey2", + "metaDataPattern2", "targetKey3", + "targetKey1", "sourceValue1", + "targetKey2", "sourceValue2", + "targetKey3", "sourceValue3" + )); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } @Test - public void assetsCustomerAttributesFetched() { - assetsCustomerAttributesFetched(asset); + public void givenFetchTelemetryToData_whenOnMsg_thenShouldFetchTelemetryToData() { + // GIVEN + var customerId = new CustomerId(UUID.randomUUID()); + + 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())) + .thenReturn(Futures.immediateFuture(timeseries)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // 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 = "{\"temp\":42," + + "\"humidity\":77," + + "\"messageBodyPattern1\":\"targetKey2\"," + + "\"messageBodyPattern2\":\"sourceKey3\"," + + "\"targetKey1\":\"sourceValue1\"," + + "\"targetKey2\":\"sourceValue2\"," + + "\"targetKey3\":\"sourceValue3\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @Test - public void deviceCustomerAttributesFetched() { - deviceCustomerAttributesFetched(device); + public void givenFetchTelemetryToMetaData_whenOnMsg_thenShouldFetchTelemetryToMetaData() { + // GIVEN + var ruleChainId = new RuleChainId(UUID.randomUUID()); + + 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())) + .thenReturn(Futures.immediateFuture(timeseries)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // 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(Map.of( + "metaDataPattern1", "sourceKey2", + "metaDataPattern2", "targetKey3", + "targetKey1", "sourceValue1", + "targetKey2", "sourceValue2", + "targetKey3", "sourceValue3" + )); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } - @Test - public void deviceCustomerTelemetryFetched() throws TbNodeException { - deviceCustomerTelemetryFetched(device); + private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId entityId) { + config.setAttrMapping(Map.of( + "sourceKey1", "targetKey1", + "${metaDataPattern1}", "$[messageBodyPattern1]", + "$[messageBodyPattern2]", "${metaDataPattern2}")); + config.setTelemetry(isTelemetry); + config.setFetchTo(fetchTo); + + 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); } + } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java new file mode 100644 index 0000000000..5801cfdeb6 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java @@ -0,0 +1,134 @@ +package org.thingsboard.rule.engine.util; + +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +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.DeviceId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.user.UserService; + +import java.util.EnumSet; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +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.Mockito.doReturn; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class EntitiesCustomerIdAsyncLoaderTest { + + private static final EnumSet SUPPORTED_ENTITY_TYPES = EnumSet.of( + EntityType.CUSTOMER, + EntityType.USER, + EntityType.ASSET, + EntityType.DEVICE + ); + @Mock + private TbContext ctxMock; + @Mock + private UserService userServiceMock; + @Mock + private AssetService assetServiceMock; + @Mock + private DeviceService deviceServiceMock; + + @Test + public void givenCustomerEntityType_whenFindEntityIdAsync_thenOK() throws ExecutionException, InterruptedException { + // GIVEN + var customer = new Customer(new CustomerId(UUID.randomUUID())); + + // WHEN + var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, customer.getId()).get(); + + // THEN + Assertions.assertEquals(customer.getId(), actualCustomerId); + } + + @Test + public void givenUserEntityType_whenFindEntityIdAsync_thenOK() throws ExecutionException, InterruptedException { + // GIVEN + var user = new User(new UserId(UUID.randomUUID())); + var expectedCustomerId = new CustomerId(UUID.randomUUID()); + user.setCustomerId(expectedCustomerId); + + when(ctxMock.getUserService()).thenReturn(userServiceMock); + doReturn(Futures.immediateFuture(user)).when(userServiceMock).findUserByIdAsync(any(), any()); + + // WHEN + var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, user.getId()).get(); + + // THEN + Assertions.assertEquals(expectedCustomerId, actualCustomerId); + } + + @Test + public void givenAssetEntityType_whenFindEntityIdAsync_thenOK() throws ExecutionException, InterruptedException { + // GIVEN + var asset = new Asset(new AssetId(UUID.randomUUID())); + var expectedCustomerId = new CustomerId(UUID.randomUUID()); + asset.setCustomerId(expectedCustomerId); + + when(ctxMock.getAssetService()).thenReturn(assetServiceMock); + doReturn(Futures.immediateFuture(asset)).when(assetServiceMock).findAssetByIdAsync(any(), any()); + + // WHEN + var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, asset.getId()).get(); + + // THEN + Assertions.assertEquals(expectedCustomerId, actualCustomerId); + } + + @Test + public void givenDeviceEntityType_whenFindEntityIdAsync_thenOK() throws ExecutionException, InterruptedException { + // GIVEN + var device = new Device(new DeviceId(UUID.randomUUID())); + var expectedCustomerId = new CustomerId(UUID.randomUUID()); + device.setCustomerId(expectedCustomerId); + + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + doReturn(Futures.immediateFuture(device)).when(deviceServiceMock).findDeviceByIdAsync(any(), any()); + + // WHEN + var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, device.getId()).get(); + + // THEN + Assertions.assertEquals(expectedCustomerId, actualCustomerId); + } + + @Test + public void givenUnsupportedEntityTypes_whenFindEntityIdAsync_thenException() { + for (var entityType : EntityType.values()) { + if (!SUPPORTED_ENTITY_TYPES.contains(entityType)) { + var entityId = EntityIdFactory.getByTypeAndUuid(entityType, UUID.randomUUID()); + + var expectedExceptionMsg = "org.thingsboard.rule.engine.api.TbNodeException: Unexpected originator EntityType: " + entityType; + + var exception = assertThrows(ExecutionException.class, + () -> EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, entityId).get()); + + assertInstanceOf(TbNodeException.class, exception.getCause()); + assertEquals(expectedExceptionMsg, exception.getMessage()); + } + } + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java index 5dfd8be03f..cb3c8b31a2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -16,12 +16,15 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; @@ -56,7 +59,9 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import java.util.EnumSet; +import java.util.NoSuchElementException; import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import static org.assertj.core.api.Assertions.assertThat; @@ -69,6 +74,22 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class EntitiesFieldsAsyncLoaderTest { + + private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + }; private static EnumSet SUPPORTED_ENTITY_TYPES; private static UUID RANDOM_UUID; private static TenantId TENANT_ID; @@ -148,12 +169,12 @@ public class EntitiesFieldsAsyncLoaderTest { initMocks(entityType, true); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - var expectedExceptionMsg = "org.thingsboard.rule.engine.api.TbNodeException: Entity not found!"; + var expectedExceptionMsg = "java.util.NoSuchElementException: Entity not found!"; var exception = assertThrows(ExecutionException.class, () -> EntitiesFieldsAsyncLoader.findAsync(ctxMock, entityId).get()); - assertInstanceOf(TbNodeException.class, exception.getCause()); + assertInstanceOf(NoSuchElementException.class, exception.getCause()); assertThat(exception.getMessage()).isEqualTo(expectedExceptionMsg); } } @@ -163,6 +184,7 @@ public class EntitiesFieldsAsyncLoaderTest { case TENANT: var tenant = Futures.immediateFuture(entityDoesNotExist ? null : new Tenant(new TenantId(RANDOM_UUID))); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getTenantService()).thenReturn(tenantServiceMock); doReturn(tenant).when(tenantServiceMock).findTenantByIdAsync(eq(TENANT_ID), any()); @@ -170,6 +192,7 @@ public class EntitiesFieldsAsyncLoaderTest { case CUSTOMER: var customer = Futures.immediateFuture(entityDoesNotExist ? null : new Customer(new CustomerId(RANDOM_UUID))); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getCustomerService()).thenReturn(customerServiceMock); doReturn(customer).when(customerServiceMock).findCustomerByIdAsync(eq(TENANT_ID), any()); @@ -177,6 +200,7 @@ public class EntitiesFieldsAsyncLoaderTest { case USER: var user = Futures.immediateFuture(entityDoesNotExist ? null : new User(new UserId(RANDOM_UUID))); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getUserService()).thenReturn(userServiceMock); doReturn(user).when(userServiceMock).findUserByIdAsync(eq(TENANT_ID), any()); @@ -184,6 +208,7 @@ public class EntitiesFieldsAsyncLoaderTest { case ASSET: var asset = Futures.immediateFuture(entityDoesNotExist ? null : new Asset(new AssetId(RANDOM_UUID))); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getAssetService()).thenReturn(assetServiceMock); doReturn(asset).when(assetServiceMock).findAssetByIdAsync(eq(TENANT_ID), any()); @@ -191,6 +216,7 @@ public class EntitiesFieldsAsyncLoaderTest { case DEVICE: var device = Futures.immediateFuture(entityDoesNotExist ? null : new Device(new DeviceId(RANDOM_UUID))); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); doReturn(device).when(deviceServiceMock).findDeviceByIdAsync(eq(TENANT_ID), any()); @@ -198,6 +224,7 @@ public class EntitiesFieldsAsyncLoaderTest { case ALARM: var alarm = Futures.immediateFuture(entityDoesNotExist ? null : new Alarm(new AlarmId(RANDOM_UUID))); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getAlarmService()).thenReturn(ruleEngineAlarmServiceMock); doReturn(alarm).when(ruleEngineAlarmServiceMock).findAlarmByIdAsync(eq(TENANT_ID), any()); @@ -205,6 +232,7 @@ public class EntitiesFieldsAsyncLoaderTest { case RULE_CHAIN: var ruleChain = Futures.immediateFuture(entityDoesNotExist ? null : new RuleChain(new RuleChainId(RANDOM_UUID))); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getRuleChainService()).thenReturn(ruleChainServiceMock); doReturn(ruleChain).when(ruleChainServiceMock).findRuleChainByIdAsync(eq(TENANT_ID), any()); @@ -212,6 +240,7 @@ public class EntitiesFieldsAsyncLoaderTest { case ENTITY_VIEW: var entityView = Futures.immediateFuture(entityDoesNotExist ? null : new EntityView(new EntityViewId(RANDOM_UUID))); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getEntityViewService()).thenReturn(entityViewServiceMock); doReturn(entityView).when(entityViewServiceMock).findEntityViewByIdAsync(eq(TENANT_ID), any()); @@ -243,4 +272,5 @@ public class EntitiesFieldsAsyncLoaderTest { throw new RuntimeException("Unexpected EntityType: " + entityId.getEntityType()); } } + } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java index 428c01fa7d..471ce290d3 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -69,7 +69,6 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRuleService; -import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.ota.OtaPackageService; @@ -135,8 +134,6 @@ public class TenantIdLoaderTest { @Mock private NotificationRequestService notificationRequestService; @Mock - private NotificationService notificationService; - @Mock private NotificationRuleService notificationRuleService; private TenantId tenantId; @@ -393,4 +390,5 @@ public class TenantIdLoaderTest { public void test_findEntityIdAsync_other_tenant() { checkTenant(new TenantId(UUID.randomUUID()), false); } + } From 02d047c112095c0a931e3214f99f96f0a9020344 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 5 Apr 2023 12:02:48 +0300 Subject: [PATCH 02/29] Refactor: fix license headers --- .../rule/engine/metadata/CalculateDeltaNode.java | 8 ++++---- .../metadata/CalculateDeltaNodeConfiguration.java | 8 ++++---- .../util/EntitiesCustomerIdAsyncLoader.java | 8 ++++---- .../util/EntitiesRelatedDeviceIdAsyncLoader.java | 8 ++++---- .../OldTbGetCustomerAttributeNodeTest.java | 8 ++++---- .../TbFetchDeviceCredentialsNodeTest.java | 8 ++++---- .../engine/metadata/TbGetAttributesNodeTest.java | 8 ++++---- .../metadata/TbGetCustomerAttributeNodeTest.java | 8 ++++---- .../metadata/TbGetOriginatorFieldsNodeTest.java | 8 ++++---- .../metadata/TbGetRelatedAttributeNodeTest.java | 8 ++++---- .../metadata/TbGetTenantAttributeNodeTest.java | 8 ++++---- .../util/EntitiesCustomerIdAsyncLoaderTest.java | 15 +++++++++++++++ .../util/EntitiesFieldsAsyncLoaderTest.java | 8 ++++---- .../rule/engine/util/TenantIdLoaderTest.java | 8 ++++---- 14 files changed, 67 insertions(+), 52 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 080344c8d3..ca96fdead9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java index 2dd66ed5ae..b52c29fa07 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java index c51136eedf..d517afaf35 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java index efb4e856fb..e0ef9ac036 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java index 54a1c54ff4..69581ef49a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java index 6b442efab1..397b307647 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index da6060e0a9..b627b8e5f8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. 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 658dbe8b78..0693a12526 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 @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. 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 9bd03b4f0b..58e3431af6 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 @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. 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 3725f24dc4..0fa3b6b367 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 @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. 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 d4f30f986b..6b7509d0d0 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 @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java index 5801cfdeb6..97201daeda 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java @@ -1,3 +1,18 @@ +/** + * 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.util; import com.google.common.util.concurrent.Futures; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java index cb3c8b31a2..70b41347f5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java index 471ce290d3..0871a82550 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. From 6906b1ba64ba4fe0e66b9b9feb419e181c6c88d4 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 7 Apr 2023 19:02:03 +0300 Subject: [PATCH 03/29] Add tests --- .../metadata/TbAbstractGetEntityAttrNode.java | 4 +- .../TbAbstractGetEntityDetailsNode.java | 7 +- .../metadata/TbGetCustomerAttributeNode.java | 2 +- .../metadata/TbGetCustomerDetailsNode.java | 5 +- .../metadata/TbGetRelatedAttributeNode.java | 2 +- .../metadata/TbGetTenantAttributeNode.java | 2 +- .../metadata/TbGetTenantDetailsNode.java | 6 +- .../TbGetTenantDetailsNodeConfiguration.java | 5 +- .../TbGetCustomerAttributeNodeTest.java | 135 +++-- .../TbGetOriginatorFieldsNodeTest.java | 45 +- .../TbGetRelatedAttributeNodeTest.java | 165 +++--- .../TbGetTenantAttributeNodeTest.java | 97 +++- .../metadata/TbGetTenantDetailsNodeTest.java | 482 ++++++++++++++++++ 13 files changed, 790 insertions(+), 167 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java 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); + } + +} From 9f0d70c87a4744032963d11c99990a4b1914fca3 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 11 Apr 2023 13:11:32 +0300 Subject: [PATCH 04/29] Add tests for customer and tenant details nodes --- .../metadata/TbGetCustomerDetailsNode.java | 2 +- ...TbGetCustomerDetailsNodeConfiguration.java | 5 +- .../TbGetCustomerDetailsNodeTest.java | 487 ++++++++++++++++++ .../metadata/TbGetTenantDetailsNodeTest.java | 288 +++-------- 4 files changed, 565 insertions(+), 217 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java index 5bf01211d5..2c7fe2302b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java @@ -92,7 +92,7 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode node.init(ctxMock, nodeConfiguration)); + + // THEN + assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void givenNoEntityDetailsSelected_whenInit_thenException() { + // GIVEN + var expectedExceptionMessage = "No entity details selected!"; + + config.setDetailsList(Collections.emptyList()); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); + + // THEN + assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); + 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.CITY)); + 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() { + // GIVEN + var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + + "' specified in the configuration doesn't belong to the current tenant."; + + 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 device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + device.setCustomerId(customer.getId()); + + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.values()), device.getId()); + + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); + + mockFindCustomer(); + + // 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\"," + + "\"customer_id\":\"" + customer.getId() + "\"," + + "\"customer_title\":\"" + customer.getTitle() + "\"," + + "\"customer_country\":\"" + customer.getCountry() + "\"," + + "\"customer_city\":\"" + customer.getCity() + "\"," + + "\"customer_state\":\"" + customer.getState() + "\"," + + "\"customer_zip\":\"" + customer.getZip() + "\"," + + "\"customer_address\":\"" + customer.getAddress() + "\"," + + "\"customer_address2\":\"" + customer.getAddress2() + "\"," + + "\"customer_phone\":\"" + customer.getPhone() + "\"," + + "\"customer_email\":\"" + customer.getEmail() + "\"," + + "\"customer_additionalInfo\":\"" + customer.getAdditionalInfo().get("description").asText() + "\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + } + + @Test + public void givenSomeEntityDetailsAndFetchToMetadata_whenOnMsg_thenShouldTellSuccessAndFetchSomeToMetaData() { + // GIVEN + var asset = new Asset(); + asset.setId(new AssetId(UUID.randomUUID())); + asset.setCustomerId(customer.getId()); + + prepareMsgAndConfig(FetchTo.METADATA, List.of(EntityDetails.ID, EntityDetails.TITLE, EntityDetails.PHONE), asset.getId()); + + when(ctxMock.getAssetService()).thenReturn(assetServiceMock); + when(assetServiceMock.findAssetByIdAsync(eq(TENANT_ID), eq(asset.getId()))).thenReturn(Futures.immediateFuture(asset)); + + mockFindCustomer(); + + // 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(msg.getMetaData().getData()); + expectedMsgMetaData.putValue("customer_id", customer.getId().getId().toString()); + expectedMsgMetaData.putValue("customer_title", customer.getTitle()); + expectedMsgMetaData.putValue("customer_phone", customer.getPhone()); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); + } + + @Test + public void givenNoEntityDetailsAndFetchToMetadata_whenOnMsg_thenShouldTellSuccessAndFetchNothingToMetaData() { + // GIVEN + var entityView = new EntityView(); + entityView.setId(new EntityViewId(UUID.randomUUID())); + entityView.setCustomerId(customer.getId()); + + prepareMsgAndConfig(FetchTo.METADATA, Collections.emptyList(), entityView.getId()); + + when(ctxMock.getEntityViewService()).thenReturn(entityViewServiceMock); + when(entityViewServiceMock.findEntityViewByIdAsync(eq(TENANT_ID), eq(entityView.getId()))).thenReturn(Futures.immediateFuture(entityView)); + + mockFindCustomer(); + + // 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(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + } + + @Test + public void givenNotPresentEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { + // GIVEN + customer.setZip(null); + customer.setAddress(null); + customer.setAddress2(null); + + var user = new User(); + user.setId(new UserId(UUID.randomUUID())); + user.setCustomerId(customer.getId()); + + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), user.getId()); + + when(ctxMock.getUserService()).thenReturn(userServiceMock); + when(userServiceMock.findUserByIdAsync(eq(TENANT_ID), eq(user.getId()))).thenReturn(Futures.immediateFuture(user)); + + mockFindCustomer(); + + // 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(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + } + + @Test + public void givenDidNotFindCustomer_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { + // GIVEN + var edge = new Edge(); + edge.setId(new EdgeId(UUID.randomUUID())); + edge.setCustomerId(customer.getId()); + + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), edge.getId()); + + when(ctxMock.getEdgeService()).thenReturn(edgeServiceMock); + when(edgeServiceMock.findEdgeByIdAsync(eq(TENANT_ID), eq(edge.getId()))).thenReturn(Futures.immediateFuture(edge)); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getCustomerService()).thenReturn(customerServiceMock); + when(customerServiceMock.findCustomerByIdAsync(eq(TENANT_ID), eq(customer.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(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + } + + @Test + public void givenDidNotFindOriginator_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { + // GIVEN + var edge = new Edge(); + edge.setId(new EdgeId(UUID.randomUUID())); + edge.setCustomerId(customer.getId()); + + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), edge.getId()); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getEdgeService()).thenReturn(edgeServiceMock); + when(edgeServiceMock.findEdgeByIdAsync(eq(TENANT_ID), eq(edge.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(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + } + + @Test + public void givenOriginatorNotAssignedToCustomer_whenOnMsg_thenShouldTellFailureAndFetchNothingToData() { + // GIVEN + var device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), device.getId()); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellFailure(actualMessageCaptor.capture(), any()); + verify(ctxMock, never()).tellSuccess(any()); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + } + + @Test + public void givenNullDescriptionAndAddInfoEntityDetails_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { + // GIVEN + customer.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":null}")); + + var device = new Device(); + device.setId(new DeviceId(UUID.randomUUID())); + device.setCustomerId(customer.getId()); + + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ADDITIONAL_INFO), device.getId()); + + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); + + mockFindCustomer(); + + // 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(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + } + + @Test + public void givenUnsupportedEntityType_whenOnMsg_thenShouldTellFailureAndFetchNothingToMetaData() { + // GIVEN + var dashboard = new Dashboard(); + dashboard.setId(new DashboardId(UUID.randomUUID())); + + prepareMsgAndConfig(FetchTo.METADATA, List.of(EntityDetails.STATE), dashboard.getId()); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellFailure(actualMessageCaptor.capture(), any()); + verify(ctxMock, never()).tellSuccess(any()); + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + } + + private void prepareMsgAndConfig(FetchTo fetchTo, List detailsList, EntityId originator) { + config.setDetailsList(detailsList); + config.setFetchTo(fetchTo); + + node.config = config; + node.fetchTo = fetchTo; + + var msgMetaData = new TbMsgMetaData(); + msgMetaData.putValue("metaKey1", "metaValue1"); + msgMetaData.putValue("metaKey2", "metaValue2"); + + var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; + + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + } + + private void mockFindCustomer() { + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getCustomerService()).thenReturn(customerServiceMock); + when(customerServiceMock.findCustomerByIdAsync(eq(TENANT_ID), eq(customer.getId()))).thenReturn(Futures.immediateFuture(customer)); + } + +} 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 index 5611e36d4b..d05ad23db6 100644 --- 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 @@ -27,7 +27,6 @@ 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; @@ -61,12 +60,25 @@ public class TbGetTenantDetailsNodeTest { private TbGetTenantDetailsNodeConfiguration config; private TbNodeConfiguration nodeConfiguration; private TbMsg msg; + private Tenant tenant; @BeforeEach public void setUp() { node = new TbGetTenantDetailsNode(); config = new TbGetTenantDetailsNodeConfiguration().defaultConfiguration(); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + 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\"}")); } @Test @@ -84,10 +96,24 @@ public class TbGetTenantDetailsNodeTest { } @Test - public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { + public void givenNoEntityDetailsSelected_whenInit_thenException() { // GIVEN + var expectedExceptionMessage = "No entity details selected!"; + + config.setDetailsList(Collections.emptyList()); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); + + // THEN + assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); + verify(ctxMock, never()).tellSuccess(any()); + } + + @Test + public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { + // GIVEN-WHEN node.init(ctxMock, nodeConfiguration); // THEN @@ -130,11 +156,10 @@ public class TbGetTenantDetailsNodeTest { @Test public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { - // SETUP + // 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_DEVICE_ORIGINATOR); msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); @@ -149,43 +174,9 @@ public class TbGetTenantDetailsNodeTest { @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"); + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.values())); - 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)); + mockFindTenant(); // WHEN node.onMsg(ctxMock, msg); @@ -210,49 +201,15 @@ public class TbGetTenantDetailsNodeTest { "\"tenant_additionalInfo\":\"" + tenant.getAdditionalInfo().get("description").asText() + "\"}"; assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); - assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @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; + prepareMsgAndConfig(FetchTo.METADATA, List.of(EntityDetails.ID, EntityDetails.TITLE, EntityDetails.PHONE)); - 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)); + mockFindTenant(); // WHEN node.onMsg(ctxMock, msg); @@ -263,55 +220,21 @@ public class TbGetTenantDetailsNodeTest { verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellFailure(any(), any()); - var expectedMsgMetaData = new TbMsgMetaData(msgMetaData.getData()); + var expectedMsgMetaData = new TbMsgMetaData(msg.getMetaData().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().getData()).isEqualTo(msg.getData()); 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"); + prepareMsgAndConfig(FetchTo.METADATA, Collections.emptyList()); - 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)); + mockFindTenant(); // WHEN node.onMsg(ctxMock, msg); @@ -322,47 +245,20 @@ public class TbGetTenantDetailsNodeTest { 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); + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @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; + tenant.setZip(null); + tenant.setAddress(null); + tenant.setAddress2(null); - var msgMetaData = new TbMsgMetaData(); - msgMetaData.putValue("metaKey1", "metaValue1"); - msgMetaData.putValue("metaKey2", "metaValue2"); + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2)); - 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)); + mockFindTenant(); // WHEN node.onMsg(ctxMock, msg); @@ -373,45 +269,16 @@ public class TbGetTenantDetailsNodeTest { 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); + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @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); + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2)); when(ctxMock.getTenantId()).thenReturn(tenant.getId()); - when(ctxMock.getTenantService()).thenReturn(tenantServiceMock); when(tenantServiceMock.findTenantByIdAsync(eq(tenant.getId()), eq(tenant.getId()))).thenReturn(Futures.immediateFuture(null)); @@ -424,34 +291,38 @@ public class TbGetTenantDetailsNodeTest { 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); + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); } @Test - public void givenNullDescription_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { + public void givenNullDescriptionAndAddInfoEntityDetails_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"); + prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ADDITIONAL_INFO)); + + mockFindTenant(); + + // 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(msg.getData()); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + } - config.setDetailsList(List.of(EntityDetails.ADDITIONAL_INFO)); - config.setFetchTo(FetchTo.DATA); + private void prepareMsgAndConfig(FetchTo fetchTo, List detailsList) { + config.setDetailsList(detailsList); + config.setFetchTo(fetchTo); node.config = config; - node.fetchTo = FetchTo.DATA; + node.fetchTo = fetchTo; var msgMetaData = new TbMsgMetaData(); msgMetaData.putValue("metaKey1", "metaValue1"); @@ -459,24 +330,13 @@ public class TbGetTenantDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", device.getId(), msgMetaData, msgData); + msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + } + private void mockFindTenant() { 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); } } From 4b59611158b65b8c54666fda21f2e44f68e3aa01 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 12 Apr 2023 12:27:20 +0300 Subject: [PATCH 05/29] Add tests for relation loaders --- .../update/DefaultDataUpdateService.java | 4 +- ...e.java => TbGetRelatedDeviceAttrNode.java} | 2 +- ...ntitiesRelatedDeviceIdAsyncLoaderTest.java | 117 +++++++++++++++ ...itiesRelatedEntitiesIdAsyncLoaderTest.java | 138 ++++++++++++++++++ 4 files changed, 258 insertions(+), 3 deletions(-) rename rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/{TbGetDeviceAttrNode.java => TbGetRelatedDeviceAttrNode.java} (96%) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index acade3afba..808fec6b53 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -33,7 +33,7 @@ import org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode; import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; import org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode; import org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode; -import org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode; +import org.thingsboard.rule.engine.metadata.TbGetRelatedDeviceAttrNode; import org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode; import org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode; import org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode; @@ -232,7 +232,7 @@ public class DefaultDataUpdateService implements DataUpdateService { TbGetOriginatorFieldsNode.class.getName(), TbFetchDeviceCredentialsNode.class.getName(), TbGetAttributesNode.class.getName(), - TbGetDeviceAttrNode.class.getName(), + TbGetRelatedDeviceAttrNode.class.getName(), TbGetRelatedAttributeNode.class.getName(), TbGetTenantAttributeNode.class.getName(), TbGetCustomerAttributeNode.class.getName(), diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDeviceAttrNode.java similarity index 96% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDeviceAttrNode.java index fdbb412e6b..53220837a9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDeviceAttrNode.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.msg.TbMsg; "metadata.cs_temperature or metadata.shared_limit ", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeDeviceAttributesConfig") -public class TbGetDeviceAttrNode extends TbAbstractGetAttributesNode { +public class TbGetRelatedDeviceAttrNode extends TbAbstractGetAttributesNode { private static final String RELATED_DEVICE_NOT_FOUND_MESSAGE = "Failed to find related device to message originator using relation query specified in the configuration!"; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java new file mode 100644 index 0000000000..dfea960c07 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java @@ -0,0 +1,117 @@ +package org.thingsboard.rule.engine.util; + +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.data.DeviceRelationsQuery; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.device.DeviceSearchQuery; +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.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; +import org.thingsboard.server.dao.device.DeviceService; + +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class EntitiesRelatedDeviceIdAsyncLoaderTest { + + private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID()); + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + @Mock + private TbContext ctxMock; + @Mock + private DeviceService deviceServiceMock; + + @Test + public void givenDeviceRelationsQuery_whenFindDeviceAsync_ShouldBuildCorrectDeviceSearchQuery() { + // GIVEN + var deviceRelationsQuery = new DeviceRelationsQuery(); + deviceRelationsQuery.setDeviceTypes(List.of("Device type 1", "Device type 2", "default")); + deviceRelationsQuery.setDirection(EntitySearchDirection.FROM); + deviceRelationsQuery.setMaxLevel(2); + deviceRelationsQuery.setRelationType(EntityRelation.CONTAINS_TYPE); + + var expectedDeviceSearchQuery = new DeviceSearchQuery(); + var parameters = new RelationsSearchParameters( + DUMMY_ORIGINATOR, + deviceRelationsQuery.getDirection(), + deviceRelationsQuery.getMaxLevel(), + deviceRelationsQuery.isFetchLastLevelOnly() + ); + expectedDeviceSearchQuery.setParameters(parameters); + expectedDeviceSearchQuery.setRelationType(deviceRelationsQuery.getRelationType()); + expectedDeviceSearchQuery.setDeviceTypes(deviceRelationsQuery.getDeviceTypes()); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(deviceServiceMock.findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery))) + .thenReturn(Futures.immediateFuture(null)); + + // WHEN + EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctxMock, DUMMY_ORIGINATOR, deviceRelationsQuery); + + // THEN + verify(deviceServiceMock, times(1)).findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery)); + } + + @Test + public void givenSeveralDevicesFound_whenFindDeviceAsync_ShouldKeepOneAndDiscardOthers() throws Exception { + // GIVEN + var deviceRelationsQuery = new DeviceRelationsQuery(); + deviceRelationsQuery.setDeviceTypes(List.of("Device type 1", "Device type 2", "default")); + deviceRelationsQuery.setDirection(EntitySearchDirection.FROM); + deviceRelationsQuery.setMaxLevel(2); + deviceRelationsQuery.setRelationType(EntityRelation.CONTAINS_TYPE); + + var expectedDeviceSearchQuery = new DeviceSearchQuery(); + var parameters = new RelationsSearchParameters( + DUMMY_ORIGINATOR, + deviceRelationsQuery.getDirection(), + deviceRelationsQuery.getMaxLevel(), + deviceRelationsQuery.isFetchLastLevelOnly() + ); + expectedDeviceSearchQuery.setParameters(parameters); + expectedDeviceSearchQuery.setRelationType(deviceRelationsQuery.getRelationType()); + expectedDeviceSearchQuery.setDeviceTypes(deviceRelationsQuery.getDeviceTypes()); + + var device1 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 1"); + var device2 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 2"); + var device3 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 3"); + + var devicesList = List.of(device1, device2, device3); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(deviceServiceMock.findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery))) + .thenReturn(Futures.immediateFuture(devicesList)); + + // WHEN + var entityIdFuture = EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctxMock, DUMMY_ORIGINATOR, deviceRelationsQuery); + + // THEN + assertNotNull(entityIdFuture); + + var actualEntityId = entityIdFuture.get(); + assertNotNull(actualEntityId); + assertEquals(device1.getId(), actualEntityId); + } + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java new file mode 100644 index 0000000000..2b27ce12bf --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java @@ -0,0 +1,138 @@ +package org.thingsboard.rule.engine.util; + +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.data.RelationsQuery; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.AssetId; +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.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntityRelationsQuery; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; +import org.thingsboard.server.dao.relation.RelationService; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class EntitiesRelatedEntitiesIdAsyncLoaderTest { + + private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID()); + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + @Mock + private TbContext ctxMock; + @Mock + private RelationService relationServiceMock; + + @Test + public void givenRelationsQuery_whenFindEntityAsync_ShouldBuildCorrectEntityRelationsQuery() { + // GIVEN + var relationsQuery = new RelationsQuery(); + var relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList()); + relationsQuery.setDirection(EntitySearchDirection.FROM); + relationsQuery.setMaxLevel(1); + relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter)); + + var expectedEntityRelationsQuery = new EntityRelationsQuery(); + var parameters = new RelationsSearchParameters( + DUMMY_ORIGINATOR, + relationsQuery.getDirection(), + relationsQuery.getMaxLevel(), + relationsQuery.isFetchLastLevelOnly() + ); + expectedEntityRelationsQuery.setParameters(parameters); + expectedEntityRelationsQuery.setFilters(relationsQuery.getFilters()); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + when(relationServiceMock.findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery))) + .thenReturn(Futures.immediateFuture(null)); + + // WHEN + EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctxMock, DUMMY_ORIGINATOR, relationsQuery); + + // THEN + verify(relationServiceMock, times(1)).findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery)); + } + + @Test + public void givenSeveralEntitiesFound_whenFindEntityAsync_ShouldKeepOneAndDiscardOthers() throws Exception { + // GIVEN + var relationsQuery = new RelationsQuery(); + var relationEntityTypeFilter = new RelationEntityTypeFilter( + EntityRelation.CONTAINS_TYPE, + List.of(EntityType.DEVICE, EntityType.ASSET) + ); + relationsQuery.setDirection(EntitySearchDirection.FROM); + relationsQuery.setMaxLevel(2); + relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter)); + + var expectedEntityRelationsQuery = new EntityRelationsQuery(); + var parameters = new RelationsSearchParameters( + DUMMY_ORIGINATOR, + relationsQuery.getDirection(), + relationsQuery.getMaxLevel(), + relationsQuery.isFetchLastLevelOnly() + ); + expectedEntityRelationsQuery.setParameters(parameters); + expectedEntityRelationsQuery.setFilters(relationsQuery.getFilters()); + + var device1 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 1"); + var device2 = new Device(new DeviceId(UUID.randomUUID())); + device1.setName("Device 2"); + var asset = new Asset(new AssetId(UUID.randomUUID())); + asset.setName("Asset"); + + var entityRelationDevice1 = new EntityRelation(); + entityRelationDevice1.setFrom(DUMMY_ORIGINATOR); + entityRelationDevice1.setTo(device1.getId()); + entityRelationDevice1.setType(EntityRelation.CONTAINS_TYPE); + + var entityRelationDevice2 = new EntityRelation(); + entityRelationDevice2.setFrom(DUMMY_ORIGINATOR); + entityRelationDevice2.setTo(device2.getId()); + entityRelationDevice2.setType(EntityRelation.CONTAINS_TYPE); + + var entityRelationAsset = new EntityRelation(); + entityRelationAsset.setFrom(DUMMY_ORIGINATOR); + entityRelationAsset.setTo(asset.getId()); + entityRelationAsset.setType(EntityRelation.CONTAINS_TYPE); + + var expectedEntityRelationsList = List.of(entityRelationDevice1, entityRelationDevice2, entityRelationAsset); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getRelationService()).thenReturn(relationServiceMock); + when(relationServiceMock.findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery))) + .thenReturn(Futures.immediateFuture(expectedEntityRelationsList)); + + // WHEN + var deviceIdFuture = EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctxMock, DUMMY_ORIGINATOR, relationsQuery); + + // THEN + assertNotNull(deviceIdFuture); + + var actualDeviceId = deviceIdFuture.get(); + assertNotNull(actualDeviceId); + assertEquals(device1.getId(), actualDeviceId); + } + +} From 8bbfa8d66033cbb440b210de6211db9554a9b090 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 12 Apr 2023 12:29:00 +0300 Subject: [PATCH 06/29] Fix license headers --- .../EntitiesRelatedDeviceIdAsyncLoaderTest.java | 15 +++++++++++++++ .../EntitiesRelatedEntitiesIdAsyncLoaderTest.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java index dfea960c07..fa21f3746b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java @@ -1,3 +1,18 @@ +/** + * 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.util; import com.google.common.util.concurrent.Futures; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java index 2b27ce12bf..aae13a52a9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java @@ -1,3 +1,18 @@ +/** + * 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.util; import com.google.common.util.concurrent.Futures; From 825c26982f1a8310afe26dde544d0df0968f1899 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 14 Apr 2023 18:00:17 +0300 Subject: [PATCH 07/29] WIP: add most test cases for CalculateDeltaNode --- .../metadata/CalculateDeltaNodeTest.java | 447 ++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java new file mode 100644 index 0000000000..bff22823f9 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -0,0 +1,447 @@ +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; +import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.timeseries.TimeseriesService; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Callable; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CalculateDeltaNodeTest { + + 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 + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + }; + @Mock + private TbContext ctxMock; + @Mock + private TimeseriesService timeseriesServiceMock; + private CalculateDeltaNode node; + private CalculateDeltaNodeConfiguration config; + private TbNodeConfiguration nodeConfiguration; + + @BeforeEach + public void setUp() throws TbNodeException { + node = new CalculateDeltaNode(); + config = new CalculateDeltaNodeConfiguration().defaultConfiguration(); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + node.init(ctxMock, nodeConfiguration); + } + + @Test + public void givenDefaultConfig_whenDefaultConfiguration_thenVerify() { + assertEquals(config.getInputValueKey(), "pulseCounter"); + assertEquals(config.getOutputValueKey(), "delta"); + assertTrue(config.isUseCache()); + assertFalse(config.isAddPeriodBetweenMsgs()); + assertEquals(config.getPeriodValueKey(), "periodInMs"); + assertTrue(config.isTellFailureIfDeltaIsNegative()); + assertEquals(config.getRound(), 0); + } + + @Test + public void givenInvalidMsgType_whenOnMsg_thenShouldTellNextOther() { + // GIVEN + var msgData = "{\"pulseCounter\": 42}"; + var msg = TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other")); + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, never()).tellFailure(any(), any()); + } + + @Test + public void givenInputKeyIsNotPresent_whenOnMsg_thenShouldTellNextOther() { + // GIVEN + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other")); + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, never()).tellFailure(any(), any()); + } + + @Test + public void happyPathWithDoubleValueAndNoCache() throws TbNodeException { + // GIVEN + config.setInputValueKey("temperature"); + config.setOutputValueKey("temp_delta"); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfiguration); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(timeseriesServiceMock.findLatest( + eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) + )).thenReturn(Futures.immediateFuture( + List.of(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.0))) + )); + + var msgData = "{\"temperature\": 42,\"airPressure\":123}"; + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + verify(ctxMock, never()).tellFailure(any(), any()); + + var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2}"; + + assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); + } + + @Test + public void happyPathWithLongValueAndNoCache() throws TbNodeException { + // GIVEN + config.setInputValueKey("temperature"); + config.setOutputValueKey("temp_delta"); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfiguration); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(timeseriesServiceMock.findLatest( + eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) + )).thenReturn(Futures.immediateFuture( + List.of(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 40L))) + )); + + var msgData = "{\"temperature\": 42,\"airPressure\":123}"; + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + verify(ctxMock, never()).tellFailure(any(), any()); + + var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2}"; + + assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); + } + + @Test + public void happyPathWithStringValueAndNoCache() throws TbNodeException { + // GIVEN + config.setInputValueKey("temperature"); + config.setOutputValueKey("temp_delta"); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfiguration); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(timeseriesServiceMock.findLatest( + eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) + )).thenReturn(Futures.immediateFuture( + List.of(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("temperature", "40.0"))) + )); + + var msgData = "{\"temperature\": 42,\"airPressure\":123}"; + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + verify(ctxMock, never()).tellFailure(any(), any()); + + var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2}"; + + assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); + } + + @Test + public void happyPathWithDoubleValueAndCache() throws TbNodeException { + // STAGE 1 + // GIVEN + config.setInputValueKey("temperature"); + config.setOutputValueKey("temp_delta"); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfiguration); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(timeseriesServiceMock.findLatest( + eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) + )).thenReturn(Futures.immediateFuture( + List.of(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.0))) + )); + + var msgData = "{\"temperature\": 42,\"airPressure\":123}"; + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + verify(ctxMock, never()).tellFailure(any(), any()); + + var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2}"; + + assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); + + // STAGE 2 + // GIVEN + reset(ctxMock); + reset(timeseriesServiceMock); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(timeseriesServiceMock, never()).findLatest(any(), any(), anyList()); + verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + verify(ctxMock, never()).tellFailure(any(), any()); + + expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":0}"; + + assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); + } + + @Test + public void happyPathWithDoubleHaveDigitsAfterPointAndNoCache() throws TbNodeException { + // STAGE 1 + // GIVEN + config.setInputValueKey("temperature"); + config.setOutputValueKey("temp_delta"); + config.setRound(null); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfiguration); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(timeseriesServiceMock.findLatest( + eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) + )).thenReturn(Futures.immediateFuture( + List.of(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.500))) + )); + + var msgData = "{\"temperature\": 42,\"airPressure\":123}"; + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + verify(ctxMock, never()).tellFailure(any(), any()); + + var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":1.5}"; + + assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); + } + + @Test + public void failedPathWithStringValueAndNoCache() { + // GIVEN + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(timeseriesServiceMock.findLatest( + eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("pulseCounter"))) + )).thenReturn(Futures.immediateFuture( + List.of(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("pulseCounter", "high"))) + )); + + var msgData = "{\"pulseCounter\":\"123\"}"; + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class); + + verify(ctxMock, times(1)).tellFailure(actualMsgCaptor.capture(), actualExceptionCaptor.capture()); + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + + var expectedExceptionMsg = "Calculation failed. Unable to parse value [high] of telemetry [pulseCounter] to Double"; + var actualException = actualExceptionCaptor.getValue(); + + assertEquals(msg, actualMsgCaptor.getValue()); + assertInstanceOf(IllegalArgumentException.class, actualException); + assertEquals(expectedExceptionMsg, actualException.getMessage()); + } + + @Test + public void failedPathWithBooleanValueAndNoCache() { + // GIVEN + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(timeseriesServiceMock.findLatest( + eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("pulseCounter"))) + )).thenReturn(Futures.immediateFuture( + List.of(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry("pulseCounter", false))) + )); + + var msgData = "{\"pulseCounter\":true}"; + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class); + + verify(ctxMock, times(1)).tellFailure(actualMsgCaptor.capture(), actualExceptionCaptor.capture()); + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + + var expectedExceptionMsg = "Calculation failed. Boolean values are not supported!"; + var actualException = actualExceptionCaptor.getValue(); + + assertEquals(msg, actualMsgCaptor.getValue()); + assertInstanceOf(IllegalArgumentException.class, actualException); + assertEquals(expectedExceptionMsg, actualException.getMessage()); + } + + @Test + public void failedPathWithJsonValueAndNoCache() { + // GIVEN + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(timeseriesServiceMock.findLatest( + eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("pulseCounter"))) + )).thenReturn(Futures.immediateFuture( + List.of(new BasicTsKvEntry(System.currentTimeMillis(), new JsonDataEntry("pulseCounter", "{\"isActive\":false}"))) + )); + + var msgData = "{\"pulseCounter\":{\"isActive\":true}}"; + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class); + + verify(ctxMock, times(1)).tellFailure(actualMsgCaptor.capture(), actualExceptionCaptor.capture()); + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + + var expectedExceptionMsg = "Calculation failed. JSON values are not supported!"; + var actualException = actualExceptionCaptor.getValue(); + + assertEquals(msg, actualMsgCaptor.getValue()); + assertInstanceOf(IllegalArgumentException.class, actualException); + assertEquals(expectedExceptionMsg, actualException.getMessage()); + } + + @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); + } + + } + +} From f77de1aafd4dd047edb5fbaa9d73dff9418e1430 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 17 Apr 2023 11:24:31 +0300 Subject: [PATCH 08/29] Tests: add all test cases for CalculateDeltaNode --- .../metadata/CalculateDeltaNodeTest.java | 152 +++++++++--------- 1 file changed, 79 insertions(+), 73 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java index bff22823f9..72ec167e0c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -1,3 +1,18 @@ +/** + * 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; @@ -24,6 +39,7 @@ import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -82,6 +98,7 @@ public class CalculateDeltaNodeTest { config = new CalculateDeltaNodeConfiguration().defaultConfiguration(); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); + node.init(ctxMock, nodeConfiguration); } @@ -126,20 +143,15 @@ public class CalculateDeltaNodeTest { } @Test - public void happyPathWithDoubleValueAndNoCache() throws TbNodeException { + public void givenDoubleValue_whenOnMsg_thenSuccess() throws TbNodeException { // GIVEN + config.setRound(1); config.setInputValueKey("temperature"); config.setOutputValueKey("temp_delta"); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctxMock, nodeConfiguration); - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(timeseriesServiceMock.findLatest( - eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) - )).thenReturn(Futures.immediateFuture( - List.of(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.0))) - )); + mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.5))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); @@ -155,26 +167,20 @@ public class CalculateDeltaNodeTest { verify(ctxMock, never()).tellNext(any(), anySet()); verify(ctxMock, never()).tellFailure(any(), any()); - var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2}"; + var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":1.5}"; assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); } @Test - public void happyPathWithLongValueAndNoCache() throws TbNodeException { + public void givenLongStringValue_whenOnMsg_thenSuccess() throws TbNodeException { // GIVEN config.setInputValueKey("temperature"); config.setOutputValueKey("temp_delta"); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctxMock, nodeConfiguration); - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(timeseriesServiceMock.findLatest( - eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) - )).thenReturn(Futures.immediateFuture( - List.of(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 40L))) - )); + mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 40L))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); @@ -196,20 +202,14 @@ public class CalculateDeltaNodeTest { } @Test - public void happyPathWithStringValueAndNoCache() throws TbNodeException { + public void givenValidStringValue_whenOnMsg_thenSuccess() throws TbNodeException { // GIVEN config.setInputValueKey("temperature"); config.setOutputValueKey("temp_delta"); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctxMock, nodeConfiguration); - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(timeseriesServiceMock.findLatest( - eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) - )).thenReturn(Futures.immediateFuture( - List.of(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("temperature", "40.0"))) - )); + mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("temperature", "40.0"))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); @@ -231,27 +231,25 @@ public class CalculateDeltaNodeTest { } @Test - public void happyPathWithDoubleValueAndCache() throws TbNodeException { + public void givenTwoMessagesAndPeriodOnAndCachingOn_whenOnMsg_thenVerify() throws TbNodeException { // STAGE 1 // GIVEN config.setInputValueKey("temperature"); config.setOutputValueKey("temp_delta"); + config.setPeriodValueKey("ts_delta"); + config.setAddPeriodBetweenMsgs(true); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctxMock, nodeConfiguration); - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(timeseriesServiceMock.findLatest( - eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) - )).thenReturn(Futures.immediateFuture( - List.of(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.0))) - )); + mockFindLatest(new BasicTsKvEntry(1L, new DoubleDataEntry("temperature", 40.0))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var firstMsgMetaData = new TbMsgMetaData(); + firstMsgMetaData.putValue("ts", String.valueOf(3L)); + var firstMsg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData); // WHEN - node.onMsg(ctxMock, msg); + node.onMsg(ctxMock, firstMsg); // THEN var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -261,7 +259,7 @@ public class CalculateDeltaNodeTest { verify(ctxMock, never()).tellNext(any(), anySet()); verify(ctxMock, never()).tellFailure(any(), any()); - var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2}"; + var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2,\"ts_delta\":2}"; assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); @@ -270,8 +268,12 @@ public class CalculateDeltaNodeTest { reset(ctxMock); reset(timeseriesServiceMock); + var secondMsgMetaData = new TbMsgMetaData(); + secondMsgMetaData.putValue("ts", String.valueOf(6L)); + var secondMsg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData); + // WHEN - node.onMsg(ctxMock, msg); + node.onMsg(ctxMock, secondMsg); // THEN actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -282,28 +284,20 @@ public class CalculateDeltaNodeTest { verify(ctxMock, never()).tellNext(any(), anySet()); verify(ctxMock, never()).tellFailure(any(), any()); - expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":0}"; + expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":0,\"ts_delta\":3}"; assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); } @Test - public void happyPathWithDoubleHaveDigitsAfterPointAndNoCache() throws TbNodeException { - // STAGE 1 + public void givenLastValueIsNull_whenOnMsh_thenDeltaShouldBeZero() throws TbNodeException { // GIVEN config.setInputValueKey("temperature"); config.setOutputValueKey("temp_delta"); - config.setRound(null); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctxMock, nodeConfiguration); - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(timeseriesServiceMock.findLatest( - eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("temperature"))) - )).thenReturn(Futures.immediateFuture( - List.of(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.500))) - )); + mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", null))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); @@ -319,21 +313,37 @@ public class CalculateDeltaNodeTest { verify(ctxMock, never()).tellNext(any(), anySet()); verify(ctxMock, never()).tellFailure(any(), any()); - var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":1.5}"; + var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":0}"; assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); } @Test - public void failedPathWithStringValueAndNoCache() { + public void givenNegativeDeltaAndTellFailureIfNegativeDeltaTrue_whenOnMsg_thenFailure() throws TbNodeException { // GIVEN - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(timeseriesServiceMock.findLatest( - eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("pulseCounter"))) - )).thenReturn(Futures.immediateFuture( - List.of(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("pulseCounter", "high"))) - )); + config.setTellFailureIfDeltaIsNegative(true); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfiguration); + + mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("pulseCounter", 200L))); + + var msgData = "{\"pulseCounter\":\"123\"}"; + var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + verify(ctxMock, times(1)).tellNext(msg, "Failure"); + verify(ctxMock, never()).tellSuccess(any()); + verify(ctxMock, never()).tellFailure(any(), any()); + verify(ctxMock, never()).tellNext(any(), anySet()); + } + + @Test + public void givenInvalidStringValue_whenOnMsg_thenException() { + // GIVEN + mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("pulseCounter", "high"))); var msgData = "{\"pulseCounter\":\"123\"}"; var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); @@ -359,15 +369,9 @@ public class CalculateDeltaNodeTest { } @Test - public void failedPathWithBooleanValueAndNoCache() { + public void givenBooleanValue_whenOnMsg_thenException() { // GIVEN - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(timeseriesServiceMock.findLatest( - eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("pulseCounter"))) - )).thenReturn(Futures.immediateFuture( - List.of(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry("pulseCounter", false))) - )); + mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry("pulseCounter", false))); var msgData = "{\"pulseCounter\":true}"; var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); @@ -393,15 +397,9 @@ public class CalculateDeltaNodeTest { } @Test - public void failedPathWithJsonValueAndNoCache() { + public void givenJsonValue_whenOnMsg_thenException() { // GIVEN - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); - when(timeseriesServiceMock.findLatest( - eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of("pulseCounter"))) - )).thenReturn(Futures.immediateFuture( - List.of(new BasicTsKvEntry(System.currentTimeMillis(), new JsonDataEntry("pulseCounter", "{\"isActive\":false}"))) - )); + mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new JsonDataEntry("pulseCounter", "{\"isActive\":false}"))); var msgData = "{\"pulseCounter\":{\"isActive\":true}}"; var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); @@ -426,6 +424,14 @@ public class CalculateDeltaNodeTest { assertEquals(expectedExceptionMsg, actualException.getMessage()); } + private void mockFindLatest(TsKvEntry tsKvEntry) { + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(timeseriesServiceMock.findLatest( + eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of(tsKvEntry.getKey()))) + )).thenReturn(Futures.immediateFuture(List.of(tsKvEntry))); + } + @RequiredArgsConstructor private static class ListMatcher implements ArgumentMatcher> { From 2b6b9537c9130d44da91ccb2408df57b061b2e06 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 17 Apr 2023 11:39:43 +0300 Subject: [PATCH 09/29] Refactor: more consistent tests cases names for CalculateDeltaNode --- .../rule/engine/metadata/CalculateDeltaNodeTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java index 72ec167e0c..048db5e79b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -143,7 +143,7 @@ public class CalculateDeltaNodeTest { } @Test - public void givenDoubleValue_whenOnMsg_thenSuccess() throws TbNodeException { + public void givenDoubleValue_whenOnMsg_thenShouldTellSuccess() throws TbNodeException { // GIVEN config.setRound(1); config.setInputValueKey("temperature"); @@ -173,7 +173,7 @@ public class CalculateDeltaNodeTest { } @Test - public void givenLongStringValue_whenOnMsg_thenSuccess() throws TbNodeException { + public void givenLongStringValue_whenOnMsg_thenShouldTellSuccess() throws TbNodeException { // GIVEN config.setInputValueKey("temperature"); config.setOutputValueKey("temp_delta"); @@ -202,7 +202,7 @@ public class CalculateDeltaNodeTest { } @Test - public void givenValidStringValue_whenOnMsg_thenSuccess() throws TbNodeException { + public void givenValidStringValue_whenOnMsg_thenShouldTellSuccess() throws TbNodeException { // GIVEN config.setInputValueKey("temperature"); config.setOutputValueKey("temp_delta"); @@ -319,7 +319,7 @@ public class CalculateDeltaNodeTest { } @Test - public void givenNegativeDeltaAndTellFailureIfNegativeDeltaTrue_whenOnMsg_thenFailure() throws TbNodeException { + public void givenNegativeDeltaAndTellFailureIfNegativeDeltaTrue_whenOnMsg_thenShouldTellFailure() throws TbNodeException { // GIVEN config.setTellFailureIfDeltaIsNegative(true); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); From 51f72d54a55fe7c5edd839f0cd166c2c2ac2625e Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 17 Apr 2023 12:50:46 +0300 Subject: [PATCH 10/29] Refactor: rollback default round value to null CalculateDeltaNode --- .../rule/engine/metadata/CalculateDeltaNodeConfiguration.java | 1 - .../thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java index b52c29fa07..00ffa3305a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java @@ -40,7 +40,6 @@ public class CalculateDeltaNodeConfiguration implements NodeConfiguration Date: Mon, 17 Apr 2023 13:42:55 +0300 Subject: [PATCH 11/29] Review fixes: rollback default configs for get details nodes --- .../TbGetCustomerDetailsNodeConfiguration.java | 5 ++--- .../metadata/TbGetTenantDetailsNodeConfiguration.java | 5 ++--- .../engine/metadata/TbGetCustomerDetailsNodeTest.java | 11 +++-------- .../engine/metadata/TbGetTenantDetailsNodeTest.java | 10 +++------- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeConfiguration.java index df5dee1743..c49a1d3c30 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeConfiguration.java @@ -18,9 +18,8 @@ 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.List; +import java.util.Collections; @Data @EqualsAndHashCode(callSuper = true) @@ -29,7 +28,7 @@ public class TbGetCustomerDetailsNodeConfiguration extends TbAbstractGetEntityDe @Override public TbGetCustomerDetailsNodeConfiguration defaultConfiguration() { var configuration = new TbGetCustomerDetailsNodeConfiguration(); - configuration.setDetailsList(List.of(EntityDetails.STATE, EntityDetails.CITY)); + configuration.setDetailsList(Collections.emptyList()); configuration.setFetchTo(FetchTo.DATA); return configuration; } 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 8ab7088442..35cdc42a54 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,9 +18,8 @@ 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.List; +import java.util.Collections; @Data @EqualsAndHashCode(callSuper = true) @@ -29,7 +28,7 @@ public class TbGetTenantDetailsNodeConfiguration extends TbAbstractGetEntityDeta @Override public TbGetTenantDetailsNodeConfiguration defaultConfiguration() { var configuration = new TbGetTenantDetailsNodeConfiguration(); - configuration.setDetailsList(List.of(EntityDetails.STATE, EntityDetails.TITLE)); + configuration.setDetailsList(Collections.emptyList()); configuration.setFetchTo(FetchTo.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index f835fcde96..0c78d8e346 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -113,6 +113,7 @@ public class TbGetCustomerDetailsNodeTest { @Test public void givenConfigWithNullFetchTo_whenInit_thenException() { // GIVEN + config.setDetailsList(List.of(EntityDetails.ID)); config.setFetchTo(null); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -141,15 +142,9 @@ public class TbGetCustomerDetailsNodeTest { } @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.CITY)); + public void givenDefaultConfig_whenInit_thenOK() { + assertThat(config.getDetailsList()).isEqualTo(Collections.emptyList()); assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA); - assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); } @Test 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 index d05ad23db6..06d1c46338 100644 --- 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 @@ -84,6 +84,7 @@ public class TbGetTenantDetailsNodeTest { @Test public void givenConfigWithNullFetchTo_whenInit_thenException() { // GIVEN + config.setDetailsList(List.of(EntityDetails.ID)); config.setFetchTo(null); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -112,15 +113,10 @@ public class TbGetTenantDetailsNodeTest { } @Test - public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { - // GIVEN-WHEN - node.init(ctxMock, nodeConfiguration); - + public void givenDefaultConfig_whenInit_thenOK() { // THEN - assertThat(node.config).isEqualTo(config); - assertThat(config.getDetailsList()).isEqualTo(List.of(EntityDetails.STATE, EntityDetails.TITLE)); + assertThat(config.getDetailsList()).isEqualTo(Collections.emptyList()); assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA); - assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); } @Test From ff587b5bc75a4f58feed103464e53142d85cdc50 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 17 Apr 2023 13:48:51 +0300 Subject: [PATCH 12/29] Review fixes: consistency in null checks --- .../rule/engine/metadata/TbGetOriginatorFieldsNode.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java index 691ca232a8..14b816c4ce 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java @@ -49,11 +49,11 @@ public class TbGetOriginatorFieldsNode extends TbAbstractNodeWithFetchTo Date: Mon, 17 Apr 2023 13:51:23 +0300 Subject: [PATCH 13/29] Review fixes: fix formatting in RelatedDeviceIdLoader --- .../rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java index e0ef9ac036..1a590cc32f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java @@ -42,10 +42,7 @@ public class EntitiesRelatedDeviceIdAsyncLoader { : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } - private static DeviceSearchQuery buildQuery( - EntityId originator, - DeviceRelationsQuery deviceRelationsQuery - ) { + private static DeviceSearchQuery buildQuery(EntityId originator, DeviceRelationsQuery deviceRelationsQuery) { var query = new DeviceSearchQuery(); var parameters = new RelationsSearchParameters( originator, From 582f9e6b02ee0ed2747a05617821652b87ce52aa Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 17 Apr 2023 13:54:02 +0300 Subject: [PATCH 14/29] Review fixes: fix formatting in RelatedEntityIdLoader --- .../rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java index be44c85f56..dbad7c9c9d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java @@ -53,7 +53,8 @@ public class EntitiesRelatedEntityIdAsyncLoader { private static EntityRelationsQuery buildQuery(EntityId originator, RelationsQuery relationsQuery) { var query = new EntityRelationsQuery(); var parameters = new RelationsSearchParameters( - originator, relationsQuery.getDirection(), + originator, + relationsQuery.getDirection(), relationsQuery.getMaxLevel(), relationsQuery.isFetchLastLevelOnly() ); From 913c6b60e234f1d29fd42d95887e05a28ebebd49 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 17 Apr 2023 14:41:56 +0300 Subject: [PATCH 15/29] Review fixes: replace direct executor with db executor --- .../metadata/TbAbstractGetAttributesNode.java | 6 +-- .../metadata/TbAbstractGetEntityAttrNode.java | 7 ++-- .../metadata/TbGetCustomerDetailsNode.java | 11 +++--- .../util/EntitiesCustomerIdAsyncLoader.java | 11 +++--- .../EntitiesRelatedDeviceIdAsyncLoader.java | 3 +- .../EntitiesRelatedEntityIdAsyncLoader.java | 5 +-- .../TbGetCustomerDetailsNodeTest.java | 38 ++++++++++++++++++- .../EntitiesCustomerIdAsyncLoaderTest.java | 31 ++++++++++++--- ...ntitiesRelatedDeviceIdAsyncLoaderTest.java | 21 ++++++++++ ...itiesRelatedEntitiesIdAsyncLoaderTest.java | 21 ++++++++++ 10 files changed, 125 insertions(+), 29 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index b88ae1a0a1..43ee96c917 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -101,7 +101,7 @@ public abstract class TbAbstractGetAttributesNode ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); + }, t -> ctx.tellFailure(msg, t), MoreExecutors.directExecutor()); } private ListenableFuture>> getAttrAsync( @@ -121,7 +121,7 @@ public abstract class TbAbstractGetAttributesNode(scope, nonExistentKeys)); } return new TbPair<>(scope, attributeKvEntryList); - }, MoreExecutors.directExecutor()); + }, ctx.getDbCallbackExecutor()); } private ListenableFuture>> getLatestTelemetry(TbContext ctx, EntityId entityId, List keys, Set>> failuresPairSet) { @@ -147,7 +147,7 @@ public abstract class TbAbstractGetAttributesNode(LATEST_TS, nonExistentKeys)); } return new TbPair<>(LATEST_TS, listTsKvEntry); - }, MoreExecutors.directExecutor()); + }, ctx.getDbCallbackExecutor()); } private TsKvEntry getValueWithTs(TsKvEntry tsKvEntry) { 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 c11e6b8c2a..2b04d6e6e7 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 @@ -65,7 +65,8 @@ public abstract class TbAbstractGetEntityAttrNode extends Tb var sourceKeys = List.copyOf(mappingsMap.keySet()); withCallback(config.isTelemetry() ? getLatestTelemetryAsync(ctx, entityId, sourceKeys) : getAttributesAsync(ctx, entityId, sourceKeys), data -> putDataAndTell(ctx, msg, data, mappingsMap, msgDataAsJsonNode), - t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); + t -> ctx.tellFailure(msg, t), + MoreExecutors.directExecutor()); } private ListenableFuture> getAttributesAsync(TbContext ctx, EntityId entityId, List attrKeys) { @@ -74,7 +75,7 @@ public abstract class TbAbstractGetEntityAttrNode extends Tb l.stream() .map(i -> (KvEntry) i) .collect(Collectors.toList()), - MoreExecutors.directExecutor()); + ctx.getDbCallbackExecutor()); } private ListenableFuture> getLatestTelemetryAsync(TbContext ctx, EntityId entityId, List timeseriesKeys) { @@ -83,7 +84,7 @@ public abstract class TbAbstractGetEntityAttrNode extends Tb l.stream() .map(i -> (KvEntry) i) .collect(Collectors.toList()), - MoreExecutors.directExecutor()); + ctx.getDbCallbackExecutor()); } private void putDataAndTell(TbContext ctx, TbMsg msg, List data, Map map, ObjectNode msgData) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java index 2c7fe2302b..65f11facf5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -70,19 +69,19 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode getCustomerFuture(ctx, device, msg.getOriginator()), MoreExecutors.directExecutor()); + device -> getCustomerFuture(ctx, device, msg.getOriginator()), ctx.getDbCallbackExecutor()); case ASSET: return Futures.transformAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), new AssetId(msg.getOriginator().getId())), - asset -> getCustomerFuture(ctx, asset, msg.getOriginator()), MoreExecutors.directExecutor()); + asset -> getCustomerFuture(ctx, asset, msg.getOriginator()), ctx.getDbCallbackExecutor()); case ENTITY_VIEW: return Futures.transformAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), new EntityViewId(msg.getOriginator().getId())), - entityView -> getCustomerFuture(ctx, entityView, msg.getOriginator()), MoreExecutors.directExecutor()); + entityView -> getCustomerFuture(ctx, entityView, msg.getOriginator()), ctx.getDbCallbackExecutor()); case USER: return Futures.transformAsync(ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), new UserId(msg.getOriginator().getId())), - user -> getCustomerFuture(ctx, user, msg.getOriginator()), MoreExecutors.directExecutor()); + user -> getCustomerFuture(ctx, user, msg.getOriginator()), ctx.getDbCallbackExecutor()); case EDGE: return Futures.transformAsync(ctx.getEdgeService().findEdgeByIdAsync(ctx.getTenantId(), new EdgeId(msg.getOriginator().getId())), - edge -> getCustomerFuture(ctx, edge, msg.getOriginator()), MoreExecutors.directExecutor()); + edge -> getCustomerFuture(ctx, edge, msg.getOriginator()), ctx.getDbCallbackExecutor()); default: return Futures.immediateFailedFuture(new NoSuchElementException("Entity with entityType '" + msg.getOriginator().getEntityType() + "' is not supported.")); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java index d517afaf35..c5b6c0771e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.HasCustomerId; @@ -34,19 +33,19 @@ public class EntitiesCustomerIdAsyncLoader { case CUSTOMER: return Futures.immediateFuture((CustomerId) originator); case USER: - return toCustomerIdAsync(ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), (UserId) originator)); + return toCustomerIdAsync(ctx, ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), (UserId) originator)); case ASSET: - return toCustomerIdAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), (AssetId) originator)); + return toCustomerIdAsync(ctx, ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), (AssetId) originator)); case DEVICE: - return toCustomerIdAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), (DeviceId) originator)); + return toCustomerIdAsync(ctx, ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), (DeviceId) originator)); default: return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType: " + originator.getEntityType())); } } - private static ListenableFuture toCustomerIdAsync(ListenableFuture future) { + private static ListenableFuture toCustomerIdAsync(TbContext ctx, ListenableFuture future) { return Futures.transformAsync(future, in -> in != null ? Futures.immediateFuture(in.getCustomerId()) - : Futures.immediateFuture(null), MoreExecutors.directExecutor()); + : Futures.immediateFuture(null), ctx.getDbCallbackExecutor()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java index 1a590cc32f..b937f01b24 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.DeviceRelationsQuery; @@ -39,7 +38,7 @@ public class EntitiesRelatedDeviceIdAsyncLoader { return Futures.transformAsync(devicesListFuture, deviceList -> CollectionUtils.isNotEmpty(deviceList) ? Futures.immediateFuture(deviceList.get(0).getId()) - : Futures.immediateFuture(null), MoreExecutors.directExecutor()); + : Futures.immediateFuture(null), ctx.getDbCallbackExecutor()); } private static DeviceSearchQuery buildQuery(EntityId originator, DeviceRelationsQuery deviceRelationsQuery) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java index dbad7c9c9d..d0caac8876 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.RelationsQuery; @@ -40,12 +39,12 @@ public class EntitiesRelatedEntityIdAsyncLoader { return Futures.transformAsync(relationListFuture, relationList -> CollectionUtils.isNotEmpty(relationList) ? Futures.immediateFuture(relationList.get(0).getTo()) - : Futures.immediateFuture(null), MoreExecutors.directExecutor()); + : Futures.immediateFuture(null), ctx.getDbCallbackExecutor()); } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) { return Futures.transformAsync(relationListFuture, relationList -> CollectionUtils.isNotEmpty(relationList) ? Futures.immediateFuture(relationList.get(0).getFrom()) - : Futures.immediateFuture(null), MoreExecutors.directExecutor()); + : Futures.immediateFuture(null), ctx.getDbCallbackExecutor()); } return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 0c78d8e346..1cb1449078 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -16,6 +16,8 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -23,6 +25,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -55,6 +58,7 @@ import org.thingsboard.server.dao.user.UserService; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -71,6 +75,21 @@ public class TbGetCustomerDetailsNodeTest { 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 + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + }; @Mock private TbContext ctxMock; @Mock @@ -209,6 +228,8 @@ public class TbGetCustomerDetailsNodeTest { mockFindCustomer(); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + // WHEN node.onMsg(ctxMock, msg); @@ -249,6 +270,8 @@ public class TbGetCustomerDetailsNodeTest { mockFindCustomer(); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + // WHEN node.onMsg(ctxMock, msg); @@ -281,6 +304,8 @@ public class TbGetCustomerDetailsNodeTest { mockFindCustomer(); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + // WHEN node.onMsg(ctxMock, msg); @@ -310,6 +335,8 @@ public class TbGetCustomerDetailsNodeTest { when(ctxMock.getUserService()).thenReturn(userServiceMock); when(userServiceMock.findUserByIdAsync(eq(TENANT_ID), eq(user.getId()))).thenReturn(Futures.immediateFuture(user)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + mockFindCustomer(); // WHEN @@ -334,13 +361,16 @@ public class TbGetCustomerDetailsNodeTest { prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), edge.getId()); + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getEdgeService()).thenReturn(edgeServiceMock); when(edgeServiceMock.findEdgeByIdAsync(eq(TENANT_ID), eq(edge.getId()))).thenReturn(Futures.immediateFuture(edge)); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getCustomerService()).thenReturn(customerServiceMock); when(customerServiceMock.findCustomerByIdAsync(eq(TENANT_ID), eq(customer.getId()))).thenReturn(Futures.immediateFuture(null)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + // WHEN node.onMsg(ctxMock, msg); @@ -368,6 +398,8 @@ public class TbGetCustomerDetailsNodeTest { when(ctxMock.getEdgeService()).thenReturn(edgeServiceMock); when(edgeServiceMock.findEdgeByIdAsync(eq(TENANT_ID), eq(edge.getId()))).thenReturn(Futures.immediateFuture(null)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + // WHEN node.onMsg(ctxMock, msg); @@ -394,6 +426,8 @@ public class TbGetCustomerDetailsNodeTest { when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + // WHEN node.onMsg(ctxMock, msg); @@ -421,6 +455,8 @@ public class TbGetCustomerDetailsNodeTest { when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + mockFindCustomer(); // WHEN diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java index 97201daeda..2edc41f936 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java @@ -16,11 +16,13 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; -import org.junit.jupiter.api.Assertions; +import com.google.common.util.concurrent.ListenableFuture; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.Customer; @@ -39,6 +41,7 @@ import org.thingsboard.server.dao.user.UserService; import java.util.EnumSet; import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -57,6 +60,21 @@ public class EntitiesCustomerIdAsyncLoaderTest { EntityType.ASSET, EntityType.DEVICE ); + private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + }; @Mock private TbContext ctxMock; @Mock @@ -75,7 +93,7 @@ public class EntitiesCustomerIdAsyncLoaderTest { var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, customer.getId()).get(); // THEN - Assertions.assertEquals(customer.getId(), actualCustomerId); + assertEquals(customer.getId(), actualCustomerId); } @Test @@ -87,12 +105,13 @@ public class EntitiesCustomerIdAsyncLoaderTest { when(ctxMock.getUserService()).thenReturn(userServiceMock); doReturn(Futures.immediateFuture(user)).when(userServiceMock).findUserByIdAsync(any(), any()); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, user.getId()).get(); // THEN - Assertions.assertEquals(expectedCustomerId, actualCustomerId); + assertEquals(expectedCustomerId, actualCustomerId); } @Test @@ -104,12 +123,13 @@ public class EntitiesCustomerIdAsyncLoaderTest { when(ctxMock.getAssetService()).thenReturn(assetServiceMock); doReturn(Futures.immediateFuture(asset)).when(assetServiceMock).findAssetByIdAsync(any(), any()); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, asset.getId()).get(); // THEN - Assertions.assertEquals(expectedCustomerId, actualCustomerId); + assertEquals(expectedCustomerId, actualCustomerId); } @Test @@ -121,12 +141,13 @@ public class EntitiesCustomerIdAsyncLoaderTest { when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); doReturn(Futures.immediateFuture(device)).when(deviceServiceMock).findDeviceByIdAsync(any(), any()); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, device.getId()).get(); // THEN - Assertions.assertEquals(expectedCustomerId, actualCustomerId); + assertEquals(expectedCustomerId, actualCustomerId); } @Test diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java index fa21f3746b..1c0faec7bc 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java @@ -16,10 +16,13 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.DeviceRelationsQuery; import org.thingsboard.server.common.data.Device; @@ -34,6 +37,7 @@ import org.thingsboard.server.dao.device.DeviceService; import java.util.List; import java.util.UUID; +import java.util.concurrent.Callable; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -47,6 +51,21 @@ public class EntitiesRelatedDeviceIdAsyncLoaderTest { private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + }; @Mock private TbContext ctxMock; @Mock @@ -76,6 +95,7 @@ public class EntitiesRelatedDeviceIdAsyncLoaderTest { when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(deviceServiceMock.findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery))) .thenReturn(Futures.immediateFuture(null)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctxMock, DUMMY_ORIGINATOR, deviceRelationsQuery); @@ -117,6 +137,7 @@ public class EntitiesRelatedDeviceIdAsyncLoaderTest { when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(deviceServiceMock.findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery))) .thenReturn(Futures.immediateFuture(devicesList)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN var entityIdFuture = EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctxMock, DUMMY_ORIGINATOR, deviceRelationsQuery); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java index aae13a52a9..fc93206c18 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java @@ -16,10 +16,13 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.Device; @@ -39,6 +42,7 @@ import org.thingsboard.server.dao.relation.RelationService; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.concurrent.Callable; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -52,6 +56,21 @@ public class EntitiesRelatedEntitiesIdAsyncLoaderTest { private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + }; @Mock private TbContext ctxMock; @Mock @@ -80,6 +99,7 @@ public class EntitiesRelatedEntitiesIdAsyncLoaderTest { when(ctxMock.getRelationService()).thenReturn(relationServiceMock); when(relationServiceMock.findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery))) .thenReturn(Futures.immediateFuture(null)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctxMock, DUMMY_ORIGINATOR, relationsQuery); @@ -138,6 +158,7 @@ public class EntitiesRelatedEntitiesIdAsyncLoaderTest { when(ctxMock.getRelationService()).thenReturn(relationServiceMock); when(relationServiceMock.findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery))) .thenReturn(Futures.immediateFuture(expectedEntityRelationsList)); + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); // WHEN var deviceIdFuture = EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctxMock, DUMMY_ORIGINATOR, relationsQuery); From acac0af2d1e662f6dbc3e3e096ea06544960a31b Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 17 Apr 2023 15:45:16 +0300 Subject: [PATCH 16/29] Refactor: add check to ensure msg data is always object --- .../thingsboard/rule/engine/metadata/CalculateDeltaNode.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index ca96fdead9..3e2618de98 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -77,6 +77,9 @@ public class CalculateDeltaNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) { if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { JsonNode json = JacksonUtil.toJsonNode(msg.getData()); + if (!json.isObject()) { + throw new IllegalArgumentException("Message body is not an object!"); + } String inputKey = config.getInputValueKey(); if (json.has(inputKey)) { DonAsynchron.withCallback(getLastValue(msg.getOriginator()), From 5d493ca0145b4621f177011f2a90dd6cdf9f730b Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 17 Apr 2023 17:39:43 +0300 Subject: [PATCH 17/29] Refactor: add guard for edge case in upgrade script --- .../update/DefaultDataUpdateService.java | 108 +++++++++++------- 1 file changed, 69 insertions(+), 39 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index 808fec6b53..a27b848530 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -33,9 +33,9 @@ import org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode; import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; import org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode; import org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode; -import org.thingsboard.rule.engine.metadata.TbGetRelatedDeviceAttrNode; import org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode; import org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode; +import org.thingsboard.rule.engine.metadata.TbGetRelatedDeviceAttrNode; import org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode; import org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; @@ -228,19 +228,21 @@ public class DefaultDataUpdateService implements DataUpdateService { private void updateEnrichmentRuleNodes() { try { - var ruleNodeTypesToUpdate = List.of( + var fetchToDataNodes = List.of(TbGetAttributesNode.class.getName(), TbGetRelatedDeviceAttrNode.class.getName()); + var addToMetadataNodes = List.of(TbGetCustomerDetailsNode.class.getName(), TbGetTenantDetailsNode.class.getName()); + var fetchToMetadataNodes = List.of(TbFetchDeviceCredentialsNode.class.getName()); + var hardCodeMetadataNodes = List.of( TbGetOriginatorFieldsNode.class.getName(), - TbFetchDeviceCredentialsNode.class.getName(), - TbGetAttributesNode.class.getName(), - TbGetRelatedDeviceAttrNode.class.getName(), TbGetRelatedAttributeNode.class.getName(), TbGetTenantAttributeNode.class.getName(), - TbGetCustomerAttributeNode.class.getName(), - TbGetCustomerDetailsNode.class.getName(), - TbGetTenantDetailsNode.class.getName() + TbGetCustomerAttributeNode.class.getName() ); + var allNodesToUpdate = new ArrayList<>(hardCodeMetadataNodes); + allNodesToUpdate.addAll(fetchToDataNodes); + allNodesToUpdate.addAll(addToMetadataNodes); + allNodesToUpdate.addAll(fetchToMetadataNodes); var ruleChainIdToTenantId = new HashMap(); - ruleNodeTypesToUpdate.forEach(ruleNodeType -> { + allNodesToUpdate.forEach(ruleNodeType -> { var ruleNodes = new PageDataIterable<>( pageLink -> ruleChainService.findAllRuleNodesByType(ruleNodeType, pageLink), 1024 ); @@ -258,52 +260,80 @@ public class DefaultDataUpdateService implements DataUpdateService { } var configObjectNode = (ObjectNode) configuration; var fetchTo = FetchTo.METADATA; - if (configObjectNode.has("fetchToMetadata")) { - var fetchToMetadata = configObjectNode.get("fetchToMetadata").asText(); - if ("true".equals(fetchToMetadata)) { - fetchTo = FetchTo.METADATA; - } else if ("false".equals(fetchToMetadata)) { - fetchTo = FetchTo.DATA; + + if (fetchToDataNodes.contains(ruleNodeType)) { + configObjectNode.remove("fetchToMetadata"); + configObjectNode.remove("addToMetadata"); + if (configObjectNode.has("fetchToData")) { + var fetchToData = configObjectNode.get("fetchToData").asText(); + if ("true".equals(fetchToData)) { + fetchTo = FetchTo.DATA; + } else if ("false".equals(fetchToData)) { + fetchTo = FetchTo.METADATA; + } else { + log.error("Failed to updated rule node: [{}] with id: [{}] " + + "Reason: fetchToData property has unexpected value: {} Allowed values: true or false!", + ruleNodeType, ruleNode.getId(), fetchToData); + continue; + } + configObjectNode.remove("fetchToData"); } else { log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: fetchToMetadata property has unexpected value: {} Allowed values: true or false!", - ruleNodeType, ruleNode.getId(), fetchToMetadata); + "Reason: fetchToData property is not present!", ruleNodeType, ruleNode.getId()); continue; } - configObjectNode.remove("fetchToMetadata"); - } - if (configObjectNode.has("fetchToData")) { - var fetchToData = configObjectNode.get("fetchToData").asText(); - if ("true".equals(fetchToData)) { - fetchTo = FetchTo.DATA; - } else if ("false".equals(fetchToData)) { - fetchTo = FetchTo.METADATA; + } else if (fetchToMetadataNodes.contains(ruleNodeType)) { + configObjectNode.remove("fetchToData"); + configObjectNode.remove("addToMetadata"); + if (configObjectNode.has("fetchToMetadata")) { + var fetchToMetadata = configObjectNode.get("fetchToMetadata").asText(); + if ("true".equals(fetchToMetadata)) { + fetchTo = FetchTo.METADATA; + } else if ("false".equals(fetchToMetadata)) { + fetchTo = FetchTo.DATA; + } else { + log.error("Failed to updated rule node: [{}] with id: [{}] " + + "Reason: fetchToMetadata property has unexpected value: {} Allowed values: true or false!", + ruleNodeType, ruleNode.getId(), fetchToMetadata); + continue; + } + configObjectNode.remove("fetchToMetadata"); } else { log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: fetchToData property has unexpected value: {} Allowed values: true or false!", - ruleNodeType, ruleNode.getId(), fetchToData); + "Reason: fetchToMetadata property is not present!", ruleNodeType, ruleNode.getId()); continue; } + } else if (addToMetadataNodes.contains(ruleNodeType)) { configObjectNode.remove("fetchToData"); - } - if (configObjectNode.has("addToMetadata")) { - var addToMetadata = configObjectNode.get("addToMetadata").asText(); - if ("true".equals(addToMetadata)) { - fetchTo = FetchTo.METADATA; - } else if ("false".equals(addToMetadata)) { - fetchTo = FetchTo.DATA; + configObjectNode.remove("fetchToMetadata"); + if (configObjectNode.has("addToMetadata")) { + var addToMetadata = configObjectNode.get("addToMetadata").asText(); + if ("true".equals(addToMetadata)) { + fetchTo = FetchTo.METADATA; + } else if ("false".equals(addToMetadata)) { + fetchTo = FetchTo.DATA; + } else { + log.error("Failed to updated rule node: [{}] with id: [{}] " + + "Reason: addToMetadata property has unexpected value: {} Allowed values: true or false!", + ruleNodeType, ruleNode.getId(), addToMetadata); + continue; + } + configObjectNode.remove("addToMetadata"); } else { log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: addToMetadata property has unexpected value: {} Allowed values: true or false!", - ruleNodeType, ruleNode.getId(), addToMetadata); + "Reason: addToMetadata property is not present!", ruleNodeType, ruleNode.getId()); continue; } - configObjectNode.remove("addToMetadata"); + } else { + log.error("Failed to updated rule node: [{}] with id: [{}] " + + "Reason: Unexpected rule node type!", ruleNodeType, ruleNode.getId()); + continue; } + configObjectNode.put("fetchTo", fetchTo.name()); ruleNode.setConfiguration(configObjectNode); - RuleChainId ruleChainId = ruleNode.getRuleChainId(); - TenantId tenantId = ruleChainIdToTenantId.computeIfAbsent(ruleChainId, + var ruleChainId = ruleNode.getRuleChainId(); + var tenantId = ruleChainIdToTenantId.computeIfAbsent(ruleChainId, id -> { RuleChain ruleChain = ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID, id); if (ruleChain == null) { From 594183b064bc2854f416e9fcdc081aa18e4eb6ed Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 12:48:18 +0300 Subject: [PATCH 18/29] Refactor: remove old tests --- .../OldTbAbstractAttributeNodeTest.java | 236 ------------------ .../OldTbGetCustomerAttributeNodeTest.java | 184 -------------- .../OldTbGetRelatedAttributeNodeTest.java | 235 ----------------- .../OldTbGetTenantAttributeNodeTest.java | 177 ------------- 4 files changed, 832 deletions(-) delete mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbAbstractAttributeNodeTest.java delete mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java delete mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetRelatedAttributeNodeTest.java delete mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetTenantAttributeNodeTest.java diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbAbstractAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbAbstractAttributeNodeTest.java deleted file mode 100644 index 3d1b362ad0..0000000000 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbAbstractAttributeNodeTest.java +++ /dev/null @@ -1,236 +0,0 @@ -/** - * 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.datastax.oss.driver.api.core.uuid.Uuids; -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.Futures; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -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.server.common.data.Device; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.RuleNodeId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.BasicTsKvEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; -import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.asset.AssetService; -import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.dao.user.UserService; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyCollection; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; - -@RunWith(MockitoJUnitRunner.class) -public abstract class OldTbAbstractAttributeNodeTest { - final CustomerId customerId = new CustomerId(Uuids.timeBased()); - final TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); - final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); - final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - final String keyAttrConf = "${word}"; - final String valueAttrConf = "${result}"; - @Mock - protected TbContext ctxMock; - @Mock - protected AttributesService attributesServiceMock; - @Mock - protected TimeseriesService timeseriesServiceMock; - @Mock - protected UserService userServiceMock; - @Mock - protected AssetService assetServiceMock; - @Mock - protected DeviceService deviceServiceMock; - TbMsg msg; - Map metaData; - TbAbstractGetEntityAttrNode node; - - protected void init(TbAbstractGetEntityAttrNode node) throws TbNodeException { - var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(getTbNodeConfig())); - - metaData = new HashMap<>(); - metaData.putIfAbsent("word", "temperature"); - metaData.putIfAbsent("result", "answer"); - - this.node = node; - this.node.init(null, nodeConfiguration); - } - - void errorThrownIfCannotLoadAttributes(User user) { - msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(any(), eq(getEntityId()), eq(SERVER_SCOPE), anyCollection())) - .thenThrow(new IllegalStateException("something wrong")); - - node.onMsg(ctxMock, msg); - final ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); - verify(ctxMock).tellFailure(same(msg), captor.capture()); - - Throwable value = captor.getValue(); - assertEquals("something wrong", value.getMessage()); - assertTrue(msg.getMetaData().getData().isEmpty()); - } - - void errorThrownIfCannotLoadAttributesAsync(User user) { - msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(any(), eq(getEntityId()), eq(SERVER_SCOPE), anyCollection())) - .thenReturn(Futures.immediateFailedFuture(new IllegalStateException("something wrong"))); - - node.onMsg(ctxMock, msg); - final ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); - verify(ctxMock).tellFailure(same(msg), captor.capture()); - - Throwable value = captor.getValue(); - assertEquals("something wrong", value.getMessage()); - assertTrue(msg.getMetaData().getData().isEmpty()); - } - - void failedChainUsedIfCustomerCannotBeFound(User user) { - msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - node.onMsg(ctxMock, msg); - var exceptionCaptor = ArgumentCaptor.forClass(NoSuchElementException.class); - verify(ctxMock).tellFailure(eq(msg), exceptionCaptor.capture()); - - assertThat(exceptionCaptor.getValue().getMessage()).contains("Did not find entity! Msg ID: "); - assertTrue(msg.getMetaData().getData().isEmpty()); - } - - void entityAttributeAddedInMetadata(EntityId entityId, String type) { - msg = TbMsg.newMsg(type, entityId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - entityAttributesFetched(getEntityId()); - } - - void usersCustomerAttributesFetched(User user) { - msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - entityAttributesFetched(getEntityId()); - } - - void assetsCustomerAttributesFetched(Asset asset) { - msg = TbMsg.newMsg("ASSET", asset.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - entityAttributesFetched(getEntityId()); - } - - void deviceCustomerAttributesFetched(Device device) { - msg = TbMsg.newMsg("DEVICE", device.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - entityAttributesFetched(getEntityId()); - } - - void deviceCustomerTelemetryFetched(Device device) throws TbNodeException { - var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(getTbNodeConfigForTelemetry())); - - TbAbstractGetEntityAttrNode node = getEmptyNode(); - node.init(null, nodeConfiguration); - - msg = TbMsg.newMsg("DEVICE", device.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - List timeseries = Lists.newArrayList(new BasicTsKvEntry(1L, new StringDataEntry("temperature", "highest"))); - - when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock); - when(timeseriesServiceMock.findLatest(any(), eq(getEntityId()), anyCollection())) - .thenReturn(Futures.immediateFuture(timeseries)); - - node.onMsg(ctxMock, msg); - verify(ctxMock).tellSuccess(msg); - assertEquals(msg.getMetaData().getValue("answer"), "highest"); - } - - protected void entityAttributesFetched(EntityId entityId) { - List attributes = List.of(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); - - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(any(), eq(entityId), eq(SERVER_SCOPE), anyCollection())) - .thenReturn(Futures.immediateFuture(attributes)); - - node.onMsg(ctxMock, msg); - - verify(ctxMock).tellSuccess(msg); - assertEquals(msg.getMetaData().getValue("answer"), "high"); - } - - protected TbGetEntityAttrNodeConfiguration getTbNodeConfig() { - return getConfig(false); - } - - protected TbGetEntityAttrNodeConfiguration getTbNodeConfigForTelemetry() { - return getConfig(true); - } - - private TbGetEntityAttrNodeConfiguration getConfig(boolean isTelemetry) { - TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration(); - Map conf = new HashMap<>(); - conf.put(keyAttrConf, valueAttrConf); - config.setAttrMapping(conf); - config.setTelemetry(isTelemetry); - config.setFetchTo(FetchTo.METADATA); - return config; - } - - protected abstract TbAbstractGetEntityAttrNode getEmptyNode(); - - abstract EntityId getEntityId(); - - void mockFindDevice(Device device) { - when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); - when(deviceServiceMock.findDeviceByIdAsync(any(), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); - } - - void mockFindAsset(Asset asset) { - when(ctxMock.getAssetService()).thenReturn(assetServiceMock); - when(assetServiceMock.findAssetByIdAsync(any(), eq(asset.getId()))).thenReturn(Futures.immediateFuture(asset)); - } - - void mockFindUser(User user) { - when(ctxMock.getUserService()).thenReturn(userServiceMock); - when(userServiceMock.findUserByIdAsync(any(), eq(user.getId()))).thenReturn(Futures.immediateFuture(user)); - } -} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java deleted file mode 100644 index 69581ef49a..0000000000 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetCustomerAttributeNodeTest.java +++ /dev/null @@ -1,184 +0,0 @@ -/** - * 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.datastax.oss.driver.api.core.uuid.Uuids; -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.Futures; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.junit.MockitoJUnitRunner; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.Device; -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.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; -import org.thingsboard.server.common.msg.TbMsgMetaData; - -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.anyCollection; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; - -@RunWith(MockitoJUnitRunner.class) -public class OldTbGetCustomerAttributeNodeTest extends OldTbAbstractAttributeNodeTest { - - User user = new User(); - Asset asset = new Asset(); - Device device = new Device(); - - @Before - public void initDataForTests() throws TbNodeException { - init(new TbGetCustomerAttributeNode()); - user.setCustomerId(customerId); - user.setId(new UserId(UUID.randomUUID())); - - asset.setCustomerId(customerId); - asset.setId(new AssetId(UUID.randomUUID())); - - device.setCustomerId(customerId); - device.setId(new DeviceId(Uuids.timeBased())); - } - - @Override - protected TbAbstractGetEntityAttrNode getEmptyNode() { - return new TbGetCustomerAttributeNode(); - } - - @Override - protected EntityId getEntityId() { - return customerId; - } - - @Test - public void errorThrownIfFetchToIsNull() { - var node = new TbGetCustomerAttributeNode(); - var config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration(); - config.setFetchTo(null); - var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - - var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); - - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); - verify(ctxMock, never()).tellSuccess(any()); - } - - @Test - public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new CustomerId(UUID.randomUUID()), new TbMsgMetaData(), "[]"); - - var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); - - assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); - verify(ctxMock, never()).tellSuccess(any()); - } - - @Test - public void errorThrownIfCannotLoadAttributes() { - mockFindUser(user); - errorThrownIfCannotLoadAttributes(user); - } - - @Test - public void errorThrownIfCannotLoadAttributesAsync() { - mockFindUser(user); - errorThrownIfCannotLoadAttributesAsync(user); - } - - @Test - public void failedChainUsedIfCustomerCannotBeFound() { - when(ctxMock.getUserService()).thenReturn(userServiceMock); - when(userServiceMock.findUserByIdAsync(any(), eq(user.getId()))).thenReturn(Futures.immediateFuture(null)); - failedChainUsedIfCustomerCannotBeFound(user); - } - - @Test - public void customerAttributeAddedInMetadata() { - entityAttributeAddedInMetadata(customerId, "CUSTOMER"); - } - - @Test - public void customerAttributeAddedInData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - - msg = TbMsg.newMsg("CUSTOMER", customerId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); - - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(any(), eq(customerId), eq(SERVER_SCOPE), anyCollection())) - .thenReturn(Futures.immediateFuture(attributes)); - - node.onMsg(ctxMock, msg); - - var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); - - var expectedMsgData = "{\"answer\":\"high\"}"; - - assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); - } - - @Test - public void usersCustomerAttributesFetched() { - mockFindUser(user); - usersCustomerAttributesFetched(user); - } - - @Test - public void assetsCustomerAttributesFetched() { - mockFindAsset(asset); - assetsCustomerAttributesFetched(asset); - } - - @Test - public void deviceCustomerAttributesFetched() { - mockFindDevice(device); - deviceCustomerAttributesFetched(device); - } - - @Test - public void deviceCustomerTelemetryFetched() throws TbNodeException { - mockFindDevice(device); - deviceCustomerTelemetryFetched(device); - } - -} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetRelatedAttributeNodeTest.java deleted file mode 100644 index 91700bb4ea..0000000000 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetRelatedAttributeNodeTest.java +++ /dev/null @@ -1,235 +0,0 @@ -/** - * 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.collect.Lists; -import com.google.common.util.concurrent.Futures; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.Device; -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.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; -import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.relation.RelationService; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -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.anyCollection; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; - -@RunWith(MockitoJUnitRunner.class) -public class OldTbGetRelatedAttributeNodeTest extends OldTbAbstractAttributeNodeTest { - User user = new User(); - Asset asset = new Asset(); - Device device = new Device(); - @Mock - private RelationService relationService; - private EntityRelation entityRelation; - - @Before - public void initDataForTests() throws TbNodeException { - init(new TbGetRelatedAttributeNode()); - entityRelation = new EntityRelation(); - entityRelation.setTo(customerId); - entityRelation.setType(EntityRelation.CONTAINS_TYPE); - when(ctxMock.getRelationService()).thenReturn(relationService); - - user.setCustomerId(customerId); - user.setId(new UserId(UUID.randomUUID())); - entityRelation.setFrom(user.getId()); - - asset.setCustomerId(customerId); - asset.setId(new AssetId(UUID.randomUUID())); - - device.setCustomerId(customerId); - device.setId(new DeviceId(UUID.randomUUID())); - } - - @Override - protected TbAbstractGetEntityAttrNode getEmptyNode() { - return new TbGetRelatedAttributeNode(); - } - - @Override - protected TbGetEntityAttrNodeConfiguration getTbNodeConfig() { - return getConfig(false); - } - - @Override - protected TbGetEntityAttrNodeConfiguration getTbNodeConfigForTelemetry() { - return getConfig(true); - } - - private TbGetEntityAttrNodeConfiguration getConfig(boolean isTelemetry) { - TbGetRelatedAttrNodeConfiguration config = new TbGetRelatedAttrNodeConfiguration(); - config = config.defaultConfiguration(); - Map conf = new HashMap<>(); - conf.put(keyAttrConf, valueAttrConf); - config.setAttrMapping(conf); - config.setTelemetry(isTelemetry); - config.setFetchTo(FetchTo.METADATA); - return config; - } - - @Override - EntityId getEntityId() { - return customerId; - } - - @Test - public void errorThrownIfFetchToIsNull() { - var node = new TbGetRelatedAttributeNode(); - var config = new TbGetRelatedAttrNodeConfiguration().defaultConfiguration(); - config.setFetchTo(null); - var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - - var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); - - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); - verify(ctxMock, never()).tellSuccess(any()); - } - - @Test - public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new DeviceId(UUID.randomUUID()), new TbMsgMetaData(), "[]"); - - var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); - - assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); - verify(ctxMock, never()).tellSuccess(any()); - } - - @Test - public void errorThrownIfCannotLoadAttributes() { - entityRelation.setFrom(user.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - errorThrownIfCannotLoadAttributes(user); - } - - @Test - public void errorThrownIfCannotLoadAttributesAsync() { - entityRelation.setFrom(user.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - errorThrownIfCannotLoadAttributesAsync(user); - } - - @Test - public void failedChainUsedIfCustomerCannotBeFound() { - entityRelation.setFrom(customerId); - entityRelation.setTo(null); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - failedChainUsedIfCustomerCannotBeFound(user); - } - - @Test - public void customerAttributeAddedInMetadata() { - entityRelation.setFrom(customerId); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - entityAttributeAddedInMetadata(customerId, "CUSTOMER"); - } - - @Test - public void customerAttributeAddedInData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - - entityRelation.setFrom(customerId); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - - msg = TbMsg.newMsg("CUSTOMER", customerId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); - - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(any(), eq(customerId), eq(SERVER_SCOPE), anyCollection())) - .thenReturn(Futures.immediateFuture(attributes)); - - node.onMsg(ctxMock, msg); - - var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); - - var expectedMsgData = "{\"answer\":\"high\"}"; - - assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); - } - - @Test - public void usersCustomerAttributesFetched() { - entityRelation.setFrom(user.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - usersCustomerAttributesFetched(user); - } - - @Test - public void assetsCustomerAttributesFetched() { - entityRelation.setFrom(asset.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - assetsCustomerAttributesFetched(asset); - } - - @Test - public void deviceCustomerAttributesFetched() { - entityRelation.setFrom(device.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - deviceCustomerAttributesFetched(device); - } - - @Test - public void deviceCustomerTelemetryFetched() throws TbNodeException { - entityRelation.setFrom(device.getId()); - entityRelation.setTo(customerId); - when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); - deviceCustomerTelemetryFetched(device); - } -} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetTenantAttributeNodeTest.java deleted file mode 100644 index 0263a1b523..0000000000 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/OldTbGetTenantAttributeNodeTest.java +++ /dev/null @@ -1,177 +0,0 @@ -/** - * 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.collect.Lists; -import com.google.common.util.concurrent.Futures; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.junit.MockitoJUnitRunner; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.Device; -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.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; -import org.thingsboard.server.common.msg.TbMsgMetaData; - -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.anyCollection; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; - -@RunWith(MockitoJUnitRunner.class) -public class OldTbGetTenantAttributeNodeTest extends OldTbAbstractAttributeNodeTest { - User user = new User(); - Asset asset = new Asset(); - Device device = new Device(); - - @Before - public void initDataForTests() throws TbNodeException { - init(new TbGetTenantAttributeNode()); - - user.setTenantId(tenantId); - user.setId(new UserId(UUID.randomUUID())); - - asset.setTenantId(tenantId); - asset.setId(new AssetId(UUID.randomUUID())); - - device.setTenantId(tenantId); - device.setId(new DeviceId(UUID.randomUUID())); - - when(ctxMock.getTenantId()).thenReturn(tenantId); - } - - @Override - protected TbAbstractGetEntityAttrNode getEmptyNode() { - return new TbGetTenantAttributeNode(); - } - - @Override - EntityId getEntityId() { - return tenantId; - } - - @Test - public void errorThrownIfFetchToIsNull() { - var node = new TbGetTenantAttributeNode(); - var config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration(); - config.setFetchTo(null); - var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - - var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); - - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); - verify(ctxMock, never()).tellSuccess(any()); - } - - @Test - public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new TenantId(UUID.randomUUID()), new TbMsgMetaData(), "[]"); - - var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); - - assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); - verify(ctxMock, never()).tellSuccess(any()); - } - - @Test - public void errorThrownIfCannotLoadAttributes() { - errorThrownIfCannotLoadAttributes(user); - } - - @Test - public void errorThrownIfCannotLoadAttributesAsync() { - errorThrownIfCannotLoadAttributesAsync(user); - } - - @Test - public void failedChainUsedIfTenantIdFromCtxCannotBeFound() { - when(ctxMock.getTenantId()).thenReturn(null); - failedChainUsedIfCustomerCannotBeFound(user); - } - - @Test - public void customerAttributeAddedInMetadata() { - entityAttributeAddedInMetadata(tenantId, "TENANT"); - } - - @Test - public void customerAttributeAddedInData() { - node.fetchTo = FetchTo.DATA; - node.config.setFetchTo(FetchTo.DATA); - - msg = TbMsg.newMsg("TENANT", tenantId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - - List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); - - when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(any(), eq(tenantId), eq(SERVER_SCOPE), anyCollection())) - .thenReturn(Futures.immediateFuture(attributes)); - - node.onMsg(ctxMock, msg); - - var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); - - var expectedMsgData = "{\"answer\":\"high\"}"; - - assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); - } - - @Test - public void usersCustomerAttributesFetched() { - usersCustomerAttributesFetched(user); - } - - @Test - public void assetsCustomerAttributesFetched() { - assetsCustomerAttributesFetched(asset); - } - - @Test - public void deviceCustomerAttributesFetched() { - deviceCustomerAttributesFetched(device); - } - - @Test - public void deviceCustomerTelemetryFetched() throws TbNodeException { - deviceCustomerTelemetryFetched(device); - } -} From b2c4d8dd2785e8a73ee675361e25e03bbee9f24e Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 12:52:42 +0300 Subject: [PATCH 19/29] Refactor: formatting fixes --- .../rule/engine/metadata/TbAbstractNodeWithFetchTo.java | 1 - .../engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java | 8 +++----- .../org/thingsboard/rule/engine/util/EntityContainer.java | 2 +- .../util/EntitiesRelatedDeviceIdAsyncLoaderTest.java | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java index 7cddc0ed39..7cb7f747e6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java @@ -15,7 +15,6 @@ */ package org.thingsboard.rule.engine.metadata; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java index 7717f7f8a4..4de50e810d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.id.EntityId; public class EntitiesAlarmOriginatorIdAsyncLoader { public static ListenableFuture findEntityIdAsync(TbContext ctx, EntityId original) { - switch (original.getEntityType()) { case ALARM: return getAlarmOriginatorAsync(ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), (AlarmId) original)); @@ -37,9 +36,8 @@ public class EntitiesAlarmOriginatorIdAsyncLoader { } private static ListenableFuture getAlarmOriginatorAsync(ListenableFuture future) { - return Futures.transformAsync(future, in -> { - return in != null ? Futures.immediateFuture(in.getOriginator()) - : Futures.immediateFuture(null); - }, MoreExecutors.directExecutor()); + return Futures.transformAsync(future, in -> in != null ? + Futures.immediateFuture(in.getOriginator()) + : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityContainer.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityContainer.java index 516b28846a..410f6b1ed9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityContainer.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityContainer.java @@ -25,4 +25,4 @@ public class EntityContainer { private EntityId entityId; private EntityType entityType; -} \ No newline at end of file +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java index 1c0faec7bc..546a726f07 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java @@ -150,4 +150,4 @@ public class EntitiesRelatedDeviceIdAsyncLoaderTest { assertEquals(device1.getId(), actualEntityId); } -} \ No newline at end of file +} From e32f893ffff6372fd8fcafedaf6ea63f0e183c45 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 13:02:13 +0300 Subject: [PATCH 20/29] Refactor: remove unnecessary check tenant entity check --- .../metadata/TbAbstractGetAttributesNode.java | 1 - .../metadata/TbAbstractGetEntityAttrNode.java | 1 - .../TbAbstractGetEntityDetailsNode.java | 1 - .../TbFetchDeviceCredentialsNode.java | 1 - .../metadata/TbGetOriginatorFieldsNode.java | 1 - .../TbGetCustomerAttributeNodeTest.java | 18 ------------------ .../TbGetCustomerDetailsNodeTest.java | 18 ------------------ .../TbGetOriginatorFieldsNodeTest.java | 19 ------------------- .../TbGetRelatedAttributeNodeTest.java | 18 ------------------ .../TbGetTenantAttributeNodeTest.java | 18 ------------------ .../metadata/TbGetTenantDetailsNodeTest.java | 18 ------------------ 11 files changed, 114 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index 43ee96c917..ffa735fd4f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -66,7 +66,6 @@ public abstract class TbAbstractGetAttributesNode extends Tb @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.checkTenantEntity(msg.getOriginator()); var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null; withCallback(findEntityAsync(ctx, msg.getOriginator()), entityId -> safeGetAttributes(ctx, msg, entityId, msgDataAsObjectNode), 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 e91bfd8410..48d4415693 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 @@ -37,7 +37,6 @@ public abstract class TbAbstractGetEntityDetailsNode { 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 3f45ae8d6d..3eb830da99 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 @@ -69,7 +69,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -203,23 +202,6 @@ public class TbGetCustomerAttributeNodeTest { verify(ctxMock, never()).tellSuccess(any()); } - @Test - public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { - // GIVEN - var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + - "' specified in the configuration doesn't belong to the current tenant."; - - 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)); - - // THEN - assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); - verify(ctxMock, never()).tellSuccess(any()); - } - @Test public void givenDidNotFindEntity_whenOnMsg_thenShouldTellFailure() { // GIVEN diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 1cb1449078..e2ab2f7863 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -64,7 +64,6 @@ 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; @@ -197,23 +196,6 @@ public class TbGetCustomerDetailsNodeTest { verify(ctxMock, never()).tellSuccess(any()); } - @Test - public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { - // GIVEN - var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + - "' specified in the configuration doesn't belong to the current tenant."; - - 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 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 8428165ab9..c7aeab1d8c 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 @@ -46,7 +46,6 @@ 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; @@ -158,24 +157,6 @@ public class TbGetOriginatorFieldsNodeTest { 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 givenValidMsgAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchToData() { // GIVEN 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 07b25b15e8..5228cd41b4 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 @@ -74,7 +74,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -225,23 +224,6 @@ public class TbGetRelatedAttributeNodeTest { verify(ctxMock, never()).tellSuccess(any()); } - @Test - public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { - // GIVEN - var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + - "' specified in the configuration doesn't belong to the current tenant."; - - 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)); - - // THEN - assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); - verify(ctxMock, never()).tellSuccess(any()); - } - @Test public void givenDidNotFindEntity_whenOnMsg_thenShouldTellFailure() { // GIVEN 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 76af5fabb4..c5c4539377 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 @@ -57,7 +57,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; 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; @@ -184,23 +183,6 @@ public class TbGetTenantAttributeNodeTest { verify(ctxMock, never()).tellSuccess(any()); } - @Test - public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { - // GIVEN - var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + - "' specified in the configuration doesn't belong to the current tenant."; - - 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)); - - // THEN - assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); - verify(ctxMock, never()).tellSuccess(any()); - } - @Test public void givenFetchAttributesToData_whenOnMsg_thenShouldFetchAttributesToData() { // GIVEN 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 index 06d1c46338..67d70b83bb 100644 --- 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 @@ -42,7 +42,6 @@ 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; @@ -150,23 +149,6 @@ public class TbGetTenantDetailsNodeTest { verify(ctxMock, never()).tellSuccess(any()); } - @Test - public void givenEntityThatDoesNotBelongToTheCurrentTenant_whenOnMsg_thenException() { - // GIVEN - var expectedExceptionMessage = "Entity with id: '" + DUMMY_DEVICE_ORIGINATOR + - "' specified in the configuration doesn't belong to the current tenant."; - - 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 From 574d976b31a76fce60310b4c79b796443f0c0882 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 13:05:22 +0300 Subject: [PATCH 21/29] Refactor: remove unnecessary no entity details selected tests --- .../metadata/TbGetCustomerDetailsNodeTest.java | 16 ---------------- .../metadata/TbGetTenantDetailsNodeTest.java | 16 ---------------- 2 files changed, 32 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index e2ab2f7863..afcb3503c9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -143,22 +143,6 @@ public class TbGetCustomerDetailsNodeTest { verify(ctxMock, never()).tellSuccess(any()); } - @Test - public void givenNoEntityDetailsSelected_whenInit_thenException() { - // GIVEN - var expectedExceptionMessage = "No entity details selected!"; - - config.setDetailsList(Collections.emptyList()); - nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - - // WHEN - var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); - - // THEN - assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); - verify(ctxMock, never()).tellSuccess(any()); - } - @Test public void givenDefaultConfig_whenInit_thenOK() { assertThat(config.getDetailsList()).isEqualTo(Collections.emptyList()); 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 index 67d70b83bb..cc22e3c2c3 100644 --- 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 @@ -95,22 +95,6 @@ public class TbGetTenantDetailsNodeTest { verify(ctxMock, never()).tellSuccess(any()); } - @Test - public void givenNoEntityDetailsSelected_whenInit_thenException() { - // GIVEN - var expectedExceptionMessage = "No entity details selected!"; - - config.setDetailsList(Collections.emptyList()); - nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - - // WHEN - var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); - - // THEN - assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage); - verify(ctxMock, never()).tellSuccess(any()); - } - @Test public void givenDefaultConfig_whenInit_thenOK() { // THEN From cf1a5feb045d1589bbbe85164c35dcee5a603421 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 13:09:22 +0300 Subject: [PATCH 22/29] Refactor: flip ifs in calculate delta node --- .../engine/metadata/CalculateDeltaNode.java | 92 ++++++++++--------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 3e2618de98..e554ad31e3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -42,6 +42,8 @@ import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static org.thingsboard.common.util.DonAsynchron.*; + @Slf4j @RuleNode(type = ComponentType.ENRICHMENT, name = "calculate delta", relationTypes = {"Success", "Failure", "Other"}, @@ -75,53 +77,53 @@ public class CalculateDeltaNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { - JsonNode json = JacksonUtil.toJsonNode(msg.getData()); - if (!json.isObject()) { - throw new IllegalArgumentException("Message body is not an object!"); - } - String inputKey = config.getInputValueKey(); - if (json.has(inputKey)) { - DonAsynchron.withCallback(getLastValue(msg.getOriginator()), - previousData -> { - double currentValue = json.get(inputKey).asDouble(); - long currentTs = msg.getMetaDataTs(); - - if (useCache) { - cache.put(msg.getOriginator(), new ValueWithTs(currentTs, currentValue)); - } - - BigDecimal delta = BigDecimal.valueOf(previousData != null ? currentValue - previousData.value : 0.0); - - if (config.isTellFailureIfDeltaIsNegative() && delta.doubleValue() < 0) { - ctx.tellNext(msg, TbRelationTypes.FAILURE); - return; - } - - if (config.getRound() != null) { - delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP); - } - - ObjectNode result = (ObjectNode) json; - if (delta.stripTrailingZeros().scale() > 0) { - result.put(config.getOutputValueKey(), delta.doubleValue()); - } else { - result.put(config.getOutputValueKey(), delta.longValueExact()); - } - - if (config.isAddPeriodBetweenMsgs()) { - long period = previousData != null ? currentTs - previousData.ts : 0; - result.put(config.getPeriodValueKey(), period); - } - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(result))); - }, - t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); - } else { - ctx.tellNext(msg, "Other"); - } - } else { + if (!msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { + ctx.tellNext(msg, "Other"); + return; + } + JsonNode json = JacksonUtil.toJsonNode(msg.getData()); + if (!json.isObject()) { + throw new IllegalArgumentException("Message body is not an object!"); + } + String inputKey = config.getInputValueKey(); + if (!json.has(inputKey)) { ctx.tellNext(msg, "Other"); + return; } + withCallback(getLastValue(msg.getOriginator()), + previousData -> { + double currentValue = json.get(inputKey).asDouble(); + long currentTs = msg.getMetaDataTs(); + + if (useCache) { + cache.put(msg.getOriginator(), new ValueWithTs(currentTs, currentValue)); + } + + BigDecimal delta = BigDecimal.valueOf(previousData != null ? currentValue - previousData.value : 0.0); + + if (config.isTellFailureIfDeltaIsNegative() && delta.doubleValue() < 0) { + ctx.tellNext(msg, TbRelationTypes.FAILURE); + return; + } + + if (config.getRound() != null) { + delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP); + } + + ObjectNode result = (ObjectNode) json; + if (delta.stripTrailingZeros().scale() > 0) { + result.put(config.getOutputValueKey(), delta.doubleValue()); + } else { + result.put(config.getOutputValueKey(), delta.longValueExact()); + } + + if (config.isAddPeriodBetweenMsgs()) { + long period = previousData != null ? currentTs - previousData.ts : 0; + result.put(config.getPeriodValueKey(), period); + } + ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(result))); + }, + t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } @Override From ff5652a60707e58e7f92dae49b2dd16553248ea0 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 13:17:57 +0300 Subject: [PATCH 23/29] Refactor: fix lenient usage in fetch device credentials node --- .../TbFetchDeviceCredentialsNodeTest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java index 397b307647..0c589df01c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java @@ -42,7 +42,8 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -68,14 +69,6 @@ public class TbFetchDeviceCredentialsNodeTest { config = new TbFetchDeviceCredentialsNodeConfiguration().defaultConfiguration(); config.setFetchTo(FetchTo.METADATA); node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); - - lenient().doReturn(deviceCredentialsServiceMock).when(ctxMock).getDeviceCredentialsService(); - lenient().doAnswer(invocation -> { - DeviceCredentials deviceCredentials = new DeviceCredentials(); - deviceCredentials.setCredentialsType(ACCESS_TOKEN); - return deviceCredentials; - }).when(deviceCredentialsServiceMock).findDeviceCredentialsByDeviceId(any(), any()); - lenient().doAnswer(invocation -> JacksonUtil.newObjectNode()).when(deviceCredentialsServiceMock).toCredentialsInfo(any()); } @AfterEach @@ -98,6 +91,13 @@ public class TbFetchDeviceCredentialsNodeTest { @Test void givenValidMsg_whenOnMsg_thenVerifyOutput() throws Exception { // GIVEN + doReturn(deviceCredentialsServiceMock).when(ctxMock).getDeviceCredentialsService(); + doAnswer(invocation -> { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setCredentialsType(ACCESS_TOKEN); + return deviceCredentials; + }).when(deviceCredentialsServiceMock).findDeviceCredentialsByDeviceId(any(), any()); + doAnswer(invocation -> JacksonUtil.newObjectNode()).when(deviceCredentialsServiceMock).toCredentialsInfo(any()); // WHEN node.onMsg(ctxMock, getTbMsg(deviceId)); @@ -135,8 +135,8 @@ public class TbFetchDeviceCredentialsNodeTest { @Test void givenGetDeviceCredentials_whenOnMsg_thenShouldTellFailure() throws Exception { // GIVEN - willAnswer(invocation -> null) - .given(deviceCredentialsServiceMock).findDeviceCredentialsByDeviceId(any(), any()); + doReturn(deviceCredentialsServiceMock).when(ctxMock).getDeviceCredentialsService(); + willAnswer(invocation -> null).given(deviceCredentialsServiceMock).findDeviceCredentialsByDeviceId(any(), any()); // WHEN node.onMsg(ctxMock, getTbMsg(deviceId)); From df25091f3c888679a7677eff6f12004566160f29 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 13:32:48 +0300 Subject: [PATCH 24/29] Refactor: add assertion for exceptions in customer details node --- .../TbGetCustomerDetailsNodeTest.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index afcb3503c9..8095ebefba 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -57,6 +57,7 @@ import org.thingsboard.server.dao.user.UserService; import java.util.Collections; import java.util.List; +import java.util.NoSuchElementException; import java.util.UUID; import java.util.concurrent.Callable; @@ -384,6 +385,7 @@ public class TbGetCustomerDetailsNodeTest { // GIVEN var device = new Device(); device.setId(new DeviceId(UUID.randomUUID())); + device.setName("Thermostat"); prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), device.getId()); @@ -399,12 +401,19 @@ public class TbGetCustomerDetailsNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class); - verify(ctxMock, times(1)).tellFailure(actualMessageCaptor.capture(), any()); + verify(ctxMock, times(1)).tellFailure(actualMessageCaptor.capture(), actualExceptionCaptor.capture()); verify(ctxMock, never()).tellSuccess(any()); - assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); - assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + var actualMsg = actualMessageCaptor.getValue(); + var actualException = actualExceptionCaptor.getValue(); + + assertThat(actualMsg.getData()).isEqualTo(msg.getData()); + assertThat(actualMsg.getMetaData()).isEqualTo(msg.getMetaData()); + + assertThat(actualException).isInstanceOf(RuntimeException.class); + assertThat(actualException.getMessage()).isEqualTo("Device with name 'Thermostat' is not assigned to Customer."); } @Test @@ -451,12 +460,19 @@ public class TbGetCustomerDetailsNodeTest { // THEN var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class); - verify(ctxMock, times(1)).tellFailure(actualMessageCaptor.capture(), any()); + verify(ctxMock, times(1)).tellFailure(actualMessageCaptor.capture(), actualExceptionCaptor.capture()); verify(ctxMock, never()).tellSuccess(any()); - assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData()); - assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); + var actualMsg = actualMessageCaptor.getValue(); + var actualException = actualExceptionCaptor.getValue(); + + assertThat(actualMsg.getData()).isEqualTo(msg.getData()); + assertThat(actualMsg.getMetaData()).isEqualTo(msg.getMetaData()); + + assertThat(actualException).isInstanceOf(NoSuchElementException.class); + assertThat(actualException.getMessage()).isEqualTo("Entity with entityType 'DASHBOARD' is not supported."); } private void prepareMsgAndConfig(FetchTo fetchTo, List detailsList, EntityId originator) { From 39e9ab26e3a127d286f87e3916e4813f8281a80e Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 13:46:21 +0300 Subject: [PATCH 25/29] Refactor: add realistic mapping in originator fields node --- .../metadata/TbGetOriginatorFieldsNodeTest.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) 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 c7aeab1d8c..06bff564de 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 @@ -103,9 +103,7 @@ public class TbGetOriginatorFieldsNodeTest { @Test public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { - // GIVEN - - // WHEN + // GIVEN-WHEN node.init(ctxMock, nodeConfiguration); // THEN @@ -122,9 +120,9 @@ public class TbGetOriginatorFieldsNodeTest { public void givenCustomConfig_whenInit_thenOK() throws TbNodeException { // GIVEN config.setFieldsMapping(Map.of( - "sourceField1", "targetKey1", - "sourceField2", "targetKey2", - "sourceField3", "targetKey3")); + "email", "originatorEmail", + "title", "originatorTitle", + "country", "originatorCountry")); config.setIgnoreNullStrings(true); config.setFetchTo(FetchTo.DATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -135,9 +133,9 @@ public class TbGetOriginatorFieldsNodeTest { // THEN assertThat(node.config).isEqualTo(config); assertThat(config.getFieldsMapping()).isEqualTo(Map.of( - "sourceField1", "targetKey1", - "sourceField2", "targetKey2", - "sourceField3", "targetKey3")); + "email", "originatorEmail", + "title", "originatorTitle", + "country", "originatorCountry")); assertThat(config.isIgnoreNullStrings()).isEqualTo(true); assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA); assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); From 2e6678c8510df595f63a40fd694453121f27e1bf Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 14:02:22 +0300 Subject: [PATCH 26/29] Refactor: node renaming rollback --- .../service/install/update/DefaultDataUpdateService.java | 4 ++-- .../thingsboard/rule/engine/metadata/CalculateDeltaNode.java | 3 +-- ...GetRelatedDeviceAttrNode.java => TbGetDeviceAttrNode.java} | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) rename rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/{TbGetRelatedDeviceAttrNode.java => TbGetDeviceAttrNode.java} (96%) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index a27b848530..8908b5f519 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -35,7 +35,7 @@ import org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode; import org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode; import org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode; import org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode; -import org.thingsboard.rule.engine.metadata.TbGetRelatedDeviceAttrNode; +import org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode; import org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode; import org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; @@ -228,7 +228,7 @@ public class DefaultDataUpdateService implements DataUpdateService { private void updateEnrichmentRuleNodes() { try { - var fetchToDataNodes = List.of(TbGetAttributesNode.class.getName(), TbGetRelatedDeviceAttrNode.class.getName()); + var fetchToDataNodes = List.of(TbGetAttributesNode.class.getName(), TbGetDeviceAttrNode.class.getName()); var addToMetadataNodes = List.of(TbGetCustomerDetailsNode.class.getName(), TbGetTenantDetailsNode.class.getName()); var fetchToMetadataNodes = List.of(TbFetchDeviceCredentialsNode.class.getName()); var hardCodeMetadataNodes = List.of( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index e554ad31e3..1b88f79ef5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -42,7 +41,7 @@ import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import static org.thingsboard.common.util.DonAsynchron.*; +import static org.thingsboard.common.util.DonAsynchron.withCallback; @Slf4j @RuleNode(type = ComponentType.ENRICHMENT, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDeviceAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java similarity index 96% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDeviceAttrNode.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java index 53220837a9..fdbb412e6b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedDeviceAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.msg.TbMsg; "metadata.cs_temperature or metadata.shared_limit ", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeDeviceAttributesConfig") -public class TbGetRelatedDeviceAttrNode extends TbAbstractGetAttributesNode { +public class TbGetDeviceAttrNode extends TbAbstractGetAttributesNode { private static final String RELATED_DEVICE_NOT_FOUND_MESSAGE = "Failed to find related device to message originator using relation query specified in the configuration!"; From e00a29761e2ade877e2a25747a304069d40b06b5 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 18 Apr 2023 15:37:36 +0300 Subject: [PATCH 27/29] Refactor: refactor upgrade script --- .../update/DefaultDataUpdateService.java | 147 +++++++----------- 1 file changed, 58 insertions(+), 89 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index 8908b5f519..c1c334aef3 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -29,15 +29,6 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.metadata.FetchTo; -import org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode; -import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; -import org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode; -import org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode; -import org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode; -import org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode; -import org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode; -import org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode; -import org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; import org.thingsboard.server.common.data.DataConstants; @@ -228,20 +219,18 @@ public class DefaultDataUpdateService implements DataUpdateService { private void updateEnrichmentRuleNodes() { try { - var fetchToDataNodes = List.of(TbGetAttributesNode.class.getName(), TbGetDeviceAttrNode.class.getName()); - var addToMetadataNodes = List.of(TbGetCustomerDetailsNode.class.getName(), TbGetTenantDetailsNode.class.getName()); - var fetchToMetadataNodes = List.of(TbFetchDeviceCredentialsNode.class.getName()); - var hardCodeMetadataNodes = List.of( - TbGetOriginatorFieldsNode.class.getName(), - TbGetRelatedAttributeNode.class.getName(), - TbGetTenantAttributeNode.class.getName(), - TbGetCustomerAttributeNode.class.getName() - ); - var allNodesToUpdate = new ArrayList<>(hardCodeMetadataNodes); - allNodesToUpdate.addAll(fetchToDataNodes); - allNodesToUpdate.addAll(addToMetadataNodes); - allNodesToUpdate.addAll(fetchToMetadataNodes); var ruleChainIdToTenantId = new HashMap(); + var allNodesToUpdate = List.of( + "org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode", + "org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode", + "org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode", + "org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode", + "org.thingsboard.rule.engine.metadata.TbGetAttributesNode", + "org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode", + "org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode", + "org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode", + "org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode" + ); allNodesToUpdate.forEach(ruleNodeType -> { var ruleNodes = new PageDataIterable<>( pageLink -> ruleChainService.findAllRuleNodesByType(ruleNodeType, pageLink), 1024 @@ -259,74 +248,35 @@ public class DefaultDataUpdateService implements DataUpdateService { continue; } var configObjectNode = (ObjectNode) configuration; - var fetchTo = FetchTo.METADATA; - - if (fetchToDataNodes.contains(ruleNodeType)) { - configObjectNode.remove("fetchToMetadata"); - configObjectNode.remove("addToMetadata"); - if (configObjectNode.has("fetchToData")) { - var fetchToData = configObjectNode.get("fetchToData").asText(); - if ("true".equals(fetchToData)) { - fetchTo = FetchTo.DATA; - } else if ("false".equals(fetchToData)) { - fetchTo = FetchTo.METADATA; - } else { - log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: fetchToData property has unexpected value: {} Allowed values: true or false!", - ruleNodeType, ruleNode.getId(), fetchToData); - continue; - } - configObjectNode.remove("fetchToData"); - } else { - log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: fetchToData property is not present!", ruleNodeType, ruleNode.getId()); - continue; - } - } else if (fetchToMetadataNodes.contains(ruleNodeType)) { - configObjectNode.remove("fetchToData"); - configObjectNode.remove("addToMetadata"); - if (configObjectNode.has("fetchToMetadata")) { - var fetchToMetadata = configObjectNode.get("fetchToMetadata").asText(); - if ("true".equals(fetchToMetadata)) { - fetchTo = FetchTo.METADATA; - } else if ("false".equals(fetchToMetadata)) { - fetchTo = FetchTo.DATA; - } else { - log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: fetchToMetadata property has unexpected value: {} Allowed values: true or false!", - ruleNodeType, ruleNode.getId(), fetchToMetadata); - continue; - } - configObjectNode.remove("fetchToMetadata"); - } else { - log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: fetchToMetadata property is not present!", ruleNodeType, ruleNode.getId()); - continue; - } - } else if (addToMetadataNodes.contains(ruleNodeType)) { - configObjectNode.remove("fetchToData"); - configObjectNode.remove("fetchToMetadata"); - if (configObjectNode.has("addToMetadata")) { - var addToMetadata = configObjectNode.get("addToMetadata").asText(); - if ("true".equals(addToMetadata)) { - fetchTo = FetchTo.METADATA; - } else if ("false".equals(addToMetadata)) { - fetchTo = FetchTo.DATA; - } else { - log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: addToMetadata property has unexpected value: {} Allowed values: true or false!", - ruleNodeType, ruleNode.getId(), addToMetadata); - continue; - } - configObjectNode.remove("addToMetadata"); - } else { - log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: addToMetadata property is not present!", ruleNodeType, ruleNode.getId()); + + FetchTo fetchTo; + + switch (ruleNodeType) { + case "org.thingsboard.rule.engine.metadata.TbGetAttributesNode": + case "org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode": + fetchTo = checkEnrichmentNodeFetchProperty(configObjectNode, "fetchToData", FetchTo.DATA, FetchTo.METADATA); + break; + case "org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode": + case "org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode": + fetchTo = checkEnrichmentNodeFetchProperty(configObjectNode, "addToMetadata", FetchTo.METADATA, FetchTo.DATA); + break; + case "org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode": + fetchTo = checkEnrichmentNodeFetchProperty(configObjectNode, "fetchToMetadata", FetchTo.METADATA, FetchTo.DATA); + break; + case "org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode": + case "org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode": + case "org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode": + case "org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode": + fetchTo = FetchTo.METADATA; + break; + default: + log.error("Failed to update rule node: [{}] with id: [{}] " + + "Reason: Unexpected rule node type!", ruleNodeType, ruleNode.getId()); continue; - } - } else { - log.error("Failed to updated rule node: [{}] with id: [{}] " + - "Reason: Unexpected rule node type!", ruleNodeType, ruleNode.getId()); + } + + if (fetchTo == null) { + log.error("Failed to update rule node: [{}] with id: [{}]", ruleNodeType, ruleNode.getId()); continue; } @@ -352,6 +302,25 @@ public class DefaultDataUpdateService implements DataUpdateService { } } + private FetchTo checkEnrichmentNodeFetchProperty(ObjectNode config, String property, FetchTo ifTrue, FetchTo ifFalse) { + if (config.has(property)) { + var value = config.get(property).asText(); + if ("true".equals(value)) { + config.remove(property); + return ifTrue; + } else if ("false".equals(value)) { + config.remove(property); + return ifFalse; + } else { + log.error(property + " property has unexpected value: {} Allowed values: true or false!", value); + return null; + } + } else { + log.error(property + " property is not present!"); + return null; + } + } + private final PaginatedUpdater deviceProfileEntityDynamicConditionsUpdater = new PaginatedUpdater<>() { From bf2181fcdd15ef667e135b6862a0fed3db6a83e6 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 2 May 2023 14:55:06 +0300 Subject: [PATCH 28/29] Refactor: fix typo, replace direct executor with db callback executor --- .../util/EntitiesAlarmOriginatorIdAsyncLoader.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java index 4de50e810d..346d5e7903 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.alarm.Alarm; @@ -26,18 +25,19 @@ import org.thingsboard.server.common.data.id.EntityId; public class EntitiesAlarmOriginatorIdAsyncLoader { - public static ListenableFuture findEntityIdAsync(TbContext ctx, EntityId original) { - switch (original.getEntityType()) { + public static ListenableFuture findEntityIdAsync(TbContext ctx, EntityId originator) { + switch (originator.getEntityType()) { case ALARM: - return getAlarmOriginatorAsync(ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), (AlarmId) original)); + return getAlarmOriginatorAsync(ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), (AlarmId) originator), ctx); default: - return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType())); + return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType " + originator.getEntityType())); } } - private static ListenableFuture getAlarmOriginatorAsync(ListenableFuture future) { + private static ListenableFuture getAlarmOriginatorAsync(ListenableFuture future, TbContext ctx) { return Futures.transformAsync(future, in -> in != null ? Futures.immediateFuture(in.getOriginator()) - : Futures.immediateFuture(null), MoreExecutors.directExecutor()); + : Futures.immediateFuture(null), ctx.getDbCallbackExecutor()); } + } From d7b8e2daef782344217fd1fd7d9a62ce38754b11 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 2 May 2023 15:45:21 +0300 Subject: [PATCH 29/29] Refactor: remove pointless tests --- .../TbGetCustomerDetailsNodeTest.java | 31 ------------------- .../metadata/TbGetTenantDetailsNodeTest.java | 20 ------------ 2 files changed, 51 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 8095ebefba..a9b32f710a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -33,7 +33,6 @@ import org.thingsboard.rule.engine.util.EntityDetails; 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.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.edge.Edge; @@ -43,7 +42,6 @@ import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; 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.msg.TbMsg; @@ -257,35 +255,6 @@ public class TbGetCustomerDetailsNodeTest { assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } - @Test - public void givenNoEntityDetailsAndFetchToMetadata_whenOnMsg_thenShouldTellSuccessAndFetchNothingToMetaData() { - // GIVEN - var entityView = new EntityView(); - entityView.setId(new EntityViewId(UUID.randomUUID())); - entityView.setCustomerId(customer.getId()); - - prepareMsgAndConfig(FetchTo.METADATA, Collections.emptyList(), entityView.getId()); - - when(ctxMock.getEntityViewService()).thenReturn(entityViewServiceMock); - when(entityViewServiceMock.findEntityViewByIdAsync(eq(TENANT_ID), eq(entityView.getId()))).thenReturn(Futures.immediateFuture(entityView)); - - mockFindCustomer(); - - when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); - - // 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(msg.getData()); - assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); - } - @Test public void givenNotPresentEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { // GIVEN 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 index cc22e3c2c3..423c770e27 100644 --- 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 @@ -191,26 +191,6 @@ public class TbGetTenantDetailsNodeTest { assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData); } - @Test - public void givenNoEntityDetailsAndFetchToMetadata_whenOnMsg_thenShouldTellSuccessAndFetchNothingToMetaData() { - // GIVEN - prepareMsgAndConfig(FetchTo.METADATA, Collections.emptyList()); - - mockFindTenant(); - - // 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(msg.getData()); - assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData()); - } - @Test public void givenNotPresentEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() { // GIVEN