Browse Source

Merge pull request #17 from dskarzh/feature/enrichment-rule-nodes-improvements-fix

Feature/enrichment rule nodes improvements fix
pull/8661/head
Shvaika Dmytro 3 years ago
committed by GitHub
parent
commit
abdf6ae101
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 123
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  2. 95
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java
  3. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java
  4. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/FetchTo.java
  5. 9
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java
  6. 12
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityAttrNode.java
  7. 8
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java
  8. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java
  9. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java
  10. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeConfiguration.java
  11. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java
  12. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java
  13. 18
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java
  14. 8
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java
  15. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java
  16. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java
  17. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java
  18. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java
  19. 20
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java
  20. 20
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java
  21. 37
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java
  22. 41
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java
  23. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityContainer.java
  24. 452
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java
  25. 238
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractAttributeNodeTest.java
  26. 115
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java
  27. 273
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java
  28. 446
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java
  29. 469
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java
  30. 116
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java
  31. 523
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java
  32. 383
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java
  33. 284
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java
  34. 170
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java
  35. 36
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java
  36. 153
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java
  37. 174
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java
  38. 4
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java

123
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -29,15 +29,6 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.metadata.FetchTo; import org.thingsboard.rule.engine.metadata.FetchTo;
import org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode;
import org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode;
import org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode;
import org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode;
import org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode;
import org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode;
import org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -228,19 +219,19 @@ public class DefaultDataUpdateService implements DataUpdateService {
private void updateEnrichmentRuleNodes() { private void updateEnrichmentRuleNodes() {
try { try {
var ruleNodeTypesToUpdate = List.of(
TbGetOriginatorFieldsNode.class.getName(),
TbFetchDeviceCredentialsNode.class.getName(),
TbGetAttributesNode.class.getName(),
TbGetDeviceAttrNode.class.getName(),
TbGetRelatedAttributeNode.class.getName(),
TbGetTenantAttributeNode.class.getName(),
TbGetCustomerAttributeNode.class.getName(),
TbGetCustomerDetailsNode.class.getName(),
TbGetTenantDetailsNode.class.getName()
);
var ruleChainIdToTenantId = new HashMap<RuleChainId, TenantId>(); var ruleChainIdToTenantId = new HashMap<RuleChainId, TenantId>();
ruleNodeTypesToUpdate.forEach(ruleNodeType -> { var allNodesToUpdate = List.of(
"org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode",
"org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode",
"org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode",
"org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode",
"org.thingsboard.rule.engine.metadata.TbGetAttributesNode",
"org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode",
"org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode",
"org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode",
"org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode"
);
allNodesToUpdate.forEach(ruleNodeType -> {
var ruleNodes = new PageDataIterable<>( var ruleNodes = new PageDataIterable<>(
pageLink -> ruleChainService.findAllRuleNodesByType(ruleNodeType, pageLink), 1024 pageLink -> ruleChainService.findAllRuleNodesByType(ruleNodeType, pageLink), 1024
); );
@ -257,53 +248,42 @@ public class DefaultDataUpdateService implements DataUpdateService {
continue; continue;
} }
var configObjectNode = (ObjectNode) configuration; var configObjectNode = (ObjectNode) configuration;
var fetchTo = FetchTo.METADATA;
if (configObjectNode.has("fetchToMetadata")) { FetchTo fetchTo;
var fetchToMetadata = configObjectNode.get("fetchToMetadata").asText();
if ("true".equals(fetchToMetadata)) { switch (ruleNodeType) {
fetchTo = FetchTo.METADATA; case "org.thingsboard.rule.engine.metadata.TbGetAttributesNode":
} else if ("false".equals(fetchToMetadata)) { case "org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode":
fetchTo = FetchTo.DATA; fetchTo = checkEnrichmentNodeFetchProperty(configObjectNode, "fetchToData", FetchTo.DATA, FetchTo.METADATA);
} else { break;
log.error("Failed to updated rule node: [{}] with id: [{}] " + case "org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode":
"Reason: fetchToMetadata property has unexpected value: {} Allowed values: true or false!", case "org.thingsboard.rule.engine.metadata.TbGetTenantDetailsNode":
ruleNodeType, ruleNode.getId(), fetchToMetadata); fetchTo = checkEnrichmentNodeFetchProperty(configObjectNode, "addToMetadata", FetchTo.METADATA, FetchTo.DATA);
continue; break;
} case "org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode":
configObjectNode.remove("fetchToMetadata"); fetchTo = checkEnrichmentNodeFetchProperty(configObjectNode, "fetchToMetadata", FetchTo.METADATA, FetchTo.DATA);
} break;
if (configObjectNode.has("fetchToData")) { case "org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode":
var fetchToData = configObjectNode.get("fetchToData").asText(); case "org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode":
if ("true".equals(fetchToData)) { case "org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode":
fetchTo = FetchTo.DATA; case "org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode":
} else if ("false".equals(fetchToData)) {
fetchTo = FetchTo.METADATA; fetchTo = FetchTo.METADATA;
} else { break;
log.error("Failed to updated rule node: [{}] with id: [{}] " + default:
"Reason: fetchToData property has unexpected value: {} Allowed values: true or false!", log.error("Failed to update rule node: [{}] with id: [{}] " +
ruleNodeType, ruleNode.getId(), fetchToData); "Reason: Unexpected rule node type!", ruleNodeType, ruleNode.getId());
continue; continue;
}
configObjectNode.remove("fetchToData");
} }
if (configObjectNode.has("addToMetadata")) {
var addToMetadata = configObjectNode.get("addToMetadata").asText(); if (fetchTo == null) {
if ("true".equals(addToMetadata)) { log.error("Failed to update rule node: [{}] with id: [{}]", ruleNodeType, ruleNode.getId());
fetchTo = FetchTo.METADATA; continue;
} else if ("false".equals(addToMetadata)) {
fetchTo = FetchTo.DATA;
} else {
log.error("Failed to updated rule node: [{}] with id: [{}] " +
"Reason: addToMetadata property has unexpected value: {} Allowed values: true or false!",
ruleNodeType, ruleNode.getId(), addToMetadata);
continue;
}
configObjectNode.remove("addToMetadata");
} }
configObjectNode.put("fetchTo", fetchTo.name()); configObjectNode.put("fetchTo", fetchTo.name());
ruleNode.setConfiguration(configObjectNode); ruleNode.setConfiguration(configObjectNode);
RuleChainId ruleChainId = ruleNode.getRuleChainId(); var ruleChainId = ruleNode.getRuleChainId();
TenantId tenantId = ruleChainIdToTenantId.computeIfAbsent(ruleChainId, var tenantId = ruleChainIdToTenantId.computeIfAbsent(ruleChainId,
id -> { id -> {
RuleChain ruleChain = ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID, id); RuleChain ruleChain = ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID, id);
if (ruleChain == null) { if (ruleChain == null) {
@ -322,6 +302,25 @@ public class DefaultDataUpdateService implements DataUpdateService {
} }
} }
private FetchTo checkEnrichmentNodeFetchProperty(ObjectNode config, String property, FetchTo ifTrue, FetchTo ifFalse) {
if (config.has(property)) {
var value = config.get(property).asText();
if ("true".equals(value)) {
config.remove(property);
return ifTrue;
} else if ("false".equals(value)) {
config.remove(property);
return ifFalse;
} else {
log.error(property + " property has unexpected value: {} Allowed values: true or false!", value);
return null;
}
} else {
log.error(property + " property is not present!");
return null;
}
}
private final PaginatedUpdater<String, DeviceProfileEntity> deviceProfileEntityDynamicConditionsUpdater = private final PaginatedUpdater<String, DeviceProfileEntity> deviceProfileEntityDynamicConditionsUpdater =
new PaginatedUpdater<>() { new PaginatedUpdater<>() {

95
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java

@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; 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.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.common.util.JacksonUtil;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
@ -42,6 +41,8 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.common.util.DonAsynchron.withCallback;
@Slf4j @Slf4j
@RuleNode(type = ComponentType.ENRICHMENT, @RuleNode(type = ComponentType.ENRICHMENT,
name = "calculate delta", relationTypes = {"Success", "Failure", "Other"}, name = "calculate delta", relationTypes = {"Success", "Failure", "Other"},
@ -54,6 +55,7 @@ import java.util.concurrent.ConcurrentHashMap;
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbEnrichmentNodeCalculateDeltaConfig") configDirective = "tbEnrichmentNodeCalculateDeltaConfig")
public class CalculateDeltaNode implements TbNode { public class CalculateDeltaNode implements TbNode {
private Map<EntityId, ValueWithTs> cache; private Map<EntityId, ValueWithTs> cache;
private CalculateDeltaNodeConfiguration config; private CalculateDeltaNodeConfiguration config;
private TbContext ctx; private TbContext ctx;
@ -74,51 +76,53 @@ public class CalculateDeltaNode implements TbNode {
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { if (!msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) {
JsonNode json = JacksonUtil.toJsonNode(msg.getData());
String inputKey = config.getInputValueKey();
if (json.has(inputKey)) {
DonAsynchron.withCallback(getLastValue(msg.getOriginator()),
previousData -> {
double currentValue = json.get(inputKey).asDouble();
long currentTs = msg.getMetaDataTs();
if (useCache) {
cache.put(msg.getOriginator(), new ValueWithTs(currentTs, currentValue));
}
BigDecimal delta = BigDecimal.valueOf(previousData != null ? currentValue - previousData.value : 0.0);
if (config.isTellFailureIfDeltaIsNegative() && delta.doubleValue() < 0) {
ctx.tellNext(msg, TbRelationTypes.FAILURE);
return;
}
if (config.getRound() != null) {
delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP);
}
ObjectNode result = (ObjectNode) json;
if (delta.stripTrailingZeros().scale() > 0) {
result.put(config.getOutputValueKey(), delta.doubleValue());
} else {
result.put(config.getOutputValueKey(), delta.longValueExact());
}
if (config.isAddPeriodBetweenMsgs()) {
long period = previousData != null ? currentTs - previousData.ts : 0;
result.put(config.getPeriodValueKey(), period);
}
ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(result)));
},
t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
} else {
ctx.tellNext(msg, "Other");
}
} else {
ctx.tellNext(msg, "Other"); ctx.tellNext(msg, "Other");
return;
} }
JsonNode json = JacksonUtil.toJsonNode(msg.getData());
if (!json.isObject()) {
throw new IllegalArgumentException("Message body is not an object!");
}
String inputKey = config.getInputValueKey();
if (!json.has(inputKey)) {
ctx.tellNext(msg, "Other");
return;
}
withCallback(getLastValue(msg.getOriginator()),
previousData -> {
double currentValue = json.get(inputKey).asDouble();
long currentTs = msg.getMetaDataTs();
if (useCache) {
cache.put(msg.getOriginator(), new ValueWithTs(currentTs, currentValue));
}
BigDecimal delta = BigDecimal.valueOf(previousData != null ? currentValue - previousData.value : 0.0);
if (config.isTellFailureIfDeltaIsNegative() && delta.doubleValue() < 0) {
ctx.tellNext(msg, TbRelationTypes.FAILURE);
return;
}
if (config.getRound() != null) {
delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP);
}
ObjectNode result = (ObjectNode) json;
if (delta.stripTrailingZeros().scale() > 0) {
result.put(config.getOutputValueKey(), delta.doubleValue());
} else {
result.put(config.getOutputValueKey(), delta.longValueExact());
}
if (config.isAddPeriodBetweenMsgs()) {
long period = previousData != null ? currentTs - previousData.ts : 0;
result.put(config.getPeriodValueKey(), period);
}
ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(result)));
},
t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
} }
@Override @Override
@ -181,4 +185,5 @@ public class CalculateDeltaNode implements TbNode {
this.value = value; this.value = value;
} }
} }
} }

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java

@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class CalculateDeltaNodeConfiguration implements NodeConfiguration<CalculateDeltaNodeConfiguration> { public class CalculateDeltaNodeConfiguration implements NodeConfiguration<CalculateDeltaNodeConfiguration> {
private String inputValueKey; private String inputValueKey;
private String outputValueKey; private String outputValueKey;
private boolean useCache; private boolean useCache;
@ -32,7 +33,7 @@ public class CalculateDeltaNodeConfiguration implements NodeConfiguration<Calcul
@Override @Override
public CalculateDeltaNodeConfiguration defaultConfiguration() { public CalculateDeltaNodeConfiguration defaultConfiguration() {
CalculateDeltaNodeConfiguration configuration = new CalculateDeltaNodeConfiguration(); var configuration = new CalculateDeltaNodeConfiguration();
configuration.setInputValueKey("pulseCounter"); configuration.setInputValueKey("pulseCounter");
configuration.setOutputValueKey("delta"); configuration.setOutputValueKey("delta");
configuration.setUseCache(true); configuration.setUseCache(true);

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/FetchTo.java

@ -16,6 +16,8 @@
package org.thingsboard.rule.engine.metadata; package org.thingsboard.rule.engine.metadata;
public enum FetchTo { public enum FetchTo {
DATA, DATA,
METADATA METADATA
} }

9
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java

@ -51,6 +51,7 @@ import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
@Slf4j @Slf4j
public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeConfiguration, T extends EntityId> extends TbAbstractNodeWithFetchTo<C> { public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeConfiguration, T extends EntityId> extends TbAbstractNodeWithFetchTo<C> {
private static final String VALUE = "value"; private static final String VALUE = "value";
private static final String TS = "ts"; private static final String TS = "ts";
private boolean isTellFailureIfAbsent; private boolean isTellFailureIfAbsent;
@ -65,7 +66,6 @@ public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeC
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
ctx.checkTenantEntity(msg.getOriginator());
var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null; var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null;
withCallback( withCallback(
findEntityIdAsync(ctx, msg), findEntityIdAsync(ctx, msg),
@ -100,7 +100,7 @@ public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeC
} else { } else {
ctx.tellFailure(outMsg, reportFailures(failuresPairSet)); ctx.tellFailure(outMsg, reportFailures(failuresPairSet));
} }
}, t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); }, t -> ctx.tellFailure(msg, t), MoreExecutors.directExecutor());
} }
private ListenableFuture<TbPair<String, List<AttributeKvEntry>>> getAttrAsync( private ListenableFuture<TbPair<String, List<AttributeKvEntry>>> getAttrAsync(
@ -120,7 +120,7 @@ public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeC
failuresPairSet.add(new TbPair<>(scope, nonExistentKeys)); failuresPairSet.add(new TbPair<>(scope, nonExistentKeys));
} }
return new TbPair<>(scope, attributeKvEntryList); return new TbPair<>(scope, attributeKvEntryList);
}, MoreExecutors.directExecutor()); }, ctx.getDbCallbackExecutor());
} }
private ListenableFuture<TbPair<String, List<TsKvEntry>>> getLatestTelemetry(TbContext ctx, EntityId entityId, List<String> keys, Set<TbPair<String, List<String>>> failuresPairSet) { private ListenableFuture<TbPair<String, List<TsKvEntry>>> getLatestTelemetry(TbContext ctx, EntityId entityId, List<String> keys, Set<TbPair<String, List<String>>> failuresPairSet) {
@ -146,7 +146,7 @@ public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeC
failuresPairSet.add(new TbPair<>(LATEST_TS, nonExistentKeys)); failuresPairSet.add(new TbPair<>(LATEST_TS, nonExistentKeys));
} }
return new TbPair<>(LATEST_TS, listTsKvEntry); return new TbPair<>(LATEST_TS, listTsKvEntry);
}, MoreExecutors.directExecutor()); }, ctx.getDbCallbackExecutor());
} }
private TsKvEntry getValueWithTs(TsKvEntry tsKvEntry) { private TsKvEntry getValueWithTs(TsKvEntry tsKvEntry) {
@ -188,4 +188,5 @@ public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeC
failuresPairSet.clear(); failuresPairSet.clear();
return new RuntimeException(errorMessage.toString()); return new RuntimeException(errorMessage.toString());
} }
} }

