diff options
author | Aurelien Poscia <aurelien.poscia@sonarsource.com> | 2022-07-13 14:48:11 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-07-14 20:03:48 +0000 |
commit | e840bd147581e8d73bd8f74c5488f1cde5844308 (patch) | |
tree | aab348ff31aba260e6c836f7b26d1f3352c4fa77 | |
parent | 70d175ec3b14e379a6a52de4124a0dfd0ca26a3e (diff) | |
download | sonarqube-e840bd147581e8d73bd8f74c5488f1cde5844308.tar.gz sonarqube-e840bd147581e8d73bd8f74c5488f1cde5844308.zip |
SONAR-16635 Use plugin-api htmlDesc to populate web api/rule/*** rule.htmlDesc and /api/hotspots/show fields
11 files changed, 167 insertions, 170 deletions
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptions.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptions.java index 1ac7e55bc33..06c05337e76 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptions.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptions.java @@ -40,7 +40,8 @@ public class MigrateHotspotRuleDescriptions extends DataChange { private static final String SELECT_DEFAULT_HOTSPOTS_DESCRIPTIONS = "select r.uuid, rds.uuid, rds.content from rules r \n" + "left join " + RULE_DESCRIPTION_SECTIONS_TABLE + " rds on r.uuid = rds.rule_uuid \n" - + "where r.rule_type = 4 and r.template_uuid is null and rds.kee = '" + DEFAULT_DESCRIPTION_KEY + "'"; + + "where r.rule_type = 4 and r.template_uuid is null and rds.kee = '" + DEFAULT_DESCRIPTION_KEY + "' and rds.rule_uuid not in" + + " (select rds2.rule_uuid from " + RULE_DESCRIPTION_SECTIONS_TABLE + " rds2 where rds2.kee != '" + DEFAULT_DESCRIPTION_KEY + "')"; private static final String INSERT_INTO_RULE_DESC_SECTIONS = "insert into " + RULE_DESCRIPTION_SECTIONS_TABLE + " (uuid, rule_uuid, kee, content) values " + "(?,?,?,?)"; diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptionsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptionsTest.java index 1e5d742c217..66c086c4bee 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptionsTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptionsTest.java @@ -228,6 +228,16 @@ public class MigrateHotspotRuleDescriptionsTest { assertThat(findRuleDescriptionSections(LEGACY_HOTSPOT_RULE)).hasSize(3); } + @Test + public void insertRuleDescriptions_skipRules_thatAlreadyHaveAdvancedDesc() throws SQLException { + insertSectionForLegacyHotspotRule(LEGACY_HOTSPOT_RULE_HTML_DESC); + insertRuleDescriptionSection(LEGACY_HOTSPOT_RULE, "root_cause", "content to check reentrant"); + + fixHotspotRuleDescriptions.execute(); + + assertThat(findRuleDescriptionSections(LEGACY_HOTSPOT_RULE)).hasSize(2); + } + private List<Map<String, Object>> findAllRuleDescriptionSections() { return db.select("select uuid, kee, rule_uuid, content from " + RULE_DESCRIPTION_SECTIONS_TABLE + "'"); diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java index ad34b1f81d4..9492aaa3f1c 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java @@ -19,85 +19,34 @@ */ package org.sonar.server.rule; +import com.google.common.collect.MoreCollectors; import java.util.Collection; -import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; import javax.annotation.CheckForNull; -import org.sonar.api.rules.RuleType; -import org.sonar.db.rule.RuleDescriptionSectionContextDto; import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; import org.sonar.markdown.Markdown; -import static java.util.Comparator.comparing; -import static java.util.stream.Collectors.toMap; -import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; -import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; -import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.INTRODUCTION_SECTION_KEY; -import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY; -import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; -import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY; import static org.sonar.db.rule.RuleDto.Format.MARKDOWN; public class RuleDescriptionFormatter { - public static final List<String> SECTION_KEYS = List.of( - INTRODUCTION_SECTION_KEY, - ROOT_CAUSE_SECTION_KEY, - ASSESS_THE_PROBLEM_SECTION_KEY, - HOW_TO_FIX_SECTION_KEY, - RESOURCES_SECTION_KEY); - - public static final Map<String, String> HOTSPOT_SECTION_TITLES = Map.of( - ROOT_CAUSE_SECTION_KEY, "What is the risk?", - ASSESS_THE_PROBLEM_SECTION_KEY, "Assess the risk", - HOW_TO_FIX_SECTION_KEY, "How can you fix it?" - ); - - public static final Map<String, String> RULE_SECTION_TITLES = Map.of( - ROOT_CAUSE_SECTION_KEY, "Why is this an issue?", - HOW_TO_FIX_SECTION_KEY, "How to fix it?", - RESOURCES_SECTION_KEY, "Resources" - ); - @CheckForNull public String getDescriptionAsHtml(RuleDto ruleDto) { if (ruleDto.getDescriptionFormat() == null) { return null; } Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = ruleDto.getRuleDescriptionSectionDtos(); - return retrieveDescription(ruleDescriptionSectionDtos, RuleType.valueOf(ruleDto.getType()), Objects.requireNonNull(ruleDto.getDescriptionFormat())); + return retrieveDescription(ruleDescriptionSectionDtos, Objects.requireNonNull(ruleDto.getDescriptionFormat())); } @CheckForNull - private static String retrieveDescription(Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos, - RuleType ruleType, RuleDto.Format descriptionFormat) { - if (ruleDescriptionSectionDtos.isEmpty()) { - return null; - } - Map<String, String> sectionKeyToHtml = ruleDescriptionSectionDtos.stream() - //TODO MMF-2765, merge operation on toMap should not be needed anymore - .sorted(comparing(RuleDescriptionSectionDto::getKey).thenComparing(s -> Optional.ofNullable(s.getContext()).map(RuleDescriptionSectionContextDto::getKey).orElse(null))) - .collect(toMap(RuleDescriptionSectionDto::getKey, section -> toHtml(descriptionFormat, section), (k1, k2) -> k1)); - if (sectionKeyToHtml.containsKey(DEFAULT_KEY)) { - return sectionKeyToHtml.get(DEFAULT_KEY); - } else { - return concatHtmlSections(sectionKeyToHtml, ruleType); - } - } - - private static String concatHtmlSections(Map<String, String> sectionKeyToHtml, RuleType ruleType) { - Map<String, String> titleMapping = ruleType.equals(RuleType.SECURITY_HOTSPOT) ? HOTSPOT_SECTION_TITLES : RULE_SECTION_TITLES; - var builder = new StringBuilder(); - for (String sectionKey : SECTION_KEYS) { - if (sectionKeyToHtml.containsKey(sectionKey)) { - Optional.ofNullable(titleMapping.get(sectionKey)).ifPresent(title -> builder.append("<h2>").append(title).append("</h2>")); - builder.append(sectionKeyToHtml.get(sectionKey)).append("<br/>"); - } - } - return builder.toString(); + private static String retrieveDescription(Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos, RuleDto.Format descriptionFormat) { + return ruleDescriptionSectionDtos.stream() + .filter(RuleDescriptionSectionDto::isDefault) + .collect(MoreCollectors.toOptional()) + .map(section -> toHtml(descriptionFormat, section)) + .orElse(null); } private static String toHtml(RuleDto.Format descriptionFormat, RuleDescriptionSectionDto ruleDescriptionSectionDto) { diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java index 8466dfaaa4f..2268e2e408b 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java @@ -19,20 +19,13 @@ */ package org.sonar.server.rule; -import java.util.Optional; -import org.apache.commons.lang.RandomStringUtils; -import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.sonar.api.rules.RuleType; -import org.sonar.db.rule.RuleDescriptionSectionContextDto; import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; -import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; -import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.INTRODUCTION_SECTION_KEY; -import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; @@ -57,33 +50,17 @@ public class RuleDescriptionFormatterTest { } @Test - public void concatHtmlDescriptionSections() { + public void getDescriptionAsHtml_ignoresAdvancedSections() { var section1 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, "<div>Root is Root</div>"); var section2 = createRuleDescriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY, "<div>This is not a problem</div>"); - var section3 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "<div>I don't want to fix</div>"); - var section4 = createRuleDescriptionSection(INTRODUCTION_SECTION_KEY, "<div>Introduction with no title</div>"); - var section5ctx1 = createRuleDescriptionSection(RESOURCES_SECTION_KEY, "<div>CTX_1</div>", "CTX_1"); - var section5ctx2 = createRuleDescriptionSection(RESOURCES_SECTION_KEY, "<div>CTX_2</div>", "CTX_2"); + var defaultRuleDescriptionSection = createDefaultRuleDescriptionSection("uuid_432", "default description"); RuleDto rule = new RuleDto().setDescriptionFormat(RuleDto.Format.HTML) .setType(RuleType.SECURITY_HOTSPOT) .addRuleDescriptionSectionDto(section1) .addRuleDescriptionSectionDto(section2) - .addRuleDescriptionSectionDto(section3) - .addRuleDescriptionSectionDto(section4) - .addRuleDescriptionSectionDto(section5ctx2) - .addRuleDescriptionSectionDto(section5ctx1); + .addRuleDescriptionSectionDto(defaultRuleDescriptionSection); String html = ruleDescriptionFormatter.getDescriptionAsHtml(rule); - assertThat(html) - .isEqualTo( - "<div>Introduction with no title</div><br/>" - + "<h2>What is the risk?</h2>" - + "<div>Root is Root</div><br/>" - + "<h2>Assess the risk</h2>" - + "<div>This is not a problem</div><br/>" - + "<h2>How can you fix it?</h2>" - + "<div>I don't want to fix</div><br/>" - + "<div>CTX_1</div><br/>" - ); + assertThat(html).isEqualTo(defaultRuleDescriptionSection.getContent()); } @Test @@ -102,13 +79,6 @@ public class RuleDescriptionFormatterTest { } private static RuleDescriptionSectionDto createRuleDescriptionSection(String key, String content) { - return createRuleDescriptionSection(key, content, null); - } - - private static RuleDescriptionSectionDto createRuleDescriptionSection(String key, String content, @Nullable String contextKey) { - RuleDescriptionSectionContextDto context = Optional.ofNullable(contextKey) - .map(c -> RuleDescriptionSectionContextDto.of(contextKey, contextKey + RandomStringUtils.randomAlphanumeric(20))) - .orElse(null); - return RuleDescriptionSectionDto.builder().key(key).content(content).context(context).build(); + return RuleDescriptionSectionDto.builder().key(key).content(content).build(); } } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java index 08652f93ee4..9c49029690f 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java @@ -21,6 +21,8 @@ package org.sonar.server.rule; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; +import org.elasticsearch.common.util.set.Sets; import org.jetbrains.annotations.Nullable; import org.sonar.api.server.rule.Context; import org.sonar.api.server.rule.RuleDescriptionSection; @@ -29,25 +31,42 @@ import org.sonar.core.util.UuidFactory; import org.sonar.db.rule.RuleDescriptionSectionContextDto; import org.sonar.db.rule.RuleDescriptionSectionDto; -import static java.util.stream.Collectors.toSet; +import static org.sonar.api.rules.RuleType.*; public class AdvancedRuleDescriptionSectionsGenerator implements RuleDescriptionSectionsGenerator { private final UuidFactory uuidFactory; + private final LegacyIssueRuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator; - public AdvancedRuleDescriptionSectionsGenerator(UuidFactory uuidFactory) { + public AdvancedRuleDescriptionSectionsGenerator(UuidFactory uuidFactory, LegacyIssueRuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator) { this.uuidFactory = uuidFactory; + this.legacyIssueRuleDescriptionSectionsGenerator = legacyIssueRuleDescriptionSectionsGenerator; } @Override public boolean isGeneratorForRule(RulesDefinition.Rule rule) { - return !rule.ruleDescriptionSections().isEmpty(); + return !rule.ruleDescriptionSections().isEmpty() && skipHotspotRulesForSonar16635(rule); + } + + private static boolean skipHotspotRulesForSonar16635(RulesDefinition.Rule rule) { + return !SECURITY_HOTSPOT.equals(rule.type()); } @Override public Set<RuleDescriptionSectionDto> generateSections(RulesDefinition.Rule rule) { - return rule.ruleDescriptionSections().stream() + Set<RuleDescriptionSectionDto> advancedSections = rule.ruleDescriptionSections().stream() .map(this::toRuleDescriptionSectionDto) - .collect(toSet()); + .collect(Collectors.toSet()); + return addLegacySectionToAdvancedSections(advancedSections, rule); + } + + /** + * This was done to preserve backward compatibility with SonarLint until they stop using htmlDesc field in api/rules/[show|search] endpoints, see SONAR-16635 + * @deprecated the method should be removed once SonarLint supports rules.descriptionSections fields, I.E in 10.x + */ + @Deprecated(since = "9.6", forRemoval = true) + private Set<RuleDescriptionSectionDto> addLegacySectionToAdvancedSections(Set<RuleDescriptionSectionDto> advancedSections, RulesDefinition.Rule rule) { + Set<RuleDescriptionSectionDto> legacySection = legacyIssueRuleDescriptionSectionsGenerator.generateSections(rule); + return Sets.union(advancedSections, legacySection); } private RuleDescriptionSectionDto toRuleDescriptionSectionDto(RuleDescriptionSection section) { diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java index d0c93c7fdbc..e2d5675d236 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java @@ -35,6 +35,7 @@ import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; +import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; public class LegacyHotspotRuleDescriptionSectionsGenerator implements RuleDescriptionSectionsGenerator { private final UuidFactory uuidFactory; @@ -45,7 +46,9 @@ public class LegacyHotspotRuleDescriptionSectionsGenerator implements RuleDescri @Override public boolean isGeneratorForRule(RulesDefinition.Rule rule) { - return SECURITY_HOTSPOT.equals(rule.type()) && rule.ruleDescriptionSections().isEmpty(); + // To prevent compatibility issues with SonarLint, this Generator is used for all hotspots rules, regardless of if they expose advanced sections or not. See SONAR-16635. + // In the future, the generator should not be used for advanced rules (add condition && rule.ruleDescriptionSections().isEmpty()) + return SECURITY_HOTSPOT.equals(rule.type()); } @Override @@ -65,6 +68,9 @@ public class LegacyHotspotRuleDescriptionSectionsGenerator implements RuleDescri } private Set<RuleDescriptionSectionDto> generateSections(String descriptionInHtml) { + if (descriptionInHtml.isEmpty()) { + return emptySet(); + } String[] split = extractSection("", descriptionInHtml); String remainingText = split[0]; String ruleDescriptionSection = split[1]; @@ -101,7 +107,10 @@ public class LegacyHotspotRuleDescriptionSectionsGenerator implements RuleDescri RuleDescriptionSectionDto assessSection = createSection(ASSESS_THE_PROBLEM_SECTION_KEY, askSection, sensitiveSection, noncompliantSection); RuleDescriptionSectionDto fixSection = createSection(HOW_TO_FIX_SECTION_KEY, recommendedSection, compliantSection, seeSection); - return Stream.of(rootSection, assessSection, fixSection) + // For backward compatibility with SonarLint, see SONAR-16635. Should be removed in 10.x + RuleDescriptionSectionDto defaultSection = createDefaultRuleDescriptionSection(uuidFactory.create(), descriptionInHtml); + + return Stream.of(rootSection, assessSection, fixSection, defaultSection) .filter(Objects::nonNull) .collect(Collectors.toSet()); } diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java index 08c4a4f4df8..b2c4425606a 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java @@ -91,65 +91,72 @@ public class AdvancedRuleDescriptionSectionsGeneratorTest { @Mock private RulesDefinition.Rule rule; + @Mock + private RuleDescriptionSectionDto LEGACY_SECTION; + + @Mock + private LegacyIssueRuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator; + @InjectMocks private AdvancedRuleDescriptionSectionsGenerator generator; @Before public void before() { when(uuidFactory.create()).thenReturn(UUID_1).thenReturn(UUID_2); + when(legacyIssueRuleDescriptionSectionsGenerator.generateSections(rule)).thenReturn(Set.of(LEGACY_SECTION)); } @Test - public void generateSections_whenOneSection_createsOneSections() { + public void generateSections_whenOneSection_createsOneSectionAndDefault() { when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_1)); Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule); assertThat(ruleDescriptionSectionDtos) .usingRecursiveFieldByFieldElementComparator() - .containsOnly(EXPECTED_SECTION_1); + .containsExactlyInAnyOrder(EXPECTED_SECTION_1, LEGACY_SECTION); } @Test - public void generateSections_whenTwoSections_createsTwoSections() { + public void generateSections_whenTwoSections_createsTwoSectionsAndDefault() { when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_1, SECTION_2)); Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule); assertThat(ruleDescriptionSectionDtos) .usingRecursiveFieldByFieldElementComparator() - .containsExactlyInAnyOrder(EXPECTED_SECTION_1, EXPECTED_SECTION_2); + .containsExactlyInAnyOrder(EXPECTED_SECTION_1, EXPECTED_SECTION_2, LEGACY_SECTION); } @Test - public void generateSections_whenTwoContextSpecificSections_createsTwoSections() { + public void generateSections_whenTwoContextSpecificSections_createsTwoSectionsAndDefault() { when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_3_WITH_CTX_1, SECTION_3_WITH_CTX_2)); Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule); assertThat(ruleDescriptionSectionDtos) .usingRecursiveFieldByFieldElementComparator() - .containsExactlyInAnyOrder(EXPECTED_SECTION_3_WITH_CTX_1, EXPECTED_SECTION_3_WITH_CTX_2); + .containsExactlyInAnyOrder(EXPECTED_SECTION_3_WITH_CTX_1, EXPECTED_SECTION_3_WITH_CTX_2, LEGACY_SECTION); } @Test - public void generateSections_whenContextSpecificSectionsAndNonContextSpecificSection_createsTwoSections() { + public void generateSections_whenContextSpecificSectionsAndNonContextSpecificSection_createsTwoSectionsAndDefault() { when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_1, SECTION_3_WITH_CTX_2)); Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule); assertThat(ruleDescriptionSectionDtos) .usingRecursiveFieldByFieldElementComparator() - .containsExactlyInAnyOrder(EXPECTED_SECTION_1, EXPECTED_SECTION_3_WITH_CTX_2); + .containsExactlyInAnyOrder(EXPECTED_SECTION_1, EXPECTED_SECTION_3_WITH_CTX_2, LEGACY_SECTION); } @Test - public void generateSections_whenNoSections_returnsEmptySet() { + public void generateSections_whenNoSections_returnsDefault() { when(rule.ruleDescriptionSections()).thenReturn(emptyList()); Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule); - assertThat(ruleDescriptionSectionDtos).isEmpty(); + assertThat(ruleDescriptionSectionDtos).containsOnly(LEGACY_SECTION); } } diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java index 6308f481229..dd4914f6d50 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java @@ -19,20 +19,16 @@ */ package org.sonar.server.rule; -import com.tngtech.java.junit.dataprovider.DataProvider; -import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.util.Map; import java.util.Set; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.core.util.UuidFactory; import org.sonar.db.rule.RuleDescriptionSectionDto; +import org.sonar.markdown.Markdown; import static java.util.stream.Collectors.toMap; -import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -40,7 +36,6 @@ import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSe import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; -@RunWith(DataProviderRunner.class) public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { /* @@ -84,6 +79,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { + " @CrossOrigin(origins = \"http://domain2.com\") // Questionable\n" + " @RequestMapping(value = \"/test1\")\n" + " public ResponseEntity<String> test1() {\n" + " return ResponseEntity.ok().body(\"ok\");\n" + " }\n" + "}\n" + "</pre>\n"; + private static final String DEFAULT_SECTION_KEY = "default"; + private final UuidFactory uuidFactory = mock(UuidFactory.class); private final RulesDefinition.Rule rule = mock(RulesDefinition.Rule.class); @@ -114,21 +111,17 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { } @Test - @UseDataProvider("descriptionsWithoutTitles") - public void parse_to_risk_description_fields_when_desc_contains_no_section(String description) { - when(rule.htmlDescription()).thenReturn(description); + public void parse_to_risk_description_fields_when_desc_contains_no_section() { + String descriptionWithoutTitles = "description without titles"; + when(rule.htmlDescription()).thenReturn(descriptionWithoutTitles); Set<RuleDescriptionSectionDto> results = generator.generateSections(rule); - assertThat(results).hasSize(1); - RuleDescriptionSectionDto uniqueSection = results.iterator().next(); - assertThat(uniqueSection.getKey()).isEqualTo(ROOT_CAUSE_SECTION_KEY); - assertThat(uniqueSection.getContent()).isEqualTo(description); - } + Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent)); + assertThat(sectionKeyToContent).hasSize(2) + .containsEntry(ROOT_CAUSE_SECTION_KEY, descriptionWithoutTitles) + .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription()); - @DataProvider - public static Object[][] descriptionsWithoutTitles() { - return new Object[][] {{randomAlphabetic(123)}, {"bar\n" + "acme\n" + "foo"}}; } @Test @@ -138,7 +131,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { Set<RuleDescriptionSectionDto> results = generator.generateSections(rule); Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent)); - assertThat(sectionKeyToContent).hasSize(2) + assertThat(sectionKeyToContent).hasSize(3) + .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription()) .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, ASKATRISK) .containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENTEDCODINGPRACTICE); } @@ -151,7 +145,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { Set<RuleDescriptionSectionDto> results = generator.generateSections(rule); Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent)); - assertThat(sectionKeyToContent).hasSize(2) + assertThat(sectionKeyToContent).hasSize(3) + .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription()) .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION) .containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENTEDCODINGPRACTICE); } @@ -163,7 +158,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { Set<RuleDescriptionSectionDto> results = generator.generateSections(rule); Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent)); - assertThat(sectionKeyToContent).hasSize(2) + assertThat(sectionKeyToContent).hasSize(3) + .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription()) .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION) .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, ASKATRISK); } @@ -175,7 +171,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { Set<RuleDescriptionSectionDto> results = generator.generateSections(rule); Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent)); - assertThat(sectionKeyToContent).hasSize(3) + assertThat(sectionKeyToContent).hasSize(4) + .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription()) .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION) .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, NONCOMPLIANTCODE) .containsEntry(HOW_TO_FIX_SECTION_KEY, COMPLIANTCODE); @@ -188,7 +185,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { Set<RuleDescriptionSectionDto> results = generator.generateSections(rule); Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent)); - assertThat(sectionKeyToContent).hasSize(3) + assertThat(sectionKeyToContent).hasSize(4) + .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription()) .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION) .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, NONCOMPLIANTCODE) .containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENTEDCODINGPRACTICE + SEE); @@ -201,7 +199,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { Set<RuleDescriptionSectionDto> results = generator.generateSections(rule); Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent)); - assertThat(sectionKeyToContent).hasSize(3) + assertThat(sectionKeyToContent).hasSize(4) + .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription()) .containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION) .containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, ASKATRISK + SENSITIVECODE) .containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENTEDCODINGPRACTICE + SEE); @@ -225,7 +224,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest { Set<RuleDescriptionSectionDto> results = generator.generateSections(rule); Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent)); - assertThat(sectionKeyToContent).hasSize(3) + assertThat(sectionKeyToContent).hasSize(4) + .containsEntry(DEFAULT_SECTION_KEY, Markdown.convertToHtml(rule.markdownDescription())) .containsEntry(ROOT_CAUSE_SECTION_KEY, ruleDescription + "<br/>" + "<h2>Exceptions</h2>" + exceptionsContent + "<br/>") diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RuleDescriptionSectionsGeneratorsTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RuleDescriptionSectionsGeneratorsTest.java index 9687ab4f7a1..03ad1283b71 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RuleDescriptionSectionsGeneratorsTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RuleDescriptionSectionsGeneratorsTest.java @@ -67,6 +67,8 @@ public class RuleDescriptionSectionsGeneratorsTest { private static final RuleDescriptionSectionDto DEFAULT_MD_SECTION_1 = RuleDescriptionSectionDto.builder().uuid(UUID_1).key("default").content(MD_CONTENT).build(); private static final RuleDescriptionSectionDto HTML_SECTION_1 = RuleDescriptionSectionDto.builder().uuid(UUID_1).key(KEY_1).content(HTML_CONTENT).build(); private static final RuleDescriptionSectionDto HTML_SECTION_2 = RuleDescriptionSectionDto.builder().uuid(UUID_2).key(KEY_2).content(HTML_CONTENT).build(); + private static final RuleDescriptionSectionDto LEGACY_HTML_SECTION = RuleDescriptionSectionDto.builder().uuid(UUID_2).key("default").content(HTML_CONTENT).build(); + private static final RuleDescriptionSectionDto LEGACY_MD_SECTION = RuleDescriptionSectionDto.builder().uuid(UUID_2).key("default").content(MD_CONTENT).build(); @Parameterized.Parameters(name = "{index} = {0}") public static List<RuleDescriptionGeneratorTestData> testData() { @@ -80,21 +82,20 @@ public class RuleDescriptionSectionsGeneratorsTest { aRuleOfType(VULNERABILITY).html(HTML_CONTENT).md(MD_CONTENT).expectedGenerator(LEGACY_ISSUE).addExpectedSection(DEFAULT_HTML_SECTION_1).build(), // HOTSPOT aRuleOfType(SECURITY_HOTSPOT).html(null).md(null).expectedGenerator(LEGACY_HOTSPOT).build(), - aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(null).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).build(), - aRuleOfType(SECURITY_HOTSPOT).html(null).md(MD_CONTENT).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_MD_HOTSPOT_SECTION_1).build(), - aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(MD_CONTENT).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).build(), + aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(null).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(), + aRuleOfType(SECURITY_HOTSPOT).html(null).md(MD_CONTENT).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_MD_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_MD_SECTION).build(), + aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(MD_CONTENT).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(), // ADVANCED RULES aRuleOfType(BUG).html(null).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(), - aRuleOfType(BUG).html(HTML_CONTENT).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(), - aRuleOfType(BUG).html(null).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(), - aRuleOfType(BUG).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(), - aRuleOfType(BUG).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).addSection(SECTION_2).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1) - .addExpectedSection( - HTML_SECTION_2).build(), - aRuleOfType(SECURITY_HOTSPOT).html(null).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(), - aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(), - aRuleOfType(SECURITY_HOTSPOT).html(null).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(), - aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build() + aRuleOfType(BUG).html(HTML_CONTENT).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(), + aRuleOfType(BUG).html(null).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).addExpectedSection(LEGACY_MD_SECTION).build(), + aRuleOfType(BUG).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(), + aRuleOfType(BUG).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).addSection(SECTION_2).expectedGenerator(ADVANCED_RULE) + .addExpectedSection(HTML_SECTION_1).addExpectedSection(HTML_SECTION_2).addExpectedSection(LEGACY_HTML_SECTION).build(), + aRuleOfType(SECURITY_HOTSPOT).html(null).md(null).addSection(SECTION_1).expectedGenerator(LEGACY_HOTSPOT).build(), + aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(null).addSection(SECTION_1).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(), + aRuleOfType(SECURITY_HOTSPOT).html(null).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_MD_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_MD_SECTION).build(), + aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build() ); } @@ -103,9 +104,9 @@ public class RuleDescriptionSectionsGeneratorsTest { private final RuleDescriptionGeneratorTestData testData; - private final RuleDescriptionSectionsGenerator advancedRuleDescriptionSectionsGenerator = new AdvancedRuleDescriptionSectionsGenerator(uuidFactory); private final RuleDescriptionSectionsGenerator legacyHotspotRuleDescriptionSectionsGenerator = new LegacyHotspotRuleDescriptionSectionsGenerator(uuidFactory); - private final RuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator = new LegacyIssueRuleDescriptionSectionsGenerator(uuidFactory); + private final LegacyIssueRuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator = new LegacyIssueRuleDescriptionSectionsGenerator(uuidFactory); + private final RuleDescriptionSectionsGenerator advancedRuleDescriptionSectionsGenerator = new AdvancedRuleDescriptionSectionsGenerator(uuidFactory, legacyIssueRuleDescriptionSectionsGenerator); Map<RuleDescriptionSectionGeneratorIdentifier, RuleDescriptionSectionsGenerator> idToGenerator = ImmutableMap.<RuleDescriptionSectionGeneratorIdentifier, RuleDescriptionSectionsGenerator>builder() .put(ADVANCED_RULE, advancedRuleDescriptionSectionsGenerator) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java index fa8f88e5382..18c7ff3cfba 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java @@ -348,13 +348,12 @@ public class RuleMapper { } if (shouldReturnField(fieldsToReturn, FIELD_DESCRIPTION_SECTIONS)) { - for (var section : ruleDto.getRuleDescriptionSectionDtos()) { - Rules.Rule.DescriptionSection.Builder sectionBuilder = ruleResponse.getDescriptionSectionsBuilder().addDescriptionSectionsBuilder() - .setKey(section.getKey()) - .setContent(retrieveDescriptionContent(ruleDto.getDescriptionFormat(), section)); - toProtobufContext(section.getContext()).ifPresent(sectionBuilder::setContext); - sectionBuilder.build(); - } + Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = ruleDto.getRuleDescriptionSectionDtos(); + Set<Rules.Rule.DescriptionSection> sections = ruleDescriptionSectionDtos.stream() + .filter(sectionDto -> !isDefaultAndMoreThanOneSectionPresent(ruleDescriptionSectionDtos, sectionDto)) + .map(sectionDto -> toDescriptionSection(ruleDto, sectionDto)) + .collect(Collectors.toSet()); + ruleResponse.setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder().addAllDescriptionSections(sections).build()); } if (shouldReturnField(fieldsToReturn, FIELD_MARKDOWN_DESCRIPTION)) { @@ -368,6 +367,23 @@ public class RuleMapper { } } + /** + * This was done to preserve backward compatibility with SonarLint until they stop using htmlDesc field in api/rules/[show|search] endpoints, see SONAR-16635 + * @deprecated the method should be removed once SonarLint supports rules.descriptionSections fields, I.E in 10.x and DB is cleaned up of non-necessary default sections. + */ + @Deprecated(since = "9.6", forRemoval = true) + private static boolean isDefaultAndMoreThanOneSectionPresent(Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos, RuleDescriptionSectionDto s) { + return ruleDescriptionSectionDtos.size() > 1 && s.isDefault(); + } + + private static Rules.Rule.DescriptionSection toDescriptionSection(RuleDto ruleDto, RuleDescriptionSectionDto section) { + Rules.Rule.DescriptionSection.Builder sectionBuilder = Rules.Rule.DescriptionSection.newBuilder() + .setKey(section.getKey()) + .setContent(retrieveDescriptionContent(ruleDto.getDescriptionFormat(), section)); + toProtobufContext(section.getContext()).ifPresent(sectionBuilder::setContext); + return sectionBuilder.build(); + } + private static String retrieveDescriptionContent(@Nullable RuleDto.Format format, RuleDescriptionSectionDto sectionDto) { return MARKDOWN.equals(format) ? Markdown.convertToHtml(sectionDto.getContent()) : diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java index 2cfd6cbe501..0b0dd9fc73f 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java @@ -370,7 +370,6 @@ public class ShowActionTest { RuleDescriptionSectionDto section4context1 = createRuleDescriptionSectionWithContext(RESOURCES_SECTION_KEY, "<div>I want to fix with Spring</div>", "ctx1"); RuleDescriptionSectionDto section4context2 = createRuleDescriptionSectionWithContext(RESOURCES_SECTION_KEY, "<div>I want to fix with Servlet</div>", "ctx2"); - RuleDto rule = createRuleWithDescriptionSections(section1, section2, section3, section4context1, section4context2); rule.setType(RuleType.SECURITY_HOTSPOT); rule.setNoteUserUuid(userDto.getUuid()); @@ -381,17 +380,7 @@ public class ShowActionTest { .executeProtobuf(ShowResponse.class); Rule resultRule = result.getRule(); - assertThat(resultRule.getHtmlDesc()) - .contains( - "<h2>What is the risk?</h2>" - + "<div>Root is Root</div><br/>" - + "<h2>Assess the risk</h2>" - + "<div>This is not a problem</div><br/>" - + "<h2>How can you fix it?</h2>" - + "<div>I don't want to fix</div><br/>" - + "<div>I want to fix with Spring</div>" - ); - + assertThat(resultRule.getHtmlDesc()).isEmpty(); assertThat(resultRule.getMdDesc()).isEqualTo(resultRule.getHtmlDesc()); assertThat(resultRule.getDescriptionSections().getDescriptionSectionsList()) @@ -406,6 +395,32 @@ public class ShowActionTest { } @Test + public void show_if_advanced_sections_and_default_filters_out_default() { + when(macroInterpreter.interpret(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); + + RuleDescriptionSectionDto section1 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, "<div>Root is Root</div>"); + RuleDescriptionSectionDto defaultSection = createDefaultRuleDescriptionSection(uuidFactory.create(), "This is the default section"); + + RuleDto rule = createRuleWithDescriptionSections(section1, defaultSection); + rule.setType(RuleType.SECURITY_HOTSPOT); + rule.setNoteUserUuid(userDto.getUuid()); + db.rules().insert(rule); + + ShowResponse result = ws.newRequest() + .setParam(PARAM_KEY, rule.getKey().toString()) + .executeProtobuf(ShowResponse.class); + + Rule resultRule = result.getRule(); + assertThat(resultRule.getHtmlDesc()).contains(defaultSection.getContent()); + + assertThat(resultRule.getMdDesc()).isEqualTo(resultRule.getHtmlDesc()); + + assertThat(resultRule.getDescriptionSections().getDescriptionSectionsList()) + .extracting(Rule.DescriptionSection::getKey, Rule.DescriptionSection::getContent) + .containsExactlyInAnyOrder(tuple(ROOT_CAUSE_SECTION_KEY, "<div>Root is Root</div>")); + } + + @Test public void show_rule_markdown_description() { when(macroInterpreter.interpret(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); |