From f61b2d0fb2133a25cde8973e8e816efeeb148f3e Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 15 Nov 2023 15:30:12 +0200 Subject: [PATCH 01/10] fix default rule chains --- .../data/json/edge/rule_chains/edge_root_rule_chain.json | 6 +++--- .../json/tenant/device_profile/rule_chain_template.json | 6 +++--- .../main/data/json/tenant/rule_chains/root_rule_chain.json | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json index 790c9d36c7..99625b3188 100644 --- a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json +++ b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json @@ -51,9 +51,9 @@ "configurationVersion": 1, "configuration": { "scope": "CLIENT_SCOPE", - "notifyDevice": "false", - "sendAttributesUpdatedNotification": "false", - "updateAttributesOnlyOnValueChange": "true" + "notifyDevice": false, + "sendAttributesUpdatedNotification": false, + "updateAttributesOnlyOnValueChange": true }, "externalId": null }, diff --git a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json index d03fcd0b7b..a6fb16af3d 100644 --- a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json +++ b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json @@ -35,9 +35,9 @@ "configurationVersion": 1, "configuration": { "scope": "CLIENT_SCOPE", - "notifyDevice": "false", - "sendAttributesUpdatedNotification": "false", - "updateAttributesOnlyOnValueChange": "true" + "notifyDevice": false, + "sendAttributesUpdatedNotification": false, + "updateAttributesOnlyOnValueChange": true } }, { diff --git a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json index 419d30ed4c..a96b1ba01f 100644 --- a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json +++ b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json @@ -34,9 +34,9 @@ "configurationVersion": 1, "configuration": { "scope": "CLIENT_SCOPE", - "notifyDevice": "false", - "sendAttributesUpdatedNotification": "false", - "updateAttributesOnlyOnValueChange": "true" + "notifyDevice": false, + "sendAttributesUpdatedNotification": false, + "updateAttributesOnlyOnValueChange": true } }, { From cb1b817b49f2e3c187591d1888ae777fac9138e0 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 16 Nov 2023 15:28:24 +0200 Subject: [PATCH 02/10] added backward-compatibility test for notifyDevice feature --- .../engine/telemetry/TbMsgAttributesNode.java | 4 +- .../telemetry/TbMsgAttributesNodeTest.java | 77 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 9bbee45477..2ebb0193f9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -43,7 +43,9 @@ import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.DataConstants.*; +import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE; +import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; +import static org.thingsboard.server.common.data.DataConstants.SCOPE; import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; @Slf4j diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java index 818c1bb97f..3aeff8df7d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java @@ -19,8 +19,20 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; 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.mockito.ArgumentCaptor; +import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; +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.DataConstants; +import org.thingsboard.server.common.data.id.DeviceId; +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.BooleanDataEntry; @@ -28,22 +40,38 @@ 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.msg.TbMsgType; import org.thingsboard.server.common.data.util.TbPair; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.willCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; @Slf4j +@ExtendWith(MockitoExtension.class) class TbMsgAttributesNodeTest { + private static final DeviceId ORIGINATOR_ID = new DeviceId(UUID.randomUUID()); + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + final String updateAttributesOnlyOnValueChangeKey = "updateAttributesOnlyOnValueChange"; @Test @@ -103,6 +131,55 @@ class TbMsgAttributesNodeTest { assertThat(filtered).containsExactlyInAnyOrderElementsOf(expected); } + // Notify device backward-compatibility test arguments + private static Stream provideNotifyDeviceMdValue() { + return Stream.of( + Arguments.of(null, true), + Arguments.of(true, true), + Arguments.of(false, false) + ); + } + + // Notify device backward-compatibility test + @ParameterizedTest + @MethodSource("provideNotifyDeviceMdValue") + void testNotifyDeviceArgumentForSaveAndNotify(Boolean mdValue, boolean expectedArgumentValue) throws TbNodeException { + var ctxMock = mock(TbContext.class); + var telemetryServiceMock = mock(RuleEngineTelemetryService.class); + TbMsgAttributesNode node = spy(TbMsgAttributesNode.class); + ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); + defaultConfig.put("notifyDevice", false); + var tbNodeConfiguration = new TbNodeConfiguration(defaultConfig); + + assertThat(defaultConfig.has("notifyDevice")).as("pre condition has notifyDevice").isTrue(); + + when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); + willCallRealMethod().given(node).init(any(TbContext.class), any(TbNodeConfiguration.class)); + willCallRealMethod().given(node).saveAttr(any(), eq(ctxMock), any(TbMsg.class), anyString(), anyBoolean()); + + node.init(ctxMock, tbNodeConfiguration); + + TbMsgMetaData md = new TbMsgMetaData(); + if (mdValue != null) { + md.putValue(NOTIFY_DEVICE_METADATA_KEY, mdValue.toString()); + } + // dummy list with one ts kv to pass the empty list check. + var testTbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR_ID, md, TbMsg.EMPTY_STRING); + List testAttrList = List.of(new BaseAttributeKvEntry(0L, new StringDataEntry("testKey", "testValue"))); + + node.saveAttr(testAttrList, ctxMock, testTbMsg, DataConstants.SHARED_SCOPE, false); + + ArgumentCaptor notifyDeviceCaptor = ArgumentCaptor.forClass(Boolean.class); + + verify(telemetryServiceMock, times(1)).saveAndNotify( + eq(TENANT_ID), eq(ORIGINATOR_ID), eq(DataConstants.SHARED_SCOPE), + eq(testAttrList), notifyDeviceCaptor.capture(), any() + ); + boolean notifyDevice = notifyDeviceCaptor.getValue(); + assertThat(notifyDevice).isEqualTo(expectedArgumentValue); + } + @Test void testUpgrade_fromVersion0() throws TbNodeException { From 9d97d0c0b5e109d2e653527fbc7102c4674a3c08 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 16 Nov 2023 15:47:25 +0200 Subject: [PATCH 03/10] save attributes node: fixed escaped boolean parameters && minor refactoring to notify device logic --- .../install/ThingsboardInstallService.java | 3 + .../install/SqlDatabaseUpgradeService.java | 24 ++++ .../engine/telemetry/TbMsgAttributesNode.java | 40 +++++- .../TbMsgAttributesNodeConfiguration.java | 4 +- .../telemetry/TbMsgAttributesNodeTest.java | 132 ++++++++++++++++-- 5 files changed, 179 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 0730bca8f8..7c8f9de0a8 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -266,6 +266,9 @@ public class ThingsboardInstallService { log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.6.0"); dataUpdateService.updateData("3.6.0"); + case "3.6.1": + log.info("Upgrading ThingsBoard from version 3.6.1 to 3.6.2 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.6.1"); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index c02b7e9cc1..5b1904953b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -792,6 +792,30 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; + case "3.6.1": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + if (isOldSchema(conn, 3006001)) { + log.info("Updating schema ..."); + try { + conn.createStatement().execute("UPDATE rule_node SET " + + "configuration = (configuration::jsonb || jsonb_build_object(" + + "'notifyDevice', CASE WHEN configuration::jsonb ->> 'notifyDevice' = 'false' THEN false ELSE true END, " + + "'sendAttributesUpdatedNotification', CASE WHEN configuration::jsonb ->> 'sendAttributesUpdatedNotification' = 'true' THEN true ELSE false END, " + + "'updateAttributesOnlyOnValueChange', CASE WHEN configuration::jsonb ->> 'updateAttributesOnlyOnValueChange' = 'true' THEN true ELSE false END" + + ")::jsonb)::varchar, " + + "configuration_version = 2 " + + "WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version = 1;"); + } catch (Exception e) { + } + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3006002;"); + log.info("Schema updated to version 3.6.2."); + } else { + log.info("Skip schema re-update to version 3.6.2. Use env flag 'SKIP_SCHEMA_VERSION_CHECK' to force the re-update."); + } + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 2ebb0193f9..59ff53557b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.kv.KvEntry; 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.TbMsgMetaData; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import java.util.ArrayList; @@ -53,7 +54,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R type = ComponentType.ACTION, name = "save attributes", configClazz = TbMsgAttributesNodeConfiguration.class, - version = 1, + version = 2, nodeDescription = "Saves attributes data", nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type. " + "If upsert(update/insert) operation is completed successfully rule node will send the incoming message via Success chain, otherwise, Failure chain is used. " + @@ -66,15 +67,15 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R ) public class TbMsgAttributesNode implements TbNode { + static final String NOTIFY_DEVICE_KEY = "notifyDevice"; + static final String SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY = "sendAttributesUpdatedNotification"; static final String UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY = "updateAttributesOnlyOnValueChange"; + private TbMsgAttributesNodeConfiguration config; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbMsgAttributesNodeConfiguration.class); - if (config.getNotifyDevice() == null) { - config.setNotifyDevice(true); - } } @Override @@ -119,7 +120,7 @@ public class TbMsgAttributesNode implements TbNode { msg.getOriginator(), scope, attributes, - checkNotifyDevice(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY)), + config.isNotifyDevice() || checkNotifyDeviceMdValue(msg.getMetaData()), sendAttributesUpdateNotification ? new AttributesUpdateNodeCallback(ctx, msg, scope, attributes) : new TelemetryNodeCallback(ctx, msg) @@ -148,8 +149,10 @@ public class TbMsgAttributesNode implements TbNode { return config.isSendAttributesUpdatedNotification() && !CLIENT_SCOPE.equals(scope); } - private boolean checkNotifyDevice(String notifyDeviceMdValue) { - return config.getNotifyDevice() || StringUtils.isEmpty(notifyDeviceMdValue) || Boolean.parseBoolean(notifyDeviceMdValue); + private boolean checkNotifyDeviceMdValue(TbMsgMetaData md) { + var notifyDeviceMdStr = md.getValue(NOTIFY_DEVICE_METADATA_KEY); + // Check for empty string for backward-compatibility. A while ago node always notified devices. + return StringUtils.isEmpty(notifyDeviceMdStr) || Boolean.parseBoolean(notifyDeviceMdStr); } private String getScope(String mdScopeValue) { @@ -168,6 +171,13 @@ public class TbMsgAttributesNode implements TbNode { hasChanges = true; ((ObjectNode) oldConfiguration).put(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, false); } + case 1: + // update notifyDevice. set true if null or property doesn't exist for backward-compatibility. + hasChanges = fixEscapedBooleanConfigParameters(oldConfiguration, NOTIFY_DEVICE_KEY, hasChanges, true); + // update sendAttributesUpdatedNotification. + hasChanges = fixEscapedBooleanConfigParameters(oldConfiguration, SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY, hasChanges, false); + // update updateAttributesOnlyOnValueChange. + hasChanges = fixEscapedBooleanConfigParameters(oldConfiguration, UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, hasChanges, false); break; default: break; @@ -175,4 +185,20 @@ public class TbMsgAttributesNode implements TbNode { return new TbPair<>(hasChanges, oldConfiguration); } + private static boolean fixEscapedBooleanConfigParameters(JsonNode oldConfiguration, String boolKey, boolean hasChanges, boolean defaultValue) { + if (oldConfiguration.hasNonNull(boolKey)) { + var value = oldConfiguration.get(boolKey); + if (value.isTextual()) { + hasChanges = true; + ((ObjectNode) oldConfiguration) + .put(boolKey, value.asBoolean(defaultValue)); + } + } else { + hasChanges = true; + ((ObjectNode) oldConfiguration) + .put(boolKey, defaultValue); + } + return hasChanges; + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java index 1dd98feb16..d8502e76d9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java @@ -24,7 +24,7 @@ public class TbMsgAttributesNodeConfiguration implements NodeConfiguration upgradeResult = node.upgrade(0, jsonNode); - ObjectNode resultNode = (ObjectNode) upgradeResult.getSecond(); assertThat(upgradeResult.getFirst()).as("upgrade result has changes").isTrue(); - assertThat(resultNode.has(updateAttributesOnlyOnValueChangeKey)).as("upgrade result has key " + updateAttributesOnlyOnValueChangeKey).isTrue(); - assertThat(resultNode.get(updateAttributesOnlyOnValueChangeKey).asBoolean()).as("upgrade result value [false] for key " + updateAttributesOnlyOnValueChangeKey).isFalse(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("upgrade result has key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("upgrade result value [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); } @Test - void testUpgrade_fromVersion0_alreadyHasupdateAttributesOnlyOnValueChange() throws TbNodeException { + void testUpgrade_fromVersion0_alreadyHasUpdateAttributesOnlyOnValueChange() throws TbNodeException { TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); willCallRealMethod().given(node).upgrade(anyInt(), any()); ObjectNode jsonNode = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); - jsonNode.remove(updateAttributesOnlyOnValueChangeKey); - jsonNode.put(updateAttributesOnlyOnValueChangeKey, true); - assertThat(jsonNode.has(updateAttributesOnlyOnValueChangeKey)).as("pre condition has no " + updateAttributesOnlyOnValueChangeKey).isTrue(); - assertThat(jsonNode.get(updateAttributesOnlyOnValueChangeKey).asBoolean()).as("pre condition has [true] for key " + updateAttributesOnlyOnValueChangeKey).isTrue(); + jsonNode.remove(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY); + jsonNode.put(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, true); + assertThat(jsonNode.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + assertThat(jsonNode.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [true] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); TbPair upgradeResult = node.upgrade(0, jsonNode); - ObjectNode resultNode = (ObjectNode) upgradeResult.getSecond(); assertThat(upgradeResult.getFirst()).as("upgrade result has changes").isFalse(); - assertThat(resultNode.has(updateAttributesOnlyOnValueChangeKey)).as("upgrade result has key " + updateAttributesOnlyOnValueChangeKey).isTrue(); - assertThat(resultNode.get(updateAttributesOnlyOnValueChangeKey).asBoolean()).as("upgrade result value [true] for key " + updateAttributesOnlyOnValueChangeKey).isTrue(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("upgrade result has key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("upgrade result value [true] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + } + + @Test + void testUpgrade_fromVersion1_AllFlagsAreBooleans() throws TbNodeException { + TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); + willCallRealMethod().given(node).upgrade(anyInt(), any()); + + ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); + + assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has no" + NOTIFY_DEVICE_KEY).isTrue(); + assertThat(defaultConfig.has(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY)).as("pre condition has no" + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isTrue(); + assertThat(defaultConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no" + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + + assertThat(defaultConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [true] for key " + NOTIFY_DEVICE_KEY).isFalse(); + assertThat(defaultConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); + assertThat(defaultConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + + TbPair upgradeResult = node.upgrade(1, defaultConfig); + + assertThat(upgradeResult.getFirst()).as("upgrade result has changes").isFalse(); + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig).as("upgraded config has changes").isEqualTo(defaultConfig); + } + + @Test + void testUpgrade_fromVersion1_NoFlagsSet() throws TbNodeException { + TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); + willCallRealMethod().given(node).upgrade(anyInt(), any()); + + ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); + defaultConfig.remove(NOTIFY_DEVICE_KEY); + defaultConfig.remove(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY); + defaultConfig.remove(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY); + + assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has " + NOTIFY_DEVICE_KEY).isFalse(); + assertThat(defaultConfig.has(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY)).as("pre condition has " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); + assertThat(defaultConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); + + TbPair upgradeResult = node.upgrade(1, defaultConfig); + + assertThat(upgradeResult.getFirst()).as("upgrade result has no changes").isTrue(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [false] for key " + NOTIFY_DEVICE_KEY).isTrue(); + assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [true] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); + } + + @Test + void testUpgrade_fromVersion1_AllFlagsAreBooleanStrings() throws TbNodeException { + TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); + willCallRealMethod().given(node).upgrade(anyInt(), any()); + + ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); + defaultConfig.put(NOTIFY_DEVICE_KEY, defaultConfig.get(NOTIFY_DEVICE_KEY).asText()); + defaultConfig.put(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY, defaultConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asText()); + defaultConfig.put(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, defaultConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asText()); + + assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has no " + NOTIFY_DEVICE_KEY).isTrue(); + assertThat(defaultConfig.has(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY)).as("pre condition has no " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isTrue(); + assertThat(defaultConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + + assertThat(defaultConfig.get(NOTIFY_DEVICE_KEY).isTextual()).as("pre condition " + NOTIFY_DEVICE_KEY + " is not textual").isTrue(); + assertThat(defaultConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isTextual()).as("pre condition " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY + " is not textual").isTrue(); + assertThat(defaultConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTextual()).as("pre condition " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY + " is not textual").isTrue(); + + + TbPair upgradeResult = node.upgrade(1, defaultConfig); + + assertThat(upgradeResult.getFirst()).as("upgrade result has no changes").isTrue(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + + assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).isBoolean()).as("pre condition " + NOTIFY_DEVICE_KEY + " is not boolean").isTrue(); + assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isBoolean()).as("pre condition " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY + " is not boolean").isTrue(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isBoolean()).as("pre condition " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY + " is not boolean").isTrue(); + + assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [true] for key " + NOTIFY_DEVICE_KEY).isFalse(); + assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + } + + @Test + void testUpgrade_fromVersion1_NotifyDeviceFlagIsNull() throws TbNodeException { + TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); + willCallRealMethod().given(node).upgrade(anyInt(), any()); + + ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); + defaultConfig.set(NOTIFY_DEVICE_KEY, NullNode.instance); + + assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has no " + NOTIFY_DEVICE_KEY).isTrue(); + + TbPair upgradeResult = node.upgrade(1, defaultConfig); + + assertThat(upgradeResult.getFirst()).as("upgrade result has no changes").isTrue(); + + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [false] or [null] for key " + NOTIFY_DEVICE_KEY).isTrue(); } } From af52ea282c9f08066f54522829cfe320513518b4 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 20 Nov 2023 18:15:25 +0200 Subject: [PATCH 04/10] updated sql upgrade script & fixed rule chain templates & fixed upgrade in rule node & minor changes to the tests --- .../json/edge/rule_chains/edge_root_rule_chain.json | 2 +- .../tenant/device_profile/rule_chain_template.json | 2 +- .../json/tenant/rule_chains/root_rule_chain.json | 2 +- .../service/install/SqlDatabaseUpgradeService.java | 3 ++- .../rule/engine/telemetry/TbMsgAttributesNode.java | 12 ++++++------ .../engine/telemetry/TbMsgAttributesNodeTest.java | 11 ++++++----- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json index 99625b3188..6b7603c026 100644 --- a/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json +++ b/application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json @@ -48,7 +48,7 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "name": "Save Client Attributes", "debugMode": false, - "configurationVersion": 1, + "configurationVersion": 2, "configuration": { "scope": "CLIENT_SCOPE", "notifyDevice": false, diff --git a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json index a6fb16af3d..0256a2ccf2 100644 --- a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json +++ b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json @@ -32,7 +32,7 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "name": "Save Client Attributes", "debugMode": false, - "configurationVersion": 1, + "configurationVersion": 2, "configuration": { "scope": "CLIENT_SCOPE", "notifyDevice": false, diff --git a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json index a96b1ba01f..0b70d087e7 100644 --- a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json +++ b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json @@ -31,7 +31,7 @@ "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "name": "Save Client Attributes", "debugMode": false, - "configurationVersion": 1, + "configurationVersion": 2, "configuration": { "scope": "CLIENT_SCOPE", "notifyDevice": false, diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 5b1904953b..3527822c4a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -801,11 +801,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService "configuration = (configuration::jsonb || jsonb_build_object(" + "'notifyDevice', CASE WHEN configuration::jsonb ->> 'notifyDevice' = 'false' THEN false ELSE true END, " + "'sendAttributesUpdatedNotification', CASE WHEN configuration::jsonb ->> 'sendAttributesUpdatedNotification' = 'true' THEN true ELSE false END, " + - "'updateAttributesOnlyOnValueChange', CASE WHEN configuration::jsonb ->> 'updateAttributesOnlyOnValueChange' = 'true' THEN true ELSE false END" + + "'updateAttributesOnlyOnValueChange', CASE WHEN configuration::jsonb ->> 'updateAttributesOnlyOnValueChange' = 'false' THEN false ELSE true END" + ")::jsonb)::varchar, " + "configuration_version = 2 " + "WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version = 1;"); } catch (Exception e) { + log.warn("Failed to execute update script for save attributes rule nodes due to: ", e); } conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3006002;"); log.info("Schema updated to version 3.6.2."); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 59ff53557b..66fb0fbde9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -173,11 +173,11 @@ public class TbMsgAttributesNode implements TbNode { } case 1: // update notifyDevice. set true if null or property doesn't exist for backward-compatibility. - hasChanges = fixEscapedBooleanConfigParameters(oldConfiguration, NOTIFY_DEVICE_KEY, hasChanges, true); + hasChanges = fixEscapedBooleanConfigParameter(oldConfiguration, NOTIFY_DEVICE_KEY, hasChanges, true); // update sendAttributesUpdatedNotification. - hasChanges = fixEscapedBooleanConfigParameters(oldConfiguration, SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY, hasChanges, false); + hasChanges = fixEscapedBooleanConfigParameter(oldConfiguration, SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY, hasChanges, false); // update updateAttributesOnlyOnValueChange. - hasChanges = fixEscapedBooleanConfigParameters(oldConfiguration, UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, hasChanges, false); + hasChanges = fixEscapedBooleanConfigParameter(oldConfiguration, UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, hasChanges, true); break; default: break; @@ -185,18 +185,18 @@ public class TbMsgAttributesNode implements TbNode { return new TbPair<>(hasChanges, oldConfiguration); } - private static boolean fixEscapedBooleanConfigParameters(JsonNode oldConfiguration, String boolKey, boolean hasChanges, boolean defaultValue) { + private boolean fixEscapedBooleanConfigParameter(JsonNode oldConfiguration, String boolKey, boolean hasChanges, boolean valueIfNull) { if (oldConfiguration.hasNonNull(boolKey)) { var value = oldConfiguration.get(boolKey); if (value.isTextual()) { hasChanges = true; ((ObjectNode) oldConfiguration) - .put(boolKey, value.asBoolean(defaultValue)); + .put(boolKey, value.asBoolean(valueIfNull)); } } else { hasChanges = true; ((ObjectNode) oldConfiguration) - .put(boolKey, defaultValue); + .put(boolKey, valueIfNull); } return hasChanges; } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java index e3949e1581..477f50128a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java @@ -137,15 +137,16 @@ class TbMsgAttributesNodeTest { private static Stream provideNotifyDeviceMdValue() { return Stream.of( Arguments.of(null, true), - Arguments.of(true, true), - Arguments.of(false, false) + Arguments.of("null", false), + Arguments.of("true", true), + Arguments.of("false", false) ); } // Notify device backward-compatibility test @ParameterizedTest @MethodSource("provideNotifyDeviceMdValue") - void testNotifyDeviceArgumentForSaveAndNotify(Boolean mdValue, boolean expectedArgumentValue) throws TbNodeException { + void givenNotifyDeviceMdValue_whenSaveAndNotify_thenVerifyExpectedArgumentForNotifyDeviceInSaveAndNotifyMethod(String mdValue, boolean expectedArgumentValue) throws TbNodeException { var ctxMock = mock(TbContext.class); var telemetryServiceMock = mock(RuleEngineTelemetryService.class); TbMsgAttributesNode node = spy(TbMsgAttributesNode.class); @@ -164,7 +165,7 @@ class TbMsgAttributesNodeTest { TbMsgMetaData md = new TbMsgMetaData(); if (mdValue != null) { - md.putValue(NOTIFY_DEVICE_METADATA_KEY, mdValue.toString()); + md.putValue(NOTIFY_DEVICE_METADATA_KEY, mdValue); } // dummy list with one ts kv to pass the empty list check. var testTbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR_ID, md, TbMsg.EMPTY_STRING); @@ -264,7 +265,7 @@ class TbMsgAttributesNodeTest { ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [false] for key " + NOTIFY_DEVICE_KEY).isTrue(); assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); - assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [true] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); + assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); } @Test From 6852879fe9d29fa398b19927ed41a4cfb1fcdd57 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 20 Nov 2023 18:59:33 +0200 Subject: [PATCH 05/10] replaced upgrade script tests with parameterized test --- .../telemetry/TbMsgAttributesNodeTest.java | 177 +++++------------- 1 file changed, 50 insertions(+), 127 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java index 477f50128a..30a3ea53c8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java @@ -183,141 +183,64 @@ class TbMsgAttributesNodeTest { assertThat(notifyDevice).isEqualTo(expectedArgumentValue); } - @Test - void testUpgrade_fromVersion0() throws TbNodeException { - - TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); - willCallRealMethod().given(node).upgrade(anyInt(), any()); - - ObjectNode jsonNode = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); - jsonNode.remove(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY); - assertThat(jsonNode.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); - - TbPair upgradeResult = node.upgrade(0, jsonNode); - - assertThat(upgradeResult.getFirst()).as("upgrade result has changes").isTrue(); - - ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); - assertThat(upgradedConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("upgrade result has key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); - assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("upgrade result value [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); - } - - @Test - void testUpgrade_fromVersion0_alreadyHasUpdateAttributesOnlyOnValueChange() throws TbNodeException { - TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); - willCallRealMethod().given(node).upgrade(anyInt(), any()); - - ObjectNode jsonNode = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); - jsonNode.remove(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY); - jsonNode.put(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, true); - assertThat(jsonNode.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); - assertThat(jsonNode.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [true] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); - - TbPair upgradeResult = node.upgrade(0, jsonNode); - - assertThat(upgradeResult.getFirst()).as("upgrade result has changes").isFalse(); - - ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); - assertThat(upgradedConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("upgrade result has key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); - assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("upgrade result value [true] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); - } - - @Test - void testUpgrade_fromVersion1_AllFlagsAreBooleans() throws TbNodeException { - TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); - willCallRealMethod().given(node).upgrade(anyInt(), any()); - - ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); - - assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has no" + NOTIFY_DEVICE_KEY).isTrue(); - assertThat(defaultConfig.has(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY)).as("pre condition has no" + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isTrue(); - assertThat(defaultConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no" + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); - - assertThat(defaultConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [true] for key " + NOTIFY_DEVICE_KEY).isFalse(); - assertThat(defaultConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); - assertThat(defaultConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); - - TbPair upgradeResult = node.upgrade(1, defaultConfig); - - assertThat(upgradeResult.getFirst()).as("upgrade result has changes").isFalse(); - ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); - assertThat(upgradedConfig).as("upgraded config has changes").isEqualTo(defaultConfig); - } - - @Test - void testUpgrade_fromVersion1_NoFlagsSet() throws TbNodeException { - TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); - willCallRealMethod().given(node).upgrade(anyInt(), any()); - - ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); - defaultConfig.remove(NOTIFY_DEVICE_KEY); - defaultConfig.remove(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY); - defaultConfig.remove(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY); - - assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has " + NOTIFY_DEVICE_KEY).isFalse(); - assertThat(defaultConfig.has(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY)).as("pre condition has " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); - assertThat(defaultConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isFalse(); - - TbPair upgradeResult = node.upgrade(1, defaultConfig); - - assertThat(upgradeResult.getFirst()).as("upgrade result has no changes").isTrue(); - - ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); - assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [false] for key " + NOTIFY_DEVICE_KEY).isTrue(); - assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); - assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); - } - - @Test - void testUpgrade_fromVersion1_AllFlagsAreBooleanStrings() throws TbNodeException { - TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); - willCallRealMethod().given(node).upgrade(anyInt(), any()); - - ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); - defaultConfig.put(NOTIFY_DEVICE_KEY, defaultConfig.get(NOTIFY_DEVICE_KEY).asText()); - defaultConfig.put(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY, defaultConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asText()); - defaultConfig.put(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY, defaultConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asText()); - assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has no " + NOTIFY_DEVICE_KEY).isTrue(); - assertThat(defaultConfig.has(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY)).as("pre condition has no " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isTrue(); - assertThat(defaultConfig.has(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY)).as("pre condition has no " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); - - assertThat(defaultConfig.get(NOTIFY_DEVICE_KEY).isTextual()).as("pre condition " + NOTIFY_DEVICE_KEY + " is not textual").isTrue(); - assertThat(defaultConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isTextual()).as("pre condition " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY + " is not textual").isTrue(); - assertThat(defaultConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTextual()).as("pre condition " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY + " is not textual").isTrue(); - - - TbPair upgradeResult = node.upgrade(1, defaultConfig); - - assertThat(upgradeResult.getFirst()).as("upgrade result has no changes").isTrue(); - - ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); - - assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).isBoolean()).as("pre condition " + NOTIFY_DEVICE_KEY + " is not boolean").isTrue(); - assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isBoolean()).as("pre condition " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY + " is not boolean").isTrue(); - assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isBoolean()).as("pre condition " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY + " is not boolean").isTrue(); - - assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [true] for key " + NOTIFY_DEVICE_KEY).isFalse(); - assertThat(upgradedConfig.get(SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).asBoolean()).as("pre condition has [true] for key " + SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY).isFalse(); - assertThat(upgradedConfig.get(UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).asBoolean()).as("pre condition has [false] for key " + UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY).isTrue(); + // Rule nodes upgrade + private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { + return Stream.of( + // default config for version 0 + Arguments.of(0, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":\"false\",\"sendAttributesUpdatedNotification\":\"false\"}", + true, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":false,\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":false}"), + // default config for version 1 with upgrade from version 0 + Arguments.of(0, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":false,\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}", + false, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":false,\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}"), + // all flags are booleans + Arguments.of(1, + "{\"scope\":\"SHARED_SCOPE\",\"notifyDevice\":true,\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}", + false, + "{\"scope\":\"SHARED_SCOPE\",\"notifyDevice\":true,\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}"), + // no boolean flags set + Arguments.of(1, + "{\"scope\":\"CLIENT_SCOPE\"}", + true, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":true,\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}"), + // all flags are boolean strings + Arguments.of(1, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":\"false\",\"sendAttributesUpdatedNotification\":\"false\",\"updateAttributesOnlyOnValueChange\":\"true\"}", + true, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":false,\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}"), + // at least one flag is boolean string + Arguments.of(1, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":\"false\",\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}", + true, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":false,\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}"), + // notify device flag is null + Arguments.of(1, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":\"null\",\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}", + true, + "{\"scope\":\"CLIENT_SCOPE\",\"notifyDevice\":true,\"sendAttributesUpdatedNotification\":false,\"updateAttributesOnlyOnValueChange\":true}") + ); } - @Test - void testUpgrade_fromVersion1_NotifyDeviceFlagIsNull() throws TbNodeException { + @ParameterizedTest + @MethodSource("givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig") + void givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig(int givenVersion, String givenConfigStr, boolean hasChanges, String expectedConfigStr) throws TbNodeException { + // GIVEN TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); willCallRealMethod().given(node).upgrade(anyInt(), any()); + JsonNode givenConfig = JacksonUtil.toJsonNode(givenConfigStr); + JsonNode expectedConfig = JacksonUtil.toJsonNode(expectedConfigStr); - ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); - defaultConfig.set(NOTIFY_DEVICE_KEY, NullNode.instance); - - assertThat(defaultConfig.has(NOTIFY_DEVICE_KEY)).as("pre condition has no " + NOTIFY_DEVICE_KEY).isTrue(); - - TbPair upgradeResult = node.upgrade(1, defaultConfig); - - assertThat(upgradeResult.getFirst()).as("upgrade result has no changes").isTrue(); + // WHEN + TbPair upgradeResult = node.upgrade(givenVersion, givenConfig); + // THEN + assertThat(upgradeResult.getFirst()).isEqualTo(hasChanges); ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); - assertThat(upgradedConfig.get(NOTIFY_DEVICE_KEY).asBoolean()).as("pre condition has [false] or [null] for key " + NOTIFY_DEVICE_KEY).isTrue(); + assertThat(upgradedConfig).isEqualTo(expectedConfig); } } From 0fe841a45479abaf4036c541e1dcda7dc7a07849 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 20 Nov 2023 19:11:38 +0200 Subject: [PATCH 06/10] removed ExtendWith annotation && added beforeEach method --- .../telemetry/TbMsgAttributesNodeTest.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java index 30a3ea53c8..af4f3c2d70 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java @@ -16,16 +16,14 @@ package org.thingsboard.rule.engine.telemetry; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; 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.mockito.ArgumentCaptor; -import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.TbContext; @@ -64,21 +62,24 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode.NOTIFY_DEVICE_KEY; -import static org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode.SEND_ATTRIBUTES_UPDATED_NOTIFICATION_KEY; -import static org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode.UPDATE_ATTRIBUTES_ONLY_ON_VALUE_CHANGE_KEY; import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; @Slf4j -@ExtendWith(MockitoExtension.class) class TbMsgAttributesNodeTest { private static final DeviceId ORIGINATOR_ID = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + TbMsgAttributesNode node; + + @BeforeEach + void setUp() { + node = spy(TbMsgAttributesNode.class); + } + @Test void testFilterChangedAttr_whenCurrentAttributesEmpty_thenReturnNewAttributes() { - TbMsgAttributesNode node = spy(TbMsgAttributesNode.class); + node = spy(TbMsgAttributesNode.class); List newAttributes = new ArrayList<>(); List filtered = node.filterChangedAttr(Collections.emptyList(), newAttributes); @@ -87,7 +88,7 @@ class TbMsgAttributesNodeTest { @Test void testFilterChangedAttr_whenCurrentAttributesContainsInAnyOrderNewAttributes_thenReturnEmptyList() { - TbMsgAttributesNode node = spy(TbMsgAttributesNode.class); + node = spy(TbMsgAttributesNode.class); List currentAttributes = List.of( new BaseAttributeKvEntry(1694000000L, new StringDataEntry("address", "Peremohy ave 1")), new BaseAttributeKvEntry(1694000000L, new BooleanDataEntry("valid", true)), @@ -108,7 +109,7 @@ class TbMsgAttributesNodeTest { @Test void testFilterChangedAttr_whenCurrentAttributesContainsInAnyOrderNewAttributes_thenReturnExpectedList() { - TbMsgAttributesNode node = spy(TbMsgAttributesNode.class); + node = spy(TbMsgAttributesNode.class); List currentAttributes = List.of( new BaseAttributeKvEntry(1694000000L, new StringDataEntry("address", "Peremohy ave 1")), new BaseAttributeKvEntry(1694000000L, new BooleanDataEntry("valid", true)), @@ -147,9 +148,9 @@ class TbMsgAttributesNodeTest { @ParameterizedTest @MethodSource("provideNotifyDeviceMdValue") void givenNotifyDeviceMdValue_whenSaveAndNotify_thenVerifyExpectedArgumentForNotifyDeviceInSaveAndNotifyMethod(String mdValue, boolean expectedArgumentValue) throws TbNodeException { + node = spy(TbMsgAttributesNode.class); var ctxMock = mock(TbContext.class); var telemetryServiceMock = mock(RuleEngineTelemetryService.class); - TbMsgAttributesNode node = spy(TbMsgAttributesNode.class); ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); defaultConfig.put("notifyDevice", false); var tbNodeConfiguration = new TbNodeConfiguration(defaultConfig); @@ -229,7 +230,7 @@ class TbMsgAttributesNodeTest { @MethodSource("givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig") void givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig(int givenVersion, String givenConfigStr, boolean hasChanges, String expectedConfigStr) throws TbNodeException { // GIVEN - TbMsgAttributesNode node = mock(TbMsgAttributesNode.class); + node = spy(TbMsgAttributesNode.class); willCallRealMethod().given(node).upgrade(anyInt(), any()); JsonNode givenConfig = JacksonUtil.toJsonNode(givenConfigStr); JsonNode expectedConfig = JacksonUtil.toJsonNode(expectedConfigStr); From ea69f09b853f365d5c6939fc2d9b54e9ed74b144 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 21 Nov 2023 10:33:49 +0200 Subject: [PATCH 07/10] replaced method source values --- .../rule/engine/telemetry/TbMsgAttributesNodeTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java index af4f3c2d70..60f31bd134 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java @@ -135,7 +135,7 @@ class TbMsgAttributesNodeTest { } // Notify device backward-compatibility test arguments - private static Stream provideNotifyDeviceMdValue() { + private static Stream givenNotifyDeviceMdValue_whenSaveAndNotify_thenVerifyExpectedArgumentForNotifyDeviceInSaveAndNotifyMethod() { return Stream.of( Arguments.of(null, true), Arguments.of("null", false), @@ -146,7 +146,7 @@ class TbMsgAttributesNodeTest { // Notify device backward-compatibility test @ParameterizedTest - @MethodSource("provideNotifyDeviceMdValue") + @MethodSource void givenNotifyDeviceMdValue_whenSaveAndNotify_thenVerifyExpectedArgumentForNotifyDeviceInSaveAndNotifyMethod(String mdValue, boolean expectedArgumentValue) throws TbNodeException { node = spy(TbMsgAttributesNode.class); var ctxMock = mock(TbContext.class); @@ -227,7 +227,7 @@ class TbMsgAttributesNodeTest { } @ParameterizedTest - @MethodSource("givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig") + @MethodSource void givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig(int givenVersion, String givenConfigStr, boolean hasChanges, String expectedConfigStr) throws TbNodeException { // GIVEN node = spy(TbMsgAttributesNode.class); From 84ba42908b9e50d7977886edf453d26dc24a3081 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 23 Nov 2023 11:43:59 +0200 Subject: [PATCH 08/10] moved save attributes node upgrade to sql file && minor changes to tests --- .../3.6.1/save_attributes_node_update.sql | 26 +++++++++++++++++++ .../install/SqlDatabaseUpgradeService.java | 10 ++----- .../engine/telemetry/TbMsgAttributesNode.java | 7 +++-- .../TbMsgAttributesNodeConfigurationTest.java | 2 +- .../telemetry/TbMsgAttributesNodeTest.java | 20 ++++++-------- 5 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql diff --git a/application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql b/application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql new file mode 100644 index 0000000000..dd1003ffe4 --- /dev/null +++ b/application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql @@ -0,0 +1,26 @@ +-- +-- 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. +-- + +UPDATE rule_node SET + configuration = (configuration::jsonb || jsonb_build_object( + 'notifyDevice', + CASE WHEN configuration::jsonb ->> 'notifyDevice' = 'false' THEN false ELSE true END, + 'sendAttributesUpdatedNotification', + CASE WHEN configuration::jsonb ->> 'sendAttributesUpdatedNotification' = 'true' THEN true ELSE false END, + 'updateAttributesOnlyOnValueChange', + CASE WHEN configuration::jsonb ->> 'updateAttributesOnlyOnValueChange' = 'false' THEN false ELSE true END)::jsonb)::varchar, + configuration_version = 2 +WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version = 1; \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 3527822c4a..be337aa2c7 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -797,14 +797,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService if (isOldSchema(conn, 3006001)) { log.info("Updating schema ..."); try { - conn.createStatement().execute("UPDATE rule_node SET " + - "configuration = (configuration::jsonb || jsonb_build_object(" + - "'notifyDevice', CASE WHEN configuration::jsonb ->> 'notifyDevice' = 'false' THEN false ELSE true END, " + - "'sendAttributesUpdatedNotification', CASE WHEN configuration::jsonb ->> 'sendAttributesUpdatedNotification' = 'true' THEN true ELSE false END, " + - "'updateAttributesOnlyOnValueChange', CASE WHEN configuration::jsonb ->> 'updateAttributesOnlyOnValueChange' = 'false' THEN false ELSE true END" + - ")::jsonb)::varchar, " + - "configuration_version = 2 " + - "WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version = 1;"); + Path saveAttributesNodeUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.6.1", "save_attributes_node_update.sql"); + loadSql(saveAttributesNodeUpdateFile, conn); } catch (Exception e) { log.warn("Failed to execute update script for save attributes rule nodes due to: ", e); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 66fb0fbde9..da24485f18 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -120,7 +120,7 @@ public class TbMsgAttributesNode implements TbNode { msg.getOriginator(), scope, attributes, - config.isNotifyDevice() || checkNotifyDeviceMdValue(msg.getMetaData()), + config.isNotifyDevice() || checkNotifyDeviceMdValue(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY)), sendAttributesUpdateNotification ? new AttributesUpdateNodeCallback(ctx, msg, scope, attributes) : new TelemetryNodeCallback(ctx, msg) @@ -149,10 +149,9 @@ public class TbMsgAttributesNode implements TbNode { return config.isSendAttributesUpdatedNotification() && !CLIENT_SCOPE.equals(scope); } - private boolean checkNotifyDeviceMdValue(TbMsgMetaData md) { - var notifyDeviceMdStr = md.getValue(NOTIFY_DEVICE_METADATA_KEY); + private boolean checkNotifyDeviceMdValue(String notifyDeviceMdValue) { // Check for empty string for backward-compatibility. A while ago node always notified devices. - return StringUtils.isEmpty(notifyDeviceMdStr) || Boolean.parseBoolean(notifyDeviceMdStr); + return StringUtils.isEmpty(notifyDeviceMdValue) || Boolean.parseBoolean(notifyDeviceMdValue); } private String getScope(String mdScopeValue) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfigurationTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfigurationTest.java index 2bc2c0ba0e..07833607d4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfigurationTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfigurationTest.java @@ -22,7 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; class TbMsgAttributesNodeConfigurationTest { @Test - void testDefaultConfig_givenupdateAttributesOnlyOnValueChange_thenTrue_sinceVersion1() { + void testDefaultConfig_givenUpdateAttributesOnlyOnValueChange_thenTrue_sinceVersion1() { assertThat(new TbMsgAttributesNodeConfiguration().defaultConfiguration().isUpdateAttributesOnlyOnValueChange()).isTrue(); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java index 60f31bd134..fe2edc941b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java @@ -67,19 +67,19 @@ import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_MET @Slf4j class TbMsgAttributesNodeTest { - private static final DeviceId ORIGINATOR_ID = new DeviceId(UUID.randomUUID()); - private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - - TbMsgAttributesNode node; + private TenantId tenantId; + private DeviceId deviceId; + private TbMsgAttributesNode node; @BeforeEach void setUp() { + tenantId = new TenantId(UUID.fromString("6c18691e-4470-4766-9739-aface71d761f")); + deviceId = new DeviceId(UUID.fromString("b66159d7-c77e-45e8-bb41-a8f557f434c1")); node = spy(TbMsgAttributesNode.class); } @Test void testFilterChangedAttr_whenCurrentAttributesEmpty_thenReturnNewAttributes() { - node = spy(TbMsgAttributesNode.class); List newAttributes = new ArrayList<>(); List filtered = node.filterChangedAttr(Collections.emptyList(), newAttributes); @@ -88,7 +88,6 @@ class TbMsgAttributesNodeTest { @Test void testFilterChangedAttr_whenCurrentAttributesContainsInAnyOrderNewAttributes_thenReturnEmptyList() { - node = spy(TbMsgAttributesNode.class); List currentAttributes = List.of( new BaseAttributeKvEntry(1694000000L, new StringDataEntry("address", "Peremohy ave 1")), new BaseAttributeKvEntry(1694000000L, new BooleanDataEntry("valid", true)), @@ -109,7 +108,6 @@ class TbMsgAttributesNodeTest { @Test void testFilterChangedAttr_whenCurrentAttributesContainsInAnyOrderNewAttributes_thenReturnExpectedList() { - node = spy(TbMsgAttributesNode.class); List currentAttributes = List.of( new BaseAttributeKvEntry(1694000000L, new StringDataEntry("address", "Peremohy ave 1")), new BaseAttributeKvEntry(1694000000L, new BooleanDataEntry("valid", true)), @@ -148,7 +146,6 @@ class TbMsgAttributesNodeTest { @ParameterizedTest @MethodSource void givenNotifyDeviceMdValue_whenSaveAndNotify_thenVerifyExpectedArgumentForNotifyDeviceInSaveAndNotifyMethod(String mdValue, boolean expectedArgumentValue) throws TbNodeException { - node = spy(TbMsgAttributesNode.class); var ctxMock = mock(TbContext.class); var telemetryServiceMock = mock(RuleEngineTelemetryService.class); ObjectNode defaultConfig = (ObjectNode) JacksonUtil.valueToTree(new TbMsgAttributesNodeConfiguration().defaultConfiguration()); @@ -157,7 +154,7 @@ class TbMsgAttributesNodeTest { assertThat(defaultConfig.has("notifyDevice")).as("pre condition has notifyDevice").isTrue(); - when(ctxMock.getTenantId()).thenReturn(TENANT_ID); + when(ctxMock.getTenantId()).thenReturn(tenantId); when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); willCallRealMethod().given(node).init(any(TbContext.class), any(TbNodeConfiguration.class)); willCallRealMethod().given(node).saveAttr(any(), eq(ctxMock), any(TbMsg.class), anyString(), anyBoolean()); @@ -169,7 +166,7 @@ class TbMsgAttributesNodeTest { md.putValue(NOTIFY_DEVICE_METADATA_KEY, mdValue); } // dummy list with one ts kv to pass the empty list check. - var testTbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR_ID, md, TbMsg.EMPTY_STRING); + var testTbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, md, TbMsg.EMPTY_STRING); List testAttrList = List.of(new BaseAttributeKvEntry(0L, new StringDataEntry("testKey", "testValue"))); node.saveAttr(testAttrList, ctxMock, testTbMsg, DataConstants.SHARED_SCOPE, false); @@ -177,7 +174,7 @@ class TbMsgAttributesNodeTest { ArgumentCaptor notifyDeviceCaptor = ArgumentCaptor.forClass(Boolean.class); verify(telemetryServiceMock, times(1)).saveAndNotify( - eq(TENANT_ID), eq(ORIGINATOR_ID), eq(DataConstants.SHARED_SCOPE), + eq(tenantId), eq(deviceId), eq(DataConstants.SHARED_SCOPE), eq(testAttrList), notifyDeviceCaptor.capture(), any() ); boolean notifyDevice = notifyDeviceCaptor.getValue(); @@ -230,7 +227,6 @@ class TbMsgAttributesNodeTest { @MethodSource void givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig(int givenVersion, String givenConfigStr, boolean hasChanges, String expectedConfigStr) throws TbNodeException { // GIVEN - node = spy(TbMsgAttributesNode.class); willCallRealMethod().given(node).upgrade(anyInt(), any()); JsonNode givenConfig = JacksonUtil.toJsonNode(givenConfigStr); JsonNode expectedConfig = JacksonUtil.toJsonNode(expectedConfigStr); From 6a981eddf46511698f7ce6708db841500bd8229f Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 23 Nov 2023 11:57:17 +0200 Subject: [PATCH 09/10] added new line to the end of sql update file --- .../src/main/data/upgrade/3.6.1/save_attributes_node_update.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql b/application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql index dd1003ffe4..6a27c8ebc2 100644 --- a/application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql +++ b/application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql @@ -23,4 +23,4 @@ UPDATE rule_node SET 'updateAttributesOnlyOnValueChange', CASE WHEN configuration::jsonb ->> 'updateAttributesOnlyOnValueChange' = 'false' THEN false ELSE true END)::jsonb)::varchar, configuration_version = 2 -WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version = 1; \ No newline at end of file +WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version = 1; From 28bc02132b965c1f042870cefb8fbb0f2468eb43 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 5 Dec 2023 11:50:24 +0200 Subject: [PATCH 10/10] fixed logic in upgrade script to use additionalActions instead of new connection --- .../install/SqlDatabaseUpgradeService.java | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 44f2b7efe6..f404edb8b1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -759,26 +759,14 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService updateSchema("3.6.0", 3006000, "3.6.1", 3006001, null); break; case "3.6.1": - updateSchema("3.6.1", 3006001, "3.6.2", 3006002, null); - break; - case "3.6.1": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - if (isOldSchema(conn, 3006001)) { - log.info("Updating schema ..."); - try { - Path saveAttributesNodeUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.6.1", "save_attributes_node_update.sql"); - loadSql(saveAttributesNodeUpdateFile, conn); - } catch (Exception e) { - log.warn("Failed to execute update script for save attributes rule nodes due to: ", e); - } - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3006002;"); - log.info("Schema updated to version 3.6.2."); - } else { - log.info("Skip schema re-update to version 3.6.2. Use env flag 'SKIP_SCHEMA_VERSION_CHECK' to force the re-update."); + updateSchema("3.6.1", 3006001, "3.6.2", 3006002, connection -> { + try { + Path saveAttributesNodeUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.6.1", "save_attributes_node_update.sql"); + loadSql(saveAttributesNodeUpdateFile, connection); + } catch (Exception e) { + log.warn("Failed to execute update script for save attributes rule nodes due to: ", e); } - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } + }); break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);