12
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityAttrNode.java

@ -40,7 +40,6 @@ public abstract class TbAbstractGetEntityAttrNode<T extends EntityId> extends Tb
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
ctx.checkTenantEntity(msg.getOriginator());
var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null; var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null;
withCallback(findEntityAsync(ctx, msg.getOriginator()), withCallback(findEntityAsync(ctx, msg.getOriginator()),
entityId -> safeGetAttributes(ctx, msg, entityId, msgDataAsObjectNode), entityId -> safeGetAttributes(ctx, msg, entityId, msgDataAsObjectNode),
@ -49,8 +48,8 @@ public abstract class TbAbstractGetEntityAttrNode<T extends EntityId> extends Tb
protected abstract ListenableFuture<T> findEntityAsync(TbContext ctx, EntityId originator); protected abstract ListenableFuture<T> findEntityAsync(TbContext ctx, EntityId originator);
protected void checkIfMappingIsNotEmptyOrThrow(TbGetEntityAttrNodeConfiguration config) throws TbNodeException { protected void checkIfMappingIsNotEmptyOrElseThrow(Map<String, String> attrMapping) throws TbNodeException {
if (config.getAttrMapping().isEmpty()) { if (attrMapping == null || attrMapping.isEmpty()) {
throw new TbNodeException("At least one attribute mapping should be specified!"); throw new TbNodeException("At least one attribute mapping should be specified!");
} }
} }
@ -65,7 +64,8 @@ public abstract class TbAbstractGetEntityAttrNode<T extends EntityId> extends Tb
var sourceKeys = List.copyOf(mappingsMap.keySet()); var sourceKeys = List.copyOf(mappingsMap.keySet());
withCallback(config.isTelemetry() ? getLatestTelemetryAsync(ctx, entityId, sourceKeys) : getAttributesAsync(ctx, entityId, sourceKeys), withCallback(config.isTelemetry() ? getLatestTelemetryAsync(ctx, entityId, sourceKeys) : getAttributesAsync(ctx, entityId, sourceKeys),
data -> putDataAndTell(ctx, msg, data, mappingsMap, msgDataAsJsonNode), data -> putDataAndTell(ctx, msg, data, mappingsMap, msgDataAsJsonNode),
t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); t -> ctx.tellFailure(msg, t),
MoreExecutors.directExecutor());
} }
private ListenableFuture<List<KvEntry>> getAttributesAsync(TbContext ctx, EntityId entityId, List<String> attrKeys) { private ListenableFuture<List<KvEntry>> getAttributesAsync(TbContext ctx, EntityId entityId, List<String> attrKeys) {
@ -74,7 +74,7 @@ public abstract class TbAbstractGetEntityAttrNode<T extends EntityId> extends Tb
l.stream() l.stream()
.map(i -> (KvEntry) i) .map(i -> (KvEntry) i)
.collect(Collectors.toList()), .collect(Collectors.toList()),
MoreExecutors.directExecutor()); ctx.getDbCallbackExecutor());
} }
private ListenableFuture<List<KvEntry>> getLatestTelemetryAsync(TbContext ctx, EntityId entityId, List<String> timeseriesKeys) { private ListenableFuture<List<KvEntry>> getLatestTelemetryAsync(TbContext ctx, EntityId entityId, List<String> timeseriesKeys) {
@ -83,7 +83,7 @@ public abstract class TbAbstractGetEntityAttrNode<T extends EntityId> extends Tb
l.stream() l.stream()
.map(i -> (KvEntry) i) .map(i -> (KvEntry) i)
.collect(Collectors.toList()), .collect(Collectors.toList()),
MoreExecutors.directExecutor()); ctx.getDbCallbackExecutor());
} }
private void putDataAndTell(TbContext ctx, TbMsg msg, List<? extends KvEntry> data, Map<String, String> map, ObjectNode msgData) { private void putDataAndTell(TbContext ctx, TbMsg msg, List<? extends KvEntry> data, Map<String, String> map, ObjectNode msgData) {

8
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java

@ -22,11 +22,14 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.util.EntityDetails;
import org.thingsboard.server.common.data.ContactBased; import org.thingsboard.server.common.data.ContactBased;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.util.List;
import static org.thingsboard.common.util.DonAsynchron.withCallback; import static org.thingsboard.common.util.DonAsynchron.withCallback;
@Slf4j @Slf4j
@ -34,7 +37,6 @@ public abstract class TbAbstractGetEntityDetailsNode<C extends TbAbstractGetEnti
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
ctx.checkTenantEntity(msg.getOriginator());
var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null; var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null;
withCallback(getDetails(ctx, msg, msgDataAsObjectNode), withCallback(getDetails(ctx, msg, msgDataAsObjectNode),
ctx::tellSuccess, ctx::tellSuccess,
@ -45,8 +47,8 @@ public abstract class TbAbstractGetEntityDetailsNode<C extends TbAbstractGetEnti
protected abstract ListenableFuture<? extends ContactBased<I>> getContactBasedFuture(TbContext ctx, TbMsg msg); protected abstract ListenableFuture<? extends ContactBased<I>> getContactBasedFuture(TbContext ctx, TbMsg msg);
protected void checkIfDetailsListIsNotEmptyOrThrow(C configuration) throws TbNodeException { protected void checkIfDetailsListIsNotEmptyOrElseThrow(List<EntityDetails> detailsList) throws TbNodeException {
if (configuration.getDetailsList().isEmpty()) { if (detailsList == null || detailsList.isEmpty()) {
throw new TbNodeException("No entity details selected!"); throw new TbNodeException("No entity details selected!");
} }
} }

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.rule.engine.metadata; package org.thingsboard.rule.engine.metadata;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
@ -60,7 +59,7 @@ public abstract class TbAbstractNodeWithFetchTo<C extends TbAbstractFetchToNodeC
} }
protected ObjectNode getMsgDataAsObjectNode(TbMsg msg) { protected ObjectNode getMsgDataAsObjectNode(TbMsg msg) {
JsonNode msgDataNode = JacksonUtil.toJsonNode(msg.getData()); var msgDataNode = JacksonUtil.toJsonNode(msg.getData());
if (msgDataNode == null || !msgDataNode.isObject()) { if (msgDataNode == null || !msgDataNode.isObject()) {
throw new IllegalArgumentException("Message body is not an object!"); throw new IllegalArgumentException("Message body is not an object!");
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.rule.engine.metadata; package org.thingsboard.rule.engine.metadata;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
@ -56,7 +55,6 @@ public class TbFetchDeviceCredentialsNode extends TbAbstractNodeWithFetchTo<TbFe
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
var originator = msg.getOriginator(); var originator = msg.getOriginator();
ctx.checkTenantEntity(originator);
var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null; var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null;
if (!EntityType.DEVICE.equals(originator.getEntityType())) { if (!EntityType.DEVICE.equals(originator.getEntityType())) {
ctx.tellFailure(msg, new RuntimeException("Unsupported originator type: " + originator.getEntityType() + "!")); ctx.tellFailure(msg, new RuntimeException("Unsupported originator type: " + originator.getEntityType() + "!"));

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeConfiguration.java

@ -24,10 +24,12 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class TbFetchDeviceCredentialsNodeConfiguration extends TbAbstractFetchToNodeConfiguration implements NodeConfiguration<TbFetchDeviceCredentialsNodeConfiguration> { public class TbFetchDeviceCredentialsNodeConfiguration extends TbAbstractFetchToNodeConfiguration implements NodeConfiguration<TbFetchDeviceCredentialsNodeConfiguration> {
@Override @Override
public TbFetchDeviceCredentialsNodeConfiguration defaultConfiguration() { public TbFetchDeviceCredentialsNodeConfiguration defaultConfiguration() {
var configuration = new TbFetchDeviceCredentialsNodeConfiguration(); var configuration = new TbFetchDeviceCredentialsNodeConfiguration();
configuration.setFetchTo(FetchTo.METADATA); configuration.setFetchTo(FetchTo.METADATA);
return configuration; return configuration;
} }
} }

2
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"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") configDirective = "tbEnrichmentNodeOriginatorAttributesConfig")
public class TbGetAttributesNode extends TbAbstractGetAttributesNode<TbGetAttributesNodeConfiguration, EntityId> { public class TbGetAttributesNode extends TbAbstractGetAttributesNode<TbGetAttributesNodeConfiguration, EntityId> {
@Override @Override
protected TbGetAttributesNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { protected TbGetAttributesNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException {
return TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class); return TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class);
@ -51,4 +52,5 @@ public class TbGetAttributesNode extends TbAbstractGetAttributesNode<TbGetAttrib
protected ListenableFuture<EntityId> findEntityIdAsync(TbContext ctx, TbMsg msg) { protected ListenableFuture<EntityId> findEntityIdAsync(TbContext ctx, TbMsg msg) {
return Futures.immediateFuture(msg.getOriginator()); return Futures.immediateFuture(msg.getOriginator());
} }
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java

@ -45,7 +45,7 @@ public class TbGetCustomerAttributeNode extends TbAbstractGetEntityAttrNode<Cust
@Override @Override
protected TbGetEntityAttrNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { protected TbGetEntityAttrNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException {
var config = TbNodeUtils.convert(configuration, TbGetEntityAttrNodeConfiguration.class); var config = TbNodeUtils.convert(configuration, TbGetEntityAttrNodeConfiguration.class);
checkIfMappingIsNotEmptyOrThrow(config); checkIfMappingIsNotEmptyOrElseThrow(config.getAttrMapping());
return config; return config;
} }

18
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java

@ -17,14 +17,12 @@ package org.thingsboard.rule.engine.metadata;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.ContactBased;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
@ -57,7 +55,7 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode<TbG
@Override @Override
protected TbGetCustomerDetailsNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { protected TbGetCustomerDetailsNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException {
var config = TbNodeUtils.convert(configuration, TbGetCustomerDetailsNodeConfiguration.class); var config = TbNodeUtils.convert(configuration, TbGetCustomerDetailsNodeConfiguration.class);
checkIfDetailsListIsNotEmptyOrThrow(config); checkIfDetailsListIsNotEmptyOrElseThrow(config.getDetailsList());
return config; return config;
} }
@ -67,23 +65,23 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode<TbG
} }
@Override @Override
protected ListenableFuture<? extends ContactBased<CustomerId>> getContactBasedFuture(TbContext ctx, TbMsg msg) { protected ListenableFuture<Customer> getContactBasedFuture(TbContext ctx, TbMsg msg) {
switch (msg.getOriginator().getEntityType()) { switch (msg.getOriginator().getEntityType()) {
case DEVICE: case DEVICE:
return Futures.transformAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId())), return Futures.transformAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId())),
device -> getCustomerFuture(ctx, device, msg.getOriginator()), MoreExecutors.directExecutor()); device -> getCustomerFuture(ctx, device, msg.getOriginator()), ctx.getDbCallbackExecutor());
case ASSET: case ASSET:
return Futures.transformAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), new AssetId(msg.getOriginator().getId())), return Futures.transformAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), new AssetId(msg.getOriginator().getId())),
asset -> getCustomerFuture(ctx, asset, msg.getOriginator()), MoreExecutors.directExecutor()); asset -> getCustomerFuture(ctx, asset, msg.getOriginator()), ctx.getDbCallbackExecutor());
case ENTITY_VIEW: case ENTITY_VIEW:
return Futures.transformAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), new EntityViewId(msg.getOriginator().getId())), return Futures.transformAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), new EntityViewId(msg.getOriginator().getId())),
entityView -> getCustomerFuture(ctx, entityView, msg.getOriginator()), MoreExecutors.directExecutor()); entityView -> getCustomerFuture(ctx, entityView, msg.getOriginator()), ctx.getDbCallbackExecutor());
case USER: case USER:
return Futures.transformAsync(ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), new UserId(msg.getOriginator().getId())), return Futures.transformAsync(ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), new UserId(msg.getOriginator().getId())),
user -> getCustomerFuture(ctx, user, msg.getOriginator()), MoreExecutors.directExecutor()); user -> getCustomerFuture(ctx, user, msg.getOriginator()), ctx.getDbCallbackExecutor());
case EDGE: case EDGE:
return Futures.transformAsync(ctx.getEdgeService().findEdgeByIdAsync(ctx.getTenantId(), new EdgeId(msg.getOriginator().getId())), return Futures.transformAsync(ctx.getEdgeService().findEdgeByIdAsync(ctx.getTenantId(), new EdgeId(msg.getOriginator().getId())),
edge -> getCustomerFuture(ctx, edge, msg.getOriginator()), MoreExecutors.directExecutor()); edge -> getCustomerFuture(ctx, edge, msg.getOriginator()), ctx.getDbCallbackExecutor());
default: default:
return Futures.immediateFailedFuture(new NoSuchElementException("Entity with entityType '" + msg.getOriginator().getEntityType() + "' is not supported.")); return Futures.immediateFailedFuture(new NoSuchElementException("Entity with entityType '" + msg.getOriginator().getEntityType() + "' is not supported."));
} }
@ -93,7 +91,7 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode<TbG
if (hasCustomerId == null) { if (hasCustomerId == null) {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} else { } else {
if (hasCustomerId.getCustomerId().isNullUid()) { if (hasCustomerId.getCustomerId() == null || hasCustomerId.getCustomerId().isNullUid()) {
if (hasCustomerId instanceof HasName) { if (hasCustomerId instanceof HasName) {
var hasName = (HasName) hasCustomerId; var hasName = (HasName) hasCustomerId;
throw new RuntimeException(originator.getEntityType().getNormalName() + " with name '" + hasName.getName() + "' is not assigned to Customer."); throw new RuntimeException(originator.getEntityType().getNormalName() + " with name '" + hasName.getName() + "' is not assigned to Customer.");

8
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java

@ -49,16 +49,15 @@ public class TbGetOriginatorFieldsNode extends TbAbstractNodeWithFetchTo<TbGetOr
@Override @Override
protected TbGetOriginatorFieldsConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { protected TbGetOriginatorFieldsConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException {
var getOriginatorFieldsConfiguration = TbNodeUtils.convert(configuration, TbGetOriginatorFieldsConfiguration.class); var config = TbNodeUtils.convert(configuration, TbGetOriginatorFieldsConfiguration.class);
if (config.getFieldsMapping().isEmpty()) { if (config.getFieldsMapping() == null || config.getFieldsMapping().isEmpty()) {
throw new TbNodeException("At least one field mapping should be specified!"); throw new TbNodeException("At least one field mapping should be specified!");
} }
return getOriginatorFieldsConfiguration; return config;
} }
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
ctx.checkTenantEntity(msg.getOriginator());
var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null; var msgDataAsObjectNode = FetchTo.DATA.equals(fetchTo) ? getMsgDataAsObjectNode(msg) : null;
withCallback(collectMappedEntityFieldsAsync(ctx, msg.getOriginator()), withCallback(collectMappedEntityFieldsAsync(ctx, msg.getOriginator()),
targetKeysToSourceValuesMap -> { targetKeysToSourceValuesMap -> {
@ -95,4 +94,5 @@ public class TbGetOriginatorFieldsNode extends TbAbstractNodeWithFetchTo<TbGetOr
}, ctx.getDbCallbackExecutor() }, ctx.getDbCallbackExecutor()
); );
} }
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java

@ -46,7 +46,7 @@ public class TbGetRelatedAttributeNode extends TbAbstractGetEntityAttrNode<Entit
@Override @Override
public TbGetRelatedAttrNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { public TbGetRelatedAttrNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException {
var config = TbNodeUtils.convert(configuration, TbGetRelatedAttrNodeConfiguration.class); var config = TbNodeUtils.convert(configuration, TbGetRelatedAttrNodeConfiguration.class);
checkIfMappingIsNotEmptyOrThrow(config); checkIfMappingIsNotEmptyOrElseThrow(config.getAttrMapping());
return config; return config;
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java

@ -44,7 +44,7 @@ public class TbGetTenantAttributeNode extends TbAbstractGetEntityAttrNode<Tenant
@Override @Override
public TbGetEntityAttrNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { public TbGetEntityAttrNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException {
var config = TbNodeUtils.convert(configuration, TbGetEntityAttrNodeConfiguration.class); var config = TbNodeUtils.convert(configuration, TbGetEntityAttrNodeConfiguration.class);
checkIfMappingIsNotEmptyOrThrow(config); checkIfMappingIsNotEmptyOrElseThrow(config.getAttrMapping());
return config; return config;
} }

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java

@ -22,7 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.ContactBased; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -44,7 +44,7 @@ public class TbGetTenantDetailsNode extends TbAbstractGetEntityDetailsNode<TbGet
@Override @Override
protected TbGetTenantDetailsNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { protected TbGetTenantDetailsNodeConfiguration loadNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException {
var config = TbNodeUtils.convert(configuration, TbGetTenantDetailsNodeConfiguration.class); var config = TbNodeUtils.convert(configuration, TbGetTenantDetailsNodeConfiguration.class);
checkIfDetailsListIsNotEmptyOrThrow(config); checkIfDetailsListIsNotEmptyOrElseThrow(config.getDetailsList());
return config; return config;
} }
@ -54,7 +54,7 @@ public class TbGetTenantDetailsNode extends TbAbstractGetEntityDetailsNode<TbGet
} }
@Override @Override
protected ListenableFuture<? extends ContactBased<TenantId>> getContactBasedFuture(TbContext ctx, TbMsg msg) { protected ListenableFuture<Tenant> getContactBasedFuture(TbContext ctx, TbMsg msg) {
return ctx.getTenantService().findTenantByIdAsync(ctx.getTenantId(), ctx.getTenantId()); return ctx.getTenantService().findTenantByIdAsync(ctx.getTenantId(), ctx.getTenantId());
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeConfiguration.java

@ -27,7 +27,7 @@ public class TbGetTenantDetailsNodeConfiguration extends TbAbstractGetEntityDeta
@Override @Override
public TbGetTenantDetailsNodeConfiguration defaultConfiguration() { public TbGetTenantDetailsNodeConfiguration defaultConfiguration() {
TbGetTenantDetailsNodeConfiguration configuration = new TbGetTenantDetailsNodeConfiguration(); var configuration = new TbGetTenantDetailsNodeConfiguration();
configuration.setDetailsList(Collections.emptyList()); configuration.setDetailsList(Collections.emptyList());
configuration.setFetchTo(FetchTo.DATA); configuration.setFetchTo(FetchTo.DATA);
return configuration; return configuration;

20
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java

@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.util;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
@ -26,20 +25,19 @@ import org.thingsboard.server.common.data.id.EntityId;
public class EntitiesAlarmOriginatorIdAsyncLoader { public class EntitiesAlarmOriginatorIdAsyncLoader {
public static ListenableFuture<EntityId> findEntityIdAsync(TbContext ctx, EntityId original) { public static ListenableFuture<EntityId> findEntityIdAsync(TbContext ctx, EntityId originator) {
switch (originator.getEntityType()) {
switch (original.getEntityType()) {
case ALARM: case ALARM:
return getAlarmOriginatorAsync(ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), (AlarmId) original)); return getAlarmOriginatorAsync(ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), (AlarmId) originator), ctx);
default: default:
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType())); return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType " + originator.getEntityType()));
} }
} }
private static ListenableFuture<EntityId> getAlarmOriginatorAsync(ListenableFuture<Alarm> future) { private static ListenableFuture<EntityId> getAlarmOriginatorAsync(ListenableFuture<Alarm> future, TbContext ctx) {
return Futures.transformAsync(future, in -> { return Futures.transformAsync(future, in -> in != null ?
return in != null ? Futures.immediateFuture(in.getOriginator()) Futures.immediateFuture(in.getOriginator())
: Futures.immediateFuture(null); : Futures.immediateFuture(null), ctx.getDbCallbackExecutor());
}, MoreExecutors.directExecutor());
} }
} }

20
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java

@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.util;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasCustomerId;
@ -29,23 +28,24 @@ import org.thingsboard.server.common.data.id.UserId;
public class EntitiesCustomerIdAsyncLoader { public class EntitiesCustomerIdAsyncLoader {
public static ListenableFuture<CustomerId> findEntityIdAsync(TbContext ctx, EntityId original) { public static ListenableFuture<CustomerId> findEntityIdAsync(TbContext ctx, EntityId originator) {
switch (original.getEntityType()) { switch (originator.getEntityType()) {
case CUSTOMER: case CUSTOMER:
return Futures.immediateFuture((CustomerId) original); return Futures.immediateFuture((CustomerId) originator);
case USER: case USER:
return getCustomerAsync(ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), (UserId) original)); return toCustomerIdAsync(ctx, ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), (UserId) originator));
case ASSET: case ASSET:
return getCustomerAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), (AssetId) original)); return toCustomerIdAsync(ctx, ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), (AssetId) originator));
case DEVICE: case DEVICE:
return getCustomerAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), (DeviceId) original)); return toCustomerIdAsync(ctx, ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), (DeviceId) originator));
default: default:
return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType())); return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType: " + originator.getEntityType()));
} }
} }
private static <T extends HasCustomerId> ListenableFuture<CustomerId> getCustomerAsync(ListenableFuture<T> future) { private static <T extends HasCustomerId> ListenableFuture<CustomerId> toCustomerIdAsync(TbContext ctx, ListenableFuture<T> future) {
return Futures.transformAsync(future, in -> in != null ? Futures.immediateFuture(in.getCustomerId()) return Futures.transformAsync(future, in -> in != null ? Futures.immediateFuture(in.getCustomerId())
: Futures.immediateFuture(null), MoreExecutors.directExecutor()); : Futures.immediateFuture(null), ctx.getDbCallbackExecutor());
} }
} }

