From 6bb200a692f7a200c0ca27900c24c8c89982e521 Mon Sep 17 00:00:00 2001 From: Zipeng WU Date: Tue, 26 Apr 2022 10:49:06 +0200 Subject: [PATCH] SONAR-16302 Adapt ExportRuleDto to handle new structure --- .../issue/AdHocRuleCreatorTest.java | 4 +- .../db/qualityprofile/ExportRuleDto.java | 28 ++- .../db/rule/RuleDescriptionSectionDto.java | 2 +- .../QualityProfileExportMapper.xml | 36 +++- .../org/sonar/db/rule/RuleMapper.xml | 8 +- .../QualityProfileExportDaoTest.java | 9 +- .../java/org/sonar/db/rule/RuleTesting.java | 8 +- .../rule/HotspotRuleDescriptionTest.java | 23 +-- .../server/rule/index/RuleIndexerTest.java | 15 +- .../server/qualityprofile/ImportedRule.java | 48 +++-- .../qualityprofile/QProfileBackuperImpl.java | 20 +- .../server/qualityprofile/QProfileParser.java | 101 ++++++---- .../org/sonar/server/rule/NewCustomRule.java | 31 +-- .../rule/NewRuleDescriptionSection.java | 39 ++++ .../org/sonar/server/rule/RuleCreator.java | 23 ++- .../server/hotspot/ws/ShowActionTest.java | 2 +- .../QProfileBackuperImplTest.java | 29 +++ .../qualityprofile/QProfileParserTest.java | 86 ++++++++ .../sonar/server/rule/RuleCreatorTest.java | 187 ++++++++++-------- .../sonar/server/rule/ws/ListActionTest.java | 31 ++- 20 files changed, 526 insertions(+), 204 deletions(-) create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewRuleDescriptionSection.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileParserTest.java diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorTest.java index d3e2524d08d..e9582b0ae18 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorTest.java @@ -104,7 +104,7 @@ public class AdHocRuleCreatorTest { .setEngineId("eslint") .setRuleId("no-cond-assign") .setName(repeat("a", 201)) - .setDescription(repeat("a", 16_777_216)) + .setDescription(repeat("a", 1_000_000)) .setSeverity(Constants.Severity.BLOCKER) .setType(ScannerReport.IssueType.BUG) .build()); @@ -112,7 +112,7 @@ public class AdHocRuleCreatorTest { RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule); assertThat(rule.getMetadata().getAdHocName()).isEqualTo(repeat("a", 200)); - assertThat(rule.getMetadata().getAdHocDescription()).isEqualTo(repeat("a", 16_777_215)); + assertThat(rule.getMetadata().getAdHocDescription()).isEqualTo(repeat("a", 1_000_000)); } @Test diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleDto.java index fcd08f0cd98..3795c115eb7 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleDto.java @@ -21,23 +21,30 @@ package org.sonar.db.qualityprofile; import java.util.LinkedList; import java.util.List; +import java.util.Optional; +import java.util.Set; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; +import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.SeverityUtil; +import static java.util.Optional.ofNullable; +import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY; + public class ExportRuleDto { private String activeRuleUuid = null; private String repository = null; private String rule = null; private String name = null; - private String description = null; private String extendedDescription = null; private String template = null; private Integer severity = null; private Integer type = null; private String tags = null; - private List params; + private List params = null; + + private Set ruleDescriptionSectionDtos = null; public boolean isCustomRule() { return template != null; @@ -71,10 +78,6 @@ public class ExportRuleDto { return tags; } - public String getDescription() { - return description; - } - public String getName() { return name; } @@ -89,4 +92,17 @@ public class ExportRuleDto { void setParams(List params) { this.params = params; } + + public Set getRuleDescriptionSections() { + return ruleDescriptionSectionDtos; + } + + public Optional getDefaultRuleDescriptionSectionDto() { + return findExistingSectionWithSameKey(DEFAULT_KEY); + } + + private Optional findExistingSectionWithSameKey(String ruleDescriptionSectionKey) { + return ofNullable(ruleDescriptionSectionDtos).flatMap(sections -> + sections.stream().filter(section -> section.getKey().equals(ruleDescriptionSectionKey)).findAny()); + } } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDescriptionSectionDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDescriptionSectionDto.java index 444f0e64522..208987e3f75 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDescriptionSectionDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDescriptionSectionDto.java @@ -25,7 +25,7 @@ import java.util.StringJoiner; import static org.sonar.api.utils.Preconditions.checkArgument; public class RuleDescriptionSectionDto { - static final String DEFAULT_KEY = "default"; + public static final String DEFAULT_KEY = "default"; private final String uuid; private final String key; diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileExportMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileExportMapper.xml index 5b1a7825a80..4cea9e1e39b 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileExportMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileExportMapper.xml @@ -3,14 +3,25 @@ + + rds.uuid as "rds_uuid", + rds.kee as "rds_kee", + rds.description as "rds_description", + + + + left outer join rule_desc_sections rds on + rds.rule_uuid = r.uuid + + + a.uuid as "activeRuleUuid", a.failure_level as "severity", r.plugin_rule_key as "rule", r.plugin_name as "repository", r.priority as "defaultSeverity", r.name, - r.description, r.description_format as "descriptionFormat", r.rule_type as "type", rt.plugin_rule_key as "template", @@ -24,7 +35,27 @@ p.value as value - select from active_rules a @@ -33,6 +64,7 @@ inner join rules r on r.uuid = a.rule_uuid and r.status != 'REMOVED' left join rules rt on rt.uuid = r.template_uuid left join rules_metadata rm on rm.rule_uuid = r.uuid + where oqp.uuid = #{id, jdbcType=VARCHAR} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml index bb07883486c..685b9be1a72 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml @@ -85,7 +85,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -127,7 +127,7 @@ - + @@ -160,7 +160,7 @@ - + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java index fb9efff61ed..5e4912e4e64 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java @@ -36,7 +36,6 @@ import org.sonar.api.rules.RuleType; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleMetadataDto; import org.sonar.db.rule.RuleParamDto; @@ -74,12 +73,14 @@ public class QualityProfileExportDaoTest { String language = "java"; RuleDefinitionDto ruleTemplate = createRule(language); RuleDefinitionDto customRule = createRule(language, RuleStatus.READY, ruleTemplate.getUuid()); + var customRuleDescription = customRule.getDefaultRuleDescriptionSectionDto().getDescription(); RuleMetadataDto customRuleMetadata = createRuleMetadata(new RuleMetadataDto() .setRuleUuid(customRule.getUuid()) .setNoteData("Extended description") .setTags(Sets.newHashSet("tag1", "tag2", "tag3"))); RuleDefinitionDto rule = createRule(language, RuleStatus.READY, null); + var ruleDescription = rule.getDefaultRuleDescriptionSectionDto().getDescription(); RuleMetadataDto ruleMetadata = createRuleMetadata(new RuleMetadataDto() .setRuleUuid(rule.getUuid())); QProfileDto profile = createProfile(language); @@ -95,8 +96,7 @@ public class QualityProfileExportDaoTest { assertThat(exportCustomRuleDto).isNotNull(); assertThat(exportCustomRuleDto.isCustomRule()).isTrue(); assertThat(exportCustomRuleDto.getParams()).isEmpty(); - //FIXME SONAR-16314 - assertThat(exportCustomRuleDto.getDescription()).isEqualTo(customRule.getRuleDescriptionSectionDtos().stream().map(RuleDescriptionSectionDto::getDescription).collect(Collectors.joining())); + assertThat(exportCustomRuleDto.getRuleDescriptionSections().iterator().next().getDescription()).isEqualTo(customRuleDescription); assertThat(exportCustomRuleDto.getExtendedDescription()).isEqualTo(customRuleMetadata.getNoteData()); assertThat(exportCustomRuleDto.getName()).isEqualTo(customRule.getName()); assertThat(exportCustomRuleDto.getRuleKey()).isEqualTo(customRule.getKey()); @@ -112,8 +112,7 @@ public class QualityProfileExportDaoTest { assertThat(exportRuleDto).isNotNull(); assertThat(exportRuleDto.isCustomRule()).isFalse(); assertThat(exportRuleDto.getParams()).isEmpty(); - //FIXME SONAR-16314 - assertThat(exportRuleDto.getDescription()).isEqualTo(rule.getRuleDescriptionSectionDtos().stream().map(RuleDescriptionSectionDto::getDescription).collect(Collectors.joining())); + assertThat(exportRuleDto.getRuleDescriptionSections().iterator().next().getDescription()).isEqualTo(ruleDescription); assertThat(exportRuleDto.getExtendedDescription()).isEqualTo(ruleMetadata.getNoteData()); assertThat(exportRuleDto.getName()).isEqualTo(rule.getName()); assertThat(exportRuleDto.getRuleKey()).isEqualTo(rule.getKey()); diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java index 28f8fc3d219..833f932a8b2 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java @@ -65,16 +65,16 @@ public class RuleTesting { } public static RuleDefinitionDto newRule(RuleKey key) { - RuleDefinitionDto ruleDefinitionDto = newRuleWithoutSection(key); + RuleDefinitionDto ruleDefinitionDto = newRuleWithoutDescriptionSection(key); ruleDefinitionDto.addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "description_" + randomAlphabetic(5))); return ruleDefinitionDto; } - public static RuleDefinitionDto newRuleWithoutSection() { - return newRuleWithoutSection(randomRuleKey()); + public static RuleDefinitionDto newRuleWithoutDescriptionSection() { + return newRuleWithoutDescriptionSection(randomRuleKey()); } - public static RuleDefinitionDto newRuleWithoutSection(RuleKey ruleKey) { + public static RuleDefinitionDto newRuleWithoutDescriptionSection(RuleKey ruleKey) { return new RuleDefinitionDto() .setRepositoryKey(ruleKey.repository()) .setRuleKey(ruleKey.rule()) diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/HotspotRuleDescriptionTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/HotspotRuleDescriptionTest.java index 494b8af7a16..03983cb664c 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/rule/HotspotRuleDescriptionTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/rule/HotspotRuleDescriptionTest.java @@ -26,18 +26,19 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.db.rule.RuleDto; +import org.sonar.db.rule.RuleTesting; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; -import static org.sonar.db.rule.RuleTesting.newRuleWithoutSection; +import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection; @RunWith(DataProviderRunner.class) public class HotspotRuleDescriptionTest { @Test public void parse_returns_all_empty_fields_when_no_description() { - RuleDefinitionDto dto = newRuleWithoutSection(); + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection(); HotspotRuleDescription result = HotspotRuleDescription.from(dto); @@ -48,7 +49,7 @@ public class HotspotRuleDescriptionTest { @Test public void parse_returns_all_empty_fields_when_empty_description() { - RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", "")); + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", "")); HotspotRuleDescription result = HotspotRuleDescription.from(dto); @@ -60,7 +61,7 @@ public class HotspotRuleDescriptionTest { @Test @UseDataProvider("descriptionsWithoutTitles") public void parse_to_risk_description_fields_when_desc_contains_no_section(String description) { - RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", description)); + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", description)); HotspotRuleDescription result = HotspotRuleDescription.from(dto); @@ -81,7 +82,7 @@ public class HotspotRuleDescriptionTest { @Test public void parse_return_null_risk_when_desc_starts_with_ask_yourself_title() { - RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto( + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( createDefaultRuleDescriptionSection("uuid", (ASKATRISK + RECOMMENTEDCODINGPRACTICE))); HotspotRuleDescription result = HotspotRuleDescription.from(dto); @@ -93,7 +94,7 @@ public class HotspotRuleDescriptionTest { @Test public void parse_return_null_vulnerable_when_no_ask_yourself_whether_title() { - RuleDefinitionDto dto = newRuleWithoutSection() + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection() .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + RECOMMENTEDCODINGPRACTICE))); HotspotRuleDescription result = HotspotRuleDescription.from(dto); @@ -105,7 +106,7 @@ public class HotspotRuleDescriptionTest { @Test public void parse_return_null_fixIt_when_desc_has_no_Recommended_Secure_Coding_Practices_title() { - RuleDefinitionDto dto = newRuleWithoutSection() + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection() .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + ASKATRISK))); HotspotRuleDescription result = HotspotRuleDescription.from(dto); @@ -117,7 +118,7 @@ public class HotspotRuleDescriptionTest { @Test public void parse_with_noncompliant_section_not_removed() { - RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto( + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + NONCOMPLIANTCODE + COMPLIANTCODE))); HotspotRuleDescription result = HotspotRuleDescription.from(dto); @@ -129,7 +130,7 @@ public class HotspotRuleDescriptionTest { @Test public void parse_moved_noncompliant_code() { - RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto( + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + RECOMMENTEDCODINGPRACTICE + NONCOMPLIANTCODE + SEE))); HotspotRuleDescription result = HotspotRuleDescription.from(dto); @@ -143,7 +144,7 @@ public class HotspotRuleDescriptionTest { @Test public void parse_moved_sensitivecode_code() { - RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto( + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + ASKATRISK + RECOMMENTEDCODINGPRACTICE + SENSITIVECODE + SEE))); HotspotRuleDescription result = HotspotRuleDescription.from(dto); @@ -160,7 +161,7 @@ public class HotspotRuleDescriptionTest { String askContent = "This is the ask section content"; String recommendedContent = "This is the recommended section content"; - RuleDefinitionDto dto = newRuleWithoutSection() + RuleDefinitionDto dto = RuleTesting.newRuleWithoutDescriptionSection() .setTemplateUuid("123") .setDescriptionFormat(RuleDto.Format.MARKDOWN) .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection( diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java index 1c8a1ee73e1..ff77b29101d 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java @@ -44,6 +44,7 @@ import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleDto.Scope; +import org.sonar.db.rule.RuleTesting; import org.sonar.server.es.EsTester; import org.sonar.server.security.SecurityStandards; import org.sonar.server.security.SecurityStandards.SQCategory; @@ -56,7 +57,7 @@ import static java.util.stream.Collectors.toSet; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; -import static org.sonar.db.rule.RuleTesting.newRuleWithoutSection; +import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection; import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE; import static org.sonar.server.security.SecurityStandards.CWES_BY_SQ_CATEGORY; import static org.sonar.server.security.SecurityStandards.SQ_CATEGORY_KEYS_ORDERING; @@ -147,7 +148,7 @@ public class RuleIndexerTest { .flatMap(t -> CWES_BY_SQ_CATEGORY.get(t).stream().map(e -> "cwe:" + e)) .collect(toSet()); SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(standards); - RuleDefinitionDto rule = dbTester.rules().insert(newRuleWithoutSection() + RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRuleWithoutDescriptionSection() .setType(RuleType.SECURITY_HOTSPOT) .setSecurityStandards(standards) .addRuleDescriptionSectionDto(RULE_DESCRIPTION_SECTION_DTO)); @@ -182,7 +183,7 @@ public class RuleIndexerTest { @Test public void log_debug_when_hotspot_rule_no_description () { - RuleDefinitionDto rule = dbTester.rules().insert(newRuleWithoutSection() + RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRuleWithoutDescriptionSection() .setType(RuleType.SECURITY_HOTSPOT)); underTest.commitAndIndex(dbTester.getSession(), rule.getUuid()); @@ -195,7 +196,7 @@ public class RuleIndexerTest { @Test public void log_debug_when_hotspot_rule_description_has_none_of_the_key_titles() { - RuleDefinitionDto rule = dbTester.rules().insert(newRuleWithoutSection() + RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRuleWithoutDescriptionSection() .setType(RuleType.SECURITY_HOTSPOT) .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), randomAlphabetic(30)))); underTest.commitAndIndex(dbTester.getSession(), rule.getUuid()); @@ -209,7 +210,7 @@ public class RuleIndexerTest { @Test public void log_debug_when_hotspot_rule_description_is_missing_fixIt_tab_content() { - RuleDefinitionDto rule = dbTester.rules().insert(newRuleWithoutSection() + RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRuleWithoutDescriptionSection() .setType(RuleType.SECURITY_HOTSPOT) .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "bar\n" + "

Ask Yourself Whether

\n" + @@ -225,7 +226,7 @@ public class RuleIndexerTest { @Test public void log_debug_when_hotspot_rule_description_is_missing_risk_tab_content() { - RuleDefinitionDto rule = dbTester.rules().insert(newRuleWithoutSection() + RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRuleWithoutDescriptionSection() .setType(RuleType.SECURITY_HOTSPOT) .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "

Ask Yourself Whether

\n" + "bar\n" + @@ -242,7 +243,7 @@ public class RuleIndexerTest { @Test public void log_debug_when_hotspot_rule_description_is_missing_vulnerable_tab_content() { - RuleDefinitionDto rule = dbTester.rules().insert(newRuleWithoutSection() + RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRuleWithoutDescriptionSection() .setType(RuleType.SECURITY_HOTSPOT) .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "bar\n" + "

Recommended Secure Coding Practices

\n" + 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 parameters = null; - + private Set ruleDescriptionSections = new HashSet<>(); public Map 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 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 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 getImportedRulesDefinitions(DbSession dbSession, List rules) { Set ruleKeys = rules.stream() .map(ImportedRule::getRuleKey) - .collect(Collectors.toSet()); + .collect(toSet()); Map rulesDefinitions = db.ruleDao().selectDefinitionByKeys(dbSession, ruleKeys).stream() .collect(Collectors.toMap(RuleDefinitionDto::getKey, identity())); Set unrecognizedRuleKeys = ruleKeys.stream() .filter(r -> !rulesDefinitions.containsKey(r)) - .collect(Collectors.toSet()); + .collect(toSet()); if (!unrecognizedRuleKeys.isEmpty()) { Map 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 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 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 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 parameters = new HashMap<>(); + private Map parameters = new HashMap<>(); + + private Set ruleDescriptionSections = new HashSet<>(); private boolean preventReactivation = false; @@ -66,16 +70,6 @@ public class NewCustomRule { return this; } - @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 params) { - this.parameters.clear(); - this.parameters.putAll(params); + this.parameters = params; + return this; + } + + public Set getRuleDescriptionSections() { + return Collections.unmodifiableSet(ruleDescriptionSections); + } + + public NewCustomRule setRuleDescriptionSections(Set 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 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 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 populate) { - return newRule(ruleType, RuleTesting::newRuleWithoutSection, populate); + return newRule(ruleType, RuleTesting::newRuleWithoutDescriptionSection, populate); } private RuleDefinitionDto newRule(RuleType ruleType, Supplier ruleDefinitionDtoSupplier, Consumer 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 { "" + rule.getName() + "" + "" + templateRule.getKey().rule() + "" + "" + rule.getDefaultRuleDescriptionSectionDto().getDescription() + "" + + "default" + rule.getDefaultRuleDescriptionSectionDto().getDescription() + "" + + "" + + "" + param.getName() + "" + + "20" + + "" + + ""); + } + + @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("" + + "" + + "" + profile.getName() + "" + + "" + profile.getLanguage() + "" + + "" + + "" + rule.getRepositoryKey() + "" + + "" + rule.getKey().rule() + "" + + "" + RuleType.valueOf(rule.getType()) + "" + + "" + activeRule.getSeverityString() + "" + "" + "" + param.getName() + "" + "20" + 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("" + + "" + + "custom rule" + + "js" + + "" + + "sonarjs" + + "s001" + + "CODE_SMELL" + + "CRITICAL" + + "custom rule name" + + "rule_mc8" + + "custom rule description" + + "" + + "bar" + + "baz" + + "" + + "" + + ""); + 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("" + + "" + + "custom rule" + + "js" + + "" + + "sonarjs" + + "s001" + + "CODE_SMELL" + + "CRITICAL" + + "custom rule name" + + "rule_mc8" + + "defaultcustom rule description" + + "" + + "bar" + + "baz" + + "" + + "" + + ""); + 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); @@ -125,13 +125,49 @@ public class RuleCreatorTest { assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule.getUuid(), templateRule.getUuid()); } + @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 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 rule = listResponse.getRulesList().stream() + .filter(r -> ruleKey.equals(r.getKey())) + .findFirst(); + assertThat(rule).isPresent(); + return rule.get(); } } -- 2.39.5