From b507c1c7bb753f87be137dd82a9b8cace24f3bcb Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Mon, 9 May 2022 12:10:19 +0200 Subject: [PATCH] SONAR-16397 update hotspot API to handle advanced rule descriptions --- .../rule/HotspotRuleDescriptionTest.java | 268 ------------------ .../sonar/server/hotspot/ws/ShowAction.java | 39 ++- .../server/hotspot/ws/ShowActionTest.java | 113 +++++++- sonar-ws/src/main/protobuf/ws-hotspots.proto | 6 +- 4 files changed, 144 insertions(+), 282 deletions(-) delete mode 100644 server/sonar-server-common/src/test/java/org/sonar/server/rule/HotspotRuleDescriptionTest.java 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 deleted file mode 100644 index 07f9045ee5a..00000000000 --- a/server/sonar-server-common/src/test/java/org/sonar/server/rule/HotspotRuleDescriptionTest.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * 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; - -import com.tngtech.java.junit.dataprovider.DataProvider; -import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import com.tngtech.java.junit.dataprovider.UseDataProvider; -import org.junit.Test; -import org.junit.runner.RunWith; -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; - -@RunWith(DataProviderRunner.class) -public class HotspotRuleDescriptionTest { - - @Test - public void parse_returns_all_empty_fields_when_no_description() { - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection(); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk()).isEmpty(); - assertThat(result.getVulnerable()).isEmpty(); - assertThat(result.getFixIt()).isEmpty(); - } - - @Test - public void parse_returns_all_empty_fields_when_empty_description() { - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", "")); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk()).isEmpty(); - assertThat(result.getVulnerable()).isEmpty(); - assertThat(result.getFixIt()).isEmpty(); - } - - @Test - @UseDataProvider("descriptionsWithoutTitles") - public void parse_to_risk_description_fields_when_desc_contains_no_section(String description) { - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", description)); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk()).contains(description); - assertThat(result.getVulnerable()).isEmpty(); - assertThat(result.getFixIt()).isEmpty(); - } - - @DataProvider - public static Object[][] descriptionsWithoutTitles() { - return new Object[][] { - {randomAlphabetic(123)}, - {"bar\n" + - "acme\n" + - "foo"} - }; - } - - @Test - public void parse_return_null_risk_when_desc_starts_with_ask_yourself_title() { - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( - createDefaultRuleDescriptionSection("uuid", (ASKATRISK + RECOMMENTEDCODINGPRACTICE))); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk()).isEmpty(); - assertThat(result.getVulnerable()).contains(ASKATRISK); - assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE); - } - - @Test - public void parse_return_null_vulnerable_when_no_ask_yourself_whether_title() { - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection() - .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + RECOMMENTEDCODINGPRACTICE))); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk()).contains(DESCRIPTION); - assertThat(result.getVulnerable()).isEmpty(); - assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE); - } - - @Test - public void parse_return_null_fixIt_when_desc_has_no_Recommended_Secure_Coding_Practices_title() { - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection() - .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + ASKATRISK))); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk()).contains(DESCRIPTION); - assertThat(result.getVulnerable()).contains(ASKATRISK); - assertThat(result.getFixIt()).isEmpty(); - } - - @Test - public void parse_with_noncompliant_section_not_removed() { - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( - createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + NONCOMPLIANTCODE + COMPLIANTCODE))); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk()).contains(DESCRIPTION); - assertThat(result.getVulnerable()).contains(NONCOMPLIANTCODE); - assertThat(result.getFixIt()).contains(COMPLIANTCODE); - } - - @Test - public void parse_moved_noncompliant_code() { - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( - createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + RECOMMENTEDCODINGPRACTICE + NONCOMPLIANTCODE + SEE))); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk()).contains(DESCRIPTION); - - assertThat(result.getVulnerable()).contains(NONCOMPLIANTCODE); - assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE + SEE); - - } - - @Test - public void parse_moved_sensitivecode_code() { - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( - createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + ASKATRISK + RECOMMENTEDCODINGPRACTICE + SENSITIVECODE + SEE))); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk()).contains(DESCRIPTION); - assertThat(result.getVulnerable()).contains(ASKATRISK + SENSITIVECODE); - assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE + SEE); - } - - @Test - public void parse_custom_rule_description() { - String ruleDescription = "This is the custom rule description"; - String exceptionsContent = "This the exceptions section content"; - String askContent = "This is the ask section content"; - String recommendedContent = "This is the recommended section content"; - - RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection() - .setTemplateUuid("123") - .setDescriptionFormat(RuleDto.Format.MARKDOWN) - .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection( - "uuid", ruleDescription + "\n" - + "== Exceptions" + "\n" - + exceptionsContent + "\n" - + "== Ask Yourself Whether" + "\n" - + askContent + "\n" - + "== Recommended Secure Coding Practices" + "\n" - + recommendedContent + "\n" - )); - - HotspotRuleDescription result = HotspotRuleDescription.from(dto); - - assertThat(result.getRisk().get()).hasToString( - ruleDescription + "
" - + "

Exceptions

" - + exceptionsContent + "
" - ); - assertThat(result.getVulnerable().get()).hasToString( - "

Ask Yourself Whether

" - + askContent + "
" - ); - assertThat(result.getFixIt().get()).hasToString( - "

Recommended Secure Coding Practices

" - + recommendedContent + "
" - ); - } - - /* - * Bunch of static constant to create rule description. - */ - private static final String DESCRIPTION = "

The use of operators pairs ( =+, =- or =! ) where the reversed, single operator was meant (+=,\n" - + - "-= or !=) will compile and run, but not produce the expected results.

\n" + - "

This rule raises an issue when =+, =-, or =! is used without any spacing between the two operators and when\n" + - "there is at least one whitespace character after.

\n"; - private static final String NONCOMPLIANTCODE = "

Noncompliant Code Example

\n" + - "
Integer target = -5;\n" +
-    "Integer num = 3;\n" +
-    "\n" +
-    "target =- num;  // Noncompliant; target = -3. Is that really what's meant?\n" +
-    "target =+ num; // Noncompliant; target = 3\n" +
-    "
\n"; - - private static final String COMPLIANTCODE = "

Compliant Solution

\n" + - "
Integer target = -5;\n" +
-    "Integer num = 3;\n" +
-    "\n" +
-    "target = -num;  // Compliant; intent to assign inverse value of num is clear\n" +
-    "target += num;\n" +
-    "
\n"; - - private static final String SEE = "

See

\n" + - "\n"; - - private static final String RECOMMENTEDCODINGPRACTICE = "

Recommended Secure Coding Practices

\n" + - "\n"; - - private static final String ASKATRISK = "

Ask Yourself Whether

\n" + - "\n"; - - private static final String SENSITIVECODE = "

Sensitive Code Example

\n" + - "
\n" +
-    "// === Java Servlet ===\n" +
-    "@Override\n" +
-    "protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n" +
-    "  resp.setHeader(\"Content-Type\", \"text/plain; charset=utf-8\");\n" +
-    "  resp.setHeader(\"Access-Control-Allow-Origin\", \"http://localhost:8080\"); // Questionable\n" +
-    "  resp.setHeader(\"Access-Control-Allow-Credentials\", \"true\"); // Questionable\n" +
-    "  resp.setHeader(\"Access-Control-Allow-Methods\", \"GET\"); // Questionable\n" +
-    "  resp.getWriter().write(\"response\");\n" +
-    "}\n" +
-    "
\n" + - "
\n" +
-    "// === Spring MVC Controller annotation ===\n" +
-    "@CrossOrigin(origins = \"http://domain1.com\") // Questionable\n" +
-    "@RequestMapping(\"\")\n" +
-    "public class TestController {\n" +
-    "    public String home(ModelMap model) {\n" +
-    "        model.addAttribute(\"message\", \"ok \");\n" +
-    "        return \"view\";\n" +
-    "    }\n" +
-    "\n" +
-    "    @CrossOrigin(origins = \"http://domain2.com\") // Questionable\n" +
-    "    @RequestMapping(value = \"/test1\")\n" +
-    "    public ResponseEntity<String> test1() {\n" +
-    "        return ResponseEntity.ok().body(\"ok\");\n" +
-    "    }\n" +
-    "}\n" +
-    "
\n"; -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java index d9ea20f6388..0658ce71649 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ShowAction.java @@ -26,11 +26,11 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.rule.RuleKey; +import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -42,15 +42,16 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.protobuf.DbIssues; import org.sonar.db.protobuf.DbIssues.Locations; +import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; import org.sonar.db.user.UserDto; +import org.sonar.markdown.Markdown; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.issue.IssueChangeWSSupport; import org.sonar.server.issue.IssueChangeWSSupport.FormattingContext; import org.sonar.server.issue.IssueChangeWSSupport.Load; import org.sonar.server.issue.TextRangeResponseFormatter; import org.sonar.server.issue.ws.UserResponseFormatter; -import org.sonar.server.rule.HotspotRuleDescription; import org.sonar.server.security.SecurityStandards; import org.sonarqube.ws.Common; import org.sonarqube.ws.Hotspots; @@ -64,8 +65,13 @@ import static com.google.common.collect.Sets.difference; import static java.lang.String.format; import static java.util.Collections.singleton; import static java.util.Optional.ofNullable; +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.ROOT_CAUSE_SECTION_KEY; import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.core.util.stream.MoreCollectors.toSet; +import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY; import static org.sonar.server.ws.WsUtils.writeProtobuf; public class ShowAction implements HotspotsWsAction { @@ -95,7 +101,9 @@ public class ShowAction implements HotspotsWsAction { .createAction("show") .setHandler(this) .setDescription("Provides the details of a Security Hotspot.") - .setSince("8.1"); + .setSince("8.1") + .setChangelog(new Change("9.5", "The fields rule.riskDescription, rule.fixRecommendations, rule.vulnerabilityDescription of the response are deprecated." + + " /api/rules/show endpoint should be used to fetch rule descriptions.")); action.createParam(PARAM_HOTSPOT_KEY) .setDescription("Key of the Security Hotspot") @@ -173,14 +181,27 @@ public class ShowAction implements HotspotsWsAction { .setSecurityCategory(sqCategory.getKey()) .setVulnerabilityProbability(sqCategory.getVulnerability().name()); - HotspotRuleDescription hotspotRuleDescription = HotspotRuleDescription.from(ruleDto); - hotspotRuleDescription.getVulnerable().ifPresent(ruleBuilder::setVulnerabilityDescription); - hotspotRuleDescription.getRisk().ifPresent(ruleBuilder::setRiskDescription); - hotspotRuleDescription.getFixIt().ifPresent(ruleBuilder::setFixRecommendations); - + Map sectionKeyToContent = getSectionKeyToContent(ruleDto); + Optional.ofNullable(sectionKeyToContent.get(DEFAULT_KEY)).ifPresent(ruleBuilder::setVulnerabilityDescription); + Optional.ofNullable(sectionKeyToContent.get(ROOT_CAUSE_SECTION_KEY)).ifPresent(ruleBuilder::setVulnerabilityDescription); + Optional.ofNullable(sectionKeyToContent.get(ASSESS_THE_PROBLEM_SECTION_KEY)).ifPresent(ruleBuilder::setRiskDescription); + Optional.ofNullable(sectionKeyToContent.get(HOW_TO_FIX_SECTION_KEY)).ifPresent(ruleBuilder::setFixRecommendations); responseBuilder.setRule(ruleBuilder.build()); } + private static Map getSectionKeyToContent(RuleDto ruleDefinitionDto) { + return ruleDefinitionDto.getRuleDescriptionSectionDtos().stream() + .collect(toMap(RuleDescriptionSectionDto::getKey, + section -> getContentAndConvertToHtmlIfNecessary(ruleDefinitionDto.getDescriptionFormat(), section))); + } + + private static String getContentAndConvertToHtmlIfNecessary(@Nullable RuleDto.Format descriptionFormat, RuleDescriptionSectionDto section) { + if (RuleDto.Format.MARKDOWN.equals(descriptionFormat)) { + return Markdown.convertToHtml(section.getContent()); + } + return section.getContent(); + } + private void formatTextRange(ShowWsResponse.Builder hotspotBuilder, IssueDto hotspot) { textRangeFormatter.formatTextRange(hotspot, hotspotBuilder::setTextRange); } @@ -221,7 +242,7 @@ public class ShowAction implements HotspotsWsAction { Map componentsByUuids = dbClient.componentDao().selectSubProjectsByComponentUuids(dbSession, componentUuids) .stream() - .collect(Collectors.toMap(ComponentDto::uuid, Function.identity(), (componentDto, componentDto2) -> componentDto2)); + .collect(toMap(ComponentDto::uuid, Function.identity(), (componentDto, componentDto2) -> componentDto2)); Set componentUuidsToLoad = copyOf(difference(componentUuids, componentsByUuids.keySet())); if (!componentUuidsToLoad.isEmpty()) { 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 ae7b290965a..7e4146efb2c 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 @@ -55,6 +55,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.protobuf.DbCommons; import org.sonar.db.protobuf.DbIssues; +import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleTesting; import org.sonar.db.user.UserDto; @@ -91,8 +92,14 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; 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.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.component.ComponentTesting.newFileDto; -import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; +import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY; +import static org.sonar.db.rule.RuleDto.Format.HTML; import static org.sonar.db.rule.RuleDto.Format.MARKDOWN; @RunWith(DataProviderRunner.class) @@ -427,6 +434,101 @@ public class ShowActionTest { }; } + + @Test + public void dispatch_description_sections_of_advanced_rule_in_relevant_field() { + ComponentDto project = dbTester.components().insertPrivateProject(); + userSessionRule.registerComponents(project); + userSessionRule.logIn().addProjectPermission(UserRole.USER, project); + ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); + + RuleDescriptionSectionDto introductionSection = generateSectionWithKey(INTRODUCTION_SECTION_KEY); + RuleDescriptionSectionDto rootCauseSection = generateSectionWithKey(ROOT_CAUSE_SECTION_KEY); + RuleDescriptionSectionDto assesTheProblemSection = generateSectionWithKey(ASSESS_THE_PROBLEM_SECTION_KEY); + RuleDescriptionSectionDto resourcesSection = generateSectionWithKey(RESOURCES_SECTION_KEY); + RuleDescriptionSectionDto howToFixSection = generateSectionWithKey(HOW_TO_FIX_SECTION_KEY); + RuleDescriptionSectionDto dummySection = generateSectionWithKey("dummySection"); + + RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, + r -> r.addRuleDescriptionSectionDto(introductionSection) + .addRuleDescriptionSectionDto(rootCauseSection) + .addRuleDescriptionSectionDto(assesTheProblemSection) + .addRuleDescriptionSectionDto(resourcesSection) + .addRuleDescriptionSectionDto(howToFixSection) + .addRuleDescriptionSectionDto(dummySection) + .setDescriptionFormat(HTML)); + + IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file); + mockChangelogAndCommentsFormattingContext(); + + Hotspots.ShowWsResponse response = newRequest(hotspot) + .executeProtobuf(Hotspots.ShowWsResponse.class); + + assertThat(response.getRule().getVulnerabilityDescription()).isEqualTo(rootCauseSection.getContent()); + assertThat(response.getRule().getRiskDescription()).isEqualTo(assesTheProblemSection.getContent()); + assertThat(response.getRule().getFixRecommendations()).isEqualTo(howToFixSection.getContent()); + } + + @Test + public void fallbacks_to_default_section_in_case_of_legacy_rule() { + ComponentDto project = dbTester.components().insertPrivateProject(); + userSessionRule.registerComponents(project); + userSessionRule.logIn().addProjectPermission(UserRole.USER, project); + ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); + + RuleDescriptionSectionDto introductionSection = generateSectionWithKey(DEFAULT_KEY); + + RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, + r -> r.addRuleDescriptionSectionDto(introductionSection) + .setDescriptionFormat(HTML)); + + IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file); + mockChangelogAndCommentsFormattingContext(); + + Hotspots.ShowWsResponse response = newRequest(hotspot) + .executeProtobuf(Hotspots.ShowWsResponse.class); + + assertThat(response.getRule().getVulnerabilityDescription()).isEqualTo(introductionSection.getContent()); + assertThat(response.getRule().getRiskDescription()).isEmpty(); + assertThat(response.getRule().getFixRecommendations()).isEmpty(); + } + + @Test + public void ignore_default_section_if_root_cause_provided() { + ComponentDto project = dbTester.components().insertPrivateProject(); + userSessionRule.registerComponents(project); + userSessionRule.logIn().addProjectPermission(UserRole.USER, project); + ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); + + RuleDescriptionSectionDto introductionSection = generateSectionWithKey(INTRODUCTION_SECTION_KEY); + RuleDescriptionSectionDto rootCauseSection = generateSectionWithKey(ROOT_CAUSE_SECTION_KEY); + RuleDescriptionSectionDto assesTheProblemSection = generateSectionWithKey(ASSESS_THE_PROBLEM_SECTION_KEY); + + RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, + r -> r.addRuleDescriptionSectionDto(introductionSection) + .addRuleDescriptionSectionDto(rootCauseSection) + .addRuleDescriptionSectionDto(assesTheProblemSection) + .setDescriptionFormat(HTML)); + + IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file); + mockChangelogAndCommentsFormattingContext(); + + Hotspots.ShowWsResponse response = newRequest(hotspot) + .executeProtobuf(Hotspots.ShowWsResponse.class); + + assertThat(response.getRule().getVulnerabilityDescription()).isEqualTo(rootCauseSection.getContent()); + assertThat(response.getRule().getRiskDescription()).isEqualTo(assesTheProblemSection.getContent()); + assertThat(response.getRule().getFixRecommendations()).isEmpty(); + } + + private RuleDescriptionSectionDto generateSectionWithKey(String assessTheProblemSectionKey) { + return RuleDescriptionSectionDto.builder() + .uuid(uuidFactory.create()) + .key(assessTheProblemSectionKey) + .content(randomAlphabetic(200)) + .build(); + } + @Test public void returns_html_description_for_custom_rules() { ComponentDto project = dbTester.components().insertPrivateProject(); @@ -436,9 +538,15 @@ public class ShowActionTest { String description = "== Title\n
line1\nline2
"; + RuleDescriptionSectionDto sectionDto = RuleDescriptionSectionDto.builder() + .uuid(uuidFactory.create()) + .key(ASSESS_THE_PROBLEM_SECTION_KEY) + .content(description) + .build(); + RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, r -> r.setTemplateUuid("123") - .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), description)) + .addRuleDescriptionSectionDto(sectionDto) .setDescriptionFormat(MARKDOWN)); IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file); @@ -450,6 +558,7 @@ public class ShowActionTest { assertThat(response.getRule().getRiskDescription()).isEqualTo("

Title

<div>line1
line2</div>"); } + @Test public void handles_null_description_for_custom_rules() { ComponentDto project = dbTester.components().insertPrivateProject(); diff --git a/sonar-ws/src/main/protobuf/ws-hotspots.proto b/sonar-ws/src/main/protobuf/ws-hotspots.proto index 4560baa7dcc..41b89d19269 100644 --- a/sonar-ws/src/main/protobuf/ws-hotspots.proto +++ b/sonar-ws/src/main/protobuf/ws-hotspots.proto @@ -90,7 +90,7 @@ message Rule { optional string name = 2; optional string securityCategory = 3; optional string vulnerabilityProbability = 4; - optional string riskDescription = 5; - optional string vulnerabilityDescription = 6; - optional string fixRecommendations = 7; + optional string riskDescription = 5 [deprecated=true]; + optional string vulnerabilityDescription = 6 [deprecated=true]; + optional string fixRecommendations = 7 [deprecated=true]; } -- 2.39.5