*/
package org.sonar.server.rule.ws;
-import org.junit.Rule;
-import org.junit.Test;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
+import org.sonar.db.issue.ImpactDto;
import org.sonar.db.rule.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.user.UserDto;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.AssertionsForClassTypes.tuple;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.sonar.server.rule.ws.UpdateAction.PARAM_TAGS;
import static org.sonar.test.JsonAssert.assertJson;
-public class UpdateActionIT {
+class UpdateActionIT {
private static final long PAST = 10000L;
- @Rule
+ @RegisterExtension
public DbTester db = DbTester.create();
- @Rule
+ @RegisterExtension
public EsTester es = EsTester.create();
- @Rule
+ @RegisterExtension
public UserSessionRule userSession = UserSessionRule.standalone();
private final DbClient dbClient = db.getDbClient();
private final WsActionTester ws = new WsActionTester(underTest);
@Test
- public void check_definition() {
+ void check_definition() {
assertThat(ws.getDef().isPost()).isTrue();
assertThat(ws.getDef().isInternal()).isFalse();
assertThat(ws.getDef().responseExampleAsString()).isNotNull();
}
@Test
- public void update_custom_rule() {
+ void update_custom_rule() {
logInAsQProfileAdministrator();
RuleDto templateRule = db.rules().insert(
r -> r.setRuleKey(RuleKey.of("java", "S001")),
}
@Test
- public void update_tags() {
+ void update_tags() {
logInAsQProfileAdministrator();
RuleDto rule = db.rules().insert(setSystemTags("stag1", "stag2"), setTags("tag1", "tag2"), r -> r.setNoteData(null).setNoteUserUuid(null));
}
@Test
- public void update_rule_remediation_function() {
+ void update_rule_remediation_function() {
logInAsQProfileAdministrator();
RuleDto rule = db.rules().insert(
}
@Test
- public void update_note() {
+ void update_note() {
UserDto userHavingUpdatingNote = db.users().insertUser();
RuleDto rule = db.rules().insert(m -> m.setNoteData("old data").setNoteUserUuid(userHavingUpdatingNote.getUuid()));
UserDto userAuthenticated = db.users().insertUser();
}
@Test
- public void fail_to_update_custom_when_description_is_empty() {
+ void fail_to_update_custom_when_description_is_empty() {
logInAsQProfileAdministrator();
RuleDto templateRule = db.rules().insert(
r -> r.setRuleKey(RuleKey.of("java", "S001")),
}
@Test
- public void throw_IllegalArgumentException_if_trying_to_update_builtin_rule_description() {
+ void throw_IllegalArgumentException_if_trying_to_update_builtin_rule_description() {
logInAsQProfileAdministrator();
RuleDto rule = db.rules().insert();
}
@Test
- public void throw_ForbiddenException_if_not_profile_administrator() {
+ void throw_ForbiddenException_if_not_profile_administrator() {
userSession.logIn();
assertThatThrownBy(() -> {
}
@Test
- public void throw_UnauthorizedException_if_not_logged_in() {
+ void throw_UnauthorizedException_if_not_logged_in() {
assertThatThrownBy(() -> {
ws.newRequest().setMethod("POST").execute();
})
}
@Test
- public void returnRuleCleanCodeFields_whenEndpointIsCalled() {
+ void returnRuleCleanCodeFields_whenEndpointIsCalled() {
UserDto userAuthenticated = db.users().insertUser();
userSession.logIn(userAuthenticated).addPermission(ADMINISTER_QUALITY_PROFILES);
assertThat(updateResponse.getRule()).extracting(Rules.Rule::getCleanCodeAttributeCategory).isEqualTo(Common.CleanCodeAttributeCategory.INTENTIONAL);
}
+ @Test
+ void update_whenImpactProvided_shouldAlsoUpdateStandardSeverity() {
+ logInAsQProfileAdministrator();
+
+ RuleDto templateRule = db.rules().insert(
+ r -> r.setRuleKey(RuleKey.of("java", "S001")),
+ r -> r.setIsTemplate(true),
+ r -> r.setCreatedAt(PAST),
+ r -> r.setUpdatedAt(PAST));
+
+ RuleDto rule = db.rules().insert(ruleDto -> ruleDto
+ .setTemplateUuid(templateRule.getUuid())
+ .setType(RuleType.BUG)
+ .setSeverity(Severity.MAJOR)
+ .replaceAllDefaultImpacts(List.of(new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH))));
+
+ Rules.UpdateResponse updateResponse = ws.newRequest().setMethod("POST")
+ .setParam("key", rule.getKey().toString())
+ .setParam("impacts", "RELIABILITY=BLOCKER")
+ .executeProtobuf(Rules.UpdateResponse.class);
+
+ Rules.Rule ruleResponse = updateResponse.getRule();
+ assertThat(ruleResponse)
+ .extracting(r -> r.getImpacts().getImpactsList().stream().findFirst()
+ .orElseThrow(() -> new IllegalStateException("Impact is a mandatory field in the response.")))
+ .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+ .containsExactly(Common.SoftwareQuality.RELIABILITY, Common.ImpactSeverity.ImpactSeverity_BLOCKER);
+ assertThat(ruleResponse.getSeverity()).isEqualTo("BLOCKER");
+ }
+
+ @Test
+ void update_whenMultipleImpactsProvided_shouldOnlyUpdateExisting() {
+ logInAsQProfileAdministrator();
+
+ RuleDto templateRule = db.rules().insert(
+ r -> r.setRuleKey(RuleKey.of("java", "S001")),
+ r -> r.setIsTemplate(true),
+ r -> r.setCreatedAt(PAST),
+ r -> r.setUpdatedAt(PAST));
+
+ RuleDto rule = db.rules().insert(ruleDto -> ruleDto
+ .setTemplateUuid(templateRule.getUuid())
+ .setType(RuleType.BUG)
+ .setSeverity(Severity.MAJOR)
+ .replaceAllDefaultImpacts(List.of(
+ new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM),
+ new ImpactDto(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM))));
+
+ Rules.UpdateResponse updateResponse = ws.newRequest().setMethod("POST")
+ .setParam("key", rule.getKey().toString())
+ .setParam("impacts", "RELIABILITY=BLOCKER;SECURITY=BLOCKER;MAINTAINABILITY=BLOCKER")
+ .executeProtobuf(Rules.UpdateResponse.class);
+
+ Rules.Rule ruleResponse = updateResponse.getRule();
+ assertThat(ruleResponse.getImpacts().getImpactsList())
+ .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+ .containsExactlyInAnyOrder(
+ tuple(Common.SoftwareQuality.RELIABILITY, Common.ImpactSeverity.ImpactSeverity_BLOCKER),
+ tuple(Common.SoftwareQuality.SECURITY, Common.ImpactSeverity.ImpactSeverity_BLOCKER));
+ assertThat(ruleResponse.getSeverity()).isEqualTo("BLOCKER");
+ }
+
+ @Test
+ void update_whenImpactsDontMatch_shouldNotUpdateAnything() {
+ logInAsQProfileAdministrator();
+
+ RuleDto templateRule = db.rules().insert(
+ r -> r.setRuleKey(RuleKey.of("java", "S001")),
+ r -> r.setIsTemplate(true),
+ r -> r.setCreatedAt(PAST),
+ r -> r.setUpdatedAt(PAST));
+
+ RuleDto rule = db.rules().insert(ruleDto -> ruleDto
+ .setTemplateUuid(templateRule.getUuid())
+ .setType(RuleType.BUG)
+ .setSeverity(Severity.MAJOR)
+ .replaceAllDefaultImpacts(List.of(
+ new ImpactDto(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM))));
+
+ Rules.UpdateResponse updateResponse = ws.newRequest().setMethod("POST")
+ .setParam("key", rule.getKey().toString())
+ .setParam("impacts", "SECURITY=BLOCKER")
+ .executeProtobuf(Rules.UpdateResponse.class);
+
+ Rules.Rule ruleResponse = updateResponse.getRule();
+ assertThat(ruleResponse.getImpacts().getImpactsList())
+ .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+ .containsExactlyInAnyOrder(
+ tuple(Common.SoftwareQuality.RELIABILITY, Common.ImpactSeverity.MEDIUM));
+ assertThat(ruleResponse.getSeverity()).isEqualTo("MAJOR");
+ }
+
private void logInAsQProfileAdministrator() {
userSession
.logIn()
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+public class ParamParsingUtils {
+ private ParamParsingUtils() {
+ // utility class
+ }
+
+ public static Pair<SoftwareQuality, Severity> parseImpact(String impact) {
+ String[] parts = impact.split("=");
+ if (parts.length != 2) {
+ throw new IllegalArgumentException("Invalid impact format: " + impact);
+ }
+ return Pair.of(SoftwareQuality.valueOf(parts[0]),
+ Severity.valueOf(parts[1]));
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.common;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.core.rule.ImpactSeverityMapper.mapImpactSeverity;
import static org.sonar.db.component.BranchType.BRANCH;
+import static org.sonar.server.common.ParamParsingUtils.parseImpact;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_SEVERITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
throw new IllegalArgumentException("One of the parameters 'severity' or 'impact' must be provided");
}
}
-
- private static Pair<SoftwareQuality, org.sonar.api.issue.impact.Severity> parseImpact(String impact) {
- String[] parts = impact.split("=");
- if (parts.length != 2) {
- throw new IllegalArgumentException("Invalid impact format: " + impact);
- }
- return Pair.of(SoftwareQuality.valueOf(parts[0]),
- org.sonar.api.issue.impact.Severity.valueOf(parts[1]));
- }
}
*/
package org.sonar.server.rule;
+import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.server.debt.DebtRemediationFunction;
private boolean changeName = false;
private boolean changeDescription = false;
private boolean changeSeverity = false;
+ private boolean changeImpacts = false;
private boolean changeStatus = false;
private boolean changeParameters = false;
private final RuleUpdateUseCase useCase;
private String name;
private String markdownDescription;
private String severity;
+ private final Map<SoftwareQuality, Severity> impactSeverities = new EnumMap<>(SoftwareQuality.class);
private RuleStatus status;
private final Map<String, String> parameters = new HashMap<>();
return this;
}
+ public Map<SoftwareQuality, Severity> getImpactSeverities() {
+ return impactSeverities;
+ }
+
+ /**
+ * Impacts to be updated (only for custom rules)
+ */
+ public RuleUpdate setImpactSeverities(Map<SoftwareQuality, Severity> impactSeverities) {
+ checkCustomRule();
+ this.impactSeverities.clear();
+ this.impactSeverities.putAll(impactSeverities);
+ changeImpacts = true;
+ return this;
+ }
+
@CheckForNull
public RuleStatus getStatus() {
return status;
return changeSeverity;
}
+ public boolean isChangeImpacts() {
+ return changeImpacts;
+ }
+
public boolean isChangeStatus() {
return changeStatus;
}
}
private boolean isCustomRuleFieldsEmpty() {
- return !changeName && !changeDescription && !changeSeverity && !changeStatus && !changeParameters;
+ return !changeName && !changeDescription && !changeSeverity && !changeStatus && !changeParameters && !changeImpacts;
}
private void checkCustomRule() {
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.rule.internal.ImpactMapper;
import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Lists.newArrayList;
import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.sonar.api.server.rule.internal.ImpactMapper.convertToDeprecatedSeverity;
+import static org.sonar.api.server.rule.internal.ImpactMapper.convertToRuleType;
+import static org.sonar.core.rule.ImpactSeverityMapper.mapImpactSeverity;
import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
@ServerSide
if (update.isChangeSeverity()) {
updateSeverity(update, rule);
}
+ if (update.isChangeImpacts()) {
+ updateImpactSeverityAndStandardSeverityIfTypeMatch(update, rule);
+ }
if (update.isChangeStatus()) {
updateStatus(update, rule);
}
.stream()
.filter(i -> i.getSoftwareQuality().equals(ImpactMapper.convertToSoftwareQuality(rule.getEnumType())))
.findFirst()
- .ifPresent(i -> i.setSeverity(ImpactMapper.convertToImpactSeverity(severity)));
+ .ifPresent(i -> i.setSeverity(mapImpactSeverity(severity)));
+ }
+
+ private static void updateImpactSeverityAndStandardSeverityIfTypeMatch(RuleUpdate update, RuleDto rule) {
+ Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts = update.getImpactSeverities();
+ if (impacts.isEmpty()) {
+ throw new IllegalArgumentException("Impacts are is missing");
+ }
+ impacts.forEach((key, value) -> rule.getDefaultImpacts()
+ .stream()
+ .filter(i -> i.getSoftwareQuality().equals(key))
+ .findFirst()
+ .ifPresent(i -> {
+ i.setSeverity(value);
+ if (Objects.equals(convertToRuleType(key), RuleType.valueOf(rule.getType()))) {
+ rule.setSeverity(convertToDeprecatedSeverity(value));
+ }
+ }));
}
private static void updateName(RuleUpdate update, RuleDto rule) {
import com.google.common.io.Resources;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.EnumMap;
import java.util.List;
+import java.util.Map;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
+import static org.sonar.server.common.ParamParsingUtils.parseImpact;
import static org.sonar.server.rule.ws.CreateAction.KEY_MAXIMUM_LENGTH;
import static org.sonar.server.rule.ws.CreateAction.NAME_MAXIMUM_LENGTH;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
public static final String PARAM_NAME = "name";
public static final String PARAM_DESCRIPTION = "markdownDescription";
public static final String PARAM_SEVERITY = "severity";
+ public static final String PARAM_IMPACTS = "impacts";
public static final String PARAM_STATUS = "status";
public static final String PARAMS = "params";
.setDescription("Update an existing rule.<br>" +
"Requires the 'Administer Quality Profiles' permission")
.setChangelog(
- new Change("10.2", "The field 'severity' and 'type' in the response have been deprecated, use 'impacts' instead."),
+ new Change("10.8", String.format("Parameter %s was added.", PARAM_IMPACTS)),
+ new Change("10.8", String.format("The parameter '%s' is not deprecated anymore.", PARAM_SEVERITY)),
+ new Change("10.8", "The field 'severity' and 'type' in the response are not deprecated anymore."),
new Change("10.4", String.format("The parameter '%s' is deprecated.", PARAM_SEVERITY)),
new Change("10.4", "Updating a removed rule is now possible."),
- new Change("10.8", String.format("The parameter '%s' is not deprecated anymore.", PARAM_SEVERITY)))
- .setSince("4.4")
- .setChangelog(
+ new Change("10.2", "The field 'severity' and 'type' in the response have been deprecated, use 'impacts' instead."),
new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"))
+ .setSince("4.4")
.setHandler(this);
action.createParam(PARAM_KEY)
.setDescription("Rule severity (Only when updating a custom rule)")
.setPossibleValues(Severity.ALL);
+ action
+ .createParam(PARAM_IMPACTS)
+ .setDescription("Rule impacts, semicolon-separated (Only when updating a custom rule impact severity)")
+ .setExampleValue("MAINTAINABILITY=HIGH;SECURITY=LOW");
+
action
.createParam(PARAM_STATUS)
.setPossibleValues(RuleStatus.values())
update.setMarkdownDescription(description);
}
String severity = request.param(PARAM_SEVERITY);
+ String impacts = request.param(PARAM_IMPACTS);
+ if (impacts != null && severity != null) {
+ throw new IllegalArgumentException("Both 'severity' and 'impacts' parameters cannot be set at the same time");
+ }
+ if (impacts != null) {
+ Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> parsedImpact = parseImpacts(impacts);
+ update.setImpactSeverities(parsedImpact);
+ }
if (severity != null) {
update.setSeverity(severity);
}
return responseBuilder.build();
}
+
+ private static Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> parseImpacts(String impacts) {
+ Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> parsedImpacts = new EnumMap<>(SoftwareQuality.class);
+ for (String impact : impacts.split(";")) {
+ Pair<SoftwareQuality, org.sonar.api.issue.impact.Severity> pair = parseImpact(impact);
+ parsedImpacts.put(pair.getKey(), pair.getValue());
+ }
+ return parsedImpacts;
+ }
}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.junit.jupiter.api.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ParamParsingUtilsTest {
+
+ @Test
+ void parseImpact_whenCorrectParam_ShouldReturnExpectedResult() {
+ Pair<SoftwareQuality, Severity> result = ParamParsingUtils.parseImpact("MAINTAINABILITY=BLOCKER");
+ assertEquals(SoftwareQuality.MAINTAINABILITY, result.getKey());
+ assertEquals(Severity.BLOCKER, result.getValue());
+ }
+
+ @Test
+ void parseImpact_whenInvalidParam_ShouldThrowException() {
+ assertThatThrownBy(() -> ParamParsingUtils.parseImpact("MAINTAINABILITY"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Invalid impact format: MAINTAINABILITY");
+ }
+
+ @Test
+ void parseImpact_whenInvalidValues_ShouldThrowException() {
+ assertThatThrownBy(() -> ParamParsingUtils.parseImpact("MAINTAINABILITY=MAJOR"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("No enum constant org.sonar.api.issue.impact.Severity.MAJOR");
+ assertThatThrownBy(() -> ParamParsingUtils.parseImpact("BUG=LOW"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("No enum constant org.sonar.api.issue.impact.SoftwareQuality.BUG");
+ }
+}
+