Browse Source

updated docs for transformation nodes && added minor fixes && added tests tests for updated logic in to email node

pull/9284/head
ShvaykaD 3 years ago
parent
commit
a0cfa0479e
  1. 9
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java
  2. 45
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java
  3. 16
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java
  4. 16
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
  5. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java
  6. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java
  7. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java
  8. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java
  9. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java
  10. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
  11. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java
  12. 189
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java

9
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java

@ -49,11 +49,10 @@ import java.util.concurrent.TimeUnit;
configClazz = TbMsgDeduplicationNodeConfiguration.class, configClazz = TbMsgDeduplicationNodeConfiguration.class,
nodeDescription = "Deduplicate messages within the same originator entity for a configurable period " + nodeDescription = "Deduplicate messages within the same originator entity for a configurable period " +
"based on a specified deduplication strategy.", "based on a specified deduplication strategy.",
nodeDetails = "Rule node allows you to select one of the following strategy to deduplicate messages: <br></br>" + nodeDetails = "Deduplication strategy: <ul><li><strong>FIRST</strong> - return first message that arrived during deduplication period.</li>" +
"<b>FIRST</b> - return first message that arrived during deduplication period.<br></br>" + "<li><strong>LAST</strong> - return last message that arrived during deduplication period.</li>" +
"<b>LAST</b> - return last message that arrived during deduplication period.<br></br>" + "<li><strong>ALL</strong> - return all messages as a single JSON array message. " +
"<b>ALL</b> - return all messages as a single JSON array message. " + "Where each element represents object with <i>msg</i> and <i>metadata</i> inner properties.</li></ul>",
"Where each element represents object with <b>msg</b> and <b>metadata</b> inner properties.<br></br>",
icon = "content_copy", icon = "content_copy",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeMsgDeduplicationConfig" configDirective = "tbActionNodeMsgDeduplicationConfig"

45
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java