37
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java

@ -17,39 +17,42 @@ package org.thingsboard.rule.engine.util;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.data.DeviceRelationsQuery; 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.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.device.DeviceService;
import java.util.List;
public class EntitiesRelatedDeviceIdAsyncLoader { public class EntitiesRelatedDeviceIdAsyncLoader {
public static ListenableFuture<DeviceId> findDeviceAsync(TbContext ctx, EntityId originator, public static ListenableFuture<DeviceId> findDeviceAsync(
DeviceRelationsQuery deviceRelationsQuery) { TbContext ctx,
DeviceService deviceService = ctx.getDeviceService(); EntityId originator,
DeviceSearchQuery query = buildQuery(originator, deviceRelationsQuery); DeviceRelationsQuery deviceRelationsQuery
) {
ListenableFuture<List<Device>> asyncDevices = deviceService.findDevicesByQuery(ctx.getTenantId(), query); var deviceService = ctx.getDeviceService();
var query = buildQuery(originator, deviceRelationsQuery);
return Futures.transformAsync(asyncDevices, d -> CollectionUtils.isNotEmpty(d) ? Futures.immediateFuture(d.get(0).getId()) var devicesListFuture = deviceService.findDevicesByQuery(ctx.getTenantId(), query);
: Futures.immediateFuture(null), MoreExecutors.directExecutor()); return Futures.transformAsync(devicesListFuture,
deviceList -> CollectionUtils.isNotEmpty(deviceList) ?
Futures.immediateFuture(deviceList.get(0).getId())
: Futures.immediateFuture(null), ctx.getDbCallbackExecutor());
} }
private static DeviceSearchQuery buildQuery(EntityId originator, DeviceRelationsQuery deviceRelationsQuery) { private static DeviceSearchQuery buildQuery(EntityId originator, DeviceRelationsQuery deviceRelationsQuery) {
DeviceSearchQuery query = new DeviceSearchQuery(); var query = new DeviceSearchQuery();
RelationsSearchParameters parameters = new RelationsSearchParameters(originator, var parameters = new RelationsSearchParameters(
deviceRelationsQuery.getDirection(), deviceRelationsQuery.getMaxLevel(), deviceRelationsQuery.isFetchLastLevelOnly()); originator,
deviceRelationsQuery.getDirection(),
deviceRelationsQuery.getMaxLevel(),
deviceRelationsQuery.isFetchLastLevelOnly()
);
query.setParameters(parameters); query.setParameters(parameters);
query.setRelationType(deviceRelationsQuery.getRelationType()); query.setRelationType(deviceRelationsQuery.getRelationType());
query.setDeviceTypes(deviceRelationsQuery.getDeviceTypes()); query.setDeviceTypes(deviceRelationsQuery.getDeviceTypes());
return query; return query;
} }
} }

41
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java

@ -17,42 +17,49 @@ package org.thingsboard.rule.engine.util;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.rule.engine.data.RelationsQuery;
import org.thingsboard.server.common.data.id.EntityId; 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.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.relation.RelationService;
import java.util.List;
public class EntitiesRelatedEntityIdAsyncLoader { public class EntitiesRelatedEntityIdAsyncLoader {
public static ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator, public static ListenableFuture<EntityId> findEntityAsync(
RelationsQuery relationsQuery) { TbContext ctx,
RelationService relationService = ctx.getRelationService(); EntityId originator,
EntityRelationsQuery query = buildQuery(originator, relationsQuery); RelationsQuery relationsQuery
ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByQuery(ctx.getTenantId(), query); ) {
var relationService = ctx.getRelationService();
var query = buildQuery(originator, relationsQuery);
var relationListFuture = relationService.findByQuery(ctx.getTenantId(), query);
if (relationsQuery.getDirection() == EntitySearchDirection.FROM) { if (relationsQuery.getDirection() == EntitySearchDirection.FROM) {
return Futures.transformAsync(asyncRelation, r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) return Futures.transformAsync(relationListFuture,
: Futures.immediateFuture(null), MoreExecutors.directExecutor()); relationList -> CollectionUtils.isNotEmpty(relationList) ?
Futures.immediateFuture(relationList.get(0).getTo())
: Futures.immediateFuture(null), ctx.getDbCallbackExecutor());
} else if (relationsQuery.getDirection() == EntitySearchDirection.TO) { } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) {
return Futures.transformAsync(asyncRelation, r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) return Futures.transformAsync(relationListFuture,
: Futures.immediateFuture(null), MoreExecutors.directExecutor()); relationList -> CollectionUtils.isNotEmpty(relationList) ?
Futures.immediateFuture(relationList.get(0).getFrom())
: Futures.immediateFuture(null), ctx.getDbCallbackExecutor());
} }
return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction"));
} }
private static EntityRelationsQuery buildQuery(EntityId originator, RelationsQuery relationsQuery) { private static EntityRelationsQuery buildQuery(EntityId originator, RelationsQuery relationsQuery) {
EntityRelationsQuery query = new EntityRelationsQuery(); var query = new EntityRelationsQuery();
RelationsSearchParameters parameters = new RelationsSearchParameters(originator, var parameters = new RelationsSearchParameters(
relationsQuery.getDirection(), relationsQuery.getMaxLevel(), relationsQuery.isFetchLastLevelOnly()); originator,
relationsQuery.getDirection(),
relationsQuery.getMaxLevel(),
relationsQuery.isFetchLastLevelOnly()
);
query.setParameters(parameters); query.setParameters(parameters);
query.setFilters(relationsQuery.getFilters()); query.setFilters(relationsQuery.getFilters());
return query; return query;
} }
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityContainer.java

@ -25,4 +25,4 @@ public class EntityContainer {
private EntityId entityId; private EntityId entityId;
private EntityType entityType; private EntityType entityType;
} }

452
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java

