diff options
author | lukasz-jarocki-sonarsource <lukasz.jarocki@sonarsource.com> | 2023-10-05 17:06:41 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-10-10 20:02:44 +0000 |
commit | 9ff811fd2d3421731dfb4097e8748cee1553d2c5 (patch) | |
tree | 45d4866bd21131d61def4f67536433aaade279c2 /server/sonar-webserver-core | |
parent | 047d3846d5e415786323625cd5101a9d54b04725 (diff) | |
download | sonarqube-9ff811fd2d3421731dfb4097e8748cee1553d2c5.tar.gz sonarqube-9ff811fd2d3421731dfb4097e8748cee1553d2c5.zip |
SONAR-20548 added event for changing the CCT data for rules
Diffstat (limited to 'server/sonar-webserver-core')
-rw-r--r-- | server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java (renamed from server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/RulesRegistrantIT.java) | 16 | ||||
-rw-r--r-- | server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java | 122 | ||||
-rw-r--r-- | server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java | 27 | ||||
-rw-r--r-- | server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java | 88 | ||||
-rw-r--r-- | server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java | 155 | ||||
-rw-r--r-- | server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/StartupRuleUpdaterTest.java | 126 |
6 files changed, 504 insertions, 30 deletions
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/RulesRegistrantIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java index 28b43b9d6a4..b1b9c715829 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/RulesRegistrantIT.java +++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/rule/registration/RulesRegistrantIT.java @@ -50,8 +50,8 @@ import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.api.server.rule.Context; import org.sonar.api.server.rule.RuleDescriptionSection; import org.sonar.api.server.rule.RulesDefinition; -import org.sonar.api.utils.DateUtils; import org.sonar.api.testfixtures.log.LogTester; +import org.sonar.api.utils.DateUtils; import org.sonar.core.util.UuidFactory; import org.sonar.core.util.UuidFactoryFast; import org.sonar.db.DbClient; @@ -105,7 +105,6 @@ import static org.mockito.Mockito.when; import static org.sonar.api.rule.RuleStatus.READY; import static org.sonar.api.rule.RuleStatus.REMOVED; import static org.sonar.api.rule.Severity.BLOCKER; -import static org.sonar.api.rule.Severity.CRITICAL; import static org.sonar.api.rule.Severity.INFO; 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; @@ -145,21 +144,22 @@ public class RulesRegistrantIT { @org.junit.Rule public LogTester logTester = new LogTester(); - private final QProfileRules qProfileRules = mock(QProfileRules.class); - private final WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class); + private final QProfileRules qProfileRules = mock(); + private final WebServerRuleFinder webServerRuleFinder = mock(); private final DbClient dbClient = db.getDbClient(); - private final MetadataIndex metadataIndex = mock(MetadataIndex.class); + private final MetadataIndex metadataIndex = mock(); private final UuidFactory uuidFactory = UuidFactoryFast.getInstance(); private RuleIndexer ruleIndexer; private ActiveRuleIndexer activeRuleIndexer; private RuleIndex ruleIndex; - private final RuleDescriptionSectionsGenerator ruleDescriptionSectionsGenerator = mock(RuleDescriptionSectionsGenerator.class); - private final RuleDescriptionSectionsGeneratorResolver resolver = mock(RuleDescriptionSectionsGeneratorResolver.class); + private final RuleDescriptionSectionsGenerator ruleDescriptionSectionsGenerator = mock(); + private final RuleDescriptionSectionsGeneratorResolver resolver = mock(); private final RulesKeyVerifier rulesKeyVerifier = new RulesKeyVerifier(); private final StartupRuleUpdater startupRuleUpdater = new StartupRuleUpdater(dbClient, system, uuidFactory, resolver); private final NewRuleCreator newRuleCreator = new NewRuleCreator(dbClient, resolver, uuidFactory, system); + private final QualityProfileChangesUpdater qualityProfileChangesUpdater = mock(); @Before public void before() { @@ -1162,7 +1162,7 @@ public class RulesRegistrantIT { reset(webServerRuleFinder); RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, metadataIndex, - rulesKeyVerifier, startupRuleUpdater, newRuleCreator); + rulesKeyVerifier, startupRuleUpdater, newRuleCreator, qualityProfileChangesUpdater); task.start(); // Execute a commit to refresh session state as the task is using its own session db.getSession().commit(); diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java new file mode 100644 index 00000000000..e58b4b8ce57 --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/QualityProfileChangesUpdater.java @@ -0,0 +1,122 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.registration; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.sonar.api.issue.impact.SoftwareQuality; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.qualityprofile.ActiveRuleDto; +import org.sonar.db.qualityprofile.QProfileChangeDto; +import org.sonar.db.qualityprofile.RuleImpactChangeDto; +import org.sonar.db.rule.RuleChangeDto; +import org.sonar.server.rule.PluginRuleUpdate; + +public class QualityProfileChangesUpdater { + + private final DbClient dbClient; + private final UuidFactory uuidFactory; + + public QualityProfileChangesUpdater(DbClient dbClient, UuidFactory uuidFactory) { + this.dbClient = dbClient; + this.uuidFactory = uuidFactory; + } + + public void updateWithoutCommit(DbSession dbSession, Set<PluginRuleUpdate> pluginRuleUpdates) { + for (PluginRuleUpdate pluginRuleUpdate : pluginRuleUpdates) { + String ruleChangeUuid = uuidFactory.create(); + RuleChangeDto ruleChangeDto = createRuleChange(ruleChangeUuid, pluginRuleUpdate); + + createRuleImpactChanges(ruleChangeUuid, pluginRuleUpdate, ruleChangeDto); + insertRuleChange(dbSession, ruleChangeDto); + + for (String qualityProfileUuid : findQualityProfilesForRule(dbSession, pluginRuleUpdate.getRuleUuid())) { + QProfileChangeDto qProfileChangeDto = new QProfileChangeDto(); + qProfileChangeDto.setUuid(uuidFactory.create()); + qProfileChangeDto.setChangeType("UPDATED"); + qProfileChangeDto.setRuleChangeUuid(ruleChangeUuid); + qProfileChangeDto.setRulesProfileUuid(qualityProfileUuid); + dbClient.qProfileChangeDao().insert(dbSession, qProfileChangeDto); + } + + } + } + + private static RuleChangeDto createRuleChange(String ruleChangeUuid, PluginRuleUpdate pluginRuleUpdate) { + RuleChangeDto ruleChangeDto = new RuleChangeDto(); + ruleChangeDto.setUuid(ruleChangeUuid); + ruleChangeDto.setRuleUuid(pluginRuleUpdate.getRuleUuid()); + ruleChangeDto.setOldCleanCodeAttribute(pluginRuleUpdate.getOldCleanCodeAttribute()); + ruleChangeDto.setNewCleanCodeAttribute(pluginRuleUpdate.getNewCleanCodeAttribute()); + return ruleChangeDto; + } + + private Set<String> findQualityProfilesForRule(DbSession dbSession, String ruleUuid) { + return dbClient.activeRuleDao().selectByRuleUuid(dbSession, ruleUuid) + .stream() + .map(ActiveRuleDto::getProfileUuid) + .collect(Collectors.toSet()); + } + + private void insertRuleChange(DbSession dbSession, RuleChangeDto ruleChangeDto) { + dbClient.ruleChangeDao().insert(dbSession, ruleChangeDto); + } + + private static void createRuleImpactChanges(String ruleChangeUuid, PluginRuleUpdate pluginRuleUpdate, RuleChangeDto ruleChangeDto) { + List<SoftwareQuality> matchingSoftwareQualities = pluginRuleUpdate.getMatchingSoftwareQualities(); + for (SoftwareQuality softwareQuality : matchingSoftwareQualities) { + RuleImpactChangeDto ruleImpactChangeDto = new RuleImpactChangeDto(); + ruleImpactChangeDto.setRuleChangeUuid(ruleChangeUuid); + ruleImpactChangeDto.setOldSeverity(pluginRuleUpdate.getOldImpacts().get(softwareQuality).name()); + ruleImpactChangeDto.setOldSoftwareQuality(softwareQuality.name()); + ruleImpactChangeDto.setNewSeverity(pluginRuleUpdate.getNewImpacts().get(softwareQuality).name()); + ruleImpactChangeDto.setNewSoftwareQuality(softwareQuality.name()); + ruleChangeDto.addRuleImpactChangeDto(ruleImpactChangeDto); + } + + List<SoftwareQuality> oldSoftwareQualities = pluginRuleUpdate.getOldImpacts().keySet() + .stream() + .filter(softwareQuality -> !matchingSoftwareQualities.contains(softwareQuality)).toList(); + + List<SoftwareQuality> newSoftwareQualities = pluginRuleUpdate.getNewImpacts().keySet() + .stream() + .filter(softwareQuality -> !matchingSoftwareQualities.contains(softwareQuality)).toList(); + + int size = Math.max(oldSoftwareQualities.size(), newSoftwareQualities.size()); + for(int i = 0; i < size; i++) { + RuleImpactChangeDto ruleImpactChangeDto = new RuleImpactChangeDto(); + ruleImpactChangeDto.setRuleChangeUuid(ruleChangeUuid); + if(i < oldSoftwareQualities.size()) { + ruleImpactChangeDto.setOldSeverity(pluginRuleUpdate.getOldImpacts().get(oldSoftwareQualities.get(i)).name()); + ruleImpactChangeDto.setOldSoftwareQuality(oldSoftwareQualities.get(i).name()); + } + if(i < newSoftwareQualities.size()) { + ruleImpactChangeDto.setNewSeverity(pluginRuleUpdate.getNewImpacts().get(newSoftwareQualities.get(i)).name()); + ruleImpactChangeDto.setNewSoftwareQuality(newSoftwareQualities.get(i).name()); + } + ruleChangeDto.addRuleImpactChangeDto(ruleImpactChangeDto); + } + + + } +} diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java index 311fa04f82a..26575827823 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java @@ -21,6 +21,7 @@ package org.sonar.server.rule.registration; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -45,6 +46,7 @@ import org.sonar.db.rule.RuleRepositoryDto; import org.sonar.server.es.metadata.MetadataIndex; import org.sonar.server.qualityprofile.ActiveRuleChange; import org.sonar.server.qualityprofile.QProfileRules; +import org.sonar.server.rule.PluginRuleUpdate; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.rule.RuleDefinitionsLoader; import org.sonar.server.rule.WebServerRuleFinder; @@ -74,11 +76,12 @@ public class RulesRegistrant implements Startable { private final RulesKeyVerifier rulesKeyVerifier; private final StartupRuleUpdater startupRuleUpdater; private final NewRuleCreator newRuleCreator; + private final QualityProfileChangesUpdater qualityProfileChangesUpdater; public RulesRegistrant(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer, ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, WebServerRuleFinder webServerRuleFinder, MetadataIndex metadataIndex, RulesKeyVerifier rulesKeyVerifier, StartupRuleUpdater startupRuleUpdater, - NewRuleCreator newRuleCreator) { + NewRuleCreator newRuleCreator, QualityProfileChangesUpdater qualityProfileChangesUpdater) { this.defLoader = defLoader; this.qProfileRules = qProfileRules; this.dbClient = dbClient; @@ -91,6 +94,7 @@ public class RulesRegistrant implements Startable { this.rulesKeyVerifier = rulesKeyVerifier; this.startupRuleUpdater = startupRuleUpdater; this.newRuleCreator = newRuleCreator; + this.qualityProfileChangesUpdater = qualityProfileChangesUpdater; } @Override @@ -103,7 +107,8 @@ public class RulesRegistrant implements Startable { for (RulesDefinition.ExtendedRepository repoDef : repositories) { if (languages.get(repoDef.language()) != null) { - registerRules(rulesRegistrationContext, repoDef.rules(), dbSession); + Set<PluginRuleUpdate> pluginRuleUpdates = registerRules(rulesRegistrationContext, repoDef.rules(), dbSession); + qualityProfileChangesUpdater.updateWithoutCommit(dbSession, pluginRuleUpdates); dbSession.commit(); } } @@ -148,8 +153,9 @@ public class RulesRegistrant implements Startable { // nothing } - private void registerRules(RulesRegistrationContext context, List<RulesDefinition.Rule> ruleDefs, DbSession session) { + private Set<PluginRuleUpdate> registerRules(RulesRegistrationContext context, List<RulesDefinition.Rule> ruleDefs, DbSession session) { Map<RulesDefinition.Rule, RuleDto> dtos = new LinkedHashMap<>(ruleDefs.size()); + Set<PluginRuleUpdate> pluginRuleUpdates = new HashSet<>(); for (RulesDefinition.Rule ruleDef : ruleDefs) { RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key()); @@ -162,8 +168,8 @@ public class RulesRegistrant implements Startable { ruleDto.setRuleKey(ruleKey); } - if (!context.isCreated(ruleDto) && startupRuleUpdater.findChangesAndUpdateRule(ruleDef, ruleDto)) { - context.updated(ruleDto); + if (!context.isCreated(ruleDto)) { + processRuleUpdates(context, pluginRuleUpdates, ruleDef, ruleDto); } if (context.isUpdated(ruleDto) || context.isRenamed(ruleDto)) { @@ -177,6 +183,17 @@ public class RulesRegistrant implements Startable { startupRuleUpdater.mergeParams(context, e.getKey(), e.getValue(), session); startupRuleUpdater.updateDeprecatedKeys(context, e.getKey(), e.getValue(), session); } + return pluginRuleUpdates; + } + + private void processRuleUpdates(RulesRegistrationContext context, Set<PluginRuleUpdate> pluginRuleUpdates, RulesDefinition.Rule ruleDef, RuleDto ruleDto) { + StartupRuleUpdater.RuleChange change = startupRuleUpdater.findChangesAndUpdateRule(ruleDef, ruleDto); + if (change.hasRuleDefinitionChanged()) { + context.updated(ruleDto); + if (change.getPluginRuleUpdate() != null) { + pluginRuleUpdates.add(change.getPluginRuleUpdate()); + } + } } private void processRemainingDbRules(RulesRegistrationContext recorder, DbSession dbSession) { diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java index 1eb8793b20c..e9eedabd6c4 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.sonar.api.issue.impact.Severity; @@ -49,6 +50,7 @@ import org.sonar.db.rule.DeprecatedRuleKeyDto; import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleParamDto; +import org.sonar.server.rule.PluginRuleUpdate; import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver; import static com.google.common.collect.Sets.difference; @@ -80,13 +82,15 @@ public class StartupRuleUpdater { /** * Returns true in case there was any change detected between rule in the database and rule from the plugin. */ - boolean findChangesAndUpdateRule(RulesDefinition.Rule ruleDef, RuleDto ruleDto) { - boolean ruleMerged = mergeRule(ruleDef, ruleDto); + RuleChange findChangesAndUpdateRule(RulesDefinition.Rule ruleDef, RuleDto ruleDto) { + RuleChange ruleChange = new RuleChange(ruleDto); + boolean ruleMerged = mergeRule(ruleDef, ruleDto, ruleChange); boolean debtDefinitionsMerged = mergeDebtDefinitions(ruleDef, ruleDto); boolean tagsMerged = mergeTags(ruleDef, ruleDto); boolean securityStandardsMerged = mergeSecurityStandards(ruleDef, ruleDto); boolean educationPrinciplesMerged = mergeEducationPrinciples(ruleDef, ruleDto); - return ruleMerged || debtDefinitionsMerged || tagsMerged || securityStandardsMerged || educationPrinciplesMerged; + ruleChange.ruleDefinitionChanged = ruleMerged || debtDefinitionsMerged || tagsMerged || securityStandardsMerged || educationPrinciplesMerged; + return ruleChange; } void updateDeprecatedKeys(RulesRegistrationContext context, RulesDefinition.Rule ruleDef, RuleDto rule, DbSession dbSession) { @@ -112,7 +116,7 @@ public class StartupRuleUpdater { .setCreatedAt(system2.now()))); } - private boolean mergeRule(RulesDefinition.Rule def, RuleDto dto) { + private boolean mergeRule(RulesDefinition.Rule def, RuleDto dto, RuleChange ruleChange) { boolean changed = false; if (!Objects.equals(dto.getName(), def.name())) { dto.setName(def.name()); @@ -156,8 +160,8 @@ public class StartupRuleUpdater { dto.setType(type); changed = true; } - changed |= mergeCleanCodeAttribute(def, dto); - changed |= mergeImpacts(def, dto, uuidFactory); + changed |= mergeCleanCodeAttribute(def, dto, ruleChange); + changed |= mergeImpacts(def, dto, uuidFactory, ruleChange); if (dto.isAdHoc()) { dto.setIsAdHoc(false); changed = true; @@ -165,13 +169,14 @@ public class StartupRuleUpdater { return changed; } - private static boolean mergeCleanCodeAttribute(RulesDefinition.Rule def, RuleDto dto) { + private static boolean mergeCleanCodeAttribute(RulesDefinition.Rule def, RuleDto dto, RuleChange ruleChange) { if (dto.getEnumType() == RuleType.SECURITY_HOTSPOT) { return false; } boolean changed = false; CleanCodeAttribute defCleanCodeAttribute = def.cleanCodeAttribute(); if (!Objects.equals(dto.getCleanCodeAttribute(), defCleanCodeAttribute) && (defCleanCodeAttribute != null)) { + ruleChange.addCleanCodeAttributeChange(dto.getCleanCodeAttribute(), defCleanCodeAttribute); dto.setCleanCodeAttribute(defCleanCodeAttribute); changed = true; } @@ -183,7 +188,7 @@ public class StartupRuleUpdater { return changed; } - boolean mergeImpacts(RulesDefinition.Rule def, RuleDto dto, UuidFactory uuidFactory) { + boolean mergeImpacts(RulesDefinition.Rule def, RuleDto dto, UuidFactory uuidFactory, RuleChange ruleChange) { if (dto.getEnumType() == RuleType.SECURITY_HOTSPOT) { return false; } @@ -200,16 +205,28 @@ public class StartupRuleUpdater { .stream() .map(e -> new ImpactDto().setUuid(uuidFactory.create()).setSoftwareQuality(e.getKey()).setSeverity(e.getValue())) .collect(Collectors.toSet())); + ruleChange.addImpactsChange(removeDuplicatedImpacts(impactsFromDb, impactsFromPlugin), removeDuplicatedImpacts(impactsFromPlugin, impactsFromDb)); + return true; } return false; } + /** + * Returns a new map that contains only the impacts from the first map that are not present in the map passed as a second argument. + */ + private static Map<SoftwareQuality, Severity> removeDuplicatedImpacts(Map<SoftwareQuality, Severity> impactsA, Map<SoftwareQuality, Severity> impactsB) { + return impactsA.entrySet().stream() + .filter(entry -> !impactsB.containsKey(entry.getKey()) || !impactsB.get(entry.getKey()).equals(entry.getValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + private static boolean mergeEducationPrinciples(RulesDefinition.Rule ruleDef, RuleDto dto) { boolean changed = false; if (dto.getEducationPrinciples().size() != ruleDef.educationPrincipleKeys().size() || - !dto.getEducationPrinciples().containsAll(ruleDef.educationPrincipleKeys())) { + !dto.getEducationPrinciples().containsAll(ruleDef.educationPrincipleKeys())) { dto.setEducationPrinciples(ruleDef.educationPrincipleKeys()); changed = true; } @@ -223,10 +240,10 @@ public class StartupRuleUpdater { dto.setSystemTags(emptySet()); changed = true; } else if (dto.getSystemTags().size() != ruleDef.tags().size() || - !dto.getSystemTags().containsAll(ruleDef.tags())) { - dto.setSystemTags(ruleDef.tags()); - changed = true; - } + !dto.getSystemTags().containsAll(ruleDef.tags())) { + dto.setSystemTags(ruleDef.tags()); + changed = true; + } return changed; } @@ -237,10 +254,10 @@ public class StartupRuleUpdater { dto.setSecurityStandards(emptySet()); changed = true; } else if (dto.getSecurityStandards().size() != ruleDef.securityStandards().size() || - !dto.getSecurityStandards().containsAll(ruleDef.securityStandards())) { - dto.setSecurityStandards(ruleDef.securityStandards()); - changed = true; - } + !dto.getSecurityStandards().containsAll(ruleDef.securityStandards())) { + dto.setSecurityStandards(ruleDef.securityStandards()); + changed = true; + } return changed; } @@ -377,4 +394,41 @@ public class StartupRuleUpdater { return changed; } + public static class RuleChange { + private boolean ruleDefinitionChanged = false; + private final String ruleUuid; + private PluginRuleUpdate pluginRuleUpdate; + + public RuleChange(RuleDto ruleDto) { + this.ruleUuid = ruleDto.getUuid(); + } + + private void createPluginRuleUpdateIfNeeded() { + if (pluginRuleUpdate == null) { + pluginRuleUpdate = new PluginRuleUpdate(); + pluginRuleUpdate.setRuleUuid(ruleUuid); + } + } + + public void addImpactsChange(Map<SoftwareQuality, Severity> oldImpacts, Map<SoftwareQuality, Severity> newImpacts) { + createPluginRuleUpdateIfNeeded(); + oldImpacts.forEach(pluginRuleUpdate::addOldImpact); + newImpacts.forEach(pluginRuleUpdate::addNewImpact); + } + + public void addCleanCodeAttributeChange(@Nullable CleanCodeAttribute oldAttribute, @Nullable CleanCodeAttribute newAttribute) { + createPluginRuleUpdateIfNeeded(); + pluginRuleUpdate.setOldCleanCodeAttribute(oldAttribute); + pluginRuleUpdate.setNewCleanCodeAttribute(newAttribute); + } + + public boolean hasRuleDefinitionChanged() { + return ruleDefinitionChanged; + } + + @CheckForNull + public PluginRuleUpdate getPluginRuleUpdate() { + return pluginRuleUpdate; + } + } } diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java new file mode 100644 index 00000000000..29d3ab8f08e --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/QualityProfileChangesUpdaterTest.java @@ -0,0 +1,155 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.registration; + +import java.util.List; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; +import org.sonar.api.rules.CleanCodeAttribute; +import org.sonar.core.util.UuidFactoryImpl; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.qualityprofile.ActiveRuleDao; +import org.sonar.db.qualityprofile.ActiveRuleDto; +import org.sonar.db.qualityprofile.QProfileChangeDao; +import org.sonar.db.qualityprofile.RuleImpactChangeDto; +import org.sonar.db.rule.RuleChangeDao; +import org.sonar.db.rule.RuleChangeDto; +import org.sonar.server.rule.PluginRuleUpdate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +public class QualityProfileChangesUpdaterTest { + + public static final String RULE_UUID = "ruleUuid"; + private final DbClient dbClient = mock(); + private final DbSession dbSession = mock(); + private final RuleChangeDao ruleChangeDao = mock(); + private final QProfileChangeDao qualityProfileChangeDao = mock(); + private final ActiveRuleDao activeRuleDao = mock(); + + private final QualityProfileChangesUpdater underTest = new QualityProfileChangesUpdater(dbClient, UuidFactoryImpl.INSTANCE); + + @Before + public void before() { + when(dbClient.ruleChangeDao()).thenReturn(ruleChangeDao); + when(dbClient.qProfileChangeDao()).thenReturn(qualityProfileChangeDao); + when(dbClient.activeRuleDao()).thenReturn(activeRuleDao); + } + + @Test + public void updateWithoutCommit_whenNoRuleChanges_thenDontInteractWithDatabase() { + underTest.updateWithoutCommit(mock(), Set.of()); + + verifyNoInteractions(dbClient); + } + + @Test + public void updateWithoutCommit_whenOneRuleChangedItsAttribute_thenInsertRuleChangeButNotImpactChange() { + PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate(); + pluginRuleUpdate.setNewCleanCodeAttribute(CleanCodeAttribute.CLEAR); + pluginRuleUpdate.setOldCleanCodeAttribute(CleanCodeAttribute.TESTED); + pluginRuleUpdate.setRuleUuid(RULE_UUID); + + underTest.updateWithoutCommit(dbSession, Set.of(pluginRuleUpdate)); + + verify(ruleChangeDao).insert(argThat(dbSession::equals), argThat(ruleChangeDto -> + ruleChangeDto.getNewCleanCodeAttribute() == CleanCodeAttribute.CLEAR + && ruleChangeDto.getOldCleanCodeAttribute() == CleanCodeAttribute.TESTED + && ruleChangeDto.getRuleUuid().equals(RULE_UUID) + && ruleChangeDto.getRuleImpactChangeDtos().isEmpty())); + } + + @Test + public void updateWithoutCommit_whenTwoRulesChangedTheirImpactsAndAttributes_thenInsertRuleChangeAndImpactChange() { + PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate(); + pluginRuleUpdate.setNewCleanCodeAttribute(CleanCodeAttribute.CLEAR); + pluginRuleUpdate.setOldCleanCodeAttribute(CleanCodeAttribute.TESTED); + pluginRuleUpdate.setRuleUuid(RULE_UUID); + + //testing here detecting the change with 2 the same software qualities + pluginRuleUpdate.addNewImpact(SoftwareQuality.RELIABILITY, Severity.LOW); + pluginRuleUpdate.addOldImpact(SoftwareQuality.RELIABILITY, Severity.MEDIUM); + + PluginRuleUpdate pluginRuleUpdate2 = new PluginRuleUpdate(); + pluginRuleUpdate2.setNewCleanCodeAttribute(CleanCodeAttribute.EFFICIENT); + pluginRuleUpdate2.setOldCleanCodeAttribute(CleanCodeAttribute.DISTINCT); + pluginRuleUpdate2.setRuleUuid("ruleUuid2"); + + //testing here detecting the change with 2 the different software qualities + pluginRuleUpdate2.addNewImpact(SoftwareQuality.SECURITY, Severity.HIGH); + pluginRuleUpdate2.addOldImpact(SoftwareQuality.RELIABILITY, Severity.MEDIUM); + + underTest.updateWithoutCommit(dbSession, Set.of(pluginRuleUpdate, pluginRuleUpdate2)); + + ArgumentCaptor<RuleChangeDto> captor = ArgumentCaptor.forClass(RuleChangeDto.class); + verify(ruleChangeDao, times(2)).insert(argThat(dbSession::equals), captor.capture()); + + RuleChangeDto firstChange = captor.getAllValues().stream().filter(change -> change.getRuleUuid().equals(RULE_UUID)).findFirst().get(); + RuleChangeDto secondChange = captor.getAllValues().stream().filter(change -> change.getRuleUuid().equals("ruleUuid2")).findFirst().get(); + + assertThat(firstChange.getNewCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR); + assertThat(firstChange.getOldCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.TESTED); + assertThat(firstChange.getRuleUuid()).isEqualTo(RULE_UUID); + assertThat(firstChange.getRuleImpactChangeDtos()).hasSize(1); + assertThat(firstChange.getRuleImpactChangeDtos()).extracting(RuleImpactChangeDto::getNewSoftwareQuality, + RuleImpactChangeDto::getOldSoftwareQuality, RuleImpactChangeDto::getOldSeverity, RuleImpactChangeDto::getNewSeverity) + .containsExactly(tuple(SoftwareQuality.RELIABILITY.name(), SoftwareQuality.RELIABILITY.name(), Severity.MEDIUM.name(), Severity.LOW.name())); + + assertThat(secondChange.getNewCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.EFFICIENT); + assertThat(secondChange.getOldCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.DISTINCT); + assertThat(secondChange.getRuleUuid()).isEqualTo("ruleUuid2"); + assertThat(secondChange.getRuleImpactChangeDtos()).hasSize(1); + assertThat(secondChange.getRuleImpactChangeDtos()).extracting(RuleImpactChangeDto::getNewSoftwareQuality, + RuleImpactChangeDto::getOldSoftwareQuality, RuleImpactChangeDto::getOldSeverity, RuleImpactChangeDto::getNewSeverity) + .containsExactly(tuple(SoftwareQuality.SECURITY.name(), SoftwareQuality.RELIABILITY.name(), Severity.MEDIUM.name(), Severity.HIGH.name())); + } + + @Test + public void updateWithoutCommit_whenOneRuleBelongingToTwoQualityProfilesChanged_thenInsertOneRuleChangeAndTwoQualityProfileChanges() { + List<ActiveRuleDto> activeRuleDtos = List.of( + new ActiveRuleDto().setProfileUuid("profileUuid1").setRuleUuid(RULE_UUID), + new ActiveRuleDto().setProfileUuid("profileUuid2").setRuleUuid(RULE_UUID)); + when(activeRuleDao.selectByRuleUuid(any(), any())).thenReturn(activeRuleDtos); + + PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate(); + pluginRuleUpdate.setNewCleanCodeAttribute(CleanCodeAttribute.CLEAR); + pluginRuleUpdate.setOldCleanCodeAttribute(CleanCodeAttribute.TESTED); + pluginRuleUpdate.setRuleUuid(RULE_UUID); + + underTest.updateWithoutCommit(dbSession, Set.of(pluginRuleUpdate)); + + verify(qualityProfileChangeDao, times(2)).insert(argThat(dbSession::equals), argThat(qProfileChangeDto -> + qProfileChangeDto.getChangeType().equals("UPDATED") + && qProfileChangeDto.getRuleChangeUuid() != null)); + } +}
\ No newline at end of file diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/StartupRuleUpdaterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/StartupRuleUpdaterTest.java new file mode 100644 index 00000000000..f68eb4b0b2f --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/StartupRuleUpdaterTest.java @@ -0,0 +1,126 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.registration; + +import java.util.Map; +import java.util.Set; +import org.junit.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; +import org.sonar.api.rule.RuleScope; +import org.sonar.api.rules.CleanCodeAttribute; +import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.issue.ImpactDto; +import org.sonar.db.rule.RuleDto; +import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class StartupRuleUpdaterTest { + + private final DbClient dbClient = mock(); + private final System2 system2 = mock(); + private final UuidFactory uuidFactory = mock(); + private final RuleDescriptionSectionsGeneratorResolver sectionsGeneratorResolver = mock(); + + private final StartupRuleUpdater underTest = new StartupRuleUpdater(dbClient, system2, uuidFactory, sectionsGeneratorResolver); + + @Test + public void findChangesAndUpdateRule_whenCleanCodeTaxonomyChanged_shouldSetAnythingChangedToTrue() { + RulesDefinition.Rule ruleDef = getDefaultRuleDef(); + when(ruleDef.cleanCodeAttribute()).thenReturn(CleanCodeAttribute.CLEAR); + Map<SoftwareQuality, Severity> newImpacts = Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW); + when(ruleDef.defaultImpacts()).thenReturn(newImpacts); + + RuleDto rule = getDefaultRuleDto(); + when(rule.getCleanCodeAttribute()).thenReturn(CleanCodeAttribute.COMPLETE); + Set<ImpactDto> oldImpacts = Set.of(new ImpactDto("uuid", SoftwareQuality.RELIABILITY, Severity.LOW)); + when(rule.getDefaultImpacts()).thenReturn(oldImpacts); + + StartupRuleUpdater.RuleChange changesAndUpdateRule = underTest.findChangesAndUpdateRule(ruleDef, rule); + + assertTrue(changesAndUpdateRule.hasRuleDefinitionChanged()); + assertThat(changesAndUpdateRule.getPluginRuleUpdate().getOldCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.COMPLETE); + assertThat(changesAndUpdateRule.getPluginRuleUpdate().getNewCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR); + assertThat(changesAndUpdateRule.getPluginRuleUpdate().getNewImpacts()).isEqualTo(newImpacts); + assertThat(changesAndUpdateRule.getPluginRuleUpdate().getOldImpacts()).containsEntry(SoftwareQuality.RELIABILITY, Severity.LOW); + } + + @Test + public void findChangesAndUpdateRule_whenImpactsChanged_thenDontIncludeUnchangedImpacts() { + RulesDefinition.Rule ruleDef = getDefaultRuleDef(); + when(ruleDef.cleanCodeAttribute()).thenReturn(CleanCodeAttribute.CLEAR); + Map<SoftwareQuality, Severity> newImpacts = Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW, SoftwareQuality.SECURITY, Severity.HIGH); + when(ruleDef.defaultImpacts()).thenReturn(newImpacts); + + RuleDto rule = getDefaultRuleDto(); + when(rule.getCleanCodeAttribute()).thenReturn(CleanCodeAttribute.COMPLETE); + Set<ImpactDto> oldImpacts = Set.of(new ImpactDto("uuid", + SoftwareQuality.RELIABILITY, Severity.LOW), + new ImpactDto("uuid2", SoftwareQuality.SECURITY, Severity.HIGH)); + when(rule.getDefaultImpacts()).thenReturn(oldImpacts); + + StartupRuleUpdater.RuleChange changesAndUpdateRule = underTest.findChangesAndUpdateRule(ruleDef, rule); + + assertTrue(changesAndUpdateRule.hasRuleDefinitionChanged()); + assertThat(changesAndUpdateRule.getPluginRuleUpdate().getNewImpacts()).containsOnly(Map.entry(SoftwareQuality.MAINTAINABILITY, Severity.LOW)); + assertThat(changesAndUpdateRule.getPluginRuleUpdate().getOldImpacts()).containsOnly(Map.entry(SoftwareQuality.RELIABILITY, Severity.LOW)); + } + + @Test + public void findChangesAndUpdateRule_whenNoCleanCodeTaxonomyChanged_thenPluginRuleChangeShouldBeNull() { + RulesDefinition.Rule ruleDef = getDefaultRuleDef(); + when(ruleDef.cleanCodeAttribute()).thenReturn(CleanCodeAttribute.COMPLETE); + Map<SoftwareQuality, Severity> newImpacts = Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW); + when(ruleDef.defaultImpacts()).thenReturn(newImpacts); + + RuleDto rule = getDefaultRuleDto(); + when(rule.getCleanCodeAttribute()).thenReturn(CleanCodeAttribute.COMPLETE); + Set<ImpactDto> oldImpacts = Set.of(new ImpactDto("uuid", + SoftwareQuality.MAINTAINABILITY, Severity.LOW)); + when(rule.getDefaultImpacts()).thenReturn(oldImpacts); + + StartupRuleUpdater.RuleChange changesAndUpdateRule = underTest.findChangesAndUpdateRule(ruleDef, rule); + + assertTrue(changesAndUpdateRule.hasRuleDefinitionChanged()); + assertThat(changesAndUpdateRule.getPluginRuleUpdate()).isNull(); + } + + private RulesDefinition.Rule getDefaultRuleDef() { + RulesDefinition.Rule ruleDef = mock(); + when(ruleDef.scope()).thenReturn(RuleScope.TEST); + when(ruleDef.repository()).thenReturn(mock()); + when(ruleDef.type()).thenReturn(RuleType.BUG); + return ruleDef; + } + + private RuleDto getDefaultRuleDto() { + RuleDto ruleDto = mock(); + when(ruleDto.getScope()).thenReturn(RuleDto.Scope.TEST); + return ruleDto; + } +}
\ No newline at end of file |