@ -15,8 +15,8 @@
*/ */
package org.thingsboard.rule.engine.mail; package org.thingsboard.rule.engine.mail;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
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;
@ -30,9 +30,9 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -42,8 +42,11 @@ import java.util.Map;
name = "to email", name = "to email",
configClazz = TbMsgToEmailNodeConfiguration.class, configClazz = TbMsgToEmailNodeConfiguration.class,
nodeDescription = "Transforms message to email message", nodeDescription = "Transforms message to email message",
nodeDetails = "Transforms message to email message by populating email fields using values derived from message metadata. " + nodeDetails = "Transforms message to email message. All email fields support templatization. " +
"Set 'SEND_EMAIL' output message type.", "Supports adding inline base64 images to the mail body from message metadata by checking for the <strong><i>images</i></strong> metadata key " +
"that should represent a map of entries: contentId to based64 image value.<br><br>" +
"Set <code>SEND_EMAIL</code> output message type if transformation completed successfully.<br><br>" +
"Output connections: <code>Success</code>, <code>Failure</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbTransformationNodeToEmailConfig", configDirective = "tbTransformationNodeToEmailConfig",
icon = "email" icon = "email"
@ -54,12 +57,12 @@ public class TbMsgToEmailNode implements TbNode {
private static final String DYNAMIC = "dynamic"; private static final String DYNAMIC = "dynamic";
private TbMsgToEmailNodeConfiguration config; private TbMsgToEmailNodeConfiguration config;
private boolean isDynamicHtmlTemplate; private boolean dynamicMailBodyType;
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class); this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class);
this.isDynamicHtmlTemplate = DYNAMIC.equals(this.config.getMailBodyType()); this.dynamicMailBodyType = DYNAMIC.equals(this.config.getMailBodyType());
} }
@Override @Override
@ -74,24 +77,22 @@ public class TbMsgToEmailNode implements TbNode {
} }
} }
private TbMsg buildEmailMsg(TbContext ctx, TbMsg msg, TbEmail email) throws JsonProcessingException { private TbMsg buildEmailMsg(TbContext ctx, TbMsg msg, TbEmail email) {
String emailJson = JacksonUtil.toString(email); String emailJson = JacksonUtil.toString(email);
return ctx.transformMsg(msg, TbMsgType.SEND_EMAIL, msg.getOriginator(), msg.getMetaData().copy(), emailJson); return ctx.transformMsg(msg, TbMsgType.SEND_EMAIL, msg.getOriginator(), msg.getMetaData().copy(), emailJson);
} }
private TbEmail convert(TbMsg msg) throws IOException { private TbEmail convert(TbMsg msg) {
TbEmail.TbEmailBuilder builder = TbEmail.builder(); TbEmail.TbEmailBuilder builder = TbEmail.builder();
builder.from(fromTemplate(this.config.getFromTemplate(), msg)); builder.from(fromTemplate(config.getFromTemplate(), msg));
builder.to(fromTemplate(this.config.getToTemplate(), msg)); builder.to(fromTemplate(config.getToTemplate(), msg));
builder.cc(fromTemplate(this.config.getCcTemplate(), msg)); builder.cc(fromTemplate(config.getCcTemplate(), msg));
builder.bcc(fromTemplate(this.config.getBccTemplate(), msg)); builder.bcc(fromTemplate(config.getBccTemplate(), msg));
if(isDynamicHtmlTemplate) { String htmlStr = dynamicMailBodyType ?
builder.html(Boolean.parseBoolean(fromTemplate(this.config.getIsHtmlTemplate(), msg))); fromTemplate(config.getIsHtmlTemplate(), msg) : config.getMailBodyType();
} else { builder.html(Boolean.parseBoolean(htmlStr));
builder.html(Boolean.parseBoolean(this.config.getMailBodyType())); builder.subject(fromTemplate(config.getSubjectTemplate(), msg));
} builder.body(fromTemplate(config.getBodyTemplate(), msg));
builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg));
builder.body(fromTemplate(this.config.getBodyTemplate(), msg));
String imagesStr = msg.getMetaData().getValue(IMAGES); String imagesStr = msg.getMetaData().getValue(IMAGES);
if (!StringUtils.isEmpty(imagesStr)) { if (!StringUtils.isEmpty(imagesStr)) {
Map<String, String> imgMap = JacksonUtil.fromString(imagesStr, new TypeReference<HashMap<String, String>>() {}); Map<String, String> imgMap = JacksonUtil.fromString(imagesStr, new TypeReference<HashMap<String, String>>() {});
@ -101,11 +102,7 @@ public class TbMsgToEmailNode implements TbNode {
} }
private String fromTemplate(String template, TbMsg msg) { private String fromTemplate(String template, TbMsg msg) {
if (!StringUtils.isEmpty(template)) { return StringUtils.isNotEmpty(template) ? TbNodeUtils.processPattern(template, msg) : null;
return TbNodeUtils.processPattern(template, msg);
} else {
return null;
}
} }
} }

16
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java

