diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 40596e03e9..7e986dabf1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -104,7 +103,7 @@ public class InstallScripts { @Autowired private ResourceService resourceService; - private Path getTenantRuleChainsDir() { + Path getTenantRuleChainsDir() { return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR); } @@ -112,7 +111,7 @@ public class InstallScripts { return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json"); } - private Path getEdgeRuleChainsDir() { + Path getEdgeRuleChainsDir() { return Paths.get(getDataDir(), JSON_DIR, EDGE_DIR, RULE_CHAINS_DIR); } @@ -148,8 +147,8 @@ public class InstallScripts { } private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) throws IOException { - try (DirectoryStream dirStream = Files.newDirectoryStream(ruleChainsPath, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) { - dirStream.forEach( + findRuleChainsFromPath(ruleChainsPath) + .forEach( path -> { try { createRuleChainFromFile(tenantId, path, null); @@ -157,9 +156,15 @@ public class InstallScripts { log.error("Unable to load rule chain from json: [{}]", path.toString()); throw new RuntimeException("Unable to load rule chain from json", e); } - } - ); + }); + } + + List findRuleChainsFromPath(Path ruleChainsPath) throws IOException { + List paths = new ArrayList<>(); + try (DirectoryStream dirStream = Files.newDirectoryStream(ruleChainsPath, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) { + dirStream.forEach(paths::add); } + return paths; } public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException { diff --git a/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java b/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java new file mode 100644 index 0000000000..820d5e2d9b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java @@ -0,0 +1,111 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.install; + +import com.fasterxml.jackson.databind.JsonNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; +import org.thingsboard.server.dao.resource.ResourceService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.service.validator.RuleChainDataValidator; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.usagerecord.ApiLimitService; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willReturn; + +@SpringBootTest(classes = {InstallScripts.class, RuleChainDataValidator.class}) +class InstallScriptsTest { + + @MockBean + RuleChainService ruleChainService; + @MockBean + DashboardService dashboardService; + @MockBean + WidgetTypeService widgetTypeService; + @MockBean + WidgetsBundleService widgetsBundleService; + @MockBean + OAuth2ConfigTemplateService oAuth2TemplateService; + @MockBean + ResourceService resourceService; + @SpyBean + InstallScripts installScripts; + + @MockBean + TenantService tenantService; + @MockBean + ApiLimitService apiLimitService; + @SpyBean + RuleChainDataValidator ruleChainValidator; + TenantId tenantId = TenantId.fromUUID(UUID.fromString("9ef79cdf-37a8-4119-b682-2e7ed4e018da")); + + @BeforeEach + void setUp() { + willReturn(true).given(tenantService).tenantExists(tenantId); + willReturn(true).given(apiLimitService).checkEntitiesLimit(any(), any()); + } + + @Test + void testDefaultRuleChainsTemplates() throws IOException { + Path tenantRuleChainsDir = installScripts.getTenantRuleChainsDir(); + List ruleChainsFromPath = installScripts.findRuleChainsFromPath(tenantRuleChainsDir); + ruleChainsFromPath.forEach(this::validateRuleChainTemplate); + } + + @Test + void testDefaultEdgeRuleChainsTemplates() throws IOException { + Path edgeChainsDir = installScripts.getEdgeRuleChainsDir(); + List ruleChainsFromPath = installScripts.findRuleChainsFromPath(edgeChainsDir); + ruleChainsFromPath.forEach(this::validateRuleChainTemplate); + } + + private void validateRuleChainTemplate(Path templateFilePath) { + JsonNode ruleChainJson = JacksonUtil.toJsonNode(templateFilePath.toFile()); + + RuleChain ruleChain = JacksonUtil.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class); + ruleChain.setTenantId(tenantId); + ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); + ruleChain.setId(new RuleChainId(UUID.randomUUID())); + + RuleChainMetaData ruleChainMetaData = JacksonUtil.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class); + ruleChainMetaData.setRuleChainId(ruleChain.getId()); + List throwables = RuleChainDataValidator.validateMetaData(ruleChainMetaData); + + assertThat(throwables).as("templateFilePath " + templateFilePath) + .containsExactlyInAnyOrderElementsOf(Collections.emptyList()); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java index 08e047904f..43878adc93 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.util.ReflectionUtils; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.service.DataValidator; @@ -41,15 +40,14 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; @Component @Slf4j public class RuleChainDataValidator extends DataValidator { - @Autowired - private RuleChainDao ruleChainDao; - @Autowired @Lazy private RuleChainService ruleChainService; @@ -88,15 +86,19 @@ public class RuleChainDataValidator extends DataValidator { } } - public static void validateMetaData(RuleChainMetaData ruleChainMetaData) { + public static List validateMetaData(RuleChainMetaData ruleChainMetaData) { ConstraintValidator.validateFields(ruleChainMetaData); - ruleChainMetaData.getNodes().forEach(RuleChainDataValidator::validateRuleNode); + List throwables = ruleChainMetaData.getNodes().stream() + .map(RuleChainDataValidator::validateRuleNode) + .filter(Objects::nonNull) + .collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) { validateCircles(ruleChainMetaData.getConnections()); } + return throwables; } - public static void validateRuleNode(RuleNode ruleNode) { + public static Throwable validateRuleNode(RuleNode ruleNode) { String errorPrefix = "'" + ruleNode.getName() + "' node configuration is invalid: "; ConstraintValidator.validateFields(ruleNode, errorPrefix); Object nodeConfig; @@ -104,11 +106,12 @@ public class RuleChainDataValidator extends DataValidator { Class nodeConfigType = ReflectionUtils.getAnnotationProperty(ruleNode.getType(), "org.thingsboard.rule.engine.api.RuleNode", "configClazz"); nodeConfig = JacksonUtil.treeToValue(ruleNode.getConfiguration(), nodeConfigType); - } catch (Exception e) { - log.warn("Failed to validate node configuration: {}", ExceptionUtils.getRootCauseMessage(e)); - return; + } catch (Throwable t) { + log.warn("Failed to validate node configuration: {}", ExceptionUtils.getRootCauseMessage(t)); + return t; } ConstraintValidator.validateFields(nodeConfig, errorPrefix); + return null; } private static void validateCircles(List connectionInfos) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java index 7f9d3e1fc2..0fdd1a04d2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidatorTest.java @@ -23,7 +23,6 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; -import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; @@ -35,8 +34,6 @@ import static org.mockito.Mockito.verify; @SpringBootTest(classes = RuleChainDataValidator.class) class RuleChainDataValidatorTest { - @MockBean - RuleChainDao ruleChainDao; @MockBean RuleChainService ruleChainService; @MockBean