@ -0,0 +1,452 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.metadata;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class CalculateDeltaNodeTest {
private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID());
private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID());
private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() {
@Override
public <T> ListenableFuture<T> executeAsync(Callable<T> task) {
try {
return Futures.immediateFuture(task.call());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void execute(@NotNull Runnable command) {
command.run();
}
};
@Mock
private TbContext ctxMock;
@Mock
private TimeseriesService timeseriesServiceMock;
private CalculateDeltaNode node;
private CalculateDeltaNodeConfiguration config;
private TbNodeConfiguration nodeConfiguration;
@BeforeEach
public void setUp() throws TbNodeException {
node = new CalculateDeltaNode();
config = new CalculateDeltaNodeConfiguration().defaultConfiguration();
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock);
node.init(ctxMock, nodeConfiguration);
}
@Test
public void givenDefaultConfig_whenDefaultConfiguration_thenVerify() {
assertEquals(config.getInputValueKey(), "pulseCounter");
assertEquals(config.getOutputValueKey(), "delta");
assertTrue(config.isUseCache());
assertFalse(config.isAddPeriodBetweenMsgs());
assertEquals(config.getPeriodValueKey(), "periodInMs");
assertTrue(config.isTellFailureIfDeltaIsNegative());
}
@Test
public void givenInvalidMsgType_whenOnMsg_thenShouldTellNextOther() {
// GIVEN
var msgData = "{\"pulseCounter\": 42}";
var msg = TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other"));
verify(ctxMock, never()).tellSuccess(any());
verify(ctxMock, never()).tellFailure(any(), any());
}
@Test
public void givenInputKeyIsNotPresent_whenOnMsg_thenShouldTellNextOther() {
// GIVEN
var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}");
// WHEN
node.onMsg(ctxMock, msg);
// THEN
verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other"));
verify(ctxMock, never()).tellSuccess(any());
verify(ctxMock, never()).tellFailure(any(), any());
}
@Test
public void givenDoubleValue_whenOnMsg_thenShouldTellSuccess() throws TbNodeException {
// GIVEN
config.setRound(1);
config.setInputValueKey("temperature");
config.setOutputValueKey("temp_delta");
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
node.init(ctxMock, nodeConfiguration);
mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.5)));
var msgData = "{\"temperature\": 42,\"airPressure\":123}";
var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture());
verify(ctxMock, never()).tellNext(any(), anyString());
verify(ctxMock, never()).tellNext(any(), anySet());
verify(ctxMock, never()).tellFailure(any(), any());
var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":1.5}";
assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData());
}
@Test
public void givenLongStringValue_whenOnMsg_thenShouldTellSuccess() throws TbNodeException {
// GIVEN
config.setInputValueKey("temperature");
config.setOutputValueKey("temp_delta");
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
node.init(ctxMock, nodeConfiguration);
mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 40L)));
var msgData = "{\"temperature\": 42,\"airPressure\":123}";
var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture());
verify(ctxMock, never()).tellNext(any(), anyString());
verify(ctxMock, never()).tellNext(any(), anySet());
verify(ctxMock, never()).tellFailure(any(), any());
var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2}";
assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData());
}
@Test
public void givenValidStringValue_whenOnMsg_thenShouldTellSuccess() throws TbNodeException {
// GIVEN
config.setInputValueKey("temperature");
config.setOutputValueKey("temp_delta");
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
node.init(ctxMock, nodeConfiguration);
mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("temperature", "40.0")));
var msgData = "{\"temperature\": 42,\"airPressure\":123}";
var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture());
verify(ctxMock, never()).tellNext(any(), anyString());
verify(ctxMock, never()).tellNext(any(), anySet());
verify(ctxMock, never()).tellFailure(any(), any());
var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2}";
assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData());
}
@Test
public void givenTwoMessagesAndPeriodOnAndCachingOn_whenOnMsg_thenVerify() throws TbNodeException {
// STAGE 1
// GIVEN
config.setInputValueKey("temperature");
config.setOutputValueKey("temp_delta");
config.setPeriodValueKey("ts_delta");
config.setAddPeriodBetweenMsgs(true);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
node.init(ctxMock, nodeConfiguration);
mockFindLatest(new BasicTsKvEntry(1L, new DoubleDataEntry("temperature", 40.0)));
var msgData = "{\"temperature\": 42,\"airPressure\":123}";
var firstMsgMetaData = new TbMsgMetaData();
firstMsgMetaData.putValue("ts", String.valueOf(3L));
var firstMsg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData);
// WHEN
node.onMsg(ctxMock, firstMsg);
// THEN
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture());
verify(ctxMock, never()).tellNext(any(), anyString());
verify(ctxMock, never()).tellNext(any(), anySet());
verify(ctxMock, never()).tellFailure(any(), any());
var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":2,\"ts_delta\":2}";
assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData());
// STAGE 2
// GIVEN
reset(ctxMock);
reset(timeseriesServiceMock);
var secondMsgMetaData = new TbMsgMetaData();
secondMsgMetaData.putValue("ts", String.valueOf(6L));
var secondMsg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData);
// WHEN
node.onMsg(ctxMock, secondMsg);
// THEN
actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(timeseriesServiceMock, never()).findLatest(any(), any(), anyList());
verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture());
verify(ctxMock, never()).tellNext(any(), anyString());
verify(ctxMock, never()).tellNext(any(), anySet());
verify(ctxMock, never()).tellFailure(any(), any());
expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":0,\"ts_delta\":3}";
assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData());
}
@Test
public void givenLastValueIsNull_whenOnMsh_thenDeltaShouldBeZero() throws TbNodeException {
// GIVEN
config.setInputValueKey("temperature");
config.setOutputValueKey("temp_delta");
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
node.init(ctxMock, nodeConfiguration);
mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", null)));
var msgData = "{\"temperature\": 42,\"airPressure\":123}";
var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture());
verify(ctxMock, never()).tellNext(any(), anyString());
verify(ctxMock, never()).tellNext(any(), anySet());
verify(ctxMock, never()).tellFailure(any(), any());
var expectedMsgData = "{\"temperature\":42,\"airPressure\":123,\"temp_delta\":0}";
assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData());
}
@Test
public void givenNegativeDeltaAndTellFailureIfNegativeDeltaTrue_whenOnMsg_thenShouldTellFailure() throws TbNodeException {
// GIVEN
config.setTellFailureIfDeltaIsNegative(true);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
node.init(ctxMock, nodeConfiguration);
mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("pulseCounter", 200L)));
var msgData = "{\"pulseCounter\":\"123\"}";
var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
verify(ctxMock, times(1)).tellNext(msg, "Failure");
verify(ctxMock, never()).tellSuccess(any());
verify(ctxMock, never()).tellFailure(any(), any());
verify(ctxMock, never()).tellNext(any(), anySet());
}
@Test
public void givenInvalidStringValue_whenOnMsg_thenException() {
// GIVEN
mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("pulseCounter", "high")));
var msgData = "{\"pulseCounter\":\"123\"}";
var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class);
verify(ctxMock, times(1)).tellFailure(actualMsgCaptor.capture(), actualExceptionCaptor.capture());
verify(ctxMock, never()).tellSuccess(any());
verify(ctxMock, never()).tellNext(any(), anyString());
verify(ctxMock, never()).tellNext(any(), anySet());
var expectedExceptionMsg = "Calculation failed. Unable to parse value [high] of telemetry [pulseCounter] to Double";
var actualException = actualExceptionCaptor.getValue();
assertEquals(msg, actualMsgCaptor.getValue());
assertInstanceOf(IllegalArgumentException.class, actualException);
assertEquals(expectedExceptionMsg, actualException.getMessage());
}
@Test
public void givenBooleanValue_whenOnMsg_thenException() {
// GIVEN
mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry("pulseCounter", false)));
var msgData = "{\"pulseCounter\":true}";
var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class);
verify(ctxMock, times(1)).tellFailure(actualMsgCaptor.capture(), actualExceptionCaptor.capture());
verify(ctxMock, never()).tellSuccess(any());
verify(ctxMock, never()).tellNext(any(), anyString());
verify(ctxMock, never()).tellNext(any(), anySet());
var expectedExceptionMsg = "Calculation failed. Boolean values are not supported!";
var actualException = actualExceptionCaptor.getValue();
assertEquals(msg, actualMsgCaptor.getValue());
assertInstanceOf(IllegalArgumentException.class, actualException);
assertEquals(expectedExceptionMsg, actualException.getMessage());
}
@Test
public void givenJsonValue_whenOnMsg_thenException() {
// GIVEN
mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new JsonDataEntry("pulseCounter", "{\"isActive\":false}")));
var msgData = "{\"pulseCounter\":{\"isActive\":true}}";
var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class);
verify(ctxMock, times(1)).tellFailure(actualMsgCaptor.capture(), actualExceptionCaptor.capture());
verify(ctxMock, never()).tellSuccess(any());
verify(ctxMock, never()).tellNext(any(), anyString());
verify(ctxMock, never()).tellNext(any(), anySet());
var expectedExceptionMsg = "Calculation failed. JSON values are not supported!";
var actualException = actualExceptionCaptor.getValue();
assertEquals(msg, actualMsgCaptor.getValue());
assertInstanceOf(IllegalArgumentException.class, actualException);
assertEquals(expectedExceptionMsg, actualException.getMessage());
}
private void mockFindLatest(TsKvEntry tsKvEntry) {
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(timeseriesServiceMock.findLatest(
eq(TENANT_ID), eq(DUMMY_DEVICE_ORIGINATOR), argThat(new ListMatcher<>(List.of(tsKvEntry.getKey())))
)).thenReturn(Futures.immediateFuture(List.of(tsKvEntry)));
}
@RequiredArgsConstructor
private static class ListMatcher<T> implements ArgumentMatcher<List<T>> {
private final List<T> expectedList;
@Override
public boolean matches(List<T> actualList) {
if (actualList == expectedList) {
return true;
}
if (actualList.size() != expectedList.size()) {
return false;
}
return actualList.containsAll(expectedList);
}
}
}

238
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractAttributeNodeTest.java

@ -1,238 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.metadata;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@RunWith(MockitoJUnitRunner.class)
public abstract class TbAbstractAttributeNodeTest {
final CustomerId customerId = new CustomerId(Uuids.timeBased());
final TenantId tenantId = TenantId.fromUUID(Uuids.timeBased());
final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased());
final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased());
final String keyAttrConf = "${word}";
final String valueAttrConf = "${result}";
@Mock
TbContext ctx;
@Mock
AttributesService attributesService;
@Mock
TimeseriesService timeseriesService;
@Mock
UserService userService;
@Mock
AssetService assetService;
@Mock
DeviceService deviceService;
TbMsg msg;
Map<String, String> metaData;
TbAbstractGetEntityAttrNode node;
void init(TbAbstractGetEntityAttrNode node) throws TbNodeException {
ObjectMapper mapper = JacksonUtil.OBJECT_MAPPER;
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(getTbNodeConfig()));
metaData = new HashMap<>();
metaData.putIfAbsent("word", "temperature");
metaData.putIfAbsent("result", "answer");
this.node = node;
this.node.init(null, nodeConfiguration);
}
void errorThrownIfCannotLoadAttributes(User user) {
msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
when(ctx.getAttributesService()).thenReturn(attributesService);
when(attributesService.find(any(), eq(getEntityId()), eq(SERVER_SCOPE), anyCollection()))
.thenThrow(new IllegalStateException("something wrong"));
node.onMsg(ctx, msg);
final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
verify(ctx).tellFailure(same(msg), captor.capture());
Throwable value = captor.getValue();
assertEquals("something wrong", value.getMessage());
assertTrue(msg.getMetaData().getData().isEmpty());
}
void errorThrownIfCannotLoadAttributesAsync(User user) {
msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
when(ctx.getAttributesService()).thenReturn(attributesService);
when(attributesService.find(any(), eq(getEntityId()), eq(SERVER_SCOPE), anyCollection()))
.thenReturn(Futures.immediateFailedFuture(new IllegalStateException("something wrong")));
node.onMsg(ctx, msg);
final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
verify(ctx).tellFailure(same(msg), captor.capture());
Throwable value = captor.getValue();
assertEquals("something wrong", value.getMessage());
assertTrue(msg.getMetaData().getData().isEmpty());
}
void failedChainUsedIfCustomerCannotBeFound(User user) {
msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
node.onMsg(ctx, msg);
var exceptionCaptor = ArgumentCaptor.forClass(NoSuchElementException.class);
verify(ctx).tellFailure(eq(msg), exceptionCaptor.capture());
assertThat(exceptionCaptor.getValue().getMessage()).contains("Did not find entity! Msg ID: ");
assertTrue(msg.getMetaData().getData().isEmpty());
}
void entityAttributeAddedInMetadata(EntityId entityId, String type) {
msg = TbMsg.newMsg(type, entityId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
entityAttributeFetched(getEntityId());
}
void usersCustomerAttributesFetched(User user) {
msg = TbMsg.newMsg("USER", user.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
entityAttributeFetched(getEntityId());
}
void assetsCustomerAttributesFetched(Asset asset) {
msg = TbMsg.newMsg("ASSET", asset.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
entityAttributeFetched(getEntityId());
}
void deviceCustomerAttributesFetched(Device device) {
msg = TbMsg.newMsg("DEVICE", device.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
entityAttributeFetched(getEntityId());
}
void deviceCustomerTelemetryFetched(Device device) throws TbNodeException {
ObjectMapper mapper = JacksonUtil.OBJECT_MAPPER;
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(getTbNodeConfigForTelemetry()));
TbAbstractGetEntityAttrNode node = getEmptyNode();
node.init(null, nodeConfiguration);
msg = TbMsg.newMsg("DEVICE", device.getId(), new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
List<TsKvEntry> timeseries = Lists.newArrayList(new BasicTsKvEntry(1L, new StringDataEntry("temperature", "highest")));
when(ctx.getTimeseriesService()).thenReturn(timeseriesService);
when(timeseriesService.findLatest(any(), eq(getEntityId()), anyCollection()))
.thenReturn(Futures.immediateFuture(timeseries));
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
assertEquals(msg.getMetaData().getValue("answer"), "highest");
}
void entityAttributeFetched(EntityId entityId) {
List<AttributeKvEntry> attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L));
when(ctx.getAttributesService()).thenReturn(attributesService);
when(attributesService.find(any(), eq(entityId), eq(SERVER_SCOPE), anyCollection()))
.thenReturn(Futures.immediateFuture(attributes));
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
assertEquals(msg.getMetaData().getValue("answer"), "high");
}
TbGetEntityAttrNodeConfiguration getTbNodeConfig() {
return getConfig(false);
}
TbGetEntityAttrNodeConfiguration getTbNodeConfigForTelemetry() {
return getConfig(true);
}
private TbGetEntityAttrNodeConfiguration getConfig(boolean isTelemetry) {
TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration();
Map<String, String> conf = new HashMap<>();
conf.put(keyAttrConf, valueAttrConf);
config.setAttrMapping(conf);
config.setTelemetry(isTelemetry);
config.setFetchTo(FetchTo.METADATA);
return config;
}
protected abstract TbAbstractGetEntityAttrNode getEmptyNode();
abstract EntityId getEntityId();
void mockFindDevice(Device device) {
when(ctx.getDeviceService()).thenReturn(deviceService);
when(deviceService.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));
}
void mockFindUser(User user) {
when(ctx.getUserService()).thenReturn(userService);
when(userService.findUserByIdAsync(any(), eq(user.getId()))).thenReturn(Futures.immediateFuture(user));
}
}

115
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java

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

273
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNodeTest.java → rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java

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

446
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java