@ -19,7 +19,7 @@ import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
@Data @Data
public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { public class TbMsgToEmailNodeConfiguration implements NodeConfiguration<TbMsgToEmailNodeConfiguration> {
private String fromTemplate; private String fromTemplate;
private String toTemplate; private String toTemplate;
@ -28,16 +28,16 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration {
private String subjectTemplate; private String subjectTemplate;
private String bodyTemplate; private String bodyTemplate;
private String isHtmlTemplate; private String isHtmlTemplate;
private String mailBodyType; private String mailBodyType; // Plain Text -> false. HTML - true. Dynamic - value used from isHtmlTemplate.
@Override @Override
public TbMsgToEmailNodeConfiguration defaultConfiguration() { public TbMsgToEmailNodeConfiguration defaultConfiguration() {
TbMsgToEmailNodeConfiguration configuration = new TbMsgToEmailNodeConfiguration(); var configuration = new TbMsgToEmailNodeConfiguration();
configuration.fromTemplate = "info@testmail.org"; configuration.setFromTemplate("info@testmail.org");
configuration.toTemplate = "${userEmail}"; configuration.setToTemplate("${userEmail}");
configuration.subjectTemplate = "Device ${deviceType} temperature high"; configuration.setSubjectTemplate("Device ${deviceType} temperature high");
configuration.bodyTemplate = "Device ${deviceName} has high temperature ${temp}"; configuration.setBodyTemplate("Device ${deviceName} has high temperature $[temperature]");
configuration.mailBodyType = "false"; configuration.setMailBodyType("false");
return configuration; return configuration;
} }
} }

16
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java

@ -43,13 +43,15 @@ import java.util.NoSuchElementException;
type = ComponentType.TRANSFORMATION, type = ComponentType.TRANSFORMATION,
name = "change originator", name = "change originator",
configClazz = TbChangeOriginatorNodeConfiguration.class, configClazz = TbChangeOriginatorNodeConfiguration.class,
nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity/Alarm Originator/Entity by name pattern", nodeDescription = "Change message originator to Tenant/Customer/Related Entity/Alarm Originator/Entity by name pattern.",
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + nodeDetails = "Configuration: <ul><li><strong>Tenant</strong> - use current tenant as new originator.</li>" +
"If multiple related entities are found, only first entity is used as new originator, other entities are discarded.<br/>" + "<li><strong>Customer</strong> - use customer of incoming message originator as new originator. " +
"Alarm Originator might be found only if the original Originator is <code>Alarm</code> entity.<br/>" + "Only for originators with one of the following type: 'User', 'Asset', 'Device' that assigned to customers.</li>" +
"Entity by name pattern lookup only if found only in case original Originator is <code>Alarm</code> entity.<br/> " + "<li><strong>Related Entity</strong> - use related entity as new originator. Lookup based on configured relation direction and relation type. " +
"Lookup of an entity by name pattern requires selection of entity type and name pattern to be specified in the configuration. " + "If multiple related entities are found, only first entity is used as new originator, other entities are discarded.</li>" +
"Allowed entity types to select: 'DEVICE', 'ASSET', 'ENTITY_VIEW', 'EDGE' and 'USER'.<br><br>" + "<li><strong>Alarm Originator</strong> - use alarm originator as new originator. Only if incoming message originator is alarm entity.</li>" +
"<li><strong>Entity by name pattern</strong> - new originator lookup requires selection one of the following entity types: " +
"'Device', 'Asset', 'Entity View', 'Edge' or 'User' and input of name pattern.</li></ul>" +
"Output connections: <code>Success</code>, <code>Failure</code>.", "Output connections: <code>Success</code>, <code>Failure</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbTransformationNodeChangeOriginatorConfig", configDirective = "tbTransformationNodeChangeOriginatorConfig",

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java

@ -44,7 +44,8 @@ import java.util.stream.Collectors;
configClazz = TbCopyKeysNodeConfiguration.class, configClazz = TbCopyKeysNodeConfiguration.class,
nodeDescription = "Copies key-values from message to message metadata or vice-versa.", nodeDescription = "Copies key-values from message to message metadata or vice-versa.",
nodeDetails = "Fetches key-values from message or message metadata based on the keys list specified in the configuration " + nodeDetails = "Fetches key-values from message or message metadata based on the keys list specified in the configuration " +
"and copies them into message metadata or message. Keys that are absent in the fetch source will be ignored. " + "and copies them into message metadata or into message itself in accordance with the fetch source. " +
"Keys that are absent in the fetch source will be ignored. " +
"Use regular expression(s) as a key(s) to copy keys by pattern.<br><br>" + "Use regular expression(s) as a key(s) to copy keys by pattern.<br><br>" +
"Output connections: <code>Success</code>, <code>Failure</code>.", "Output connections: <code>Success</code>, <code>Failure</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java

@ -43,7 +43,7 @@ import java.util.stream.Collectors;
name = "delete key-values", name = "delete key-values",
version = 1, version = 1,
configClazz = TbDeleteKeysNodeConfiguration.class, configClazz = TbDeleteKeysNodeConfiguration.class,
nodeDescription = "Removes key-values from message or metadata.", nodeDescription = "Removes key-values from message or message metadata.",
nodeDetails = "Removes key-values from message or message metadata based on the keys list specified in the configuration. " + nodeDetails = "Removes key-values from message or message metadata based on the keys list specified in the configuration. " +
"Use regular expression(s) as a key(s) to remove keys by pattern.<br><br>" + "Use regular expression(s) as a key(s) to remove keys by pattern.<br><br>" +
"Output connections: <code>Success</code>, <code>Failure</code>.", "Output connections: <code>Success</code>, <code>Failure</code>.",

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java

@ -38,9 +38,8 @@ import java.util.concurrent.ExecutionException;
name = "json path", name = "json path",
configClazz = TbJsonPathNodeConfiguration.class, configClazz = TbJsonPathNodeConfiguration.class,
nodeDescription = "Transforms incoming message body using JSONPath expression.", nodeDescription = "Transforms incoming message body using JSONPath expression.",
nodeDetails = "JSONPath expression specifies a path to an element or a set of elements in a JSON structure. <br/>" + nodeDetails = "JSONPath expression specifies a path to an element or a set of elements in a JSON structure. " +
"<b>'$'</b> represents the root object or array. <br/>" + "<strong><i>$</i></strong> represents the root object or array.<br><br>" +
"If JSONPath expression evaluation failed, incoming message routes via <code>Failure</code> chain.<br><br>" +
"Output connections: <code>Success</code>, <code>Failure</code>.", "Output connections: <code>Success</code>, <code>Failure</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
icon = "functions", icon = "functions",

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java

@ -39,9 +39,9 @@ import java.util.concurrent.ExecutionException;
name = "rename keys", name = "rename keys",
version = 1, version = 1,
configClazz = TbRenameKeysNodeConfiguration.class, configClazz = TbRenameKeysNodeConfiguration.class,
nodeDescription = "Renames message or message metadata key names to the new key names selected in the key mapping.", nodeDescription = "Renames message or message metadata key names.",
nodeDetails = "If the key name that is selected in the key mapping is missed in the " + nodeDetails = "Renames key names in the message or message metadata based on the provided key names mapping. " +
"selected source(message or message metadata), it will be ignored.<br><br>" + "If key to rename doesn't exists in the specified source(message or message metadata) it will be ignored.<br><br>" +
"Output connections: <code>Success</code>, <code>Failure</code>.", "Output connections: <code>Success</code>, <code>Failure</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbTransformationNodeRenameKeysConfig", configDirective = "tbTransformationNodeRenameKeysConfig",

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java

@ -40,9 +40,8 @@ import java.util.concurrent.ExecutionException;
name = "split array msg", name = "split array msg",
configClazz = EmptyNodeConfiguration.class, configClazz = EmptyNodeConfiguration.class,
nodeDescription = "Split array message into several messages", nodeDescription = "Split array message into several messages",
nodeDetails = "Split the array fetched from the message. If the msg data is not a JSON array returns the " nodeDetails = "Split array message and returns inner objects as separate messages. " +
+ "incoming message as outbound message with <code>Failure</code> chain, otherwise returns " "Message metadata and type for each output message will be taken from original message.<br><br>" +
+ "inner objects of the extracted array as separate messages via <code>Success</code> chain.<br><br>" +
"Output connections: <code>Success</code>, <code>Failure</code>.", "Output connections: <code>Success</code>, <code>Failure</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
icon = "content_copy", icon = "content_copy",

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java

@ -34,9 +34,9 @@ import java.util.List;
configClazz = TbTransformMsgNodeConfiguration.class, configClazz = TbTransformMsgNodeConfiguration.class,
nodeDescription = "Change Message payload, Metadata or Message type using JavaScript", nodeDescription = "Change Message payload, Metadata or Message type using JavaScript",
nodeDetails = "JavaScript function receive 3 input parameters <br/> " + nodeDetails = "JavaScript function receive 3 input parameters <br/> " +
"<code>metadata</code> - is a Message metadata.<br/>" + "<code>msg</code> - is a message payload.<br/>" +
"<code>msg</code> - is a Message payload.<br/>" + "<code>metadata</code> - is a message metadata.<br/>" +
"<code>msgType</code> - is a Message type.<br/>" + "<code>msgType</code> - is a message type.<br/>" +
"Should return the following structure:<br/>" + "Should return the following structure:<br/>" +
"<code>{ msg: <i style=\"color: #666;\">new payload</i>,<br/>&nbsp&nbsp&nbspmetadata: <i style=\"color: #666;\">new metadata</i>,<br/>&nbsp&nbsp&nbspmsgType: <i style=\"color: #666;\">new msgType</i> }</code><br/>" + "<code>{ msg: <i style=\"color: #666;\">new payload</i>,<br/>&nbsp&nbsp&nbspmetadata: <i style=\"color: #666;\">new metadata</i>,<br/>&nbsp&nbsp&nbspmsgType: <i style=\"color: #666;\">new msgType</i> }</code><br/>" +
"All fields in resulting object are optional and will be taken from original message if not specified.<br><br>" + "All fields in resulting object are optional and will be taken from original message if not specified.<br><br>" +

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

@ -44,8 +44,7 @@ public class EntitiesCustomerIdAsyncLoader {
} }
private static <T extends HasCustomerId> ListenableFuture<CustomerId> toCustomerIdAsync(TbContext ctx, 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.transform(future, in -> in != null ? in.getCustomerId() : null, ctx.getDbCallbackExecutor());
: Futures.immediateFuture(null), ctx.getDbCallbackExecutor());
} }
} }

