From bff2ed2898e62496878f534f80febb5b2925d2fa Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Fri, 5 Jul 2024 18:02:30 +0300 Subject: [PATCH 1/3] added tests for generator node and set default value for entity type --- .../rule/engine/debug/TbMsgGeneratorNode.java | 41 +++- .../TbMsgGeneratorNodeConfiguration.java | 1 + .../engine/debug/TbMsgGeneratorNodeTest.java | 214 +++++++++++++++++- 3 files changed, 245 insertions(+), 11 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 196c0fa036..f25f280749 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -42,6 +43,8 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; +import java.util.EnumSet; +import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -54,7 +57,7 @@ import static org.thingsboard.server.common.data.DataConstants.QUEUE_NAME; type = ComponentType.ACTION, name = "generator", configClazz = TbMsgGeneratorNodeConfiguration.class, - version = 1, + version = 2, hasQueueName = true, nodeDescription = "Periodically generates messages", nodeDetails = "Generates messages with configurable period. Javascript function used for message generation.", @@ -66,6 +69,9 @@ import static org.thingsboard.server.common.data.DataConstants.QUEUE_NAME; public class TbMsgGeneratorNode implements TbNode { + private static final Set supportedEntityTypes = EnumSet.of(EntityType.DEVICE, EntityType.ASSET, EntityType.ENTITY_VIEW, + EntityType.TENANT, EntityType.CUSTOMER, EntityType.USER, EntityType.DASHBOARD, EntityType.EDGE, EntityType.RULE_NODE); + private TbMsgGeneratorNodeConfiguration config; private ScriptEngine scriptEngine; private long delay; @@ -83,12 +89,10 @@ public class TbMsgGeneratorNode implements TbNode { this.delay = TimeUnit.SECONDS.toMillis(config.getPeriodInSeconds()); this.currentMsgCount = 0; this.queueName = ctx.getQueueName(); - if (!StringUtils.isEmpty(config.getOriginatorId())) { - originatorId = EntityIdFactory.getByTypeAndUuid(config.getOriginatorType(), config.getOriginatorId()); - ctx.checkTenantEntity(originatorId); - } else { - originatorId = ctx.getSelfId(); + if (!supportedEntityTypes.contains(config.getOriginatorType())) { + throw new TbNodeException("Originator type '" + config.getOriginatorType() + "' is not supported.", true); } + originatorId = getOriginatorId(ctx); log.debug("[{}] Initializing generator with config {}", originatorId, configuration); updateGeneratorState(ctx); } @@ -173,6 +177,19 @@ public class TbMsgGeneratorNode implements TbNode { return msg != null ? msg.getCustomerId() : null; } + private EntityId getOriginatorId(TbContext ctx) throws TbNodeException { + if (EntityType.RULE_NODE.equals(config.getOriginatorType())) { + return ctx.getSelfId(); + } else if (EntityType.TENANT.equals(config.getOriginatorType())) { + return ctx.getTenantId(); + } else if (!StringUtils.isEmpty(config.getOriginatorId())) { + var entityId = EntityIdFactory.getByTypeAndUuid(config.getOriginatorType(), config.getOriginatorId()); + ctx.checkTenantEntity(entityId); + return entityId; + } + return ctx.getSelfId(); + } + @Override public void destroy() { log.debug("[{}] Stopping generator", originatorId); @@ -195,6 +212,18 @@ public class TbMsgGeneratorNode implements TbNode { hasChanges = true; ((ObjectNode) oldConfiguration).remove(QUEUE_NAME); } + case 1: + String originatorType = "originatorType"; + if (oldConfiguration.has(originatorType)) { + var origType = oldConfiguration.get(originatorType); + if (origType.isNull()) { + ((ObjectNode) oldConfiguration).put(originatorType, EntityType.RULE_NODE.name()); + hasChanges = true; + } else if (EntityType.TENANT.name().equals(origType.asText())) { + ((ObjectNode) oldConfiguration).putNull("originatorId"); + hasChanges = true; + } + } break; default: break; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java index 017b6e4381..2160ec450c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java @@ -42,6 +42,7 @@ public class TbMsgGeneratorNodeConfiguration implements NodeConfiguration node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) + .isInstanceOf(TbNodeException.class) + .hasMessage("Originator type 'NOTIFICATION' is not supported."); + } + + @ParameterizedTest + @MethodSource + public void givenOriginatorEntityType_whenInit_thenVerifyOriginatorId(EntityType entityType, + String originatorId, + EntityId expectedOriginatorId, + Consumer mockCtx) throws TbNodeException { + // GIVEN + config.setOriginatorType(entityType); + config.setOriginatorId(originatorId); + + mockCtx.accept(ctxMock); + + // WHEN + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // THEN + then(ctxMock).should().isLocalEntity(expectedOriginatorId); + } + + private static Stream givenOriginatorEntityType_whenInit_thenVerifyOriginatorId() { + return Stream.of( + Arguments.of(EntityType.RULE_NODE, null, RULE_NODE_ID, + (Consumer) ctxMock -> given(ctxMock.getSelfId()).willReturn(RULE_NODE_ID)), + Arguments.of(EntityType.TENANT, null, TenantId.fromUUID(UUID.fromString("c7f7b865-3e4c-40d3-b333-a7ec2fd871ee")), + (Consumer) ctxMock -> given(ctxMock.getTenantId()).willReturn(TenantId.fromUUID(UUID.fromString("c7f7b865-3e4c-40d3-b333-a7ec2fd871ee")))), + Arguments.of(EntityType.ASSET, "cbb9a3d3-02f1-482b-90ab-2417dcd35f20", new AssetId(UUID.fromString("cbb9a3d3-02f1-482b-90ab-2417dcd35f20")), + (Consumer) ctxMock -> given(ctxMock.getQueueName()).willReturn("Main")) + ); + } + + @Test + public void givenMsgCountAndDelay_whenInit_thenVerifyInvocationOfOnMsgMethod() throws TbNodeException, InterruptedException { + // GIVEN + var awaitTellSelfLatch = new CountDownLatch(5); + config.setMsgCount(5); + + given(ctxMock.getSelfId()).willReturn(RULE_NODE_ID); + given(ctxMock.isLocalEntity(any())).willReturn(true); + given(ctxMock.createScriptEngine(any(), any(), any(), any(), any())).willReturn(scriptEngineMock); + + // creation of tickMsg + TbMsg tickMsg = TbMsg.newMsg(TbMsgType.GENERATOR_NODE_SELF_MSG, RULE_NODE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + given(ctxMock.newMsg(null, TbMsgType.GENERATOR_NODE_SELF_MSG, RULE_NODE_ID, null, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING)).willReturn(tickMsg); + + // invocation of tellSelf() method + willAnswer(invocationOnMock -> { + executorService.execute(() -> { + node.onMsg(ctxMock, invocationOnMock.getArgument(0)); + awaitTellSelfLatch.countDown(); + }); + return null; + }).given(ctxMock).tellSelf(any(), any(Long.class)); + + // creation of first message + TbMsg firstMsg = TbMsg.newMsg(TbMsg.EMPTY_STRING, RULE_NODE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + given(ctxMock.newMsg(null, TbMsg.EMPTY_STRING, RULE_NODE_ID, null, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT)).willReturn(firstMsg); + + // creation of generated message + TbMsgMetaData metaData = new TbMsgMetaData(Map.of("data", "40")); + TbMsg generatedMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, RULE_NODE_ID, metaData, "{ \"temp\": 42, \"humidity\": 77 }"); + given(scriptEngineMock.executeGenerateAsync(any())).willReturn(Futures.immediateFuture(generatedMsg)); + + // creation of prev message + TbMsg prevMsg = TbMsg.newMsg(generatedMsg.getType(), RULE_NODE_ID, generatedMsg.getMetaData(), generatedMsg.getData()); + given(ctxMock.newMsg(null, generatedMsg.getType(), RULE_NODE_ID, null, generatedMsg.getMetaData(), generatedMsg.getData())).willReturn(prevMsg); + + // WHEN + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + awaitTellSelfLatch.await(); + + // THEN + + // verify invocation of tellSelf() method + ArgumentCaptor actualTickMsg = ArgumentCaptor.forClass(TbMsg.class); + then(ctxMock).should(times(6)).tellSelf(actualTickMsg.capture(), any(Long.class)); + assertThat(actualTickMsg.getValue()).usingRecursiveComparison().ignoringFields("ctx").isEqualTo(tickMsg); + + // verify invocation of enqueueForTellNext() method + ArgumentCaptor actualGeneratedMsg = ArgumentCaptor.forClass(TbMsg.class); + then(ctxMock).should(times(5)).enqueueForTellNext(actualGeneratedMsg.capture(), eq(TbNodeConnectionType.SUCCESS)); + assertThat(actualGeneratedMsg.getValue()).usingRecursiveComparison().ignoringFields("ctx", "ts", "id").isEqualTo(generatedMsg); + } + + @Test + public void givenOriginatorIsNotLocalEntity_whenInit_thenDestroy() throws TbNodeException { + // GIVEN + config.setOriginatorType(EntityType.DEVICE); + config.setOriginatorId("2e8b77f1-ee33-4207-a3d7-556fb16e0151"); + ReflectionTestUtils.setField(node, "initialized", new AtomicBoolean(true)); + + given(ctxMock.isLocalEntity(any())).willReturn(false); + + // WHEN + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // THEN + then(node).should().destroy(); + } + // Rule nodes upgrade private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { return Stream.of( @@ -32,22 +225,33 @@ public class TbMsgGeneratorNodeTest extends AbstractRuleNodeUpgradeTest { Arguments.of(0, "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":null, \"queueName\":null, \"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}", true, - "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":null, \"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}"), + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":\"RULE_NODE\", \"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}"), // default config for version 0 with queueName Arguments.of(0, "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":null, \"queueName\":\"Main\", \"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}", true, - "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":null, \"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}"), + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":\"RULE_NODE\", \"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}"), // default config for version 1 with upgrade from version 0 Arguments.of(0, "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":null, \"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}", - false, - "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":null, \"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}") + true, + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":\"RULE_NODE\", \"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}"), + // config for version 2 with upgrade from version 1 (originatorType is not selected) + Arguments.of(1, + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":null,\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}", + true, + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":\"RULE_NODE\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}"), + // config for version 2 with upgrade from version 1 (originatorType is TENANT) + Arguments.of(1, + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":\"ae540d15-7ef6-41d4-9176-bf788324a5c3\",\"originatorType\":\"TENANT\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}", + true, + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":\"TENANT\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}") ); } @Override protected TbNode getTestNode() { - return spy(TbMsgGeneratorNode.class); + return node; } + } From a40ce3305e9afba5b18852ee80ea26cde71c6989 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Tue, 9 Jul 2024 11:14:25 +0300 Subject: [PATCH 2/3] added validation for originatorId and changed upgrade script --- .../rule/engine/debug/TbMsgGeneratorNode.java | 20 +-- .../engine/debug/TbMsgGeneratorNodeTest.java | 114 ++++++++++++------ 2 files changed, 89 insertions(+), 45 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index f25f280749..fed7b88bad 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -180,14 +180,16 @@ public class TbMsgGeneratorNode implements TbNode { private EntityId getOriginatorId(TbContext ctx) throws TbNodeException { if (EntityType.RULE_NODE.equals(config.getOriginatorType())) { return ctx.getSelfId(); - } else if (EntityType.TENANT.equals(config.getOriginatorType())) { + } + if (EntityType.TENANT.equals(config.getOriginatorType())) { return ctx.getTenantId(); - } else if (!StringUtils.isEmpty(config.getOriginatorId())) { - var entityId = EntityIdFactory.getByTypeAndUuid(config.getOriginatorType(), config.getOriginatorId()); - ctx.checkTenantEntity(entityId); - return entityId; } - return ctx.getSelfId(); + if (StringUtils.isBlank(config.getOriginatorId())) { + throw new TbNodeException("Originator entity must be selected.", true); + } + var entityId = EntityIdFactory.getByTypeAndUuid(config.getOriginatorType(), config.getOriginatorId()); + ctx.checkTenantEntity(entityId); + return entityId; } @Override @@ -216,12 +218,10 @@ public class TbMsgGeneratorNode implements TbNode { String originatorType = "originatorType"; if (oldConfiguration.has(originatorType)) { var origType = oldConfiguration.get(originatorType); - if (origType.isNull()) { + var origId = oldConfiguration.get("originatorId"); + if (origType.isNull() || origType.asText().isEmpty() || origId.isNull() || origId.asText().isEmpty()) { ((ObjectNode) oldConfiguration).put(originatorType, EntityType.RULE_NODE.name()); hasChanges = true; - } else if (EntityType.TENANT.name().equals(origType.asText())) { - ((ObjectNode) oldConfiguration).putNull("originatorId"); - hasChanges = true; } } break; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeTest.java index 059f7b2674..f387b5f43f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeTest.java @@ -22,7 +22,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -36,7 +37,6 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; @@ -45,30 +45,38 @@ import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; +import java.util.EnumSet; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.BDDMockito.willAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @ExtendWith(MockitoExtension.class) public class TbMsgGeneratorNodeTest extends AbstractRuleNodeUpgradeTest { - private static final RuleNodeId RULE_NODE_ID = new RuleNodeId(UUID.fromString("1c649392-1f53-4377-b12f-1ba172611746")); + private static final Set supportedEntityTypes = EnumSet.of(EntityType.DEVICE, EntityType.ASSET, EntityType.ENTITY_VIEW, + EntityType.TENANT, EntityType.CUSTOMER, EntityType.USER, EntityType.DASHBOARD, EntityType.EDGE, EntityType.RULE_NODE); + + private final RuleNodeId RULE_NODE_ID = new RuleNodeId(UUID.fromString("1c649392-1f53-4377-b12f-1ba172611746")); + private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("4470dfc2-f621-42b2-b82c-b5776d424140")); private final ThingsBoardThreadFactory factory = ThingsBoardThreadFactory.forName("msg-generator-node-test"); @@ -107,45 +115,71 @@ public class TbMsgGeneratorNodeTest extends AbstractRuleNodeUpgradeTest { assertThat(config.getTbelScript()).isEqualTo(TbMsgGeneratorNodeConfiguration.DEFAULT_SCRIPT); } - @Test - public void givenUnsupportedEntityType_whenInit_thenThrowsException() { + @ParameterizedTest + @EnumSource(EntityType.class) + public void givenEntityType_whenInit_thenVerifyException(EntityType entityType) { // GIVEN - config.setOriginatorType(EntityType.NOTIFICATION); + config.setOriginatorType(entityType); + var configuration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); // WHEN-THEN - assertThatThrownBy(() -> node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) - .isInstanceOf(TbNodeException.class) - .hasMessage("Originator type 'NOTIFICATION' is not supported."); + if (entityType == EntityType.RULE_NODE || entityType == EntityType.TENANT) { + assertThatNoException().isThrownBy(() -> node.init(ctxMock, configuration)); + } else { + String errorMsg = supportedEntityTypes.contains(entityType) ? "Originator entity must be selected." + : "Originator type '" + entityType + "' is not supported."; + assertThatThrownBy(() -> node.init(ctxMock, configuration)) + .isInstanceOf(TbNodeException.class) + .hasMessage(errorMsg); + } } - @ParameterizedTest - @MethodSource - public void givenOriginatorEntityType_whenInit_thenVerifyOriginatorId(EntityType entityType, - String originatorId, - EntityId expectedOriginatorId, - Consumer mockCtx) throws TbNodeException { + @Test + public void givenOriginatorEntityTypeIsRuleNode_whenInit_thenVerifyOriginatorId() throws TbNodeException { // GIVEN - config.setOriginatorType(entityType); - config.setOriginatorId(originatorId); + config.setOriginatorType(EntityType.RULE_NODE); - mockCtx.accept(ctxMock); + given(ctxMock.getSelfId()).willReturn(RULE_NODE_ID); // WHEN node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // THEN - then(ctxMock).should().isLocalEntity(expectedOriginatorId); + then(ctxMock).should().isLocalEntity(RULE_NODE_ID); } - private static Stream givenOriginatorEntityType_whenInit_thenVerifyOriginatorId() { - return Stream.of( - Arguments.of(EntityType.RULE_NODE, null, RULE_NODE_ID, - (Consumer) ctxMock -> given(ctxMock.getSelfId()).willReturn(RULE_NODE_ID)), - Arguments.of(EntityType.TENANT, null, TenantId.fromUUID(UUID.fromString("c7f7b865-3e4c-40d3-b333-a7ec2fd871ee")), - (Consumer) ctxMock -> given(ctxMock.getTenantId()).willReturn(TenantId.fromUUID(UUID.fromString("c7f7b865-3e4c-40d3-b333-a7ec2fd871ee")))), - Arguments.of(EntityType.ASSET, "cbb9a3d3-02f1-482b-90ab-2417dcd35f20", new AssetId(UUID.fromString("cbb9a3d3-02f1-482b-90ab-2417dcd35f20")), - (Consumer) ctxMock -> given(ctxMock.getQueueName()).willReturn("Main")) - ); + @Test + public void givenOriginatorEntityTypeIsTenant_whenInit_thenVerifyOriginatorId() throws TbNodeException { + // GIVEN + config.setOriginatorType(EntityType.TENANT); + + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + + // WHEN + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // THEN + then(ctxMock).should().isLocalEntity(TENANT_ID); + } + + @ParameterizedTest + @ValueSource(strings = {"ASSET", "DEVICE", "ENTITY_VIEW", "CUSTOMER", "USER", "DASHBOARD", "EDGE"}) + public void givenOriginatorEntityType_whenInit_thenVerifyOriginatorId(String entityTypeStr) throws TbNodeException { + // GIVEN + EntityType entityType = EntityType.valueOf(entityTypeStr); + config.setOriginatorType(entityType); + UUID entityId = UUID.randomUUID(); + config.setOriginatorId(entityId.toString()); + + // WHEN + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // THEN + ArgumentCaptor actualEntityIdCaptor = ArgumentCaptor.forClass(EntityId.class); + then(ctxMock).should().isLocalEntity(actualEntityIdCaptor.capture()); + EntityId actualEntityId = actualEntityIdCaptor.getValue(); + assertThat(actualEntityId.getEntityType()).isEqualTo(entityType); + assertThat(actualEntityId.getId()).isEqualTo(entityId); } @Test @@ -191,6 +225,9 @@ public class TbMsgGeneratorNodeTest extends AbstractRuleNodeUpgradeTest { // THEN + //verify creation of prev message + then(ctxMock).should(times(1)).newMsg(null, TbMsg.EMPTY_STRING, RULE_NODE_ID, null, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + // verify invocation of tellSelf() method ArgumentCaptor actualTickMsg = ArgumentCaptor.forClass(TbMsg.class); then(ctxMock).should(times(6)).tellSelf(actualTickMsg.capture(), any(Long.class)); @@ -199,11 +236,11 @@ public class TbMsgGeneratorNodeTest extends AbstractRuleNodeUpgradeTest { // verify invocation of enqueueForTellNext() method ArgumentCaptor actualGeneratedMsg = ArgumentCaptor.forClass(TbMsg.class); then(ctxMock).should(times(5)).enqueueForTellNext(actualGeneratedMsg.capture(), eq(TbNodeConnectionType.SUCCESS)); - assertThat(actualGeneratedMsg.getValue()).usingRecursiveComparison().ignoringFields("ctx", "ts", "id").isEqualTo(generatedMsg); + assertThat(actualGeneratedMsg.getValue()).usingRecursiveComparison().ignoringFields("ctx").isEqualTo(prevMsg); } @Test - public void givenOriginatorIsNotLocalEntity_whenInit_thenDestroy() throws TbNodeException { + public void givenOriginatorIsNotLocalEntity_whenOnPartitionChangeMsg_thenDestroy() { // GIVEN config.setOriginatorType(EntityType.DEVICE); config.setOriginatorId("2e8b77f1-ee33-4207-a3d7-556fb16e0151"); @@ -212,7 +249,8 @@ public class TbMsgGeneratorNodeTest extends AbstractRuleNodeUpgradeTest { given(ctxMock.isLocalEntity(any())).willReturn(false); // WHEN - node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + var partitionChangeMsgMock = mock(PartitionChangeMsg.class); + node.onPartitionChangeMsg(ctxMock, partitionChangeMsgMock); // THEN then(node).should().destroy(); @@ -241,11 +279,17 @@ public class TbMsgGeneratorNodeTest extends AbstractRuleNodeUpgradeTest { "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":null,\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}", true, "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":\"RULE_NODE\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}"), - // config for version 2 with upgrade from version 1 (originatorType is TENANT) + // config for version 2 with upgrade from version 1 (originatorId is not selected) Arguments.of(1, - "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":\"ae540d15-7ef6-41d4-9176-bf788324a5c3\",\"originatorType\":\"TENANT\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}", + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":\"DEVICE\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}", true, - "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":\"TENANT\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}") + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":null,\"originatorType\":\"RULE_NODE\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}"), + // config for version 2 with upgrade from version 1 (originatorType and originatorId are selected) + Arguments.of(1, + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":\"92b8e1ce-1b58-4f23-b127-7a0a031b0677\",\"originatorType\":\"DEVICE\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}", + false, + "{\"msgCount\":0,\"periodInSeconds\":1,\"originatorId\":\"92b8e1ce-1b58-4f23-b127-7a0a031b0677\",\"originatorType\":\"DEVICE\",\"scriptLang\":\"TBEL\",\"jsScript\":\"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\",\"tbelScript\": \"var msg = { temp: 42, humidity: 77 };\\nvar metadata = { data: 40 };\\nvar msgType = \\\"POST_TELEMETRY_REQUEST\\\";\\n\\nreturn { msg: msg, metadata: metadata, msgType: msgType };\"}") + ); } From c7005971ad614e945887b603e2b2b2381b32d2e3 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Tue, 9 Jul 2024 12:41:33 +0300 Subject: [PATCH 3/3] refactored tests --- .../rule/engine/debug/TbMsgGeneratorNode.java | 15 ++++++++------- .../engine/debug/TbMsgGeneratorNodeTest.java | 16 ++++++++-------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index fed7b88bad..c7bf1423f8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -216,13 +216,14 @@ public class TbMsgGeneratorNode implements TbNode { } case 1: String originatorType = "originatorType"; - if (oldConfiguration.has(originatorType)) { - var origType = oldConfiguration.get(originatorType); - var origId = oldConfiguration.get("originatorId"); - if (origType.isNull() || origType.asText().isEmpty() || origId.isNull() || origId.asText().isEmpty()) { - ((ObjectNode) oldConfiguration).put(originatorType, EntityType.RULE_NODE.name()); - hasChanges = true; - } + String originatorId = "originatorId"; + boolean hasType = oldConfiguration.hasNonNull(originatorType); + boolean hasOriginatorId = oldConfiguration.hasNonNull(originatorId) && + StringUtils.isNotBlank(oldConfiguration.get(originatorId).asText()); + boolean hasOriginatorFields = hasType && hasOriginatorId; + if (!hasOriginatorFields) { + hasChanges = true; + ((ObjectNode) oldConfiguration).put(originatorType, EntityType.RULE_NODE.name()); } break; default: diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeTest.java index f387b5f43f..c1a2282083 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeTest.java @@ -38,6 +38,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -130,7 +131,9 @@ public class TbMsgGeneratorNodeTest extends AbstractRuleNodeUpgradeTest { : "Originator type '" + entityType + "' is not supported."; assertThatThrownBy(() -> node.init(ctxMock, configuration)) .isInstanceOf(TbNodeException.class) - .hasMessage(errorMsg); + .hasMessage(errorMsg) + .extracting(e -> ((TbNodeException) e).isUnrecoverable()) + .isEqualTo(true); } } @@ -168,18 +171,15 @@ public class TbMsgGeneratorNodeTest extends AbstractRuleNodeUpgradeTest { // GIVEN EntityType entityType = EntityType.valueOf(entityTypeStr); config.setOriginatorType(entityType); - UUID entityId = UUID.randomUUID(); - config.setOriginatorId(entityId.toString()); + String entityIdStr = "5751f55e-089e-4be0-b10a-dd942053dcf0"; + config.setOriginatorId(entityIdStr); + EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityIdStr); // WHEN node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // THEN - ArgumentCaptor actualEntityIdCaptor = ArgumentCaptor.forClass(EntityId.class); - then(ctxMock).should().isLocalEntity(actualEntityIdCaptor.capture()); - EntityId actualEntityId = actualEntityIdCaptor.getValue(); - assertThat(actualEntityId.getEntityType()).isEqualTo(entityType); - assertThat(actualEntityId.getId()).isEqualTo(entityId); + then(ctxMock).should().isLocalEntity(entityId); } @Test