@ -15,17 +15,23 @@
*/ */
package org.thingsboard.rule.engine.metadata; 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 com.google.common.util.concurrent.Futures;
import org.junit.Before; import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Test; import lombok.RequiredArgsConstructor;
import org.junit.runner.RunWith; 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.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.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.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; 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.Device;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
@ -33,150 +39,426 @@ import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; 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.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; 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.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.TbMsg; 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.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.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat; 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.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class)
public class TbGetCustomerAttributeNodeTest extends TbAbstractAttributeNodeTest { public class TbGetCustomerAttributeNodeTest {
User user = new User();
Asset asset = new Asset();
Device device = new Device();
@Before private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID());
public void initDataForTests() throws TbNodeException { private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID());
init(new TbGetCustomerAttributeNode()); private static final CustomerId CUSTOMER_ID = new CustomerId(UUID.randomUUID());
user.setCustomerId(customerId); private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() {
user.setId(new UserId(UUID.randomUUID())); @Override
public <T> ListenableFuture<T> executeAsync(Callable<T> task) {
try {
return Futures.immediateFuture(task.call());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
asset.setCustomerId(customerId); @Override
asset.setId(new AssetId(UUID.randomUUID())); 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); @BeforeEach
device.setId(new DeviceId(Uuids.timeBased())); public void setUp() {
} node = new TbGetCustomerAttributeNode();
config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration();
@Override nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
protected TbAbstractGetEntityAttrNode getEmptyNode() {
return new TbGetCustomerAttributeNode();
}
@Override
EntityId getEntityId() {
return customerId;
} }
@Test @Test
public void errorThrownIfFetchToIsNull() { public void givenConfigWithNullFetchTo_whenInit_thenException() {
var node = new TbGetCustomerAttributeNode(); // GIVEN
var config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration();
config.setFetchTo(null); config.setFetchTo(null);
var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); 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!"); // THEN
verify(ctx, never()).tellSuccess(any()); assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!");
verify(ctxMock, never()).tellSuccess(any());
} }
@Test @Test
public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException {
node.fetchTo = FetchTo.DATA; // GIVEN
node.config.setFetchTo(FetchTo.DATA);
msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new CustomerId(UUID.randomUUID()), new TbMsgMetaData(), "[]");
var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctx, msg)); // WHEN
node.init(ctxMock, nodeConfiguration);
assertThat(exception.getMessage()).isEqualTo("Message body is not an object!"); // THEN
verify(ctx, never()).tellSuccess(any()); 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 @Test
public void errorThrownIfCannotLoadAttributes() { public void givenCustomConfig_whenInit_thenOK() throws TbNodeException {
mockFindUser(user); // GIVEN
errorThrownIfCannotLoadAttributes(user); 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 @Test
public void errorThrownIfCannotLoadAttributesAsync() { public void givenEmptyAttributesMapping_whenInit_thenException() {
mockFindUser(user); // GIVEN
errorThrownIfCannotLoadAttributesAsync(user); var expectedExceptionMessage = "At least one attribute mapping should be specified!";
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 @Test
public void failedChainUsedIfCustomerCannotBeFound() { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() {
when(ctx.getUserService()).thenReturn(userService); // GIVEN
when(userService.findUserByIdAsync(any(), eq(user.getId()))).thenReturn(Futures.immediateFuture(null)); node.fetchTo = FetchTo.DATA;
failedChainUsedIfCustomerCannotBeFound(user); msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]");
// WHEN
var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg));
// THEN
assertThat(exception.getMessage()).isEqualTo("Message body is not an object!");
verify(ctxMock, never()).tellSuccess(any());
} }
@Test @Test
public void customerAttributeAddedInMetadata() { public void givenDidNotFindEntity_whenOnMsg_thenShouldTellFailure() {
entityAttributeAddedInMetadata(customerId, "CUSTOMER"); // GIVEN
var userId = new UserId(UUID.randomUUID());
msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", userId, new TbMsgMetaData(), "{}");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getUserService()).thenReturn(userServiceMock);
doReturn(Futures.immediateFuture(null)).when(userServiceMock).findUserByIdAsync(eq(TENANT_ID), eq(userId));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// 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 @Test
public void customerAttributeAddedInData() { public void givenFetchAttributesToData_whenOnMsg_thenShouldFetchAttributesToData() {
node.fetchTo = FetchTo.DATA; // GIVEN
node.config.setFetchTo(FetchTo.DATA); var device = new Device(new DeviceId(UUID.randomUUID()));
device.setCustomerId(CUSTOMER_ID);
prepareMsgAndConfig(FetchTo.DATA, false, device.getId());
List<AttributeKvEntry> attributesList = List.of(
new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L)
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
msg = TbMsg.newMsg("CUSTOMER", customerId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock);
doReturn(Futures.immediateFuture(device)).when(deviceServiceMock).findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()));
List<AttributeKvEntry> attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock);
when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(attributesList));
when(ctx.getAttributesService()).thenReturn(attributesService); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
when(attributesService.find(any(), eq(customerId), eq(SERVER_SCOPE), anyCollection()))
.thenReturn(Futures.immediateFuture(attributes));
node.onMsg(ctx, msg); // WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, times(1)).tellSuccess(actualMessageCaptor.capture());
var expectedMsgData = "{\"answer\":\"high\"}"; 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().getData()).isEqualTo(expectedMsgData);
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
} }
@Test @Test
public void usersCustomerAttributesFetched() { public void givenFetchAttributesToMetaData_whenOnMsg_thenShouldFetchAttributesToMetaData() {
mockFindUser(user); // GIVEN
usersCustomerAttributesFetched(user); var user = new User(new UserId(UUID.randomUUID()));
user.setCustomerId(CUSTOMER_ID);
prepareMsgAndConfig(FetchTo.METADATA, false, user.getId());
List<AttributeKvEntry> attributesList = List.of(
new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L)
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getUserService()).thenReturn(userServiceMock);
doReturn(Futures.immediateFuture(user)).when(userServiceMock).findUserByIdAsync(eq(TENANT_ID), eq(user.getId()));
when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock);
when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(attributesList));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
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 @Test
public void assetsCustomerAttributesFetched() { public void givenFetchTelemetryToData_whenOnMsg_thenShouldFetchTelemetryToData() {
mockFindAsset(asset); // GIVEN
assetsCustomerAttributesFetched(asset); var customer = new Customer(new CustomerId(UUID.randomUUID()));
prepareMsgAndConfig(FetchTo.DATA, true, customer.getId());
List<TsKvEntry> timeseriesList = List.of(
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3"))
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock);
when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(customer.getId()), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(timeseriesList));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
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 @Test
public void deviceCustomerAttributesFetched() { public void givenFetchTelemetryToMetaData_whenOnMsg_thenShouldFetchTelemetryToMetaData() {
mockFindDevice(device); // GIVEN
deviceCustomerAttributesFetched(device); var asset = new Asset(new AssetId(UUID.randomUUID()));
asset.setCustomerId(new CustomerId(UUID.randomUUID()));
prepareMsgAndConfig(FetchTo.METADATA, true, asset.getId());
List<TsKvEntry> timeseriesList = List.of(
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3"))
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getAssetService()).thenReturn(assetServiceMock);
doReturn(Futures.immediateFuture(asset)).when(assetServiceMock).findAssetByIdAsync(eq(TENANT_ID), eq(asset.getId()));
when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock);
when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(asset.getCustomerId()), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(timeseriesList));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
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 private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId originator) {
public void deviceCustomerTelemetryFetched() throws TbNodeException { config.setAttrMapping(Map.of(
mockFindDevice(device); "sourceKey1", "targetKey1",
deviceCustomerTelemetryFetched(device); "${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", originator, msgMetaData, msgData);
} }
@RequiredArgsConstructor
private static class ListMatcher<T> implements ArgumentMatcher<List<T>> {
private final List<T> expectedList;
@Override
public boolean matches(List<T> actualList) {
if (actualList == expectedList) {
return true;
}
if (actualList.size() != expectedList.size()) {
return false;
}
return actualList.containsAll(expectedList);
}
}
} }

469
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java

@ -0,0 +1,469 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.metadata;
import com.google.common.util.concurrent.Futures;
import 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.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.util.EntityDetails;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
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.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.user.UserService;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class TbGetCustomerDetailsNodeTest {
private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID());
private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID());
private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() {
@Override
public <T> ListenableFuture<T> executeAsync(Callable<T> 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 CustomerService customerServiceMock;
@Mock
private DeviceService deviceServiceMock;
@Mock
private AssetService assetServiceMock;
@Mock
private EntityViewService entityViewServiceMock;
@Mock
private UserService userServiceMock;
@Mock
private EdgeService edgeServiceMock;
private TbGetCustomerDetailsNode node;
private TbGetCustomerDetailsNodeConfiguration config;
private TbNodeConfiguration nodeConfiguration;
private TbMsg msg;
private Customer customer;
@BeforeEach
public void setUp() {
node = new TbGetCustomerDetailsNode();
config = new TbGetCustomerDetailsNodeConfiguration().defaultConfiguration();
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
customer = new Customer();
customer.setId(new CustomerId(UUID.randomUUID()));
customer.setTitle("Customer title");
customer.setCountry("Customer country");
customer.setCity("Customer city");
customer.setState("Customer state");
customer.setZip("123456");
customer.setAddress("Customer address 1");
customer.setAddress2("Customer address 2");
customer.setPhone("+123456789");
customer.setEmail("email@tenant.com");
customer.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":\"Customer description\"}"));
}
@Test
public void givenConfigWithNullFetchTo_whenInit_thenException() {
// GIVEN
config.setDetailsList(List.of(EntityDetails.ID));
config.setFetchTo(null);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
// WHEN
var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration));
// THEN
assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!");
verify(ctxMock, never()).tellSuccess(any());
}
@Test
public void givenDefaultConfig_whenInit_thenOK() {
assertThat(config.getDetailsList()).isEqualTo(Collections.emptyList());
assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA);
}
@Test
public void givenCustomConfig_whenInit_thenOK() throws TbNodeException {
// GIVEN
config.setDetailsList(List.of(EntityDetails.ID, EntityDetails.PHONE));
config.setFetchTo(FetchTo.METADATA);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
// WHEN
node.init(ctxMock, nodeConfiguration);
// THEN
assertThat(node.config).isEqualTo(config);
assertThat(config.getDetailsList()).isEqualTo(List.of(EntityDetails.ID, EntityDetails.PHONE));
assertThat(config.getFetchTo()).isEqualTo(FetchTo.METADATA);
assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA);
}
@Test
public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() {
// GIVEN
node.fetchTo = FetchTo.DATA;
msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]");
// WHEN
var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg));
// THEN
assertThat(exception.getMessage()).isEqualTo("Message body is not an object!");
verify(ctxMock, never()).tellSuccess(any());
}
@Test
public void givenAllEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchAllToData() {
// GIVEN
var device = new Device();
device.setId(new DeviceId(UUID.randomUUID()));
device.setCustomerId(customer.getId());
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.values()), device.getId());
when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock);
when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device));
mockFindCustomer();
when(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 = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"," +
"\"customer_id\":\"" + customer.getId() + "\"," +
"\"customer_title\":\"" + customer.getTitle() + "\"," +
"\"customer_country\":\"" + customer.getCountry() + "\"," +
"\"customer_city\":\"" + customer.getCity() + "\"," +
"\"customer_state\":\"" + customer.getState() + "\"," +
"\"customer_zip\":\"" + customer.getZip() + "\"," +
"\"customer_address\":\"" + customer.getAddress() + "\"," +
"\"customer_address2\":\"" + customer.getAddress2() + "\"," +
"\"customer_phone\":\"" + customer.getPhone() + "\"," +
"\"customer_email\":\"" + customer.getEmail() + "\"," +
"\"customer_additionalInfo\":\"" + customer.getAdditionalInfo().get("description").asText() + "\"}";
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData);
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
}
@Test
public void givenSomeEntityDetailsAndFetchToMetadata_whenOnMsg_thenShouldTellSuccessAndFetchSomeToMetaData() {
// GIVEN
var asset = new Asset();
asset.setId(new AssetId(UUID.randomUUID()));
asset.setCustomerId(customer.getId());
prepareMsgAndConfig(FetchTo.METADATA, List.of(EntityDetails.ID, EntityDetails.TITLE, EntityDetails.PHONE), asset.getId());
when(ctxMock.getAssetService()).thenReturn(assetServiceMock);
when(assetServiceMock.findAssetByIdAsync(eq(TENANT_ID), eq(asset.getId()))).thenReturn(Futures.immediateFuture(asset));
mockFindCustomer();
when(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(msg.getMetaData().getData());
expectedMsgMetaData.putValue("customer_id", customer.getId().getId().toString());
expectedMsgMetaData.putValue("customer_title", customer.getTitle());
expectedMsgMetaData.putValue("customer_phone", customer.getPhone());
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData());
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData);
}
@Test
public void givenNotPresentEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() {
// GIVEN
customer.setZip(null);
customer.setAddress(null);
customer.setAddress2(null);
var user = new User();
user.setId(new UserId(UUID.randomUUID()));
user.setCustomerId(customer.getId());
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), user.getId());
when(ctxMock.getUserService()).thenReturn(userServiceMock);
when(userServiceMock.findUserByIdAsync(eq(TENANT_ID), eq(user.getId()))).thenReturn(Futures.immediateFuture(user));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
mockFindCustomer();
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture());
verify(ctxMock, never()).tellFailure(any(), any());
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData());
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
}
@Test
public void givenDidNotFindCustomer_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() {
// GIVEN
var edge = new Edge();
edge.setId(new EdgeId(UUID.randomUUID()));
edge.setCustomerId(customer.getId());
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), edge.getId());
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getEdgeService()).thenReturn(edgeServiceMock);
when(edgeServiceMock.findEdgeByIdAsync(eq(TENANT_ID), eq(edge.getId()))).thenReturn(Futures.immediateFuture(edge));
when(ctxMock.getCustomerService()).thenReturn(customerServiceMock);
when(customerServiceMock.findCustomerByIdAsync(eq(TENANT_ID), eq(customer.getId()))).thenReturn(Futures.immediateFuture(null));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture());
verify(ctxMock, never()).tellFailure(any(), any());
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData());
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
}
@Test
public void givenDidNotFindOriginator_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() {
// GIVEN
var edge = new Edge();
edge.setId(new EdgeId(UUID.randomUUID()));
edge.setCustomerId(customer.getId());
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), edge.getId());
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getEdgeService()).thenReturn(edgeServiceMock);
when(edgeServiceMock.findEdgeByIdAsync(eq(TENANT_ID), eq(edge.getId()))).thenReturn(Futures.immediateFuture(null));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture());
verify(ctxMock, never()).tellFailure(any(), any());
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData());
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
}
@Test
public void givenOriginatorNotAssignedToCustomer_whenOnMsg_thenShouldTellFailureAndFetchNothingToData() {
// GIVEN
var device = new Device();
device.setId(new DeviceId(UUID.randomUUID()));
device.setName("Thermostat");
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2), device.getId());
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock);
when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class);
verify(ctxMock, times(1)).tellFailure(actualMessageCaptor.capture(), actualExceptionCaptor.capture());
verify(ctxMock, never()).tellSuccess(any());
var actualMsg = actualMessageCaptor.getValue();
var actualException = actualExceptionCaptor.getValue();
assertThat(actualMsg.getData()).isEqualTo(msg.getData());
assertThat(actualMsg.getMetaData()).isEqualTo(msg.getMetaData());
assertThat(actualException).isInstanceOf(RuntimeException.class);
assertThat(actualException.getMessage()).isEqualTo("Device with name 'Thermostat' is not assigned to Customer.");
}
@Test
public void givenNullDescriptionAndAddInfoEntityDetails_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() {
// GIVEN
customer.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":null}"));
var device = new Device();
device.setId(new DeviceId(UUID.randomUUID()));
device.setCustomerId(customer.getId());
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ADDITIONAL_INFO), device.getId());
when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock);
when(deviceServiceMock.findDeviceByIdAsync(eq(TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
mockFindCustomer();
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture());
verify(ctxMock, never()).tellFailure(any(), any());
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData());
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
}
@Test
public void givenUnsupportedEntityType_whenOnMsg_thenShouldTellFailureAndFetchNothingToMetaData() {
// GIVEN
var dashboard = new Dashboard();
dashboard.setId(new DashboardId(UUID.randomUUID()));
prepareMsgAndConfig(FetchTo.METADATA, List.of(EntityDetails.STATE), dashboard.getId());
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
var actualExceptionCaptor = ArgumentCaptor.forClass(Exception.class);
verify(ctxMock, times(1)).tellFailure(actualMessageCaptor.capture(), actualExceptionCaptor.capture());
verify(ctxMock, never()).tellSuccess(any());
var actualMsg = actualMessageCaptor.getValue();
var actualException = actualExceptionCaptor.getValue();
assertThat(actualMsg.getData()).isEqualTo(msg.getData());
assertThat(actualMsg.getMetaData()).isEqualTo(msg.getMetaData());
assertThat(actualException).isInstanceOf(NoSuchElementException.class);
assertThat(actualException.getMessage()).isEqualTo("Entity with entityType 'DASHBOARD' is not supported.");
}
private void prepareMsgAndConfig(FetchTo fetchTo, List<EntityDetails> detailsList, EntityId originator) {
config.setDetailsList(detailsList);
config.setFetchTo(fetchTo);
node.config = config;
node.fetchTo = fetchTo;
var msgMetaData = new TbMsgMetaData();
msgMetaData.putValue("metaKey1", "metaValue1");
msgMetaData.putValue("metaKey2", "metaValue2");
var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}";
msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData);
}
private void mockFindCustomer() {
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getCustomerService()).thenReturn(customerServiceMock);
when(customerServiceMock.findCustomerByIdAsync(eq(TENANT_ID), eq(customer.getId()))).thenReturn(Futures.immediateFuture(customer));
}
}

