From 58f51455646297a7a41c756ac09738b5f86e026d Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 5 Apr 2023 11:55:29 +0300 Subject: [PATCH] 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); } + }