From: Léo Geoffroy Date: Tue, 8 Oct 2024 07:11:33 +0000 (+0200) Subject: SONAR-23250 Add backward and forward severity mapping for quality profile X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=b29e456b954d8a64efa8d7205a614f3c0887e04d;p=sonarqube.git SONAR-23250 Add backward and forward severity mapping for quality profile --- diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileImpactSeverityMapper.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileImpactSeverityMapper.java new file mode 100644 index 00000000000..ed37db38cb9 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileImpactSeverityMapper.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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 com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import java.util.EnumMap; +import java.util.Map; +import javax.annotation.CheckForNull; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; +import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.internal.ImpactMapper; + +/** + * Class to map impact severity and rule severity during the override of severity of quality profile. + * We want to keep the severities synchronized, if the rule type or the impacts severities are customized + */ +public class QProfileImpactSeverityMapper { + + private static final BiMap SEVERITY_MAPPING = new ImmutableBiMap.Builder() + .put(Severity.INFO, org.sonar.api.rule.Severity.INFO) + .put(Severity.LOW, org.sonar.api.rule.Severity.MINOR) + .put(Severity.MEDIUM, org.sonar.api.rule.Severity.MAJOR) + .put(Severity.HIGH, org.sonar.api.rule.Severity.CRITICAL) + .put(Severity.BLOCKER, org.sonar.api.rule.Severity.BLOCKER) + .build(); + + private QProfileImpactSeverityMapper() { + } + + public static Map mapImpactSeverities(String severity, Map ruleImpacts, RuleType ruleType) { + if (ruleType == RuleType.SECURITY_HOTSPOT) { + return Map.of(); + } + SoftwareQuality softwareQuality = ImpactMapper.convertToSoftwareQuality(ruleType); + Map result = new EnumMap<>(ruleImpacts); + if (ruleImpacts.containsKey(softwareQuality)) { + result.put(softwareQuality, SEVERITY_MAPPING.inverse().get(severity)); + } else if (ruleImpacts.size() == 1) { + result.replaceAll((sq, sev) -> SEVERITY_MAPPING.inverse().get(severity)); + } + return result; + } + + @CheckForNull + public static String mapSeverity(Map impacts, RuleType ruleType, String ruleSeverity) { + SoftwareQuality softwareQuality = ImpactMapper.convertToSoftwareQuality(ruleType); + if (impacts.containsKey(softwareQuality)) { + return SEVERITY_MAPPING.get(impacts.get(softwareQuality)); + } else if (impacts.size() == 1) { + return SEVERITY_MAPPING.get(impacts.entrySet().iterator().next().getValue()); + } + return ruleSeverity; + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileImpactSeverityMapperTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileImpactSeverityMapperTest.java new file mode 100644 index 00000000000..a13fb9cc04f --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileImpactSeverityMapperTest.java @@ -0,0 +1,147 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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.util.Map; +import org.junit.jupiter.api.Test; +import org.sonar.api.issue.impact.SoftwareQuality; +import org.sonar.api.rule.Severity; +import org.sonar.api.rules.RuleType; + +import static org.assertj.core.api.Assertions.assertThat; + +class QProfileImpactSeverityMapperTest { + + public static final Map IMPACTS = Map.of( + SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH, + SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW, + SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.INFO); + + @Test + void mapImpactSeverities_whenSecurityHotspot_shouldReturnEmptyMap() { + Map result = QProfileImpactSeverityMapper.mapImpactSeverities(Severity.MAJOR, + Map.of(SoftwareQuality.MAINTAINABILITY, + org.sonar.api.issue.impact.Severity.HIGH), + RuleType.SECURITY_HOTSPOT); + + assertThat(result).isEmpty(); + } + + @Test + void mapImpactSeverities_whenOneImpact_shouldReturnOverriddenImpact() { + Map result = QProfileImpactSeverityMapper.mapImpactSeverities(Severity.INFO, + Map.of(SoftwareQuality.MAINTAINABILITY, + org.sonar.api.issue.impact.Severity.HIGH), + RuleType.CODE_SMELL); + + assertThat(result).hasSize(1).containsEntry(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.INFO); + } + + @Test + void mapImpactSeverities_whenOneDifferentImpact_shouldReturnOverriddenImpact() { + Map result = QProfileImpactSeverityMapper.mapImpactSeverities(Severity.INFO, + Map.of(SoftwareQuality.RELIABILITY, + org.sonar.api.issue.impact.Severity.HIGH), + RuleType.CODE_SMELL); + + assertThat(result).hasSize(1).containsEntry(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.INFO); + } + + @Test + void mapImpactSeverities_whenMultipleImpact_shouldReturnOverriddenImpactMatchingCodeSmell() { + + Map result = QProfileImpactSeverityMapper.mapImpactSeverities(Severity.BLOCKER, IMPACTS, RuleType.CODE_SMELL); + + assertThat(result).hasSize(3) + .containsEntry(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.BLOCKER) + .containsEntry(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW) + .containsEntry(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.INFO); + + result = QProfileImpactSeverityMapper.mapImpactSeverities(Severity.BLOCKER, IMPACTS, RuleType.BUG); + + assertThat(result).hasSize(3) + .containsEntry(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH) + .containsEntry(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.BLOCKER) + .containsEntry(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.INFO); + + result = QProfileImpactSeverityMapper.mapImpactSeverities(Severity.BLOCKER, IMPACTS, RuleType.VULNERABILITY); + + assertThat(result).hasSize(3) + .containsEntry(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH) + .containsEntry(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW) + .containsEntry(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.BLOCKER); + } + + @Test + void mapImpactSeverities_whenMultipleImpactNotMatchingRuleType_shouldReturnRuleImpacts() { + Map impacts = Map.of( + SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW, + SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.INFO); + + Map result = QProfileImpactSeverityMapper.mapImpactSeverities(Severity.BLOCKER, impacts, RuleType.CODE_SMELL); + assertThat(result).hasSize(2) + .containsEntry(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW) + .containsEntry(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.INFO); + } + + @Test + void mapSeverity_whenOneImpact_ShouldReturnMappedImpactSeverity() { + String severity = QProfileImpactSeverityMapper.mapSeverity( + Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH), + RuleType.BUG, Severity.BLOCKER); + + assertThat(severity).isEqualTo(Severity.CRITICAL); + } + + @Test + void mapSeverity_whenMultipleImpacts_ShouldReturnMappedImpactSeverity() { + String severity = QProfileImpactSeverityMapper.mapSeverity( + IMPACTS, + RuleType.BUG, Severity.BLOCKER); + + assertThat(severity).isEqualTo(Severity.MINOR); + + severity = QProfileImpactSeverityMapper.mapSeverity( + IMPACTS, + RuleType.VULNERABILITY, Severity.BLOCKER); + + assertThat(severity).isEqualTo(Severity.INFO); + + severity = QProfileImpactSeverityMapper.mapSeverity( + IMPACTS, + RuleType.CODE_SMELL, Severity.BLOCKER); + + assertThat(severity).isEqualTo(Severity.CRITICAL); + } + + @Test + void mapImpactSeverities_whenMultipleImpactNotMatchingRuleType_shouldReturnRuleSeverity() { + Map impacts = Map.of( + SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW, + SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.INFO); + + String severity = QProfileImpactSeverityMapper.mapSeverity( + impacts, + RuleType.CODE_SMELL, Severity.BLOCKER); + + assertThat(severity).isEqualTo(Severity.BLOCKER); + } + +}