116
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java

@ -32,7 +32,7 @@ import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
@ -46,7 +46,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -54,8 +53,10 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class TbGetOriginatorFieldsNodeTest { 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 DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID());
private static final TenantId DUMMY_TENANT_ID = new TenantId(UUID.randomUUID());
private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() {
@Override @Override
public <T> ListenableFuture<T> executeAsync(Callable<T> task) { public <T> ListenableFuture<T> executeAsync(Callable<T> task) {
try { try {
@ -73,7 +74,7 @@ public class TbGetOriginatorFieldsNodeTest {
@Mock @Mock
private TbContext ctxMock; private TbContext ctxMock;
@Mock @Mock
private DeviceService deviceService; private DeviceService deviceServiceMock;
private TbGetOriginatorFieldsNode node; private TbGetOriginatorFieldsNode node;
private TbGetOriginatorFieldsConfiguration config; private TbGetOriginatorFieldsConfiguration config;
private TbNodeConfiguration nodeConfiguration; private TbNodeConfiguration nodeConfiguration;
@ -81,15 +82,14 @@ public class TbGetOriginatorFieldsNodeTest {
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
config = new TbGetOriginatorFieldsConfiguration();
node = new TbGetOriginatorFieldsNode(); node = new TbGetOriginatorFieldsNode();
config = new TbGetOriginatorFieldsConfiguration().defaultConfiguration();
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
} }
@Test @Test
public void givenConfigWithNullFetchTo_whenOnInit_thenException() { public void givenConfigWithNullFetchTo_whenInit_thenException() {
// GIVEN // GIVEN
config = config.defaultConfiguration();
config.setFetchTo(null); config.setFetchTo(null);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
@ -97,17 +97,13 @@ public class TbGetOriginatorFieldsNodeTest {
var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration));
// THEN // THEN
assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!");
verify(ctxMock, never()).tellSuccess(any()); verify(ctxMock, never()).tellSuccess(any());
} }
@Test @Test
public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException { public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException {
// GIVEN // GIVEN-WHEN
config = config.defaultConfiguration();
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
// WHEN
node.init(ctxMock, nodeConfiguration); node.init(ctxMock, nodeConfiguration);
// THEN // THEN
@ -116,6 +112,7 @@ public class TbGetOriginatorFieldsNodeTest {
"name", "originatorName", "name", "originatorName",
"type", "originatorType")); "type", "originatorType"));
assertThat(config.isIgnoreNullStrings()).isEqualTo(false); assertThat(config.isIgnoreNullStrings()).isEqualTo(false);
assertThat(config.getFetchTo()).isEqualTo(FetchTo.METADATA);
assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA); assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA);
} }
@ -123,9 +120,9 @@ public class TbGetOriginatorFieldsNodeTest {
public void givenCustomConfig_whenInit_thenOK() throws TbNodeException { public void givenCustomConfig_whenInit_thenOK() throws TbNodeException {
// GIVEN // GIVEN
config.setFieldsMapping(Map.of( config.setFieldsMapping(Map.of(
"sourceField1", "targetKey1", "email", "originatorEmail",
"sourceField2", "targetKey2", "title", "originatorTitle",
"sourceField3", "targetKey3")); "country", "originatorCountry"));
config.setIgnoreNullStrings(true); config.setIgnoreNullStrings(true);
config.setFetchTo(FetchTo.DATA); config.setFetchTo(FetchTo.DATA);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
@ -136,10 +133,11 @@ public class TbGetOriginatorFieldsNodeTest {
// THEN // THEN
assertThat(node.config).isEqualTo(config); assertThat(node.config).isEqualTo(config);
assertThat(config.getFieldsMapping()).isEqualTo(Map.of( assertThat(config.getFieldsMapping()).isEqualTo(Map.of(
"sourceField1", "targetKey1", "email", "originatorEmail",
"sourceField2", "targetKey2", "title", "originatorTitle",
"sourceField3", "targetKey3")); "country", "originatorCountry"));
assertThat(config.isIgnoreNullStrings()).isEqualTo(true); assertThat(config.isIgnoreNullStrings()).isEqualTo(true);
assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA);
assertThat(node.fetchTo).isEqualTo(FetchTo.DATA); assertThat(node.fetchTo).isEqualTo(FetchTo.DATA);
} }
@ -147,7 +145,7 @@ public class TbGetOriginatorFieldsNodeTest {
public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() {
// GIVEN // GIVEN
node.fetchTo = FetchTo.DATA; node.fetchTo = FetchTo.DATA;
msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_ENTITY_ID, new TbMsgMetaData(), "[]"); msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]");
// WHEN // WHEN
var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg));
@ -157,29 +155,11 @@ public class TbGetOriginatorFieldsNodeTest {
verify(ctxMock, never()).tellSuccess(any()); verify(ctxMock, never()).tellSuccess(any());
} }
@Test
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("SOME_MESSAGE_TYPE", 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 @Test
public void givenValidMsgAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchToData() { public void givenValidMsgAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchToData() {
// GIVEN // GIVEN
var device = new Device(); var device = new Device();
device.setId((DeviceId) DUMMY_ENTITY_ID); device.setId(DUMMY_DEVICE_ORIGINATOR);
device.setName("Test device"); device.setName("Test device");
device.setType("Test device type"); device.setType("Test device type");
@ -194,10 +174,11 @@ public class TbGetOriginatorFieldsNodeTest {
node.fetchTo = FetchTo.DATA; node.fetchTo = FetchTo.DATA;
var msgMetaData = new TbMsgMetaData(); var msgMetaData = new TbMsgMetaData();
var msgData = "{\"temp\":42,\"humidity\":77}"; var msgData = "{\"temp\":42,\"humidity\":77}";
msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, msgMetaData, msgData); msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData);
when(ctxMock.getDeviceService()).thenReturn(deviceService); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock);
when(deviceService.findDeviceByIdAsync(any(), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID);
when(deviceServiceMock.findDeviceByIdAsync(eq(DUMMY_TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
@ -219,7 +200,7 @@ public class TbGetOriginatorFieldsNodeTest {
public void givenValidMsgAndFetchToMetaData_whenOnMsg_thenShouldTellSuccessAndFetchToMetaData() { public void givenValidMsgAndFetchToMetaData_whenOnMsg_thenShouldTellSuccessAndFetchToMetaData() {
// GIVEN // GIVEN
var device = new Device(); var device = new Device();
device.setId((DeviceId) DUMMY_ENTITY_ID); device.setId(DUMMY_DEVICE_ORIGINATOR);
device.setName("Test device"); device.setName("Test device");
device.setType("Test device type"); device.setType("Test device type");
@ -236,10 +217,11 @@ public class TbGetOriginatorFieldsNodeTest {
"testKey1", "testValue1", "testKey1", "testValue1",
"testKey2", "123")); "testKey2", "123"));
var msgData = "[\"value1\",\"value2\"]"; var msgData = "[\"value1\",\"value2\"]";
msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, msgMetaData, msgData); msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData);
when(ctxMock.getDeviceService()).thenReturn(deviceService); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock);
when(deviceService.findDeviceByIdAsync(any(), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID);
when(deviceServiceMock.findDeviceByIdAsync(eq(DUMMY_TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
@ -266,7 +248,7 @@ public class TbGetOriginatorFieldsNodeTest {
public void givenNullEntityFieldsAndIgnoreNullStringsFalse_whenOnMsg_thenShouldTellSuccessAndFetchNullField() { public void givenNullEntityFieldsAndIgnoreNullStringsFalse_whenOnMsg_thenShouldTellSuccessAndFetchNullField() {
// GIVEN // GIVEN
var device = new Device(); var device = new Device();
device.setId((DeviceId) DUMMY_ENTITY_ID); device.setId(DUMMY_DEVICE_ORIGINATOR);
device.setName("Test device"); device.setName("Test device");
device.setType("Test device type"); device.setType("Test device type");
@ -283,10 +265,11 @@ public class TbGetOriginatorFieldsNodeTest {
"testKey1", "testValue1", "testKey1", "testValue1",
"testKey2", "123")); "testKey2", "123"));
var msgData = "[\"value1\",\"value2\"]"; var msgData = "[\"value1\",\"value2\"]";
msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_ENTITY_ID, msgMetaData, msgData); msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData);
when(ctxMock.getDeviceService()).thenReturn(deviceService); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock);
when(deviceService.findDeviceByIdAsync(any(), eq(device.getId()))).thenReturn(Futures.immediateFuture(device)); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID);
when(deviceServiceMock.findDeviceByIdAsync(eq(DUMMY_TENANT_ID), eq(device.getId()))).thenReturn(Futures.immediateFuture(device));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
@ -311,35 +294,17 @@ public class TbGetOriginatorFieldsNodeTest {
} }
@Test @Test
public void givenEmptyFieldsMapping_whenOnMsg_thenShouldTellSuccessWithSameMsg() { public void givenEmptyFieldsMapping_whenInit_thenException() {
// GIVEN // GIVEN
config.setFieldsMapping(Collections.emptyMap()); config.setFieldsMapping(Collections.emptyMap());
config.setIgnoreNullStrings(false); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
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);
// WHEN // WHEN
node.onMsg(ctxMock, msg); var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration));
// THEN // THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); assertThat(exception.getMessage()).isEqualTo("At least one field mapping should be specified!");
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); verify(ctxMock, never()).tellSuccess(any());
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);
} }
@Test @Test
@ -373,4 +338,5 @@ public class TbGetOriginatorFieldsNodeTest {
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msgData); assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msgData);
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData);
} }
}
}

523
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java

