]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23250 Add backward and forward severity mapping for quality profile
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Tue, 8 Oct 2024 07:11:33 +0000 (09:11 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Oct 2024 20:03:01 +0000 (20:03 +0000)
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileImpactSeverityMapper.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileImpactSeverityMapperTest.java [new file with mode: 0644]

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 (file)
index 0000000..ed37db3
--- /dev/null
@@ -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, String> SEVERITY_MAPPING = new ImmutableBiMap.Builder<Severity, String>()
+    .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<SoftwareQuality, Severity> mapImpactSeverities(String severity, Map<SoftwareQuality, Severity> ruleImpacts, RuleType ruleType) {
+    if (ruleType == RuleType.SECURITY_HOTSPOT) {
+      return Map.of();
+    }
+    SoftwareQuality softwareQuality = ImpactMapper.convertToSoftwareQuality(ruleType);
+    Map<SoftwareQuality, Severity> 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<SoftwareQuality, Severity> 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 (file)
index 0000000..a13fb9c
--- /dev/null
@@ -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<SoftwareQuality, org.sonar.api.issue.impact.Severity> 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<SoftwareQuality, org.sonar.api.issue.impact.Severity> 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<SoftwareQuality, org.sonar.api.issue.impact.Severity> 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<SoftwareQuality, org.sonar.api.issue.impact.Severity> 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<SoftwareQuality, org.sonar.api.issue.impact.Severity> 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<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts = Map.of(
+      SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW,
+      SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.INFO);
+
+    Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> 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<SoftwareQuality, org.sonar.api.issue.impact.Severity> 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);
+  }
+
+}