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 extends EntityId> 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);
}
+
}