@ -15,221 +15,488 @@
*/ */
package org.thingsboard.rule.engine.metadata; package org.thingsboard.rule.engine.metadata;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import org.junit.Before; import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Test; import lombok.RequiredArgsConstructor;
import org.junit.runner.RunWith; 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.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock; 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.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.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.data.RelationsQuery;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; 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.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.relation.EntityRelation; 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.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; 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.relation.RelationService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.HashMap; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat; 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.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class)
public class TbGetRelatedAttributeNodeTest extends TbAbstractAttributeNodeTest { public class TbGetRelatedAttributeNodeTest {
User user = new User();
Asset asset = new Asset(); private static final EntityId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID());
Device device = new Device(); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID());
private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() {
@Override
public <T> ListenableFuture<T> executeAsync(Callable<T> task) {
try {
return Futures.immediateFuture(task.call());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void execute(@NotNull Runnable command) {
command.run();
}
};
@Mock @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 EntityRelation entityRelation;
private TbMsg msg;
@Before @BeforeEach
public void initDataForTests() throws TbNodeException { public void setUp() {
init(new TbGetRelatedAttributeNode()); node = new TbGetRelatedAttributeNode();
config = new TbGetRelatedAttrNodeConfiguration().defaultConfiguration();
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
entityRelation = new EntityRelation(); 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 @Test
protected TbAbstractGetEntityAttrNode getEmptyNode() { public void givenConfigWithNullFetchTo_whenInit_thenException() {
return new TbGetRelatedAttributeNode(); // GIVEN
} config.setFetchTo(null);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
@Override // WHEN
TbGetEntityAttrNodeConfiguration getTbNodeConfig() { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration));
return getConfig(false);
}
@Override // THEN
TbGetEntityAttrNodeConfiguration getTbNodeConfigForTelemetry() { assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!");
return getConfig(true); verify(ctxMock, never()).tellSuccess(any());
} }
private TbGetEntityAttrNodeConfiguration getConfig(boolean isTelemetry) { @Test
TbGetRelatedAttrNodeConfiguration config = new TbGetRelatedAttrNodeConfiguration(); public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException {
config = config.defaultConfiguration(); // GIVEN
Map<String, String> conf = new HashMap<>();
conf.put(keyAttrConf, valueAttrConf); // WHEN
config.setAttrMapping(conf); node.init(ctxMock, nodeConfiguration);
config.setTelemetry(isTelemetry);
config.setFetchTo(FetchTo.METADATA); // THEN
return config; 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 @Test
EntityId getEntityId() { public void givenCustomConfig_whenInit_thenOK() throws TbNodeException {
return customerId; // 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 @Test
public void errorThrownIfFetchToIsNull() { public void givenEmptyAttributesMapping_whenInit_thenException() {
var node = new TbGetRelatedAttributeNode(); // GIVEN
var config = new TbGetRelatedAttrNodeConfiguration().defaultConfiguration(); var expectedExceptionMessage = "At least one attribute mapping should be specified!";
config.setFetchTo(null);
var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
var exception = assertThrows(TbNodeException.class, () -> node.init(ctx, nodeConfiguration)); config.setAttrMapping(Collections.emptyMap());
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); // WHEN
verify(ctx, never()).tellSuccess(any()); var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration));
// THEN
assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage);
verify(ctxMock, never()).tellSuccess(any());
} }
@Test @Test
public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() {
// GIVEN
node.fetchTo = FetchTo.DATA; node.fetchTo = FetchTo.DATA;
node.config.setFetchTo(FetchTo.DATA); msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]");
msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new DeviceId(UUID.randomUUID()), 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!"); assertThat(exception.getMessage()).isEqualTo("Message body is not an object!");
verify(ctx, never()).tellSuccess(any()); verify(ctxMock, never()).tellSuccess(any());
} }
@Test @Test
public void errorThrownIfCannotLoadAttributes() { public void givenDidNotFindEntity_whenOnMsg_thenShouldTellFailure() {
entityRelation.setFrom(user.getId()); // GIVEN
entityRelation.setTo(customerId); prepareMsgAndConfig(FetchTo.METADATA, false, DUMMY_DEVICE_ORIGINATOR);
when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation)));
errorThrownIfCannotLoadAttributes(user);
}
@Test when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
public void errorThrownIfCannotLoadAttributesAsync() {
entityRelation.setFrom(user.getId());
entityRelation.setTo(customerId);
when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation)));
errorThrownIfCannotLoadAttributesAsync(user);
}
@Test when(ctxMock.getRelationService()).thenReturn(relationServiceMock);
public void failedChainUsedIfCustomerCannotBeFound() { doReturn(Futures.immediateFuture(null)).when(relationServiceMock).findByQuery(eq(TENANT_ID), any());
entityRelation.setFrom(customerId);
entityRelation.setTo(null);
when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation)));
failedChainUsedIfCustomerCannotBeFound(user);
}
@Test when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
public void customerAttributeAddedInMetadata() {
entityRelation.setFrom(customerId); // WHEN
entityRelation.setTo(customerId); node.onMsg(ctxMock, msg);
when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation)));
entityAttributeAddedInMetadata(customerId, "CUSTOMER"); // 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 @Test
public void customerAttributeAddedInData() { public void givenFetchAttributesToData_whenOnMsg_thenShouldFetchAttributesToData() {
node.fetchTo = FetchTo.DATA; // GIVEN
node.config.setFetchTo(FetchTo.DATA); var customer = new Customer(new CustomerId(UUID.randomUUID()));
var user = new User(new UserId(UUID.randomUUID()));
prepareMsgAndConfig(FetchTo.DATA, false, customer.getId());
entityRelation.setFrom(customerId); entityRelation.setFrom(customer.getId());
entityRelation.setTo(customerId); entityRelation.setTo(user.getId());
when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); entityRelation.setType(EntityRelation.CONTAINS_TYPE);
msg = TbMsg.newMsg("CUSTOMER", customerId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); List<AttributeKvEntry> attributes = List.of(
new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L)
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
List<AttributeKvEntry> attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctx.getAttributesService()).thenReturn(attributesService); when(ctxMock.getRelationService()).thenReturn(relationServiceMock);
when(attributesService.find(any(), eq(customerId), eq(SERVER_SCOPE), anyCollection())) doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any());
when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock);
when(attributesServiceMock.find(eq(TENANT_ID), eq(user.getId()), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(attributes)); .thenReturn(Futures.immediateFuture(attributes));
node.onMsg(ctx, msg); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, times(1)).tellSuccess(actualMessageCaptor.capture());
var expectedMsgData = "{\"answer\":\"high\"}"; 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().getData()).isEqualTo(expectedMsgData);
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
} }
@Test @Test
public void usersCustomerAttributesFetched() { public void givenFetchAttributesToMetaData_whenOnMsg_thenShouldFetchAttributesToMetaData() {
entityRelation.setFrom(user.getId()); // GIVEN
entityRelation.setTo(customerId); var firstCustomer = new Customer(new CustomerId(UUID.randomUUID()));
when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); var secondCustomer = new Customer(new CustomerId(UUID.randomUUID()));
usersCustomerAttributesFetched(user);
prepareMsgAndConfig(FetchTo.METADATA, false, firstCustomer.getId());
entityRelation.setFrom(firstCustomer.getId());
entityRelation.setTo(secondCustomer.getId());
entityRelation.setType(EntityRelation.MANAGES_TYPE);
List<AttributeKvEntry> attributes = List.of(
new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L)
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getRelationService()).thenReturn(relationServiceMock);
doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any());
when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock);
when(attributesServiceMock.find(eq(TENANT_ID), eq(secondCustomer.getId()), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(attributes));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
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 @Test
public void assetsCustomerAttributesFetched() { public void givenFetchTelemetryToData_whenOnMsg_thenShouldFetchTelemetryToData() {
entityRelation.setFrom(asset.getId()); // GIVEN
entityRelation.setTo(customerId); var dashboard = new Dashboard(new DashboardId(UUID.randomUUID()));
when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); var entityView = new EntityView(new EntityViewId(UUID.randomUUID()));
assetsCustomerAttributesFetched(asset);
prepareMsgAndConfig(FetchTo.DATA, true, dashboard.getId());
entityRelation.setFrom(dashboard.getId());
entityRelation.setTo(entityView.getId());
entityRelation.setType(EntityRelation.CONTAINS_TYPE);
List<TsKvEntry> timeseries = List.of(
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3"))
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getRelationService()).thenReturn(relationServiceMock);
doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any());
when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock);
when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(entityView.getId()), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(timeseries));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
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 @Test
public void deviceCustomerAttributesFetched() { public void givenFetchTelemetryToMetaData_whenOnMsg_thenShouldFetchTelemetryToMetaData() {
entityRelation.setFrom(device.getId()); // GIVEN
entityRelation.setTo(customerId); var tenant = new Tenant(new TenantId(UUID.randomUUID()));
when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); var device = new Device(new DeviceId(UUID.randomUUID()));
deviceCustomerAttributesFetched(device);
prepareMsgAndConfig(FetchTo.METADATA, true, tenant.getId());
entityRelation.setFrom(tenant.getId());
entityRelation.setTo(device.getId());
entityRelation.setType(EntityRelation.CONTAINS_TYPE);
List<TsKvEntry> timeseries = List.of(
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3"))
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(tenant.getId());
when(ctxMock.getRelationService()).thenReturn(relationServiceMock);
doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(tenant.getId()), any());
when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock);
when(timeseriesServiceMock.findLatest(eq(tenant.getId()), eq(device.getId()), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(timeseries));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
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 private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId originator) {
public void deviceCustomerTelemetryFetched() throws TbNodeException { config.setAttrMapping(Map.of(
entityRelation.setFrom(device.getId()); "sourceKey1", "targetKey1",
entityRelation.setTo(customerId); "${metaDataPattern1}", "$[messageBodyPattern1]",
when(relationService.findByQuery(any(), any())).thenReturn(Futures.immediateFuture(List.of(entityRelation))); "$[messageBodyPattern2]", "${metaDataPattern2}"));
deviceCustomerTelemetryFetched(device); 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", originator, msgMetaData, msgData);
} }
@RequiredArgsConstructor
private static class ListMatcher<T> implements ArgumentMatcher<List<T>> {
private final List<T> expectedList;
@Override
public boolean matches(List<T> actualList) {
if (actualList == expectedList) {
return true;
}
if (actualList.size() != expectedList.size()) {
return false;
}
return actualList.containsAll(expectedList);
}
}
} }

383
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java

@ -15,38 +15,47 @@
*/ */
package org.thingsboard.rule.engine.metadata; package org.thingsboard.rule.engine.metadata;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import org.junit.Before; import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Test; import lombok.RequiredArgsConstructor;
import org.junit.runner.RunWith; 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.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.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.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.CustomerId;
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.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; 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.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; 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.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.TbMsg; 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.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.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@ -54,124 +63,332 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class)
public class TbGetTenantAttributeNodeTest extends TbAbstractAttributeNodeTest { public class TbGetTenantAttributeNodeTest {
User user = new User();
Asset asset = new Asset(); private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID());
Device device = new Device(); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID());
private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() {
@Override
public <T> ListenableFuture<T> executeAsync(Callable<T> 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 @Test
public void initDataForTests() throws TbNodeException { public void givenConfigWithNullFetchTo_whenInit_thenException() {
init(new TbGetTenantAttributeNode()); // GIVEN
config.setFetchTo(null);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
user.setTenantId(tenantId); // WHEN
user.setId(new UserId(UUID.randomUUID())); var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration));
asset.setTenantId(tenantId); // THEN
asset.setId(new AssetId(UUID.randomUUID())); assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!");
verify(ctxMock, never()).tellSuccess(any());
}
device.setTenantId(tenantId); @Test
device.setId(new DeviceId(UUID.randomUUID())); public void givenDefaultConfig_whenInit_thenOK() throws TbNodeException {
// GIVEN
when(ctx.getTenantId()).thenReturn(tenantId); // WHEN
} node.init(ctxMock, nodeConfiguration);
@Override // THEN
protected TbAbstractGetEntityAttrNode getEmptyNode() { assertThat(node.config).isEqualTo(config);
return new TbGetTenantAttributeNode(); assertThat(config.getAttrMapping()).isEqualTo(Map.of("alarmThreshold", "threshold"));
assertThat(config.isTelemetry()).isEqualTo(false);
assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA);
} }
@Override @Test
EntityId getEntityId() { public void givenCustomConfig_whenInit_thenOK() throws TbNodeException {
return tenantId; // 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 @Test
public void errorThrownIfFetchToIsNull() { public void givenEmptyAttributesMapping_whenInit_thenException() {
var node = new TbGetTenantAttributeNode(); // GIVEN
var config = new TbGetEntityAttrNodeConfiguration().defaultConfiguration(); var expectedExceptionMessage = "At least one attribute mapping should be specified!";
config.setFetchTo(null);
var nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
var exception = assertThrows(TbNodeException.class, () -> node.init(ctx, nodeConfiguration)); config.setAttrMapping(Collections.emptyMap());
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be NULL!"); // WHEN
verify(ctx, never()).tellSuccess(any()); var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration));
// THEN
assertThat(exception.getMessage()).isEqualTo(expectedExceptionMessage);
verify(ctxMock, never()).tellSuccess(any());
} }
@Test @Test
public void errorThrownIfMsgDataIsNotAnObjectAndFetchToData() { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() {
// GIVEN
node.fetchTo = FetchTo.DATA; node.fetchTo = FetchTo.DATA;
node.config.setFetchTo(FetchTo.DATA); msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]");
msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", new TenantId(UUID.randomUUID()), 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!"); assertThat(exception.getMessage()).isEqualTo("Message body is not an object!");
verify(ctx, never()).tellSuccess(any()); verify(ctxMock, never()).tellSuccess(any());
} }
@Test @Test
public void errorThrownIfCannotLoadAttributes() { public void givenFetchAttributesToData_whenOnMsg_thenShouldFetchAttributesToData() {
errorThrownIfCannotLoadAttributes(user); // GIVEN
} var deviceId = new DeviceId(UUID.randomUUID());
@Test prepareMsgAndConfig(FetchTo.DATA, false, deviceId);
public void errorThrownIfCannotLoadAttributesAsync() {
errorThrownIfCannotLoadAttributesAsync(user);
}
@Test List<AttributeKvEntry> attributesList = List.of(
public void failedChainUsedIfTenantIdFromCtxCannotBeFound() { new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L),
when(ctx.getTenantId()).thenReturn(null); new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L),
failedChainUsedIfCustomerCannotBeFound(user); new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L)
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock);
when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(attributesList));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
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 @Test
public void customerAttributeAddedInMetadata() { public void givenFetchAttributesToMetaData_whenOnMsg_thenShouldFetchAttributesToMetaData() {
entityAttributeAddedInMetadata(tenantId, "TENANT"); // GIVEN
prepareMsgAndConfig(FetchTo.METADATA, false, TENANT_ID);
List<AttributeKvEntry> attributesList = List.of(
new BaseAttributeKvEntry(new StringDataEntry("sourceKey1", "sourceValue1"), 1L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey2", "sourceValue2"), 2L),
new BaseAttributeKvEntry(new StringDataEntry("sourceKey3", "sourceValue3"), 3L)
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock);
when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(attributesList));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
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 @Test
public void customerAttributeAddedInData() { public void givenFetchTelemetryToData_whenOnMsg_thenShouldFetchTelemetryToData() {
node.fetchTo = FetchTo.DATA; // GIVEN
node.config.setFetchTo(FetchTo.DATA); var customerId = new CustomerId(UUID.randomUUID());
msg = TbMsg.newMsg("TENANT", tenantId, new TbMsgMetaData(metaData), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); prepareMsgAndConfig(FetchTo.DATA, true, customerId);
List<AttributeKvEntry> attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); List<TsKvEntry> timeseries = List.of(
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3"))
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctx.getAttributesService()).thenReturn(attributesService); when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(attributesService.find(any(), eq(tenantId), eq(SERVER_SCOPE), anyCollection()))
.thenReturn(Futures.immediateFuture(attributes));
node.onMsg(ctx, msg); when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock);
when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(TENANT_ID), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(timeseries));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, times(1)).tellSuccess(actualMessageCaptor.capture());
var expectedMsgData = "{\"answer\":\"high\"}"; 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().getData()).isEqualTo(expectedMsgData);
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
} }
@Test @Test
public void usersCustomerAttributesFetched() { public void givenFetchTelemetryToMetaData_whenOnMsg_thenShouldFetchTelemetryToMetaData() {
usersCustomerAttributesFetched(user); // GIVEN
} var ruleChainId = new RuleChainId(UUID.randomUUID());
@Test prepareMsgAndConfig(FetchTo.METADATA, true, ruleChainId);
public void assetsCustomerAttributesFetched() {
assetsCustomerAttributesFetched(asset); List<TsKvEntry> timeseries = List.of(
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey1", "sourceValue1")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey2", "sourceValue2")),
new BasicTsKvEntry(1L, new StringDataEntry("sourceKey3", "sourceValue3"))
);
var expectedPatternProcessedKeysList = List.of("sourceKey1", "sourceKey2", "sourceKey3");
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getTimeseriesService()).thenReturn(timeseriesServiceMock);
when(timeseriesServiceMock.findLatest(eq(TENANT_ID), eq(TENANT_ID), argThat(new ListMatcher<>(expectedPatternProcessedKeysList))))
.thenReturn(Futures.immediateFuture(timeseries));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
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 private void prepareMsgAndConfig(FetchTo fetchTo, boolean isTelemetry, EntityId originator) {
public void deviceCustomerAttributesFetched() { config.setAttrMapping(Map.of(
deviceCustomerAttributesFetched(device); "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", originator, msgMetaData, msgData);
} }
@Test @RequiredArgsConstructor
public void deviceCustomerTelemetryFetched() throws TbNodeException { private static class ListMatcher<T> implements ArgumentMatcher<List<T>> {
deviceCustomerTelemetryFetched(device);
private final List<T> expectedList;
@Override
public boolean matches(List<T> actualList) {
if (actualList == expectedList) {
return true;
}
if (actualList.size() != expectedList.size()) {
return false;
}
return actualList.containsAll(expectedList);
}
} }
} }

284
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java

