diff options
author | Zipeng WU <zipeng.wu@sonarsource.com> | 2022-04-26 10:49:06 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-05-06 20:02:43 +0000 |
commit | 6bb200a692f7a200c0ca27900c24c8c89982e521 (patch) | |
tree | 6a4a5c604c60dcbfc555c541e29f8f5d1cab97a2 /server/sonar-webserver-webapi | |
parent | 838c48d8e94d0d780582cdfb4429e5cb57615f6f (diff) | |
download | sonarqube-6bb200a692f7a200c0ca27900c24c8c89982e521.tar.gz sonarqube-6bb200a692f7a200c0ca27900c24c8c89982e521.zip |
SONAR-16302 Adapt ExportRuleDto to handle new structure
Diffstat (limited to 'server/sonar-webserver-webapi')
11 files changed, 435 insertions, 162 deletions
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedRule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedRule.java index be99ea779a7..a9d2e60c01e 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedRule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedRule.java @@ -19,28 +19,34 @@ */ package org.sonar.server.qualityprofile; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.sonar.api.rule.RuleKey; +import org.sonar.server.rule.NewRuleDescriptionSection; class ImportedRule { - private RuleKey ruleKey = null; - private RuleKey templateKey = null; + private String key = null; + + private String repository = null; + + private String template = null; private String name = null; private String type = null; private String severity = null; private String description = null; private Map<String, String> parameters = null; - + private Set<NewRuleDescriptionSection> ruleDescriptionSections = new HashSet<>(); public Map<String, String> getParameters() { return parameters; } public RuleKey getRuleKey() { - return ruleKey; + return RuleKey.of(repository, key); } public RuleKey getTemplateKey() { - return templateKey; + return RuleKey.of(repository, template); } public String getName() { @@ -59,16 +65,6 @@ class ImportedRule { return description; } - ImportedRule setRuleKey(RuleKey ruleKey) { - this.ruleKey = ruleKey; - return this; - } - - ImportedRule setTemplateKey(RuleKey templateKey) { - this.templateKey = templateKey; - return this; - } - ImportedRule setType(String type) { this.type = type; return this; @@ -95,6 +91,26 @@ class ImportedRule { } boolean isCustomRule() { - return templateKey != null; + return template != null; + } + + public Set<NewRuleDescriptionSection> getRuleDescriptionSections() { + return ruleDescriptionSections; + } + + public void addRuleDescriptionSection(NewRuleDescriptionSection ruleDescriptionSection) { + this.ruleDescriptionSections.add(ruleDescriptionSection); + } + + public void setRepository(String repository) { + this.repository = repository; + } + + public void setTemplate(String template) { + this.template = template; + } + + public void setKey(String key) { + this.key = key; } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java index 30dffad218e..de4bbb89cf6 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java @@ -45,10 +45,12 @@ import org.sonar.db.rule.DeprecatedRuleKeyDto; import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.server.qualityprofile.builtin.QProfileName; import org.sonar.server.rule.NewCustomRule; +import org.sonar.server.rule.NewRuleDescriptionSection; import org.sonar.server.rule.RuleCreator; import static com.google.common.base.Preconditions.checkArgument; import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toSet; @ServerSide public class QProfileBackuperImpl implements QProfileBackuper { @@ -88,15 +90,20 @@ public class QProfileBackuperImpl implements QProfileBackuper { List<ImportedRule> importedRules = new ArrayList<>(exportRules.size()); for (ExportRuleDto exportRuleDto : exportRules) { + var ruleKey = exportRuleDto.getRuleKey(); ImportedRule importedRule = new ImportedRule(); importedRule.setName(exportRuleDto.getName()); - importedRule.setDescription(exportRuleDto.getDescription()); - importedRule.setRuleKey(exportRuleDto.getRuleKey()); + importedRule.setRepository(ruleKey.repository()); + importedRule.setKey(ruleKey.rule()); importedRule.setSeverity(exportRuleDto.getSeverityString()); if (importedRule.isCustomRule()) { - importedRule.setTemplateKey(exportRuleDto.getTemplateRuleKey()); + importedRule.setTemplate(exportRuleDto.getTemplateRuleKey().rule()); } importedRule.setType(exportRuleDto.getRuleType().name()); + exportRuleDto.getRuleDescriptionSections() + .stream() + .map(r -> new NewRuleDescriptionSection(r.getKey(), r.getDescription())) + .forEach(importedRule::addRuleDescriptionSection); importedRule.setParameters(exportRuleDto.getParams().stream().collect(Collectors.toMap(ExportRuleParamDto::getKey, ExportRuleParamDto::getValue))); importedRules.add(importedRule); } @@ -154,13 +161,13 @@ public class QProfileBackuperImpl implements QProfileBackuper { private Map<RuleKey, RuleDefinitionDto> getImportedRulesDefinitions(DbSession dbSession, List<ImportedRule> rules) { Set<RuleKey> ruleKeys = rules.stream() .map(ImportedRule::getRuleKey) - .collect(Collectors.toSet()); + .collect(toSet()); Map<RuleKey, RuleDefinitionDto> rulesDefinitions = db.ruleDao().selectDefinitionByKeys(dbSession, ruleKeys).stream() .collect(Collectors.toMap(RuleDefinitionDto::getKey, identity())); Set<RuleKey> unrecognizedRuleKeys = ruleKeys.stream() .filter(r -> !rulesDefinitions.containsKey(r)) - .collect(Collectors.toSet()); + .collect(toSet()); if (!unrecognizedRuleKeys.isEmpty()) { Map<String, DeprecatedRuleKeyDto> deprecatedRuleKeysByUuid = db.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream() @@ -209,11 +216,12 @@ public class QProfileBackuperImpl implements QProfileBackuper { private static NewCustomRule importedRuleToNewCustomRule(ImportedRule r) { return NewCustomRule.createForCustomRule(r.getRuleKey().rule(), r.getTemplateKey()) .setName(r.getName()) - .setMarkdownDescription(r.getDescription()) .setSeverity(r.getSeverity()) .setStatus(RuleStatus.READY) .setPreventReactivation(true) .setType(RuleType.valueOf(r.getType())) + .setMarkdownDescription(r.getDescription()) + .setRuleDescriptionSections(r.getRuleDescriptionSections()) .setParameters(r.getParameters()); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileParser.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileParser.java index 7ea8fd88198..f345d2fc4d4 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileParser.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileParser.java @@ -42,15 +42,20 @@ import org.sonar.api.utils.text.XmlWriter; import org.sonar.db.qualityprofile.ExportRuleDto; import org.sonar.db.qualityprofile.ExportRuleParamDto; import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.db.rule.RuleDescriptionSectionDto; +import org.sonar.server.rule.NewRuleDescriptionSection; @ServerSide public class QProfileParser { private static final String ATTRIBUTE_PROFILE = "profile"; private static final String ATTRIBUTE_NAME = "name"; private static final String ATTRIBUTE_LANGUAGE = "language"; - private static final String ATTRIBUTE_RULES = "rules"; private static final String ATTRIBUTE_RULE = "rule"; + private static final String ATTRIBUTE_DESCRIPTION_SECTIONS = "descriptionSections"; + private static final String ATTRIBUTE_DESCRIPTION_SECTION = "descriptionSection"; + private static final String ATTRIBUTE_DESCRIPTION_SECTION_KEY = "key"; + private static final String ATTRIBUTE_DESCRIPTION_SECTION_DESCRIPTION = "content"; private static final String ATTRIBUTE_REPOSITORY_KEY = "repositoryKey"; private static final String ATTRIBUTE_KEY = "key"; private static final String ATTRIBUTE_PRIORITY = "priority"; @@ -80,7 +85,19 @@ public class QProfileParser { if (ruleToExport.isCustomRule()) { xml.prop(ATTRIBUTE_NAME, ruleToExport.getName()); xml.prop(ATTRIBUTE_TEMPLATE_KEY, ruleToExport.getTemplateRuleKey().rule()); - xml.prop(ATTRIBUTE_DESCRIPTION, ruleToExport.getDescription()); + if (!ruleToExport.getRuleDescriptionSections().isEmpty()) { + ruleToExport.getDefaultRuleDescriptionSectionDto() + .map(RuleDescriptionSectionDto::getDescription) + .ifPresent(desc -> xml.prop(ATTRIBUTE_DESCRIPTION, desc)); + } + xml.begin(ATTRIBUTE_DESCRIPTION_SECTIONS); + for (RuleDescriptionSectionDto ruleDescriptionSection : ruleToExport.getRuleDescriptionSections()) { + xml.begin(ATTRIBUTE_DESCRIPTION_SECTION) + .prop(ATTRIBUTE_DESCRIPTION_SECTION_KEY, ruleDescriptionSection.getKey()) + .prop(ATTRIBUTE_DESCRIPTION_SECTION_DESCRIPTION, ruleDescriptionSection.getDescription()) + .end(); + } + xml.end(ATTRIBUTE_DESCRIPTION_SECTIONS); } xml.begin(ATTRIBUTE_PARAMETERS); @@ -144,39 +161,10 @@ public class QProfileParser { while (rulesCursor.getNext() != null) { SMInputCursor ruleCursor = rulesCursor.childElementCursor(); Map<String, String> parameters = new HashMap<>(); - String repositoryKey = null; - String key = null; - String templateKey = null; ImportedRule rule = new ImportedRule(); - while (ruleCursor.getNext() != null) { - String nodeName = ruleCursor.getLocalName(); - if (StringUtils.equals(ATTRIBUTE_REPOSITORY_KEY, nodeName)) { - repositoryKey = StringUtils.trim(ruleCursor.collectDescendantText(false)); - } else if (StringUtils.equals(ATTRIBUTE_KEY, nodeName)) { - key = StringUtils.trim(ruleCursor.collectDescendantText(false)); - } else if (StringUtils.equals(ATTRIBUTE_TEMPLATE_KEY, nodeName)) { - templateKey = StringUtils.trim(ruleCursor.collectDescendantText(false)); - } else if (StringUtils.equals(ATTRIBUTE_NAME, nodeName)) { - rule.setName(StringUtils.trim(ruleCursor.collectDescendantText(false))); - } else if (StringUtils.equals(ATTRIBUTE_TYPE, nodeName)) { - rule.setType(StringUtils.trim(ruleCursor.collectDescendantText(false))); - } else if (StringUtils.equals(ATTRIBUTE_DESCRIPTION, nodeName)) { - rule.setDescription(StringUtils.trim(ruleCursor.collectDescendantText(false))); - } else if (StringUtils.equals(ATTRIBUTE_PRIORITY, nodeName)) { - rule.setSeverity(StringUtils.trim(ruleCursor.collectDescendantText(false))); - } else if (StringUtils.equals(ATTRIBUTE_PARAMETERS, nodeName)) { - SMInputCursor propsCursor = ruleCursor.childElementCursor(ATTRIBUTE_PARAMETER); - readParameters(propsCursor, parameters); - rule.setParameters(parameters); - } - } - RuleKey ruleKey = RuleKey.of(repositoryKey, key); - rule.setRuleKey(ruleKey); - - if (templateKey != null) { - rule.setTemplateKey(RuleKey.of(repositoryKey, templateKey)); - } + readRule(ruleCursor, parameters, rule); + var ruleKey = rule.getRuleKey(); if (activatedKeys.contains(ruleKey)) { duplicatedKeys.add(ruleKey); } @@ -190,6 +178,34 @@ public class QProfileParser { return activations; } + private static void readRule(SMInputCursor ruleCursor, Map<String, String> parameters, ImportedRule rule) throws XMLStreamException { + while (ruleCursor.getNext() != null) { + String nodeName = ruleCursor.getLocalName(); + if (StringUtils.equals(ATTRIBUTE_REPOSITORY_KEY, nodeName)) { + rule.setRepository(StringUtils.trim(ruleCursor.collectDescendantText(false))); + } else if (StringUtils.equals(ATTRIBUTE_KEY, nodeName)) { + rule.setKey(StringUtils.trim(ruleCursor.collectDescendantText(false))); + } else if (StringUtils.equals(ATTRIBUTE_TEMPLATE_KEY, nodeName)) { + rule.setTemplate(StringUtils.trim(ruleCursor.collectDescendantText(false))); + } else if (StringUtils.equals(ATTRIBUTE_NAME, nodeName)) { + rule.setName(StringUtils.trim(ruleCursor.collectDescendantText(false))); + } else if (StringUtils.equals(ATTRIBUTE_TYPE, nodeName)) { + rule.setType(StringUtils.trim(ruleCursor.collectDescendantText(false))); + } else if (StringUtils.equals(ATTRIBUTE_DESCRIPTION, nodeName)) { + rule.setDescription(StringUtils.trim(ruleCursor.collectDescendantText(false))); + } else if (StringUtils.equals(ATTRIBUTE_PRIORITY, nodeName)) { + rule.setSeverity(StringUtils.trim(ruleCursor.collectDescendantText(false))); + } else if (StringUtils.equals(ATTRIBUTE_PARAMETERS, nodeName)) { + SMInputCursor propsCursor = ruleCursor.childElementCursor(ATTRIBUTE_PARAMETER); + readParameters(propsCursor, parameters); + rule.setParameters(parameters); + } else if (StringUtils.equals(ATTRIBUTE_DESCRIPTION_SECTIONS, nodeName)) { + SMInputCursor propsCursor = ruleCursor.childElementCursor(ATTRIBUTE_DESCRIPTION_SECTION); + readDescriptionSections(propsCursor, rule); + } + } + } + private static void readParameters(SMInputCursor propsCursor, Map<String, String> parameters) throws XMLStreamException { while (propsCursor.getNext() != null) { SMInputCursor propCursor = propsCursor.childElementCursor(); @@ -208,4 +224,23 @@ public class QProfileParser { } } } + + private static void readDescriptionSections(SMInputCursor propsCursor, ImportedRule importedRule) throws XMLStreamException { + while (propsCursor.getNext() != null) { + SMInputCursor propCursor = propsCursor.childElementCursor(); + String key = null; + String description = null; + while (propCursor.getNext() != null) { + String nodeName = propCursor.getLocalName(); + if (StringUtils.equals(ATTRIBUTE_DESCRIPTION_SECTION_KEY, nodeName)) { + key = StringUtils.trim(propCursor.collectDescendantText(false)); + } else if (StringUtils.equals(ATTRIBUTE_DESCRIPTION_SECTION_DESCRIPTION, nodeName)) { + description = StringUtils.trim(propCursor.collectDescendantText(false)); + } + } + if (key != null && description != null) { + importedRule.addRuleDescriptionSection(new NewRuleDescriptionSection(key, description)); + } + } + } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewCustomRule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewCustomRule.java index a803ede0bfc..d824eb92769 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewCustomRule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewCustomRule.java @@ -21,8 +21,11 @@ package org.sonar.server.rule; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.rule.RuleKey; @@ -34,12 +37,13 @@ public class NewCustomRule { private String ruleKey; private RuleKey templateKey; private String name; - private String htmlDescription; private String markdownDescription; private String severity; private RuleStatus status; private RuleType type; - private final Map<String, String> parameters = new HashMap<>(); + private Map<String, String> parameters = new HashMap<>(); + + private Set<NewRuleDescriptionSection> ruleDescriptionSections = new HashSet<>(); private boolean preventReactivation = false; @@ -67,16 +71,6 @@ public class NewCustomRule { } @CheckForNull - public String htmlDescription() { - return htmlDescription; - } - - public NewCustomRule setHtmlDescription(@Nullable String htmlDescription) { - this.htmlDescription = htmlDescription; - return this; - } - - @CheckForNull public String markdownDescription() { return markdownDescription; } @@ -122,8 +116,16 @@ public class NewCustomRule { } public NewCustomRule setParameters(Map<String, String> params) { - this.parameters.clear(); - this.parameters.putAll(params); + this.parameters = params; + return this; + } + + public Set<NewRuleDescriptionSection> getRuleDescriptionSections() { + return Collections.unmodifiableSet(ruleDescriptionSections); + } + + public NewCustomRule setRuleDescriptionSections(Set<NewRuleDescriptionSection> sections) { + this.ruleDescriptionSections = sections; return this; } @@ -147,5 +149,4 @@ public class NewCustomRule { newRule.templateKey = templateKey; return newRule; } - } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewRuleDescriptionSection.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewRuleDescriptionSection.java new file mode 100644 index 00000000000..081262028d2 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewRuleDescriptionSection.java @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.rule; + +public class NewRuleDescriptionSection { + private final String key; + + private final String description; + + public NewRuleDescriptionSection(String key, String description) { + this.key = key; + this.description = description; + } + + public String getKey() { + return key; + } + + public String getDescription() { + return description; + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java index b7d321877a7..4b36e35c7f6 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java @@ -168,11 +168,18 @@ public class RuleCreator { } private static void validateDescription(List<String> errors, NewCustomRule newRule) { - if (Strings.isNullOrEmpty(newRule.htmlDescription()) && Strings.isNullOrEmpty(newRule.markdownDescription())) { + boolean missingDescription = newRule.getRuleDescriptionSections().isEmpty() ? + Strings.isNullOrEmpty(newRule.markdownDescription()) : + noDescriptionSectionHasContent(newRule); + if (missingDescription) { errors.add("The description is missing"); } } + private static boolean noDescriptionSectionHasContent(NewCustomRule newRule) { + return newRule.getRuleDescriptionSections().stream().map(NewRuleDescriptionSection::getDescription).allMatch(Strings::isNullOrEmpty); + } + private static void validateRuleKey(List<String> errors, String ruleKey) { if (!ruleKey.matches("^[\\w]+$")) { errors.add(format("The rule key \"%s\" is invalid, it should only contain: a-z, 0-9, \"_\"", ruleKey)); @@ -207,10 +214,20 @@ public class RuleCreator { .setCreatedAt(system2.now()) .setUpdatedAt(system2.now()); - if (newRule.markdownDescription() != null) { + ruleDefinition.setDescriptionFormat(Format.MARKDOWN); + + if (newRule.getRuleDescriptionSections().isEmpty() && newRule.markdownDescription() != null) { RuleDescriptionSectionDto ruleDescriptionSectionDto = createDefaultRuleDescriptionSection(uuidFactory.create(), newRule.markdownDescription()); - ruleDefinition.setDescriptionFormat(Format.MARKDOWN); ruleDefinition.addRuleDescriptionSectionDto(ruleDescriptionSectionDto); + } else { + for (NewRuleDescriptionSection ruleDescriptionSection : newRule.getRuleDescriptionSections()) { + RuleDescriptionSectionDto ruleDescriptionSectionDto = RuleDescriptionSectionDto.builder() + .uuid(uuidFactory.create()) + .key(ruleDescriptionSection.getKey()) + .description(ruleDescriptionSection.getDescription()) + .build(); + ruleDefinition.addRuleDescriptionSectionDto(ruleDescriptionSectionDto); + } } dbClient.ruleDao().insert(dbSession, ruleDefinition); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java index d791c487edc..ec3a28866ac 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java @@ -1033,7 +1033,7 @@ public class ShowActionTest { } private RuleDefinitionDto newRuleWithoutSection(RuleType ruleType, Consumer<RuleDefinitionDto> populate) { - return newRule(ruleType, RuleTesting::newRuleWithoutSection, populate); + return newRule(ruleType, RuleTesting::newRuleWithoutDescriptionSection, populate); } private RuleDefinitionDto newRule(RuleType ruleType, Supplier<RuleDefinitionDto> ruleDefinitionDtoSupplier, Consumer<RuleDefinitionDto> populate) { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java index 08e74098b12..542424a8661 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java @@ -56,6 +56,7 @@ import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; +import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection; public class QProfileBackuperImplTest { @@ -166,6 +167,34 @@ public class QProfileBackuperImplTest { "<name>" + rule.getName() + "</name>" + "<templateKey>" + templateRule.getKey().rule() + "</templateKey>" + "<description>" + rule.getDefaultRuleDescriptionSectionDto().getDescription() + "</description>" + + "<descriptionSections><descriptionSection><key>default</key><content>" + rule.getDefaultRuleDescriptionSectionDto().getDescription() + "</content></descriptionSection></descriptionSections>" + + "<parameters><parameter>" + + "<key>" + param.getName() + "</key>" + + "<value>20</value>" + + "</parameter></parameters>" + + "</rule></rules></profile>"); + } + + @Test + public void backup_custom_rules_without_description_section() { + var rule = newRuleWithoutDescriptionSection(); + db.rules().insert(rule); + RuleParamDto param = db.rules().insertRuleParam(rule); + QProfileDto profile = createProfile(rule.getLanguage()); + ActiveRuleDto activeRule = activate(profile, rule, param); + + StringWriter writer = new StringWriter(); + underTest.backup(db.getSession(), profile, writer); + + assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + + "<profile>" + + "<name>" + profile.getName() + "</name>" + + "<language>" + profile.getLanguage() + "</language>" + + "<rules><rule>" + + "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" + + "<key>" + rule.getKey().rule() + "</key>" + + "<type>" + RuleType.valueOf(rule.getType()) + "</type>" + + "<priority>" + activeRule.getSeverityString() + "</priority>" + "<parameters><parameter>" + "<key>" + param.getName() + "</key>" + "<value>20</value>" + diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileParserTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileParserTest.java new file mode 100644 index 00000000000..aab08268815 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileParserTest.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualityprofile; + +import java.io.Reader; +import java.io.StringReader; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class QProfileParserTest { + + @Test + public void readOlderVersionXml() { + Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" + + "<profile>" + + "<name>custom rule</name>" + + "<language>js</language>" + + "<rules><rule>" + + "<repositoryKey>sonarjs</repositoryKey>" + + "<key>s001</key>" + + "<type>CODE_SMELL</type>" + + "<priority>CRITICAL</priority>" + + "<name>custom rule name</name>" + + "<templateKey>rule_mc8</templateKey>" + + "<description>custom rule description</description>" + + "<parameters><parameter>" + + "<key>bar</key>" + + "<value>baz</value>" + + "</parameter>" + + "</parameters>" + + "</rule></rules></profile>"); + var parser = new QProfileParser(); + var importedQProfile = parser.readXml(backup); + assertThat(importedQProfile.getRules()).hasSize(1); + var importedRule = importedQProfile.getRules().get(0); + assertThat(importedRule.getDescription()).isEqualTo("custom rule description"); + } + + @Test + public void readNewVersionXml() { + Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" + + "<profile>" + + "<name>custom rule</name>" + + "<language>js</language>" + + "<rules><rule>" + + "<repositoryKey>sonarjs</repositoryKey>" + + "<key>s001</key>" + + "<type>CODE_SMELL</type>" + + "<priority>CRITICAL</priority>" + + "<name>custom rule name</name>" + + "<templateKey>rule_mc8</templateKey>" + + "<descriptionSections><descriptionSection><key>default</key><content>custom rule description</content></descriptionSection></descriptionSections>" + + "<parameters><parameter>" + + "<key>bar</key>" + + "<value>baz</value>" + + "</parameter>" + + "</parameters>" + + "</rule></rules></profile>"); + var parser = new QProfileParser(); + var importedQProfile = parser.readXml(backup); + assertThat(importedQProfile.getRules()).hasSize(1); + var importedRule = importedQProfile.getRules().get(0); + assertThat(importedRule.getRuleDescriptionSections()).hasSize(1); + var section = importedRule.getRuleDescriptionSections().iterator().next(); + assertThat(section.getKey()).isEqualTo("default"); + assertThat(section.getDescription()).isEqualTo("custom rule description"); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java index 082fe1de06a..428f4d4ed04 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.assertj.core.api.Fail; import org.junit.Rule; @@ -63,7 +64,6 @@ public class RuleCreatorTest { private System2 system2 = new TestSystem2().setNow(Instant.now().toEpochMilli()); - @Rule public DbTester dbTester = DbTester.create(system2); @@ -126,12 +126,48 @@ public class RuleCreatorTest { } @Test + public void create_custom_rule_with_both_markdown_description_and_description_sections() { + // insert template rule + RuleDto templateRule = createTemplateRule(); + // Create custom rule + NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) + .setName("My custom") + .setMarkdownDescription("Markdown description") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "new description section"))) + .setSeverity(Severity.MAJOR) + .setStatus(RuleStatus.READY); + RuleKey customRuleKey = underTest.create(dbSession, newRule); + + RuleDto rule = dbTester.getDbClient().ruleDao().selectOrFailByKey(dbSession, customRuleKey); + assertThat(rule).isNotNull(); + assertThat(rule.getKey()).isEqualTo(RuleKey.of("java", "CUSTOM_RULE")); + assertThat(rule.getPluginKey()).isEqualTo("sonarjava"); + assertThat(rule.getTemplateUuid()).isEqualTo(templateRule.getUuid()); + assertThat(rule.getName()).isEqualTo("My custom"); + assertThat(rule.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("new description section"); + assertThat(rule.getSeverityString()).isEqualTo("MAJOR"); + assertThat(rule.getStatus()).isEqualTo(RuleStatus.READY); + assertThat(rule.getLanguage()).isEqualTo("java"); + assertThat(rule.getConfigKey()).isEqualTo("S001"); + assertThat(rule.getDefRemediationFunction()).isEqualTo("LINEAR_OFFSET"); + assertThat(rule.getDefRemediationGapMultiplier()).isEqualTo("1h"); + assertThat(rule.getDefRemediationBaseEffort()).isEqualTo("5min"); + assertThat(rule.getGapDescription()).isEqualTo("desc"); + assertThat(rule.getTags()).containsOnly("usertag1", "usertag2"); + assertThat(rule.getSystemTags()).containsOnly("tag1", "tag4"); + assertThat(rule.getSecurityStandards()).containsOnly("owaspTop10:a1", "cwe:123"); + assertThat(rule.isExternal()).isFalse(); + assertThat(rule.isAdHoc()).isFalse(); + } + + @Test public void create_custom_rule_with_empty_parameter_value() { // insert template rule RuleDto templateRule = createTemplateRule(); + NewRuleDescriptionSection defaultSection = new NewRuleDescriptionSection("default", "some description"); NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) .setName("My custom") - .setHtmlDescription("Some description") + .setRuleDescriptionSections(Set.of(defaultSection)) .setSeverity(Severity.MAJOR) .setStatus(RuleStatus.READY) .setParameters(ImmutableMap.of("regex", "")); @@ -153,7 +189,7 @@ public class RuleCreatorTest { RuleDefinitionDto templateRule = createTemplateRuleWithIntArrayParam(); NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) .setName("My custom") - .setHtmlDescription("Some description") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) .setSeverity(Severity.MAJOR) .setStatus(RuleStatus.READY); @@ -174,7 +210,7 @@ public class RuleCreatorTest { RuleDefinitionDto templateRule = createTemplateRuleWithIntArrayParam(); NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) .setName("My custom") - .setHtmlDescription("Some description") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) .setSeverity(Severity.MAJOR) .setStatus(RuleStatus.READY) .setParameters(ImmutableMap.of("myIntegers", "1,3")); @@ -197,13 +233,13 @@ public class RuleCreatorTest { NewCustomRule firstRule = NewCustomRule.createForCustomRule("CUSTOM_RULE_1", templateRule.getKey()) .setName("My custom") - .setHtmlDescription("Some description") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) .setSeverity(Severity.MAJOR) .setStatus(RuleStatus.READY); NewCustomRule secondRule = NewCustomRule.createForCustomRule("CUSTOM_RULE_2", templateRule.getKey()) .setName("My custom") - .setHtmlDescription("Some description") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) .setSeverity(Severity.MAJOR) .setStatus(RuleStatus.READY); @@ -224,16 +260,14 @@ public class RuleCreatorTest { dbTester.rules().insert(rule); dbSession.commit(); - assertThatThrownBy(() -> { - // Create custom rule with unknown template rule - NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey()) - .setName("My custom") - .setHtmlDescription("Some description") - .setSeverity(Severity.MAJOR) - .setStatus(RuleStatus.READY) - .setParameters(ImmutableMap.of("regex", "a.*")); - underTest.create(dbSession, Collections.singletonList(newRule)); - }) + NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey()) + .setName("My custom") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) + .setSeverity(Severity.MAJOR) + .setStatus(RuleStatus.READY) + .setParameters(ImmutableMap.of("regex", "a.*")); + + assertThatThrownBy(() -> underTest.create(dbSession, Collections.singletonList(newRule))) .isInstanceOf(IllegalArgumentException.class) .hasMessage("This rule is not a template rule: java:S001"); } @@ -338,7 +372,7 @@ public class RuleCreatorTest { // Create custom rule with same key, but with different values NewCustomRule newRule = NewCustomRule.createForCustomRule(key, templateRule.getKey()) .setName("New name") - .setHtmlDescription("New description") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) .setSeverity(Severity.MAJOR) .setStatus(RuleStatus.READY) .setParameters(ImmutableMap.of("regex", "c.*")) @@ -359,15 +393,14 @@ public class RuleCreatorTest { // insert template rule RuleDto templateRule = createTemplateRule(); - assertThatThrownBy(() -> { - NewCustomRule newRule = NewCustomRule.createForCustomRule("*INVALID*", templateRule.getKey()) - .setName("My custom") - .setHtmlDescription("Some description") - .setSeverity(Severity.MAJOR) - .setStatus(RuleStatus.READY) - .setParameters(ImmutableMap.of("regex", "a.*")); - underTest.create(dbSession, newRule); - }) + NewCustomRule newRule = NewCustomRule.createForCustomRule("*INVALID*", templateRule.getKey()) + .setName("My custom") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) + .setSeverity(Severity.MAJOR) + .setStatus(RuleStatus.READY) + .setParameters(ImmutableMap.of("regex", "a.*")); + + assertThatThrownBy(() -> underTest.create(dbSession, newRule)) .isInstanceOf(BadRequestException.class) .hasMessage("The rule key \"*INVALID*\" is invalid, it should only contain: a-z, 0-9, \"_\""); } @@ -379,22 +412,21 @@ public class RuleCreatorTest { // Create a custom rule AtomicReference<NewCustomRule> newRule = new AtomicReference<>(NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) .setName("My custom") - .setHtmlDescription("Some description") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) .setSeverity(Severity.MAJOR) .setStatus(RuleStatus.READY) .setParameters(ImmutableMap.of("regex", "a.*"))); underTest.create(dbSession, newRule.get()); - assertThatThrownBy(() -> { - // Create another custom rule having same key - newRule.set(NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) - .setName("My another custom") - .setHtmlDescription("Some description") - .setSeverity(Severity.MAJOR) - .setStatus(RuleStatus.READY) - .setParameters(ImmutableMap.of("regex", "a.*"))); - underTest.create(dbSession, newRule.get()); - }) + // Create another custom rule having same key + newRule.set(NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) + .setName("My another custom") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) + .setSeverity(Severity.MAJOR) + .setStatus(RuleStatus.READY) + .setParameters(ImmutableMap.of("regex", "a.*"))); + + assertThatThrownBy(() -> underTest.create(dbSession, newRule.get())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("A rule with the key 'CUSTOM_RULE' already exists"); } @@ -404,14 +436,13 @@ public class RuleCreatorTest { // insert template rule RuleDto templateRule = createTemplateRule(); - assertThatThrownBy(() -> { - NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) - .setHtmlDescription("Some description") - .setSeverity(Severity.MAJOR) - .setStatus(RuleStatus.READY) - .setParameters(ImmutableMap.of("regex", "a.*")); - underTest.create(dbSession, newRule); - }) + NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) + .setSeverity(Severity.MAJOR) + .setStatus(RuleStatus.READY) + .setParameters(ImmutableMap.of("regex", "a.*")); + + assertThatThrownBy(() -> underTest.create(dbSession, newRule)) .isInstanceOf(BadRequestException.class) .hasMessage("The name is missing"); } @@ -438,14 +469,13 @@ public class RuleCreatorTest { // insert template rule RuleDto templateRule = createTemplateRule(); - assertThatThrownBy(() -> { - NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) - .setName("My custom") - .setHtmlDescription("Some description") - .setStatus(RuleStatus.READY) - .setParameters(ImmutableMap.of("regex", "a.*")); - underTest.create(dbSession, newRule); - }) + NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) + .setName("My custom") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) + .setStatus(RuleStatus.READY) + .setParameters(ImmutableMap.of("regex", "a.*")); + + assertThatThrownBy(() -> underTest.create(dbSession, newRule)) .isInstanceOf(BadRequestException.class) .hasMessage("The severity is missing"); } @@ -455,15 +485,14 @@ public class RuleCreatorTest { // insert template rule RuleDto templateRule = createTemplateRule(); - assertThatThrownBy(() -> { - NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) - .setName("My custom") - .setHtmlDescription("Some description") - .setSeverity("INVALID") - .setStatus(RuleStatus.READY) - .setParameters(ImmutableMap.of("regex", "a.*")); - underTest.create(dbSession, newRule); - }) + NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) + .setName("My custom") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) + .setSeverity("INVALID") + .setStatus(RuleStatus.READY) + .setParameters(ImmutableMap.of("regex", "a.*")); + + assertThatThrownBy(() -> underTest.create(dbSession, newRule)) .isInstanceOf(BadRequestException.class) .hasMessage("Severity \"INVALID\" is invalid"); } @@ -473,14 +502,13 @@ public class RuleCreatorTest { // insert template rule RuleDto templateRule = createTemplateRule(); - assertThatThrownBy(() -> { - NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) - .setName("My custom") - .setHtmlDescription("Some description") - .setSeverity(Severity.MAJOR) - .setParameters(ImmutableMap.of("regex", "a.*")); - underTest.create(dbSession, newRule); - }) + NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey()) + .setName("My custom") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) + .setSeverity(Severity.MAJOR) + .setParameters(ImmutableMap.of("regex", "a.*")); + + assertThatThrownBy(() -> underTest.create(dbSession, newRule)) .isInstanceOf(BadRequestException.class) .hasMessage("The status is missing"); } @@ -492,16 +520,15 @@ public class RuleCreatorTest { dbTester.rules().insert(rule); dbSession.commit(); - assertThatThrownBy(() -> { - // Create custom rule with unknown template rule - NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey()) - .setName("My custom") - .setHtmlDescription("Some description") - .setSeverity(Severity.MAJOR) - .setStatus(RuleStatus.READY) - .setParameters(ImmutableMap.of("regex", "a.*")); - underTest.create(dbSession, newRule); - }) + // Create custom rule with unknown template rule + NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey()) + .setName("My custom") + .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description"))) + .setSeverity(Severity.MAJOR) + .setStatus(RuleStatus.READY) + .setParameters(ImmutableMap.of("regex", "a.*")); + + assertThatThrownBy(() -> underTest.create(dbSession, newRule)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("This rule is not a template rule: java:S001"); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ListActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ListActionTest.java index 9a17510240d..3e6c8eee400 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ListActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ListActionTest.java @@ -19,6 +19,7 @@ */ package org.sonar.server.rule.ws; +import java.util.Optional; import org.junit.Rule; import org.junit.Test; import org.sonar.api.rule.RuleKey; @@ -33,6 +34,9 @@ import static org.assertj.core.api.Assertions.assertThat; public class ListActionTest { + private static final String RULE_KEY_1 = "S001"; + private static final String RULE_KEY_2 = "S002"; + @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); @@ -48,8 +52,8 @@ public class ListActionTest { @Test public void return_rules_in_protobuf() { - dbTester.rules().insert(RuleTesting.newRule(RuleKey.of("java", "S001")).setConfigKey(null).setName(null)); - dbTester.rules().insert(RuleTesting.newRule(RuleKey.of("java", "S002")).setConfigKey("I002").setName("Rule Two")); + dbTester.rules().insert(RuleTesting.newRule(RuleKey.of("java", RULE_KEY_1)).setConfigKey(null).setName(null)); + dbTester.rules().insert(RuleTesting.newRule(RuleKey.of("java", RULE_KEY_2)).setConfigKey("I002").setName("Rule Two")); dbTester.getSession().commit(); Rules.ListResponse listResponse = tester.newRequest() @@ -57,11 +61,22 @@ public class ListActionTest { assertThat(listResponse.getRulesCount()).isEqualTo(2); - assertThat(listResponse.getRules(0).getKey()).isEqualTo("S001"); - assertThat(listResponse.getRules(0).getInternalKey()).isEmpty(); - assertThat(listResponse.getRules(0).getName()).isEmpty(); - assertThat(listResponse.getRules(1).getKey()).isEqualTo("S002"); - assertThat(listResponse.getRules(1).getInternalKey()).isEqualTo("I002"); - assertThat(listResponse.getRules(1).getName()).isEqualTo("Rule Two"); + Rules.ListResponse.Rule ruleS001 = getRule(listResponse, RULE_KEY_1); + assertThat(ruleS001.getKey()).isEqualTo(RULE_KEY_1); + assertThat(ruleS001.getInternalKey()).isEmpty(); + assertThat(ruleS001.getName()).isEmpty(); + + Rules.ListResponse.Rule ruleS002 = getRule(listResponse, RULE_KEY_2); + assertThat(ruleS002.getKey()).isEqualTo(RULE_KEY_2); + assertThat(ruleS002.getInternalKey()).isEqualTo("I002"); + assertThat(ruleS002.getName()).isEqualTo("Rule Two"); + } + + private Rules.ListResponse.Rule getRule(Rules.ListResponse listResponse, String ruleKey) { + Optional<Rules.ListResponse.Rule> rule = listResponse.getRulesList().stream() + .filter(r -> ruleKey.equals(r.getKey())) + .findFirst(); + assertThat(rule).isPresent(); + return rule.get(); } } |