189
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java

@ -15,12 +15,15 @@
*/ */
package org.thingsboard.rule.engine.mail; package org.thingsboard.rule.engine.mail;
import com.datastax.oss.driver.api.core.uuid.Uuids; import lombok.Data;
import org.junit.Test; import lombok.RequiredArgsConstructor;
import org.junit.runner.RunWith; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
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.TbEmail; import org.thingsboard.rule.engine.api.TbEmail;
@ -28,85 +31,135 @@ 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.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.RuleNodeId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
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 java.io.IOException; import java.util.UUID;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotSame; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class)
public class TbMsgToEmailNodeTest { public class TbMsgToEmailNodeTest {
private TbMsgToEmailNode emailNode; private static final int EXPECTED_TEMPERATURE = 30;
private static final String EXPECTED_DEVICE_NAME = "TH-001";
private static final String EXPECTED_DEVICE_TYPE = "thermostat";
private static final String EXPECTED_SUBJECT = "Device " + EXPECTED_DEVICE_TYPE + " temperature high";
private static final String EXPECTED_BODY = "Device " + EXPECTED_DEVICE_NAME + " has high temperature " + EXPECTED_TEMPERATURE;
private static final String EXPECTED_TO_EMAIL = "user@email.io";
private static final String DYNAMIC_MAIL_BODY_TYPE = "dynamic";
private EntityId originator;
private TbMsgToEmailNode node;
private TbMsgToEmailNodeConfiguration config;
private TbContext ctxMock;
@BeforeEach
void setUp() throws TbNodeException {
ctxMock = mock(TbContext.class);
originator = new DeviceId(UUID.randomUUID());
config = new TbMsgToEmailNodeConfiguration().defaultConfiguration();
node = new TbMsgToEmailNode();
node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)));
}
@Mock @AfterEach
private TbContext ctx; void tearDown() {
node.destroy();
}
private EntityId originator = new DeviceId(Uuids.timeBased()); @Test
private TbMsgMetaData metaData = new TbMsgMetaData(); void givenDefaultConfig_whenVerify_thenOK() {
private String rawJson = "{\"name\": \"temp\", \"passed\": 5 , \"complex\": {\"val\":12, \"count\":100}}"; // GIVEN-WHEN-THEN
assertThat(config.getFromTemplate()).isEqualTo("info@testmail.org");
assertThat(config.getToTemplate()).isEqualTo("${userEmail}");
assertThat(config.getSubjectTemplate()).isEqualTo("Device ${deviceType} temperature high");
assertThat(config.getBodyTemplate()).isEqualTo("Device ${deviceName} has high temperature $[temperature]");
}
private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); static Stream<MailBodyTypeTestConfig> MailBodyTypeTestConfig() {
private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); return Stream.of(
new MailBodyTypeTestConfig(false, "false", null),
new MailBodyTypeTestConfig(false, null, null),
new MailBodyTypeTestConfig(false, DYNAMIC_MAIL_BODY_TYPE, "false"),
new MailBodyTypeTestConfig(true, DYNAMIC_MAIL_BODY_TYPE, "true"),
new MailBodyTypeTestConfig(true, "true", null)
);
}
@Test @ParameterizedTest
public void msgCanBeConverted() { @MethodSource("MailBodyTypeTestConfig")
initWithScript(); public void givenMailBodyTypeTestConfig_whenOnMsg_thenVerify(MailBodyTypeTestConfig testConfig) throws TbNodeException {
metaData.putValue("username", "oreo"); // GIVEN
metaData.putValue("userEmail", "user@email.io"); String mailBodyType = testConfig.getMailBodyType();
metaData.putValue("name", "temp"); config.setMailBodyType(mailBodyType);
metaData.putValue("passed", "5"); if (DYNAMIC_MAIL_BODY_TYPE.equals(mailBodyType)) {
metaData.putValue("count", "100"); config.setIsHtmlTemplate("${html}");
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); }
node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)));
emailNode.onMsg(ctx, msg);
var md = new TbMsgMetaData();
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); md.putValue("userEmail", EXPECTED_TO_EMAIL);
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); md.putValue("deviceType", EXPECTED_DEVICE_TYPE);
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); md.putValue("deviceName", EXPECTED_DEVICE_NAME);
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); if (testConfig.getMdValue() != null) {
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); md.putValue("html", testConfig.getMdValue());
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); }
var msgDataStr = "{\"temperature\": " + EXPECTED_TEMPERATURE + "}";
assertEquals(TbMsgType.SEND_EMAIL, typeCaptor.getValue()); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, md, msgDataStr);
assertEquals(originator, originatorCaptor.getValue());
assertEquals("oreo", metadataCaptor.getValue().getValue("username")); // WHEN
assertNotSame(metaData, metadataCaptor.getValue()); node.onMsg(ctxMock, msg);
TbEmail actual = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), TbEmail.class); // THEN
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
TbEmail expected = TbEmail.builder() var typeCaptor = ArgumentCaptor.forClass(TbMsgType.class);
.from("test@mail.org") var originatorCaptor = ArgumentCaptor.forClass(EntityId.class);
.to("user@email.io") var metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
.subject("Hi oreo there") var dataCaptor = ArgumentCaptor.forClass(String.class);
.body("temp is to high. Current 5 and 100") verify(ctxMock, times(1)).transformMsg(
msgCaptor.capture(),
typeCaptor.capture(),
originatorCaptor.capture(),
metadataCaptor.capture(),
dataCaptor.capture()
);
verify(ctxMock, never()).tellFailure(any(), any());
Assertions.assertEquals(TbMsgType.SEND_EMAIL, typeCaptor.getValue());
Assertions.assertEquals(originator, originatorCaptor.getValue());
Assertions.assertNotSame(md, metadataCaptor.getValue());
var actual = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), TbEmail.class);
var expected = getExpectedTbEmail(testConfig.isExpectedHtmlValue());
Assertions.assertEquals(expected, actual);
}
private TbEmail getExpectedTbEmail(boolean html) {
return TbEmail.builder()
.from(config.getFromTemplate())
.to(EXPECTED_TO_EMAIL)
.subject(EXPECTED_SUBJECT)
.body(EXPECTED_BODY)
.html(html)
.build(); .build();
assertEquals(expected, actual);
} }
private void initWithScript() { @Data
try { @RequiredArgsConstructor
TbMsgToEmailNodeConfiguration config = new TbMsgToEmailNodeConfiguration(); static class MailBodyTypeTestConfig {
config.setFromTemplate("test@mail.org"); private final boolean expectedHtmlValue;
config.setToTemplate("${userEmail}"); private final String mailBodyType;
config.setSubjectTemplate("Hi ${username} there"); private final String mdValue;
config.setBodyTemplate("${name} is to high. Current ${passed} and ${count}");
config.setMailBodyType("false");
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
emailNode = new TbMsgToEmailNode();
emailNode.init(ctx, nodeConfiguration);
} catch (TbNodeException ex) {
throw new IllegalStateException(ex);
}
} }
} }

Loading…
Cancel
Save