@ -0,0 +1,284 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.metadata;
import com.google.common.util.concurrent.Futures;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.util.EntityDetails;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class TbGetTenantDetailsNodeTest {
private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID());
@Mock
private TbContext ctxMock;
@Mock
private TenantService tenantServiceMock;
private TbGetTenantDetailsNode node;
private TbGetTenantDetailsNodeConfiguration config;
private TbNodeConfiguration nodeConfiguration;
private TbMsg msg;
private Tenant tenant;
@BeforeEach
public void setUp() {
node = new TbGetTenantDetailsNode();
config = new TbGetTenantDetailsNodeConfiguration().defaultConfiguration();
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
tenant = new Tenant();
tenant.setId(new TenantId(UUID.randomUUID()));
tenant.setTitle("Tenant title");
tenant.setCountry("Tenant country");
tenant.setCity("Tenant city");
tenant.setState("Tenant state");
tenant.setZip("123456");
tenant.setAddress("Tenant address 1");
tenant.setAddress2("Tenant address 2");
tenant.setPhone("+123456789");
tenant.setEmail("email@tenant.com");
tenant.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":\"Tenant description\"}"));
}
@Test
public void givenConfigWithNullFetchTo_whenInit_thenException() {
// GIVEN
config.setDetailsList(List.of(EntityDetails.ID));
config.setFetchTo(null);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
// WHEN
var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration));
// THEN
assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!");
verify(ctxMock, never()).tellSuccess(any());
}
@Test
public void givenDefaultConfig_whenInit_thenOK() {
// THEN
assertThat(config.getDetailsList()).isEqualTo(Collections.emptyList());
assertThat(config.getFetchTo()).isEqualTo(FetchTo.DATA);
}
@Test
public void givenCustomConfig_whenInit_thenOK() throws TbNodeException {
// GIVEN
config.setDetailsList(List.of(EntityDetails.ID, EntityDetails.PHONE));
config.setFetchTo(FetchTo.METADATA);
nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
// WHEN
node.init(ctxMock, nodeConfiguration);
// THEN
assertThat(node.config).isEqualTo(config);
assertThat(config.getDetailsList()).isEqualTo(List.of(EntityDetails.ID, EntityDetails.PHONE));
assertThat(config.getFetchTo()).isEqualTo(FetchTo.METADATA);
assertThat(node.fetchTo).isEqualTo(FetchTo.METADATA);
}
@Test
public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() {
// GIVEN
node.fetchTo = FetchTo.DATA;
msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]");
// WHEN
var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg));
// THEN
assertThat(exception.getMessage()).isEqualTo("Message body is not an object!");
verify(ctxMock, never()).tellSuccess(any());
}
@Test
public void givenAllEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchAllToData() {
// GIVEN
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.values()));
mockFindTenant();
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture());
verify(ctxMock, never()).tellFailure(any(), any());
var expectedMsgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"," +
"\"tenant_id\":\"" + tenant.getId() + "\"," +
"\"tenant_title\":\"" + tenant.getTitle() + "\"," +
"\"tenant_country\":\"" + tenant.getCountry() + "\"," +
"\"tenant_city\":\"" + tenant.getCity() + "\"," +
"\"tenant_state\":\"" + tenant.getState() + "\"," +
"\"tenant_zip\":\"" + tenant.getZip() + "\"," +
"\"tenant_address\":\"" + tenant.getAddress() + "\"," +
"\"tenant_address2\":\"" + tenant.getAddress2() + "\"," +
"\"tenant_phone\":\"" + tenant.getPhone() + "\"," +
"\"tenant_email\":\"" + tenant.getEmail() + "\"," +
"\"tenant_additionalInfo\":\"" + tenant.getAdditionalInfo().get("description").asText() + "\"}";
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData);
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
}
@Test
public void givenSomeEntityDetailsAndFetchToMetadata_whenOnMsg_thenShouldTellSuccessAndFetchSomeToMetaData() {
// GIVEN
prepareMsgAndConfig(FetchTo.METADATA, List.of(EntityDetails.ID, EntityDetails.TITLE, EntityDetails.PHONE));
mockFindTenant();
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture());
verify(ctxMock, never()).tellFailure(any(), any());
var expectedMsgMetaData = new TbMsgMetaData(msg.getMetaData().getData());
expectedMsgMetaData.putValue("tenant_id", tenant.getId().getId().toString());
expectedMsgMetaData.putValue("tenant_title", tenant.getTitle());
expectedMsgMetaData.putValue("tenant_phone", tenant.getPhone());
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData());
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(expectedMsgMetaData);
}
@Test
public void givenNotPresentEntityDetailsAndFetchToData_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() {
// GIVEN
tenant.setZip(null);
tenant.setAddress(null);
tenant.setAddress2(null);
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2));
mockFindTenant();
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture());
verify(ctxMock, never()).tellFailure(any(), any());
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData());
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
}
@Test
public void givenDidNotFindTenant_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() {
// GIVEN
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ZIP, EntityDetails.ADDRESS, EntityDetails.ADDRESS2));
when(ctxMock.getTenantId()).thenReturn(tenant.getId());
when(ctxMock.getTenantService()).thenReturn(tenantServiceMock);
when(tenantServiceMock.findTenantByIdAsync(eq(tenant.getId()), eq(tenant.getId()))).thenReturn(Futures.immediateFuture(null));
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture());
verify(ctxMock, never()).tellFailure(any(), any());
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData());
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
}
@Test
public void givenNullDescriptionAndAddInfoEntityDetails_whenOnMsg_thenShouldTellSuccessAndFetchNothingToData() {
// GIVEN
tenant.setAdditionalInfo(JacksonUtil.toJsonNode("{\"someProperty\":\"someValue\",\"description\":null}"));
prepareMsgAndConfig(FetchTo.DATA, List.of(EntityDetails.ADDITIONAL_INFO));
mockFindTenant();
// WHEN
node.onMsg(ctxMock, msg);
// THEN
var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture());
verify(ctxMock, never()).tellFailure(any(), any());
assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(msg.getData());
assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msg.getMetaData());
}
private void prepareMsgAndConfig(FetchTo fetchTo, List<EntityDetails> detailsList) {
config.setDetailsList(detailsList);
config.setFetchTo(fetchTo);
node.config = config;
node.fetchTo = fetchTo;
var msgMetaData = new TbMsgMetaData();
msgMetaData.putValue("metaKey1", "metaValue1");
msgMetaData.putValue("metaKey2", "metaValue2");
var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}";
msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData);
}
private void mockFindTenant() {
when(ctxMock.getTenantId()).thenReturn(tenant.getId());
when(ctxMock.getTenantService()).thenReturn(tenantServiceMock);
when(tenantServiceMock.findTenantByIdAsync(eq(tenant.getId()), eq(tenant.getId()))).thenReturn(Futures.immediateFuture(tenant));
}
}

170
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java

@ -0,0 +1,170 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.util;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.Customer;
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.Callable;
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<EntityType> SUPPORTED_ENTITY_TYPES = EnumSet.of(
EntityType.CUSTOMER,
EntityType.USER,
EntityType.ASSET,
EntityType.DEVICE
);
private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() {
@Override
public <T> ListenableFuture<T> executeAsync(Callable<T> 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 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
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(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, user.getId()).get();
// THEN
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(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, asset.getId()).get();
// THEN
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(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
var actualCustomerId = EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctxMock, device.getId()).get();
// THEN
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());
}
}
}
}

36
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java

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

153
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java

@ -0,0 +1,153 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.util;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.data.DeviceRelationsQuery;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.device.DeviceService;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class EntitiesRelatedDeviceIdAsyncLoaderTest {
private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID());
private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID());
private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() {
@Override
public <T> ListenableFuture<T> executeAsync(Callable<T> 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 DeviceService deviceServiceMock;
@Test
public void givenDeviceRelationsQuery_whenFindDeviceAsync_ShouldBuildCorrectDeviceSearchQuery() {
// GIVEN
var deviceRelationsQuery = new DeviceRelationsQuery();
deviceRelationsQuery.setDeviceTypes(List.of("Device type 1", "Device type 2", "default"));
deviceRelationsQuery.setDirection(EntitySearchDirection.FROM);
deviceRelationsQuery.setMaxLevel(2);
deviceRelationsQuery.setRelationType(EntityRelation.CONTAINS_TYPE);
var expectedDeviceSearchQuery = new DeviceSearchQuery();
var parameters = new RelationsSearchParameters(
DUMMY_ORIGINATOR,
deviceRelationsQuery.getDirection(),
deviceRelationsQuery.getMaxLevel(),
deviceRelationsQuery.isFetchLastLevelOnly()
);
expectedDeviceSearchQuery.setParameters(parameters);
expectedDeviceSearchQuery.setRelationType(deviceRelationsQuery.getRelationType());
expectedDeviceSearchQuery.setDeviceTypes(deviceRelationsQuery.getDeviceTypes());
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock);
when(deviceServiceMock.findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery)))
.thenReturn(Futures.immediateFuture(null));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctxMock, DUMMY_ORIGINATOR, deviceRelationsQuery);
// THEN
verify(deviceServiceMock, times(1)).findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery));
}
@Test
public void givenSeveralDevicesFound_whenFindDeviceAsync_ShouldKeepOneAndDiscardOthers() throws Exception {
// GIVEN
var deviceRelationsQuery = new DeviceRelationsQuery();
deviceRelationsQuery.setDeviceTypes(List.of("Device type 1", "Device type 2", "default"));
deviceRelationsQuery.setDirection(EntitySearchDirection.FROM);
deviceRelationsQuery.setMaxLevel(2);
deviceRelationsQuery.setRelationType(EntityRelation.CONTAINS_TYPE);
var expectedDeviceSearchQuery = new DeviceSearchQuery();
var parameters = new RelationsSearchParameters(
DUMMY_ORIGINATOR,
deviceRelationsQuery.getDirection(),
deviceRelationsQuery.getMaxLevel(),
deviceRelationsQuery.isFetchLastLevelOnly()
);
expectedDeviceSearchQuery.setParameters(parameters);
expectedDeviceSearchQuery.setRelationType(deviceRelationsQuery.getRelationType());
expectedDeviceSearchQuery.setDeviceTypes(deviceRelationsQuery.getDeviceTypes());
var device1 = new Device(new DeviceId(UUID.randomUUID()));
device1.setName("Device 1");
var device2 = new Device(new DeviceId(UUID.randomUUID()));
device1.setName("Device 2");
var device3 = new Device(new DeviceId(UUID.randomUUID()));
device1.setName("Device 3");
var devicesList = List.of(device1, device2, device3);
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock);
when(deviceServiceMock.findDevicesByQuery(eq(TENANT_ID), eq(expectedDeviceSearchQuery)))
.thenReturn(Futures.immediateFuture(devicesList));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
var entityIdFuture = EntitiesRelatedDeviceIdAsyncLoader.findDeviceAsync(ctxMock, DUMMY_ORIGINATOR, deviceRelationsQuery);
// THEN
assertNotNull(entityIdFuture);
var actualEntityId = entityIdFuture.get();
assertNotNull(actualEntityId);
assertEquals(device1.getId(), actualEntityId);
}
}

174
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntitiesIdAsyncLoaderTest.java

@ -0,0 +1,174 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.util;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.data.RelationsQuery;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.relation.RelationService;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class EntitiesRelatedEntitiesIdAsyncLoaderTest {
private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID());
private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID());
private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() {
@Override
public <T> ListenableFuture<T> executeAsync(Callable<T> 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 RelationService relationServiceMock;
@Test
public void givenRelationsQuery_whenFindEntityAsync_ShouldBuildCorrectEntityRelationsQuery() {
// GIVEN
var relationsQuery = new RelationsQuery();
var relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList());
relationsQuery.setDirection(EntitySearchDirection.FROM);
relationsQuery.setMaxLevel(1);
relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter));
var expectedEntityRelationsQuery = new EntityRelationsQuery();
var parameters = new RelationsSearchParameters(
DUMMY_ORIGINATOR,
relationsQuery.getDirection(),
relationsQuery.getMaxLevel(),
relationsQuery.isFetchLastLevelOnly()
);
expectedEntityRelationsQuery.setParameters(parameters);
expectedEntityRelationsQuery.setFilters(relationsQuery.getFilters());
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getRelationService()).thenReturn(relationServiceMock);
when(relationServiceMock.findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery)))
.thenReturn(Futures.immediateFuture(null));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctxMock, DUMMY_ORIGINATOR, relationsQuery);
// THEN
verify(relationServiceMock, times(1)).findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery));
}
@Test
public void givenSeveralEntitiesFound_whenFindEntityAsync_ShouldKeepOneAndDiscardOthers() throws Exception {
// GIVEN
var relationsQuery = new RelationsQuery();
var relationEntityTypeFilter = new RelationEntityTypeFilter(
EntityRelation.CONTAINS_TYPE,
List.of(EntityType.DEVICE, EntityType.ASSET)
);
relationsQuery.setDirection(EntitySearchDirection.FROM);
relationsQuery.setMaxLevel(2);
relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter));
var expectedEntityRelationsQuery = new EntityRelationsQuery();
var parameters = new RelationsSearchParameters(
DUMMY_ORIGINATOR,
relationsQuery.getDirection(),
relationsQuery.getMaxLevel(),
relationsQuery.isFetchLastLevelOnly()
);
expectedEntityRelationsQuery.setParameters(parameters);
expectedEntityRelationsQuery.setFilters(relationsQuery.getFilters());
var device1 = new Device(new DeviceId(UUID.randomUUID()));
device1.setName("Device 1");
var device2 = new Device(new DeviceId(UUID.randomUUID()));
device1.setName("Device 2");
var asset = new Asset(new AssetId(UUID.randomUUID()));
asset.setName("Asset");
var entityRelationDevice1 = new EntityRelation();
entityRelationDevice1.setFrom(DUMMY_ORIGINATOR);
entityRelationDevice1.setTo(device1.getId());
entityRelationDevice1.setType(EntityRelation.CONTAINS_TYPE);
var entityRelationDevice2 = new EntityRelation();
entityRelationDevice2.setFrom(DUMMY_ORIGINATOR);
entityRelationDevice2.setTo(device2.getId());
entityRelationDevice2.setType(EntityRelation.CONTAINS_TYPE);
var entityRelationAsset = new EntityRelation();
entityRelationAsset.setFrom(DUMMY_ORIGINATOR);
entityRelationAsset.setTo(asset.getId());
entityRelationAsset.setType(EntityRelation.CONTAINS_TYPE);
var expectedEntityRelationsList = List.of(entityRelationDevice1, entityRelationDevice2, entityRelationAsset);
when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
when(ctxMock.getRelationService()).thenReturn(relationServiceMock);
when(relationServiceMock.findByQuery(eq(TENANT_ID), eq(expectedEntityRelationsQuery)))
.thenReturn(Futures.immediateFuture(expectedEntityRelationsList));
when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR);
// WHEN
var deviceIdFuture = EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctxMock, DUMMY_ORIGINATOR, relationsQuery);
// THEN
assertNotNull(deviceIdFuture);
var actualDeviceId = deviceIdFuture.get();
assertNotNull(actualDeviceId);
assertEquals(device1.getId(), actualDeviceId);
}
}

4
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java

@ -69,7 +69,6 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleService; 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.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
@ -135,8 +134,6 @@ public class TenantIdLoaderTest {
@Mock @Mock
private NotificationRequestService notificationRequestService; private NotificationRequestService notificationRequestService;
@Mock @Mock
private NotificationService notificationService;
@Mock
private NotificationRuleService notificationRuleService; private NotificationRuleService notificationRuleService;
private TenantId tenantId; private TenantId tenantId;
@ -393,4 +390,5 @@ public class TenantIdLoaderTest {
public void test_findEntityIdAsync_other_tenant() { public void test_findEntityIdAsync_other_tenant() {
checkTenant(new TenantId(UUID.randomUUID()), false); checkTenant(new TenantId(UUID.randomUUID()), false);
} }
} }

Loading…
Cancel
Save