--- /dev/null
+/*
+ * 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.db.rule;
+
+import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+import org.sonarqube.ws.Common;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RuleChangeDaoIT {
+
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+
+ private final RuleChangeDao underTest = db.getDbClient().ruleChangeDao();
+
+ @Test
+ public void insert_shouldInsertRuleChangeWithNullableImpacts() {
+ RuleChangeDto ruleChangeDto = new RuleChangeDto();
+ ruleChangeDto.setNewCleanCodeAttribute(CleanCodeAttribute.CLEAR);
+ ruleChangeDto.setOldCleanCodeAttribute(CleanCodeAttribute.CONVENTIONAL);
+ ruleChangeDto.setRuleUuid("ruleUuid");
+ ruleChangeDto.setUuid("uuid");
+
+ RuleImpactChangeDto ruleImpactChangeDto = new RuleImpactChangeDto();
+ ruleImpactChangeDto.setNewSoftwareQuality(Common.SoftwareQuality.RELIABILITY.name());
+ ruleImpactChangeDto.setOldSoftwareQuality(Common.SoftwareQuality.RELIABILITY.name());
+ ruleImpactChangeDto.setNewSeverity(Severity.LOW.name());
+ ruleImpactChangeDto.setOldSeverity(Severity.HIGH.name());
+
+ RuleImpactChangeDto ruleImpactChangeDto2 = new RuleImpactChangeDto();
+ ruleImpactChangeDto2.setNewSoftwareQuality(Common.SoftwareQuality.SECURITY.name());
+ ruleImpactChangeDto2.setNewSeverity(Severity.MEDIUM.name());
+
+ RuleImpactChangeDto ruleImpactChangeDto3 = new RuleImpactChangeDto();
+ ruleImpactChangeDto2.setOldSoftwareQuality(Common.SoftwareQuality.MAINTAINABILITY.name());
+ ruleImpactChangeDto2.setOldSeverity(Severity.MEDIUM.name());
+
+ Set<RuleImpactChangeDto> impactChanges = Set.of(ruleImpactChangeDto, ruleImpactChangeDto2, ruleImpactChangeDto3);
+ impactChanges.forEach(i -> i.setRuleChangeUuid(ruleChangeDto.getUuid()));
+
+ ruleChangeDto.setRuleImpactChangeDtos(impactChanges);
+ DbSession session = db.getSession();
+
+ underTest.insert(session, ruleChangeDto);
+ session.commit();
+
+ assertThat(db.select("select * from rule_impact_changes")).hasSize(3);
+ assertThat(db.select("select * from rule_changes")).hasSize(1);
+ }
+}
import org.sonar.db.report.RegulatoryReportDao;
import org.sonar.db.report.ReportScheduleDao;
import org.sonar.db.report.ReportSubscriptionDao;
+import org.sonar.db.rule.RuleChangeDao;
import org.sonar.db.rule.RuleDao;
import org.sonar.db.rule.RuleRepositoryDao;
import org.sonar.db.scannercache.ScannerAnalysisCacheDao;
ReportScheduleDao.class,
RoleDao.class,
RuleDao.class,
+ RuleChangeDao.class,
RuleRepositoryDao.class,
SamlMessageIdDao.class,
ScannerAnalysisCacheDao.class,
import org.sonar.db.report.RegulatoryReportDao;
import org.sonar.db.report.ReportScheduleDao;
import org.sonar.db.report.ReportSubscriptionDao;
+import org.sonar.db.rule.RuleChangeDao;
import org.sonar.db.rule.RuleDao;
import org.sonar.db.rule.RuleRepositoryDao;
import org.sonar.db.scannercache.ScannerAnalysisCacheDao;
private final ReportSubscriptionDao reportSubscriptionDao;
private final GithubOrganizationGroupDao githubOrganizationGroupDao;
private final GithubPermissionsMappingDao githubPermissionsMappingDao;
+ private final RuleChangeDao ruleChangeDao;
public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) {
this.database = database;
reportScheduleDao = getDao(map, ReportScheduleDao.class);
reportSubscriptionDao = getDao(map, ReportSubscriptionDao.class);
anticipatedTransitionDao = getDao(map, AnticipatedTransitionDao.class);
+ ruleChangeDao = getDao(map, RuleChangeDao.class);
}
public DbSession openSession(boolean batch) {
public AnticipatedTransitionDao anticipatedTransitionDao() {
return anticipatedTransitionDao;
}
+
+ public RuleChangeDao ruleChangeDao() {
+ return ruleChangeDao;
+ }
}
import org.sonar.db.report.RegulatoryReportMapper;
import org.sonar.db.report.ReportScheduleMapper;
import org.sonar.db.report.ReportSubscriptionMapper;
+import org.sonar.db.rule.RuleChangeMapper;
import org.sonar.db.rule.RuleMapper;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.db.rule.RuleRepositoryMapper;
ReportSubscriptionMapper.class,
RoleMapper.class,
RuleMapper.class,
+ RuleChangeMapper.class,
RuleRepositoryMapper.class,
SamlMessageIdMapper.class,
ScannerAnalysisCacheMapper.class,
private Set<RuleImpactChangeDto> ruleImpactChangeDtos;
private long createdAt;
+ private String ruleChangeUuid;
public String getUuid() {
return uuid;
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
+ public void setRuleChangeUuid(String ruleChangeUuid) {
+ this.ruleChangeUuid = ruleChangeUuid;
+ }
+ public String getRuleChangeUuid() {
+ return ruleChangeUuid;
+ }
}
--- /dev/null
+/*
+ * 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.db.rule;
+
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+
+public class RuleChangeDao implements Dao {
+
+ /**
+ * Inserts a rule change with its impacts.
+ * The method doesn't commit its transaction.
+ */
+ public void insert(DbSession session, RuleChangeDto ruleChangeDto) {
+ for (RuleImpactChangeDto ruleImpactChangeDto : ruleChangeDto.getRuleImpactChangeDtos()) {
+ session.getMapper(RuleChangeMapper.class).insertRuleImpactChange(ruleImpactChangeDto);
+ }
+ session.getMapper(RuleChangeMapper.class).insertRuleChange(ruleChangeDto);
+ }
+}
--- /dev/null
+/*
+ * 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.db.rule;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+
+public class RuleChangeDto {
+
+ private String uuid;
+ private CleanCodeAttribute oldCleanCodeAttribute;
+ private CleanCodeAttribute newCleanCodeAttribute;
+ private Set<RuleImpactChangeDto> ruleImpactChangeDtos = new HashSet<>();
+ private String ruleUuid;
+
+ public CleanCodeAttribute getOldCleanCodeAttribute() {
+ return oldCleanCodeAttribute;
+ }
+
+ public void setOldCleanCodeAttribute(CleanCodeAttribute oldCleanCodeAttribute) {
+ this.oldCleanCodeAttribute = oldCleanCodeAttribute;
+ }
+
+ public CleanCodeAttribute getNewCleanCodeAttribute() {
+ return newCleanCodeAttribute;
+ }
+
+ public void setNewCleanCodeAttribute(CleanCodeAttribute newCleanCodeAttribute) {
+ this.newCleanCodeAttribute = newCleanCodeAttribute;
+ }
+
+ public Set<RuleImpactChangeDto> getRuleImpactChangeDtos() {
+ return ruleImpactChangeDtos;
+ }
+
+ public void setRuleImpactChangeDtos(Set<RuleImpactChangeDto> ruleImpactChangeDtos) {
+ this.ruleImpactChangeDtos = ruleImpactChangeDtos;
+ }
+
+ public String getRuleUuid() {
+ return ruleUuid;
+ }
+
+ public void setRuleUuid(String ruleUuid) {
+ this.ruleUuid = ruleUuid;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public void addRuleImpactChangeDto(RuleImpactChangeDto ruleImpactChangeDto) {
+ this.ruleImpactChangeDtos.add(ruleImpactChangeDto);
+ }
+
+}
--- /dev/null
+/*
+ * 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.db.rule;
+
+import org.apache.ibatis.annotations.Param;
+import org.sonar.db.qualityprofile.RuleImpactChangeDto;
+
+public interface RuleChangeMapper {
+
+ void insertRuleChange(@Param("dto") RuleChangeDto dto);
+
+ void insertRuleImpactChange(@Param("dto") RuleImpactChangeDto dto);
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.rule.RuleChangeMapper">
+
+ <insert id="insertRuleChange" parameterType="Map" useGeneratedKeys="false">
+ insert into rule_changes (
+ uuid,
+ new_clean_code_attribute,
+ old_clean_code_attribute,
+ rule_uuid
+ )
+ values (
+ #{dto.uuid,jdbcType=VARCHAR},
+ #{dto.newCleanCodeAttribute,jdbcType=VARCHAR},
+ #{dto.oldCleanCodeAttribute,jdbcType=VARCHAR},
+ #{dto.ruleUuid,jdbcType=VARCHAR}
+ )
+ </insert>
+
+ <insert id="insertRuleImpactChange" parameterType="Map" useGeneratedKeys="false">
+ insert into rule_impact_changes (
+ new_software_quality,
+ old_software_quality,
+ new_severity,
+ old_severity,
+ rule_change_uuid
+ )
+ values (
+ #{dto.newSoftwareQuality,jdbcType=VARCHAR},
+ #{dto.oldSoftwareQuality,jdbcType=VARCHAR},
+ #{dto.newSeverity,jdbcType=VARCHAR},
+ #{dto.oldSeverity,jdbcType=VARCHAR},
+ #{dto.ruleChangeUuid,jdbcType=VARCHAR}
+ )
+ </insert>
+
+</mapper>
+
CREATE UNIQUE NULLS DISTINCT INDEX "UNIQ_RULE_DESC_SECTIONS" ON "RULE_DESC_SECTIONS"("RULE_UUID" NULLS FIRST, "KEE" NULLS FIRST, "CONTEXT_KEY" NULLS FIRST);
CREATE TABLE "RULE_IMPACT_CHANGES"(
- "NEW_SOFTWARE_QUALITY" CHARACTER VARYING(40) NOT NULL,
- "OLD_SOFTWARE_QUALITY" CHARACTER VARYING(40) NOT NULL,
- "NEW_SEVERITY" CHARACTER VARYING(40) NOT NULL,
- "OLD_SEVERITY" CHARACTER VARYING(40) NOT NULL,
+ "NEW_SOFTWARE_QUALITY" CHARACTER VARYING(40),
+ "OLD_SOFTWARE_QUALITY" CHARACTER VARYING(40),
+ "NEW_SEVERITY" CHARACTER VARYING(40),
+ "OLD_SEVERITY" CHARACTER VARYING(40),
"RULE_CHANGE_UUID" CHARACTER VARYING(40) NOT NULL
);
CREATE INDEX "RULE_IMPACT_CHANGES_R_C_UUID" ON "RULE_IMPACT_CHANGES"("RULE_CHANGE_UUID" NULLS FIRST);
@Override
public void execute(DdlChange.Context context, String tableName) throws SQLException {
context.execute(new CreateTableBuilder(getDialect(), tableName)
- .addColumn(newVarcharColumnDefBuilder().setColumnName("new_software_quality").setIsNullable(false).setLimit(40).build())
- .addColumn(newVarcharColumnDefBuilder().setColumnName("old_software_quality").setIsNullable(false).setLimit(40).build())
- .addColumn(newVarcharColumnDefBuilder().setColumnName("new_severity").setIsNullable(false).setLimit(40).build())
- .addColumn(newVarcharColumnDefBuilder().setColumnName("old_severity").setIsNullable(false).setLimit(40).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("new_software_quality").setIsNullable(true).setLimit(40).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("old_software_quality").setIsNullable(true).setLimit(40).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("new_severity").setIsNullable(true).setLimit(40).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("old_severity").setIsNullable(true).setLimit(40).build())
.addColumn(newVarcharColumnDefBuilder().setColumnName("rule_change_uuid").setIsNullable(false).setLimit(40).build())
.build());
}
underTest.execute();
db.assertTableExists(TABLE_NAME);
- db.assertColumnDefinition(TABLE_NAME, "new_software_quality", Types.VARCHAR, 40, false);
- db.assertColumnDefinition(TABLE_NAME, "old_software_quality", Types.VARCHAR, 40, false);
- db.assertColumnDefinition(TABLE_NAME, "new_severity", Types.VARCHAR, 40, false);
- db.assertColumnDefinition(TABLE_NAME, "old_severity", Types.VARCHAR, 40, false);
+ db.assertColumnDefinition(TABLE_NAME, "new_software_quality", Types.VARCHAR, 40, true);
+ db.assertColumnDefinition(TABLE_NAME, "old_software_quality", Types.VARCHAR, 40, true);
+ db.assertColumnDefinition(TABLE_NAME, "new_severity", Types.VARCHAR, 40, true);
+ db.assertColumnDefinition(TABLE_NAME, "old_severity", Types.VARCHAR, 40, true);
db.assertColumnDefinition(TABLE_NAME, "rule_change_uuid", Types.VARCHAR, UUID_SIZE, false);
}
--- /dev/null
+/*
+ * 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;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.CleanCodeAttribute;
+
+/**
+ * Represents a single update of a single rule done by new version of plugins at startup
+ */
+public class PluginRuleUpdate {
+
+ private String ruleUuid;
+
+ private CleanCodeAttribute newCleanCodeAttribute;
+ private CleanCodeAttribute oldCleanCodeAttribute;
+ private final Map<SoftwareQuality, Severity> newImpacts = new EnumMap<>(SoftwareQuality.class);
+ private final Map<SoftwareQuality, Severity> oldImpacts = new EnumMap<>(SoftwareQuality.class);
+
+ public String getRuleUuid() {
+ return ruleUuid;
+ }
+
+ public void setRuleUuid(String ruleUuid) {
+ this.ruleUuid = ruleUuid;
+ }
+
+ public void addOldImpact(SoftwareQuality softwareQuality, Severity severity) {
+ oldImpacts.put(softwareQuality, severity);
+ }
+
+ public void addNewImpact(SoftwareQuality softwareQuality, Severity severity) {
+ newImpacts.put(softwareQuality, severity);
+ }
+
+ public Map<SoftwareQuality, Severity> getNewImpacts() {
+ return newImpacts;
+ }
+
+ public Map<SoftwareQuality, Severity> getOldImpacts() {
+ return oldImpacts;
+ }
+
+ public List<SoftwareQuality> getMatchingSoftwareQualities() {
+ return newImpacts.keySet().stream().filter(oldImpacts::containsKey).toList();
+ }
+
+ public CleanCodeAttribute getNewCleanCodeAttribute() {
+ return newCleanCodeAttribute;
+ }
+
+ public void setNewCleanCodeAttribute(@Nullable CleanCodeAttribute newCleanCodeAttribute) {
+ this.newCleanCodeAttribute = newCleanCodeAttribute;
+ }
+
+ public CleanCodeAttribute getOldCleanCodeAttribute() {
+ return oldCleanCodeAttribute;
+ }
+
+ public void setOldCleanCodeAttribute(@Nullable CleanCodeAttribute oldCleanCodeAttribute) {
+ this.oldCleanCodeAttribute = oldCleanCodeAttribute;
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import org.junit.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PluginRuleUpdateTest {
+
+ @Test
+ public void addOldImpact_whenOldImpactAdded_shouldContainOneImpact() {
+ PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate();
+
+ pluginRuleUpdate.addOldImpact(SoftwareQuality.RELIABILITY, Severity.LOW);
+
+ assertThat(pluginRuleUpdate.getOldImpacts()).hasSize(1);
+ }
+
+ @Test
+ public void addNewImpact_whenNewImpactAdded_shouldContainOneImpact() {
+ PluginRuleUpdate pluginRuleUpdate = new PluginRuleUpdate();
+
+ pluginRuleUpdate.addNewImpact(SoftwareQuality.RELIABILITY, Severity.LOW);
+
+ assertThat(pluginRuleUpdate.getNewImpacts()).hasSize(1);
+ }
+
+}
import org.sonar.server.rule.index.RuleQuery;
/**
- * Operations related to activation and deactivation of rules on user profiles.
+ * Operations related to activation and deactivation of rules on Quality profiles.
*/
@ServerSide
public interface QProfileRules {
--- /dev/null
+/*
+ * 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 com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.elasticsearch.common.util.set.Sets;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleScope;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.api.rules.RuleType;
+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.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;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.issue.ImpactDto;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.QProfileChangeDto;
+import org.sonar.db.qualityprofile.QProfileChangeQuery;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
+import org.sonar.db.rule.RuleDescriptionSectionContextDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleDto.Scope;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.db.rule.RuleRepositoryDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.es.metadata.MetadataIndex;
+import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.QProfileRules;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.rule.RuleDefinitionsLoader;
+import org.sonar.server.rule.RuleDescriptionSectionsGenerator;
+import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
+import org.sonar.server.rule.WebServerRuleFinder;
+import org.sonar.server.rule.index.RuleIndex;
+import org.sonar.server.rule.index.RuleIndexDefinition;
+import org.sonar.server.rule.index.RuleIndexer;
+import org.sonar.server.rule.index.RuleQuery;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static java.lang.String.format;
+import static java.lang.String.valueOf;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+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.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;
+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.api.server.rule.RulesDefinition.NewRepository;
+import static org.sonar.api.server.rule.RulesDefinition.NewRule;
+import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10;
+import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.builder;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
+import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
+
+@RunWith(DataProviderRunner.class)
+public class RulesRegistrantIT {
+
+ private static final String FAKE_PLUGIN_KEY = "unittest";
+ private static final Date DATE1 = DateUtils.parseDateTime("2014-01-01T19:10:03+0100");
+ private static final Date DATE2 = DateUtils.parseDateTime("2014-02-01T12:10:03+0100");
+ private static final Date DATE3 = DateUtils.parseDateTime("2014-03-01T12:10:03+0100");
+
+ private static final RuleKey EXTERNAL_RULE_KEY1 = RuleKey.of("external_eslint", "rule1");
+ private static final RuleKey EXTERNAL_HOTSPOT_RULE_KEY = RuleKey.of("external_eslint", "hotspot");
+
+ private static final RuleKey RULE_KEY1 = RuleKey.of("fake", "rule1");
+ private static final RuleKey RULE_KEY2 = RuleKey.of("fake", "rule2");
+ private static final RuleKey RULE_KEY3 = RuleKey.of("fake", "rule3");
+ private static final RuleKey HOTSPOT_RULE_KEY = RuleKey.of("fake", "hotspot");
+
+ private final TestSystem2 system = new TestSystem2().setNow(DATE1.getTime());
+
+ @org.junit.Rule
+ public DbTester db = DbTester.create(system);
+ @org.junit.Rule
+ public EsTester es = EsTester.create();
+ @org.junit.Rule
+ public LogTester logTester = new LogTester();
+
+ private final QProfileRules qProfileRules = mock();
+ private final WebServerRuleFinder webServerRuleFinder = mock();
+ private final DbClient dbClient = db.getDbClient();
+ 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();
+ 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() {
+ ruleIndexer = new RuleIndexer(es.client(), dbClient);
+ ruleIndex = new RuleIndex(es.client(), system);
+ activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client());
+ when(resolver.generateFor(any())).thenAnswer(answer -> {
+ RulesDefinition.Rule rule = answer.getArgument(0, RulesDefinition.Rule.class);
+ String description = rule.htmlDescription() == null ? rule.markdownDescription() : rule.htmlDescription();
+
+ Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = rule.ruleDescriptionSections().stream()
+ .map(s -> builder()
+ .uuid(UuidFactoryFast.getInstance().create())
+ .key(s.getKey())
+ .content(s.getHtmlContent())
+ .context(s.getContext().map(c -> RuleDescriptionSectionContextDto.of(c.getKey(), c.getDisplayName())).orElse(null))
+ .build()
+ )
+ .collect(Collectors.toSet());
+ return Sets.union(ruleDescriptionSectionDtos, Set.of(builder().uuid(UuidFactoryFast.getInstance().create()).key("default").content(description).build()));
+ });
+
+ when(ruleDescriptionSectionsGenerator.isGeneratorForRule(any())).thenReturn(true);
+ }
+
+ @Test
+ public void insert_new_rules() {
+ execute(new FakeRepositoryV1());
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ verifyRule(rule1, RuleType.CODE_SMELL, BLOCKER);
+ assertThat(rule1.isExternal()).isFalse();
+ assertThat(rule1.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL);
+ assertThat(rule1.getDefaultImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity).containsOnly(tuple(SoftwareQuality.RELIABILITY, Severity.HIGH));
+ assertThat(rule1.getDefRemediationFunction()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
+ assertThat(rule1.getDefRemediationGapMultiplier()).isEqualTo("5d");
+ assertThat(rule1.getDefRemediationBaseEffort()).isEqualTo("10h");
+
+ RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
+ verifyHotspot(hotspotRule);
+
+ List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
+ assertThat(params).hasSize(2);
+ RuleParamDto param = getParam(params, "param1");
+ assertThat(param.getDescription()).isEqualTo("parameter one");
+ assertThat(param.getDefaultValue()).isEqualTo("default1");
+
+ // verify index
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ assertThat(rule2.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.EFFICIENT);
+ assertThat(rule2.getDefaultImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity).containsOnly(tuple(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid(), hotspotRule.getUuid());
+ verifyIndicesMarkedAsInitialized();
+
+ // verify repositories
+ assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
+ }
+
+ private void verifyHotspot(RuleDto hotspotRule) {
+ assertThat(hotspotRule.getName()).isEqualTo("Hotspot");
+ assertThat(hotspotRule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Minimal hotspot");
+ assertThat(hotspotRule.getCreatedAt()).isEqualTo(RulesRegistrantIT.DATE1.getTime());
+ assertThat(hotspotRule.getUpdatedAt()).isEqualTo(RulesRegistrantIT.DATE1.getTime());
+ assertThat(hotspotRule.getType()).isEqualTo(RuleType.SECURITY_HOTSPOT.getDbConstant());
+ assertThat(hotspotRule.getSecurityStandards()).containsExactly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
+ assertThat(hotspotRule.getDefaultImpacts()).isEmpty();
+ assertThat(hotspotRule.getCleanCodeAttribute()).isNull();
+ }
+
+ @Test
+ public void insert_new_external_rule() {
+ execute(new ExternalRuleRepository());
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(2);
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_RULE_KEY1);
+ verifyRule(rule1, RuleType.CODE_SMELL, BLOCKER);
+ assertThat(rule1.isExternal()).isTrue();
+ assertThat(rule1.getDefRemediationFunction()).isNull();
+ assertThat(rule1.getDefRemediationGapMultiplier()).isNull();
+ assertThat(rule1.getDefRemediationBaseEffort()).isNull();
+
+ RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_HOTSPOT_RULE_KEY);
+ verifyHotspot(hotspotRule);
+ }
+
+ private void verifyRule(RuleDto rule, RuleType type, String expectedSeverity) {
+ assertThat(rule.getName()).isEqualTo("One");
+ assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description of One");
+ assertThat(rule.getSeverityString()).isEqualTo(expectedSeverity);
+ assertThat(rule.getTags()).isEmpty();
+ assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2", "tag3");
+ assertThat(rule.getConfigKey()).isEqualTo("config1");
+ assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
+ assertThat(rule.getCreatedAt()).isEqualTo(DATE1.getTime());
+ assertThat(rule.getScope()).isEqualTo(Scope.ALL);
+ assertThat(rule.getUpdatedAt()).isEqualTo(DATE1.getTime());
+ assertThat(rule.getType()).isEqualTo(type.getDbConstant());
+ assertThat(rule.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY);
+ assertThat(rule.isAdHoc()).isFalse();
+ assertThat(rule.getEducationPrinciples()).containsOnly("concept1", "concept2", "concept3");
+ }
+
+ @Test
+ public void insert_then_remove_rule() {
+ String ruleKey = randomAlphanumeric(5);
+
+ // register one rule
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule(ruleKey)
+ .setName(randomAlphanumeric(5))
+ .setHtmlDescription(randomAlphanumeric(20));
+ repo.done();
+ });
+
+ // verify db
+ List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
+ assertThat(rules)
+ .extracting(RuleDto::getKey)
+ .extracting(RuleKey::rule)
+ .containsExactly(ruleKey);
+ RuleDto rule = rules.iterator().next();
+
+ // verify index
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
+ .containsExactly(rule.getUuid());
+ verifyIndicesMarkedAsInitialized();
+
+ // register no rule
+ execute(context -> context.createRepository("fake", "java").done());
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession()))
+ .extracting(RuleDto::getKey)
+ .extracting(RuleKey::rule)
+ .containsExactly(ruleKey);
+ assertThat(dbClient.ruleDao().selectAll(db.getSession()))
+ .extracting(RuleDto::getStatus)
+ .containsExactly(REMOVED);
+
+ // verify index
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
+ .isEmpty();
+ verifyIndicesNotMarkedAsInitialized();
+ }
+
+ @Test
+ public void mass_insert_then_remove_rule() {
+ int numberOfRules = 5000;
+
+ // register many rules
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ IntStream.range(0, numberOfRules)
+ .mapToObj(i -> "rule-" + i)
+ .forEach(ruleKey -> repo.createRule(ruleKey)
+ .setName(randomAlphanumeric(20))
+ .setHtmlDescription(randomAlphanumeric(20)));
+ repo.done();
+ });
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession()))
+ .hasSize(numberOfRules)
+ .extracting(RuleDto::getStatus)
+ .containsOnly(READY);
+
+ // verify index
+ assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
+ .isNotEmpty();
+
+ // register no rule
+ execute(context -> context.createRepository("fake", "java").done());
+
+ // verify db
+ assertThat(dbClient.ruleDao().selectAll(db.getSession()))
+ .hasSize(numberOfRules)
+ .extracting(RuleDto::getStatus)
+ .containsOnly(REMOVED);
+
+ // verify index (documents are still in the index, but all are removed)
+ assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
+ .isEmpty();
+ }
+
+ @Test
+ public void delete_repositories_that_have_been_uninstalled() {
+ RuleRepositoryDto repository = new RuleRepositoryDto("findbugs", "java", "Findbugs");
+ DbSession dbSession = db.getSession();
+ db.getDbClient().ruleRepositoryDao().insert(dbSession, singletonList(repository));
+ dbSession.commit();
+
+ execute(new FakeRepositoryV1());
+
+ assertThat(db.getDbClient().ruleRepositoryDao().selectAll(dbSession)).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
+ }
+
+ @Test
+ public void update_and_remove_rules_on_changes() {
+ execute(new FakeRepositoryV1());
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
+ assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
+ verifyIndicesMarkedAsInitialized();
+
+ // user adds tags and sets markdown note
+ rule1.setTags(newHashSet("usertag1", "usertag2"));
+ rule1.setNoteData("user *note*");
+ rule1.setNoteUserUuid("marius");
+ dbClient.ruleDao().update(db.getSession(), rule1);
+ db.getSession().commit();
+
+ system.setNow(DATE2.getTime());
+ execute(new FakeRepositoryV2());
+
+ verifyIndicesNotMarkedAsInitialized();
+ // rule1 has been updated
+ rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThatRule1IsV2(rule1);
+
+ List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
+ assertThat(params).hasSize(2);
+ RuleParamDto param = getParam(params, "param1");
+ assertThat(param.getDescription()).isEqualTo("parameter one v2");
+ assertThat(param.getDefaultValue()).isEqualTo("default1 v2");
+
+ // rule2 has been removed -> status set to REMOVED but db row is not deleted
+ rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ assertThat(rule2.getStatus()).isEqualTo(REMOVED);
+ assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
+
+ // rule3 has been created
+ RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
+ assertThat(rule3).isNotNull();
+ assertThat(rule3.getStatus()).isEqualTo(READY);
+
+ // verify index
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
+
+ // verify repositories
+ assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
+
+ system.setNow(DATE3.getTime());
+ execute(new FakeRepositoryV3());
+ rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
+ assertThat(rule3.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Rule Three V2");
+ assertThat(rule3.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
+ }
+
+ private void assertThatRule1IsV2(RuleDto rule1) {
+ assertThat(rule1.getName()).isEqualTo("One v2");
+ RuleDescriptionSectionDto defaultRuleDescriptionSection = rule1.getDefaultRuleDescriptionSection();
+ assertThat(defaultRuleDescriptionSection.getContent()).isEqualTo("Description of One v2");
+ assertThat(defaultRuleDescriptionSection.getKey()).isEqualTo(DEFAULT_KEY);
+ assertThat(rule1.getDescriptionFormat()).isEqualTo(RuleDto.Format.HTML);
+ assertThat(rule1.getSeverityString()).isEqualTo(INFO);
+ assertThat(rule1.getTags()).containsOnly("usertag1", "usertag2");
+ assertThat(rule1.getSystemTags()).containsOnly("tag1", "tag4");
+ assertThat(rule1.getConfigKey()).isEqualTo("config1 v2");
+ assertThat(rule1.getNoteData()).isEqualTo("user *note*");
+ assertThat(rule1.getNoteUserUuid()).isEqualTo("marius");
+ assertThat(rule1.getStatus()).isEqualTo(READY);
+ assertThat(rule1.getType()).isEqualTo(RuleType.BUG.getDbConstant());
+ assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
+ assertThat(rule1.getUpdatedAt()).isEqualTo(DATE2.getTime());
+ assertThat(rule1.getEducationPrinciples()).containsOnly("concept1","concept4");
+ }
+
+ @Test
+ public void add_new_tag() {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One")
+ .setTags("tag1");
+ repo.done();
+ });
+
+ RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getSystemTags()).containsOnly("tag1");
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One")
+ .setTags("tag1", "tag2");
+ repo.done();
+ });
+
+ rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2");
+ }
+
+ @Test
+ public void add_new_security_standards() {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One")
+ .addOwaspTop10(Y2021, OwaspTop10.A1)
+ .addCwe(123);
+ repo.done();
+ });
+
+ RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getSecurityStandards()).containsOnly("cwe:123", "owaspTop10-2021:a1");
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One")
+ .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
+ .addCwe(1, 123, 863);
+ repo.done();
+ });
+
+ rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getSecurityStandards()).containsOnly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
+ }
+
+ @Test
+ public void update_only_rule_name() {
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name1")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name2")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ // rule1 has been updated
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
+ assertThat(rule1.getName()).isEqualTo("Name2");
+ assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description");
+
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getTotal()).isOne();
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
+ }
+
+ @Test
+ public void update_template_rule_key_should_also_update_custom_rules() {
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("squid", "java");
+ repo.createRule("rule")
+ .setName("Name1")
+ .setHtmlDescription("Description")
+ .setTemplate(true);
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("squid", "rule"));
+
+ // insert custom rule
+ db.rules().insert(new RuleDto()
+ .setRuleKey(RuleKey.of("squid", "custom"))
+ .setLanguage("java")
+ .setScope(Scope.ALL)
+ .setTemplateUuid(rule1.getUuid())
+ .setName("custom1"));
+ db.commit();
+
+ // re-key rule
+ execute(context -> {
+ NewRepository repo = context.createRepository("java", "java");
+ repo.createRule("rule")
+ .setName("Name1")
+ .setHtmlDescription("Description")
+ .addDeprecatedRuleKey("squid", "rule")
+ .setTemplate(true);
+ repo.done();
+ });
+
+ // template rule and custom rule have been updated
+ rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "rule"));
+ RuleDto custom = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "custom"));
+ }
+
+ @Test
+ public void update_if_rule_key_renamed_and_deprecated_key_declared() {
+ String ruleKey1 = "rule1";
+ String ruleKey2 = "rule2";
+ String repository = "fake";
+
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository, "java");
+ repo.createRule(ruleKey1)
+ .setName("Name1")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey1));
+ SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
+ assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
+ assertThat(searchRule1.getTotal()).isOne();
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository, "java");
+ repo.createRule(ruleKey2)
+ .setName("Name2")
+ .setHtmlDescription("Description")
+ .addDeprecatedRuleKey(repository, ruleKey1);
+ repo.done();
+ });
+
+ // rule2 is actually rule1
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey2));
+ assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
+ assertThat(rule2.getName()).isEqualTo("Name2");
+ assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
+
+ SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
+ assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
+ assertThat(searchRule2.getTotal()).isOne();
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
+ }
+
+ @Test
+ public void update_if_repository_changed_and_deprecated_key_declared() {
+ String ruleKey = "rule";
+ String repository1 = "fake1";
+ String repository2 = "fake2";
+
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository1, "java");
+ repo.createRule(ruleKey)
+ .setName("Name1")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey));
+ SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
+ assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
+ assertThat(searchRule1.getTotal()).isOne();
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository2, "java");
+ repo.createRule(ruleKey)
+ .setName("Name2")
+ .setHtmlDescription("Description")
+ .addDeprecatedRuleKey(repository1, ruleKey);
+ repo.done();
+ });
+
+ // rule2 is actually rule1
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey));
+ assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
+ assertThat(rule2.getName()).isEqualTo("Name2");
+ assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
+
+ SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
+ assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
+ assertThat(searchRule2.getTotal()).isOne();
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
+ }
+
+ @Test
+ @UseDataProvider("allRenamingCases")
+ public void update_if_only_renamed_and_deprecated_key_declared(String ruleKey1, String repo1, String ruleKey2, String repo2) {
+ String name = "Name1";
+ String description = "Description";
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repo1, "java");
+ repo.createRule(ruleKey1)
+ .setName(name)
+ .setHtmlDescription(description);
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo1, ruleKey1));
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
+ .containsOnly(rule1.getUuid());
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repo2, "java");
+ repo.createRule(ruleKey2)
+ .setName(name)
+ .setHtmlDescription(description)
+ .addDeprecatedRuleKey(repo1, ruleKey1);
+ repo.done();
+ });
+
+ // rule2 is actually rule1
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo2, ruleKey2));
+ assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
+ assertThat(rule2.getName()).isEqualTo(rule1.getName());
+ assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
+
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
+ .containsOnly(rule2.getUuid());
+ }
+
+ @DataProvider
+ public static Object[][] allRenamingCases() {
+ return new Object[][]{
+ {"repo1", "rule1", "repo1", "rule2"},
+ {"repo1", "rule1", "repo2", "rule1"},
+ {"repo1", "rule1", "repo2", "rule2"},
+ };
+ }
+
+ @Test
+ public void update_if_repository_and_key_changed_and_deprecated_key_declared_among_others() {
+ String ruleKey1 = "rule1";
+ String ruleKey2 = "rule2";
+ String repository1 = "fake1";
+ String repository2 = "fake2";
+
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository1, "java");
+ repo.createRule(ruleKey1)
+ .setName("Name1")
+ .setHtmlDescription("Description");
+ repo.done();
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey1));
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getUuids())
+ .containsOnly(rule1.getUuid());
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository(repository2, "java");
+ repo.createRule(ruleKey2)
+ .setName("Name2")
+ .setHtmlDescription("Description")
+ .addDeprecatedRuleKey("foo", "bar")
+ .addDeprecatedRuleKey(repository1, ruleKey1)
+ .addDeprecatedRuleKey("some", "noise");
+ repo.done();
+ });
+
+ // rule2 is actually rule1
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey2));
+ assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
+
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getUuids())
+ .containsOnly(rule1.getUuid());
+ }
+
+ @Test
+ public void update_only_rule_description() {
+ system.setNow(DATE1.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name")
+ .setHtmlDescription("Desc1");
+ repo.done();
+ });
+
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name")
+ .setHtmlDescription("Desc2");
+ repo.done();
+ });
+
+ // rule1 has been updated
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
+ assertThat(rule1.getName()).isEqualTo("Name");
+ assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
+
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc2"), new SearchOptions()).getTotal()).isOne();
+ assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc1"), new SearchOptions()).getTotal()).isZero();
+ }
+
+ @Test
+ public void update_several_rule_descriptions() {
+ system.setNow(DATE1.getTime());
+
+ RuleDescriptionSection section1context1 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx1 content", "ctx_1");
+ RuleDescriptionSection section1context2 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 content", "ctx_2");
+ RuleDescriptionSection section2context1 = createRuleDescriptionSection(RESOURCES_SECTION_KEY, "section2 content", "ctx_1");
+ RuleDescriptionSection section2context2 = createRuleDescriptionSection(RESOURCES_SECTION_KEY,"section2 ctx2 content", "ctx_2");
+ RuleDescriptionSection section3noContext = createRuleDescriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY, "section3 content", null);
+ RuleDescriptionSection section4noContext = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, "section4 content", null);
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name")
+ .addDescriptionSection(section1context1)
+ .addDescriptionSection(section1context2)
+ .addDescriptionSection(section2context1)
+ .addDescriptionSection(section2context2)
+ .addDescriptionSection(section3noContext)
+ .addDescriptionSection(section4noContext)
+ .setHtmlDescription("Desc1");
+ repo.done();
+ });
+
+ RuleDescriptionSection section1context2updated = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 updated content", "ctx_2");
+ RuleDescriptionSection section2updatedWithoutContext = createRuleDescriptionSection(RESOURCES_SECTION_KEY, section2context1.getHtmlContent(), null);
+ RuleDescriptionSection section4updatedWithContext1 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_1");
+ RuleDescriptionSection section4updatedWithContext2 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_2");
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ repo.createRule("rule")
+ .setName("Name")
+ .addDescriptionSection(section1context1)
+ .addDescriptionSection(section1context2updated)
+ .addDescriptionSection(section2updatedWithoutContext)
+ .addDescriptionSection(section3noContext)
+ .addDescriptionSection(section4updatedWithContext1)
+ .addDescriptionSection(section4updatedWithContext2)
+ .setHtmlDescription("Desc2");
+ repo.done();
+
+ });
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
+ assertThat(rule1.getName()).isEqualTo("Name");
+ assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
+
+ Set<RuleDescriptionSection> expectedSections = Set.of(section1context1, section1context2updated,
+ section2updatedWithoutContext, section3noContext, section4updatedWithContext1, section4updatedWithContext2);
+ assertThat(rule1.getRuleDescriptionSectionDtos()).hasSize(expectedSections.size() + 1);
+ expectedSections.forEach(apiSection -> assertSectionExists(apiSection, rule1.getRuleDescriptionSectionDtos()));
+ }
+
+ private static RuleDescriptionSection createRuleDescriptionSection(String sectionKey, String description, @Nullable String contextKey) {
+ Context context = Optional.ofNullable(contextKey).map(key -> new Context(contextKey, contextKey + randomAlphanumeric(10))).orElse(null);
+ return RuleDescriptionSection.builder().sectionKey(sectionKey)
+ .htmlContent(description)
+ .context(context)
+ .build();
+ }
+
+ private static void assertSectionExists(RuleDescriptionSection apiSection, Set<RuleDescriptionSectionDto> sectionDtos) {
+ sectionDtos.stream()
+ .filter(sectionDto -> sectionDto.getKey().equals(apiSection.getKey()) && sectionDto.getContent().equals(apiSection.getHtmlContent()))
+ .filter(sectionDto -> isSameContext(apiSection.getContext(), sectionDto.getContext()))
+ .findAny()
+ .orElseThrow(() -> new AssertionError(format("Impossible to find a section dto matching the API section %s", apiSection.getKey())));
+ }
+
+ private static boolean isSameContext(Optional<Context> apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
+ if (apiContext.isEmpty() && contextDto == null) {
+ return true;
+ }
+ return apiContext.filter(context -> isSameContext(context, contextDto)).isPresent();
+ }
+
+ private static boolean isSameContext(Context apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
+ if (contextDto == null) {
+ return false;
+ }
+ return Objects.equals(apiContext.getKey(), contextDto.getKey()) && Objects.equals(apiContext.getDisplayName(), contextDto.getDisplayName());
+ }
+
+ @Test
+ public void rule_previously_created_as_adhoc_becomes_none_adhoc() {
+ RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake").setIsExternal(true).setIsAdHoc(true));
+ system.setNow(DATE2.getTime());
+ execute(context -> {
+ NewRepository repo = context.createExternalRepository("fake", rule.getLanguage());
+ repo.createRule(rule.getRuleKey())
+ .setName(rule.getName())
+ .setHtmlDescription(rule.getDefaultRuleDescriptionSection().getContent());
+ repo.done();
+ });
+
+ RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
+ assertThat(reloaded.isAdHoc()).isFalse();
+ }
+
+ @Test
+ public void remove_no_more_defined_external_rule() {
+ RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
+ .setStatus(READY)
+ .setIsExternal(true)
+ .setIsAdHoc(false));
+
+ execute();
+
+ RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
+ assertThat(reloaded.getStatus()).isEqualTo(REMOVED);
+ }
+
+ @Test
+ public void do_not_remove_no_more_defined_ad_hoc_rule() {
+ RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
+ .setStatus(READY)
+ .setIsExternal(true)
+ .setIsAdHoc(true));
+
+ execute();
+
+ RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
+ assertThat(reloaded.getStatus()).isEqualTo(READY);
+ }
+
+ @Test
+ public void disable_then_enable_rule() {
+ // Install rule
+ system.setNow(DATE1.getTime());
+ execute(new FakeRepositoryV1());
+
+ // Uninstall rule
+ system.setNow(DATE2.getTime());
+ execute();
+
+ RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getStatus()).isEqualTo(REMOVED);
+ assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isZero();
+
+ // Re-install rule
+ system.setNow(DATE3.getTime());
+ execute(new FakeRepositoryV1());
+
+ rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
+ assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isOne();
+ }
+
+ @Test
+ public void do_not_update_rules_when_no_changes() {
+ execute(new FakeRepositoryV1());
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
+
+ system.setNow(DATE2.getTime());
+ execute(new FakeRepositoryV1());
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
+ assertThat(rule1.getUpdatedAt()).isEqualTo(DATE1.getTime());
+ }
+
+ @Test
+ public void do_not_update_already_removed_rules() {
+ execute(new FakeRepositoryV1());
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
+
+ RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
+ RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
+ assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
+
+ assertThat(rule2.getStatus()).isEqualTo(READY);
+
+ system.setNow(DATE2.getTime());
+ execute(new FakeRepositoryV2());
+
+ // On MySQL, need to update a rule otherwise rule2 will be seen as READY, but why ???
+ dbClient.ruleDao().update(db.getSession(), rule1);
+ db.getSession().commit();
+
+ // rule2 is removed
+ rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
+ assertThat(rule2.getStatus()).isEqualTo(REMOVED);
+
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
+
+ system.setNow(DATE3.getTime());
+ execute(new FakeRepositoryV2());
+ db.getSession().commit();
+
+ // -> rule2 is still removed, but not update at DATE3
+ rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
+ assertThat(rule2.getStatus()).isEqualTo(REMOVED);
+ assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
+
+ assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
+ }
+
+ @Test
+ public void mass_insert() {
+ execute(new BigRepository());
+ assertThat(db.countRowsOfTable("rules")).isEqualTo(BigRepository.SIZE);
+ assertThat(db.countRowsOfTable("rules_parameters")).isEqualTo(BigRepository.SIZE * 20);
+ assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).hasSize(BigRepository.SIZE);
+ }
+
+ @Test
+ public void manage_repository_extensions() {
+ execute(new FindbugsRepository(), new FbContribRepository());
+ List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
+ assertThat(rules).hasSize(2);
+ for (RuleDto rule : rules) {
+ assertThat(rule.getRepositoryKey()).isEqualTo("findbugs");
+ }
+ }
+
+ @Test
+ public void remove_system_tags_when_plugin_does_not_provide_any() {
+ // Rule already exists in DB, with some system tags
+ db.rules().insert(new RuleDto()
+ .setRuleKey("rule1")
+ .setRepositoryKey("findbugs")
+ .setName("Rule One")
+ .setType(RuleType.CODE_SMELL)
+ .setScope(Scope.ALL)
+ .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule one description"))
+ .setDescriptionFormat(RuleDto.Format.HTML)
+ .setSystemTags(newHashSet("tag1", "tag2")));
+ db.getSession().commit();
+
+ // Synchronize rule without tag
+ execute(new FindbugsRepository());
+
+ List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
+ assertThat(rules).hasSize(1).extracting(RuleDto::getKey, RuleDto::getSystemTags)
+ .containsOnly(tuple(RuleKey.of("findbugs", "rule1"), emptySet()));
+ }
+
+ @Test
+ public void rules_that_deprecate_previous_rule_must_be_recorded() {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "rule1");
+ repo.done();
+ });
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey")
+ .addDeprecatedRuleKey("fake", "rule1")
+ .addDeprecatedRuleKey("fake", "rule2");
+ repo.done();
+ });
+
+ List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
+ Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
+ assertThat(rules).hasSize(1);
+ assertThat(deprecatedRuleKeys).hasSize(2);
+ }
+
+ @Test
+ public void rules_that_remove_deprecated_key_must_remove_records() {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "rule1");
+ repo.done();
+ });
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey")
+ .addDeprecatedRuleKey("fake", "rule1")
+ .addDeprecatedRuleKey("fake", "rule2");
+ repo.done();
+ });
+
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
+ Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
+ assertThat(deprecatedRuleKeys).hasSize(2);
+
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey");
+ repo.done();
+ });
+
+ assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
+ deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
+ assertThat(deprecatedRuleKeys).isEmpty();
+ }
+
+ @Test
+ public void declaring_two_rules_with_same_deprecated_RuleKey_should_throw_ISE() {
+ assertThatThrownBy(() -> {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey1")
+ .addDeprecatedRuleKey("fake", "old");
+ createRule(repo, "newKey2")
+ .addDeprecatedRuleKey("fake", "old");
+ repo.done();
+ });
+ })
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("The following deprecated rule keys are declared at least twice [fake:old]");
+ }
+
+ @Test
+ public void declaring_a_rule_with_a_deprecated_RuleKey_still_used_should_throw_ISE() {
+ assertThatThrownBy(() -> {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey1");
+ createRule(repo, "newKey2")
+ .addDeprecatedRuleKey("fake", "newKey1");
+ repo.done();
+ });
+ })
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("The following rule keys are declared both as deprecated and used key [fake:newKey1]");
+ }
+
+ @Test
+ public void updating_the_deprecated_to_a_new_ruleKey_should_throw_an_ISE() {
+ // On this new rule add a deprecated key
+ execute(context -> createRule(context, "javascript", "javascript", "s103",
+ r -> r.addDeprecatedRuleKey("javascript", "linelength")));
+
+ assertThatThrownBy(() -> {
+ // This rule should have been moved to another repository
+ execute(context -> createRule(context, "javascript", "sonarjs", "s103",
+ r -> r.addDeprecatedRuleKey("javascript", "linelength")));
+ })
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("An incorrect state of deprecated rule keys has been detected.\n " +
+ "The deprecated rule key [javascript:linelength] was previously deprecated by [javascript:s103]. [javascript:s103] should be a deprecated key of [sonarjs:s103],");
+ }
+
+ @Test
+ public void deprecate_rule_that_deprecated_another_rule() {
+ execute(context -> createRule(context, "javascript", "javascript", "s103"));
+ execute(context -> createRule(context, "javascript", "javascript", "s104",
+ r -> r.addDeprecatedRuleKey("javascript", "s103")));
+
+ // This rule should have been moved to another repository
+ execute(context -> createRule(context, "javascript", "sonarjs", "s105",
+ r -> r.addDeprecatedRuleKey("javascript", "s103")
+ .addDeprecatedRuleKey("javascript", "s104")));
+ }
+
+ @Test
+ public void declaring_a_rule_with_an_existing_RuleKey_still_used_should_throw_IAE() {
+ assertThatThrownBy(() -> {
+ execute(context -> {
+ NewRepository repo = context.createRepository("fake", "java");
+ createRule(repo, "newKey1");
+ createRule(repo, "newKey1");
+ repo.done();
+ });
+ })
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("The rule 'newKey1' of repository 'fake' is declared several times");
+ }
+
+ @Test
+ public void removed_rule_should_appear_in_changelog() {
+ //GIVEN
+ QProfileDto qProfileDto = db.qualityProfiles().insert();
+ RuleDto ruleDto = db.rules().insert(RULE_KEY1);
+ db.qualityProfiles().activateRule(qProfileDto, ruleDto);
+ ActiveRuleChange arChange = new ActiveRuleChange(DEACTIVATED, ActiveRuleDto.createFor(qProfileDto, ruleDto), ruleDto);
+ when(qProfileRules.deleteRule(any(DbSession.class), eq(ruleDto))).thenReturn(List.of(arChange));
+ //WHEN
+ execute(context -> context.createRepository("fake", "java").done());
+ //THEN
+ List<QProfileChangeDto> qProfileChangeDtos = dbClient.qProfileChangeDao().selectByQuery(db.getSession(), new QProfileChangeQuery(qProfileDto.getKee()));
+ assertThat(qProfileChangeDtos).extracting(QProfileChangeDto::getRulesProfileUuid, QProfileChangeDto::getChangeType)
+ .contains(tuple(qProfileDto.getRulesProfileUuid(), "DEACTIVATED"));
+ }
+
+ @Test
+ public void removed_rule_should_be_deleted_when_renamed_repository() {
+ //GIVEN
+ RuleDto removedRuleDto = db.rules().insert(RuleKey.of("old_repo", "removed_rule"));
+ RuleDto renamedRuleDto = db.rules().insert(RuleKey.of("old_repo", "renamed_rule"));
+ //WHEN
+ execute(context -> createRule(context, "java", "new_repo", renamedRuleDto.getRuleKey(),
+ rule -> rule.addDeprecatedRuleKey(renamedRuleDto.getRepositoryKey(), renamedRuleDto.getRuleKey())));
+ //THEN
+ verify(qProfileRules).deleteRule(any(DbSession.class), eq(removedRuleDto));
+ }
+
+ private void execute(RulesDefinition... defs) {
+ ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
+ when(pluginRepository.getPluginKey(any(RulesDefinition.class))).thenReturn(FAKE_PLUGIN_KEY);
+ RuleDefinitionsLoader loader = new RuleDefinitionsLoader(pluginRepository, defs);
+ Languages languages = mock(Languages.class);
+ when(languages.get(any())).thenReturn(mock(Language.class));
+ reset(webServerRuleFinder);
+
+ RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, metadataIndex,
+ rulesKeyVerifier, startupRuleUpdater, newRuleCreator, qualityProfileChangesUpdater);
+ task.start();
+ // Execute a commit to refresh session state as the task is using its own session
+ db.getSession().commit();
+
+ verify(webServerRuleFinder).startCaching();
+ }
+
+ private NewRule createRule(NewRepository repo, String key) {
+ return repo.createRule(key)
+ .setName(key + " name")
+ .setHtmlDescription("Description of " + key)
+ .setSeverity(BLOCKER)
+ .setInternalKey("config1")
+ .setTags("tag1", "tag2", "tag3")
+ .setType(RuleType.CODE_SMELL)
+ .setStatus(RuleStatus.BETA);
+ }
+
+ @SafeVarargs
+ private void createRule(RulesDefinition.Context context, String language, String repositoryKey, String ruleKey, Consumer<NewRule>... consumers) {
+ NewRepository repo = context.createRepository(repositoryKey, language);
+ NewRule newRule = repo.createRule(ruleKey)
+ .setName(ruleKey)
+ .setHtmlDescription("Description of One")
+ .setSeverity(BLOCKER)
+ .setType(RuleType.CODE_SMELL)
+ .setStatus(RuleStatus.BETA);
+
+ Arrays.stream(consumers).forEach(c -> c.accept(newRule));
+ repo.done();
+ }
+
+ private void verifyIndicesMarkedAsInitialized() {
+ verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_RULE, true);
+ verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_ACTIVE_RULE, true);
+ reset(metadataIndex);
+ }
+
+ private void verifyIndicesNotMarkedAsInitialized() {
+ verifyNoInteractions(metadataIndex);
+ }
+
+ private RuleParamDto getParam(List<RuleParamDto> params, String key) {
+ for (RuleParamDto param : params) {
+ if (param.getName().equals(key)) {
+ return param;
+ }
+ }
+ return null;
+ }
+
+ static class FakeRepositoryV1 implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("fake", "java");
+ NewRule rule1 = repo.createRule(RULE_KEY1.rule())
+ .setName("One")
+ .setHtmlDescription("Description of One")
+ .setSeverity(BLOCKER)
+ .setInternalKey("config1")
+ .setTags("tag1", "tag2", "tag3")
+ .setScope(RuleScope.ALL)
+ .setType(RuleType.CODE_SMELL)
+ .setStatus(RuleStatus.BETA)
+ .setGapDescription("java.S115.effortToFix")
+ .addEducationPrincipleKeys("concept1", "concept2", "concept3")
+ .addDefaultImpact(SoftwareQuality.RELIABILITY, Severity.HIGH);
+ rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("5d", "10h"));
+
+ rule1.createParam("param1").setDescription("parameter one").setDefaultValue("default1");
+ rule1.createParam("param2").setDescription("parameter two").setDefaultValue("default2");
+
+ repo.createRule(HOTSPOT_RULE_KEY.rule())
+ .setName("Hotspot")
+ .setHtmlDescription("Minimal hotspot")
+ .setType(RuleType.SECURITY_HOTSPOT)
+ .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
+ .addCwe(1, 123, 863);
+
+ repo.createRule(RULE_KEY2.rule())
+ .setName("Two")
+ .setHtmlDescription("Minimal rule")
+ .setCleanCodeAttribute(CleanCodeAttribute.EFFICIENT);
+ repo.done();
+ }
+ }
+
+ /**
+ * FakeRepositoryV1 with some changes
+ */
+ static class FakeRepositoryV2 implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("fake", "java");
+
+ // almost all the attributes of rule1 are changed
+ NewRule rule1 = repo.createRule(RULE_KEY1.rule())
+ .setName("One v2")
+ .setHtmlDescription("Description of One v2")
+ .setSeverity(INFO)
+ .setInternalKey("config1 v2")
+ // tag2 and tag3 removed, tag4 added
+ .setTags("tag1", "tag4")
+ .setType(RuleType.BUG)
+ .setStatus(READY)
+ .setGapDescription("java.S115.effortToFix.v2")
+ .addEducationPrincipleKeys("concept1", "concept4");
+ rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("6d", "2h"));
+ rule1.createParam("param1").setDescription("parameter one v2").setDefaultValue("default1 v2");
+ rule1.createParam("param2").setDescription("parameter two v2").setDefaultValue("default2 v2");
+
+ // rule2 is dropped, rule3 is new
+ repo.createRule(RULE_KEY3.rule())
+ .setName("Three")
+ .setHtmlDescription("Rule Three");
+
+ repo.done();
+ }
+ }
+
+ static class FakeRepositoryV3 implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("fake", "java");
+ // rule 3 is dropped
+ repo.createRule(RULE_KEY3.rule())
+ .setName("Three")
+ .setMarkdownDescription("Rule Three V2");
+
+ repo.done();
+ }
+ }
+
+ static class ExternalRuleRepository implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createExternalRepository("eslint", "js");
+ repo.createRule(RULE_KEY1.rule())
+ .setName("One")
+ .setHtmlDescription("Description of One")
+ .setSeverity(BLOCKER)
+ .setInternalKey("config1")
+ .setTags("tag1", "tag2", "tag3")
+ .setScope(RuleScope.ALL)
+ .setType(RuleType.CODE_SMELL)
+ .setStatus(RuleStatus.BETA)
+ .addEducationPrincipleKeys("concept1", "concept2", "concept3");
+
+ repo.createRule(EXTERNAL_HOTSPOT_RULE_KEY.rule())
+ .setName("Hotspot")
+ .setHtmlDescription("Minimal hotspot")
+ .setType(RuleType.SECURITY_HOTSPOT)
+ .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
+ .addCwe(1, 123, 863);
+
+ repo.done();
+ }
+ }
+
+ static class BigRepository implements RulesDefinition {
+ static final int SIZE = 500;
+
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("big", "java");
+ for (int i = 0; i < SIZE; i++) {
+ NewRule rule = repo.createRule("rule" + i)
+ .setName("name of " + i)
+ .setHtmlDescription("description of " + i);
+ for (int j = 0; j < 20; j++) {
+ rule.createParam("param" + j);
+ }
+
+ }
+ repo.done();
+ }
+ }
+
+ static class FindbugsRepository implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("findbugs", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One");
+ repo.done();
+ }
+ }
+
+ static class FbContribRepository implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewExtendedRepository repo = context.createRepository("findbugs", "java");
+ repo.createRule("rule2")
+ .setName("Rule Two")
+ .setHtmlDescription("Description of Rule Two");
+ repo.done();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+
+ }
+}
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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;
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;
this.rulesKeyVerifier = rulesKeyVerifier;
this.startupRuleUpdater = startupRuleUpdater;
this.newRuleCreator = newRuleCreator;
+ this.qualityProfileChangesUpdater = qualityProfileChangesUpdater;
}
@Override
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();
}
}
// 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());
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)) {
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) {
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;
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;
/**
* 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) {
.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());
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;
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;
}
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;
}
.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;
}
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;
}
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;
}
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;
+ }
+ }
}
--- /dev/null
+/*
+ * 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
+++ /dev/null
-/*
- * 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 com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import org.elasticsearch.common.util.set.Sets;
-import org.jetbrains.annotations.Nullable;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.issue.impact.Severity;
-import org.sonar.api.issue.impact.SoftwareQuality;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.RuleScope;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rules.CleanCodeAttribute;
-import org.sonar.api.rules.RuleType;
-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.core.util.UuidFactory;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.issue.ImpactDto;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.QProfileChangeDto;
-import org.sonar.db.qualityprofile.QProfileChangeQuery;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.DeprecatedRuleKeyDto;
-import org.sonar.db.rule.RuleDescriptionSectionContextDto;
-import org.sonar.db.rule.RuleDescriptionSectionDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleDto.Scope;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.db.rule.RuleRepositoryDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.SearchIdResult;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.es.metadata.MetadataIndex;
-import org.sonar.server.plugins.ServerPluginRepository;
-import org.sonar.server.qualityprofile.ActiveRuleChange;
-import org.sonar.server.qualityprofile.QProfileRules;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import org.sonar.server.rule.RuleDefinitionsLoader;
-import org.sonar.server.rule.RuleDescriptionSectionsGenerator;
-import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
-import org.sonar.server.rule.WebServerRuleFinder;
-import org.sonar.server.rule.index.RuleIndex;
-import org.sonar.server.rule.index.RuleIndexDefinition;
-import org.sonar.server.rule.index.RuleIndexer;
-import org.sonar.server.rule.index.RuleQuery;
-
-import static com.google.common.collect.Sets.newHashSet;
-import static java.lang.String.format;
-import static java.lang.String.valueOf;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singletonList;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-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;
-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.api.server.rule.RulesDefinition.NewRepository;
-import static org.sonar.api.server.rule.RulesDefinition.NewRule;
-import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10;
-import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.builder;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
-import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
-
-@RunWith(DataProviderRunner.class)
-public class RulesRegistrantIT {
-
- private static final String FAKE_PLUGIN_KEY = "unittest";
- private static final Date DATE1 = DateUtils.parseDateTime("2014-01-01T19:10:03+0100");
- private static final Date DATE2 = DateUtils.parseDateTime("2014-02-01T12:10:03+0100");
- private static final Date DATE3 = DateUtils.parseDateTime("2014-03-01T12:10:03+0100");
-
- private static final RuleKey EXTERNAL_RULE_KEY1 = RuleKey.of("external_eslint", "rule1");
- private static final RuleKey EXTERNAL_HOTSPOT_RULE_KEY = RuleKey.of("external_eslint", "hotspot");
-
- private static final RuleKey RULE_KEY1 = RuleKey.of("fake", "rule1");
- private static final RuleKey RULE_KEY2 = RuleKey.of("fake", "rule2");
- private static final RuleKey RULE_KEY3 = RuleKey.of("fake", "rule3");
- private static final RuleKey HOTSPOT_RULE_KEY = RuleKey.of("fake", "hotspot");
-
- private final TestSystem2 system = new TestSystem2().setNow(DATE1.getTime());
-
- @org.junit.Rule
- public DbTester db = DbTester.create(system);
- @org.junit.Rule
- public EsTester es = EsTester.create();
- @org.junit.Rule
- public LogTester logTester = new LogTester();
-
- private final QProfileRules qProfileRules = mock(QProfileRules.class);
- private final WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class);
- private final DbClient dbClient = db.getDbClient();
- private final MetadataIndex metadataIndex = mock(MetadataIndex.class);
- 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 RulesKeyVerifier rulesKeyVerifier = new RulesKeyVerifier();
- private final StartupRuleUpdater startupRuleUpdater = new StartupRuleUpdater(dbClient, system, uuidFactory, resolver);
- private final NewRuleCreator newRuleCreator = new NewRuleCreator(dbClient, resolver, uuidFactory, system);
-
- @Before
- public void before() {
- ruleIndexer = new RuleIndexer(es.client(), dbClient);
- ruleIndex = new RuleIndex(es.client(), system);
- activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client());
- when(resolver.generateFor(any())).thenAnswer(answer -> {
- RulesDefinition.Rule rule = answer.getArgument(0, RulesDefinition.Rule.class);
- String description = rule.htmlDescription() == null ? rule.markdownDescription() : rule.htmlDescription();
-
- Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = rule.ruleDescriptionSections().stream()
- .map(s -> builder()
- .uuid(UuidFactoryFast.getInstance().create())
- .key(s.getKey())
- .content(s.getHtmlContent())
- .context(s.getContext().map(c -> RuleDescriptionSectionContextDto.of(c.getKey(), c.getDisplayName())).orElse(null))
- .build()
- )
- .collect(Collectors.toSet());
- return Sets.union(ruleDescriptionSectionDtos, Set.of(builder().uuid(UuidFactoryFast.getInstance().create()).key("default").content(description).build()));
- });
-
- when(ruleDescriptionSectionsGenerator.isGeneratorForRule(any())).thenReturn(true);
- }
-
- @Test
- public void insert_new_rules() {
- execute(new FakeRepositoryV1());
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- verifyRule(rule1, RuleType.CODE_SMELL, BLOCKER);
- assertThat(rule1.isExternal()).isFalse();
- assertThat(rule1.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL);
- assertThat(rule1.getDefaultImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity).containsOnly(tuple(SoftwareQuality.RELIABILITY, Severity.HIGH));
- assertThat(rule1.getDefRemediationFunction()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
- assertThat(rule1.getDefRemediationGapMultiplier()).isEqualTo("5d");
- assertThat(rule1.getDefRemediationBaseEffort()).isEqualTo("10h");
-
- RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
- verifyHotspot(hotspotRule);
-
- List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
- assertThat(params).hasSize(2);
- RuleParamDto param = getParam(params, "param1");
- assertThat(param.getDescription()).isEqualTo("parameter one");
- assertThat(param.getDefaultValue()).isEqualTo("default1");
-
- // verify index
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- assertThat(rule2.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.EFFICIENT);
- assertThat(rule2.getDefaultImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity).containsOnly(tuple(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid(), hotspotRule.getUuid());
- verifyIndicesMarkedAsInitialized();
-
- // verify repositories
- assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
- }
-
- private void verifyHotspot(RuleDto hotspotRule) {
- assertThat(hotspotRule.getName()).isEqualTo("Hotspot");
- assertThat(hotspotRule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Minimal hotspot");
- assertThat(hotspotRule.getCreatedAt()).isEqualTo(RulesRegistrantIT.DATE1.getTime());
- assertThat(hotspotRule.getUpdatedAt()).isEqualTo(RulesRegistrantIT.DATE1.getTime());
- assertThat(hotspotRule.getType()).isEqualTo(RuleType.SECURITY_HOTSPOT.getDbConstant());
- assertThat(hotspotRule.getSecurityStandards()).containsExactly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
- assertThat(hotspotRule.getDefaultImpacts()).isEmpty();
- assertThat(hotspotRule.getCleanCodeAttribute()).isNull();
- }
-
- @Test
- public void insert_new_external_rule() {
- execute(new ExternalRuleRepository());
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(2);
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_RULE_KEY1);
- verifyRule(rule1, RuleType.CODE_SMELL, BLOCKER);
- assertThat(rule1.isExternal()).isTrue();
- assertThat(rule1.getDefRemediationFunction()).isNull();
- assertThat(rule1.getDefRemediationGapMultiplier()).isNull();
- assertThat(rule1.getDefRemediationBaseEffort()).isNull();
-
- RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_HOTSPOT_RULE_KEY);
- verifyHotspot(hotspotRule);
- }
-
- private void verifyRule(RuleDto rule, RuleType type, String expectedSeverity) {
- assertThat(rule.getName()).isEqualTo("One");
- assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description of One");
- assertThat(rule.getSeverityString()).isEqualTo(expectedSeverity);
- assertThat(rule.getTags()).isEmpty();
- assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2", "tag3");
- assertThat(rule.getConfigKey()).isEqualTo("config1");
- assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
- assertThat(rule.getCreatedAt()).isEqualTo(DATE1.getTime());
- assertThat(rule.getScope()).isEqualTo(Scope.ALL);
- assertThat(rule.getUpdatedAt()).isEqualTo(DATE1.getTime());
- assertThat(rule.getType()).isEqualTo(type.getDbConstant());
- assertThat(rule.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY);
- assertThat(rule.isAdHoc()).isFalse();
- assertThat(rule.getEducationPrinciples()).containsOnly("concept1", "concept2", "concept3");
- }
-
- @Test
- public void insert_then_remove_rule() {
- String ruleKey = randomAlphanumeric(5);
-
- // register one rule
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule(ruleKey)
- .setName(randomAlphanumeric(5))
- .setHtmlDescription(randomAlphanumeric(20));
- repo.done();
- });
-
- // verify db
- List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
- assertThat(rules)
- .extracting(RuleDto::getKey)
- .extracting(RuleKey::rule)
- .containsExactly(ruleKey);
- RuleDto rule = rules.iterator().next();
-
- // verify index
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
- .containsExactly(rule.getUuid());
- verifyIndicesMarkedAsInitialized();
-
- // register no rule
- execute(context -> context.createRepository("fake", "java").done());
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession()))
- .extracting(RuleDto::getKey)
- .extracting(RuleKey::rule)
- .containsExactly(ruleKey);
- assertThat(dbClient.ruleDao().selectAll(db.getSession()))
- .extracting(RuleDto::getStatus)
- .containsExactly(REMOVED);
-
- // verify index
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
- .isEmpty();
- verifyIndicesNotMarkedAsInitialized();
- }
-
- @Test
- public void mass_insert_then_remove_rule() {
- int numberOfRules = 5000;
-
- // register many rules
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- IntStream.range(0, numberOfRules)
- .mapToObj(i -> "rule-" + i)
- .forEach(ruleKey -> repo.createRule(ruleKey)
- .setName(randomAlphanumeric(20))
- .setHtmlDescription(randomAlphanumeric(20)));
- repo.done();
- });
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession()))
- .hasSize(numberOfRules)
- .extracting(RuleDto::getStatus)
- .containsOnly(READY);
-
- // verify index
- assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
- .isNotEmpty();
-
- // register no rule
- execute(context -> context.createRepository("fake", "java").done());
-
- // verify db
- assertThat(dbClient.ruleDao().selectAll(db.getSession()))
- .hasSize(numberOfRules)
- .extracting(RuleDto::getStatus)
- .containsOnly(REMOVED);
-
- // verify index (documents are still in the index, but all are removed)
- assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
- .isEmpty();
- }
-
- @Test
- public void delete_repositories_that_have_been_uninstalled() {
- RuleRepositoryDto repository = new RuleRepositoryDto("findbugs", "java", "Findbugs");
- DbSession dbSession = db.getSession();
- db.getDbClient().ruleRepositoryDao().insert(dbSession, singletonList(repository));
- dbSession.commit();
-
- execute(new FakeRepositoryV1());
-
- assertThat(db.getDbClient().ruleRepositoryDao().selectAll(dbSession)).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
- }
-
- @Test
- public void update_and_remove_rules_on_changes() {
- execute(new FakeRepositoryV1());
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
- assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
- verifyIndicesMarkedAsInitialized();
-
- // user adds tags and sets markdown note
- rule1.setTags(newHashSet("usertag1", "usertag2"));
- rule1.setNoteData("user *note*");
- rule1.setNoteUserUuid("marius");
- dbClient.ruleDao().update(db.getSession(), rule1);
- db.getSession().commit();
-
- system.setNow(DATE2.getTime());
- execute(new FakeRepositoryV2());
-
- verifyIndicesNotMarkedAsInitialized();
- // rule1 has been updated
- rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThatRule1IsV2(rule1);
-
- List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
- assertThat(params).hasSize(2);
- RuleParamDto param = getParam(params, "param1");
- assertThat(param.getDescription()).isEqualTo("parameter one v2");
- assertThat(param.getDefaultValue()).isEqualTo("default1 v2");
-
- // rule2 has been removed -> status set to REMOVED but db row is not deleted
- rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- assertThat(rule2.getStatus()).isEqualTo(REMOVED);
- assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
-
- // rule3 has been created
- RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
- assertThat(rule3).isNotNull();
- assertThat(rule3.getStatus()).isEqualTo(READY);
-
- // verify index
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
-
- // verify repositories
- assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
-
- system.setNow(DATE3.getTime());
- execute(new FakeRepositoryV3());
- rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
- assertThat(rule3.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Rule Three V2");
- assertThat(rule3.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
- }
-
- private void assertThatRule1IsV2(RuleDto rule1) {
- assertThat(rule1.getName()).isEqualTo("One v2");
- RuleDescriptionSectionDto defaultRuleDescriptionSection = rule1.getDefaultRuleDescriptionSection();
- assertThat(defaultRuleDescriptionSection.getContent()).isEqualTo("Description of One v2");
- assertThat(defaultRuleDescriptionSection.getKey()).isEqualTo(DEFAULT_KEY);
- assertThat(rule1.getDescriptionFormat()).isEqualTo(RuleDto.Format.HTML);
- assertThat(rule1.getSeverityString()).isEqualTo(INFO);
- assertThat(rule1.getTags()).containsOnly("usertag1", "usertag2");
- assertThat(rule1.getSystemTags()).containsOnly("tag1", "tag4");
- assertThat(rule1.getConfigKey()).isEqualTo("config1 v2");
- assertThat(rule1.getNoteData()).isEqualTo("user *note*");
- assertThat(rule1.getNoteUserUuid()).isEqualTo("marius");
- assertThat(rule1.getStatus()).isEqualTo(READY);
- assertThat(rule1.getType()).isEqualTo(RuleType.BUG.getDbConstant());
- assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
- assertThat(rule1.getUpdatedAt()).isEqualTo(DATE2.getTime());
- assertThat(rule1.getEducationPrinciples()).containsOnly("concept1","concept4");
- }
-
- @Test
- public void add_new_tag() {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One")
- .setTags("tag1");
- repo.done();
- });
-
- RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getSystemTags()).containsOnly("tag1");
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One")
- .setTags("tag1", "tag2");
- repo.done();
- });
-
- rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2");
- }
-
- @Test
- public void add_new_security_standards() {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One")
- .addOwaspTop10(Y2021, OwaspTop10.A1)
- .addCwe(123);
- repo.done();
- });
-
- RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getSecurityStandards()).containsOnly("cwe:123", "owaspTop10-2021:a1");
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One")
- .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
- .addCwe(1, 123, 863);
- repo.done();
- });
-
- rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getSecurityStandards()).containsOnly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
- }
-
- @Test
- public void update_only_rule_name() {
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name1")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name2")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- // rule1 has been updated
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
- assertThat(rule1.getName()).isEqualTo("Name2");
- assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description");
-
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getTotal()).isOne();
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
- }
-
- @Test
- public void update_template_rule_key_should_also_update_custom_rules() {
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("squid", "java");
- repo.createRule("rule")
- .setName("Name1")
- .setHtmlDescription("Description")
- .setTemplate(true);
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("squid", "rule"));
-
- // insert custom rule
- db.rules().insert(new RuleDto()
- .setRuleKey(RuleKey.of("squid", "custom"))
- .setLanguage("java")
- .setScope(Scope.ALL)
- .setTemplateUuid(rule1.getUuid())
- .setName("custom1"));
- db.commit();
-
- // re-key rule
- execute(context -> {
- NewRepository repo = context.createRepository("java", "java");
- repo.createRule("rule")
- .setName("Name1")
- .setHtmlDescription("Description")
- .addDeprecatedRuleKey("squid", "rule")
- .setTemplate(true);
- repo.done();
- });
-
- // template rule and custom rule have been updated
- rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "rule"));
- RuleDto custom = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "custom"));
- }
-
- @Test
- public void update_if_rule_key_renamed_and_deprecated_key_declared() {
- String ruleKey1 = "rule1";
- String ruleKey2 = "rule2";
- String repository = "fake";
-
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository, "java");
- repo.createRule(ruleKey1)
- .setName("Name1")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey1));
- SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
- assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
- assertThat(searchRule1.getTotal()).isOne();
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository, "java");
- repo.createRule(ruleKey2)
- .setName("Name2")
- .setHtmlDescription("Description")
- .addDeprecatedRuleKey(repository, ruleKey1);
- repo.done();
- });
-
- // rule2 is actually rule1
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey2));
- assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
- assertThat(rule2.getName()).isEqualTo("Name2");
- assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
-
- SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
- assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
- assertThat(searchRule2.getTotal()).isOne();
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
- }
-
- @Test
- public void update_if_repository_changed_and_deprecated_key_declared() {
- String ruleKey = "rule";
- String repository1 = "fake1";
- String repository2 = "fake2";
-
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository1, "java");
- repo.createRule(ruleKey)
- .setName("Name1")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey));
- SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
- assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
- assertThat(searchRule1.getTotal()).isOne();
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository2, "java");
- repo.createRule(ruleKey)
- .setName("Name2")
- .setHtmlDescription("Description")
- .addDeprecatedRuleKey(repository1, ruleKey);
- repo.done();
- });
-
- // rule2 is actually rule1
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey));
- assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
- assertThat(rule2.getName()).isEqualTo("Name2");
- assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
-
- SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
- assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
- assertThat(searchRule2.getTotal()).isOne();
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
- }
-
- @Test
- @UseDataProvider("allRenamingCases")
- public void update_if_only_renamed_and_deprecated_key_declared(String ruleKey1, String repo1, String ruleKey2, String repo2) {
- String name = "Name1";
- String description = "Description";
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repo1, "java");
- repo.createRule(ruleKey1)
- .setName(name)
- .setHtmlDescription(description);
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo1, ruleKey1));
- assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
- .containsOnly(rule1.getUuid());
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repo2, "java");
- repo.createRule(ruleKey2)
- .setName(name)
- .setHtmlDescription(description)
- .addDeprecatedRuleKey(repo1, ruleKey1);
- repo.done();
- });
-
- // rule2 is actually rule1
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo2, ruleKey2));
- assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
- assertThat(rule2.getName()).isEqualTo(rule1.getName());
- assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
-
- assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
- .containsOnly(rule2.getUuid());
- }
-
- @DataProvider
- public static Object[][] allRenamingCases() {
- return new Object[][]{
- {"repo1", "rule1", "repo1", "rule2"},
- {"repo1", "rule1", "repo2", "rule1"},
- {"repo1", "rule1", "repo2", "rule2"},
- };
- }
-
- @Test
- public void update_if_repository_and_key_changed_and_deprecated_key_declared_among_others() {
- String ruleKey1 = "rule1";
- String ruleKey2 = "rule2";
- String repository1 = "fake1";
- String repository2 = "fake2";
-
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository1, "java");
- repo.createRule(ruleKey1)
- .setName("Name1")
- .setHtmlDescription("Description");
- repo.done();
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey1));
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getUuids())
- .containsOnly(rule1.getUuid());
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository(repository2, "java");
- repo.createRule(ruleKey2)
- .setName("Name2")
- .setHtmlDescription("Description")
- .addDeprecatedRuleKey("foo", "bar")
- .addDeprecatedRuleKey(repository1, ruleKey1)
- .addDeprecatedRuleKey("some", "noise");
- repo.done();
- });
-
- // rule2 is actually rule1
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey2));
- assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
-
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getUuids())
- .containsOnly(rule1.getUuid());
- }
-
- @Test
- public void update_only_rule_description() {
- system.setNow(DATE1.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name")
- .setHtmlDescription("Desc1");
- repo.done();
- });
-
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name")
- .setHtmlDescription("Desc2");
- repo.done();
- });
-
- // rule1 has been updated
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
- assertThat(rule1.getName()).isEqualTo("Name");
- assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
-
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc2"), new SearchOptions()).getTotal()).isOne();
- assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc1"), new SearchOptions()).getTotal()).isZero();
- }
-
- @Test
- public void update_several_rule_descriptions() {
- system.setNow(DATE1.getTime());
-
- RuleDescriptionSection section1context1 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx1 content", "ctx_1");
- RuleDescriptionSection section1context2 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 content", "ctx_2");
- RuleDescriptionSection section2context1 = createRuleDescriptionSection(RESOURCES_SECTION_KEY, "section2 content", "ctx_1");
- RuleDescriptionSection section2context2 = createRuleDescriptionSection(RESOURCES_SECTION_KEY,"section2 ctx2 content", "ctx_2");
- RuleDescriptionSection section3noContext = createRuleDescriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY, "section3 content", null);
- RuleDescriptionSection section4noContext = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, "section4 content", null);
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name")
- .addDescriptionSection(section1context1)
- .addDescriptionSection(section1context2)
- .addDescriptionSection(section2context1)
- .addDescriptionSection(section2context2)
- .addDescriptionSection(section3noContext)
- .addDescriptionSection(section4noContext)
- .setHtmlDescription("Desc1");
- repo.done();
- });
-
- RuleDescriptionSection section1context2updated = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 updated content", "ctx_2");
- RuleDescriptionSection section2updatedWithoutContext = createRuleDescriptionSection(RESOURCES_SECTION_KEY, section2context1.getHtmlContent(), null);
- RuleDescriptionSection section4updatedWithContext1 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_1");
- RuleDescriptionSection section4updatedWithContext2 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_2");
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- repo.createRule("rule")
- .setName("Name")
- .addDescriptionSection(section1context1)
- .addDescriptionSection(section1context2updated)
- .addDescriptionSection(section2updatedWithoutContext)
- .addDescriptionSection(section3noContext)
- .addDescriptionSection(section4updatedWithContext1)
- .addDescriptionSection(section4updatedWithContext2)
- .setHtmlDescription("Desc2");
- repo.done();
-
- });
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
- assertThat(rule1.getName()).isEqualTo("Name");
- assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
-
- Set<RuleDescriptionSection> expectedSections = Set.of(section1context1, section1context2updated,
- section2updatedWithoutContext, section3noContext, section4updatedWithContext1, section4updatedWithContext2);
- assertThat(rule1.getRuleDescriptionSectionDtos()).hasSize(expectedSections.size() + 1);
- expectedSections.forEach(apiSection -> assertSectionExists(apiSection, rule1.getRuleDescriptionSectionDtos()));
- }
-
- private static RuleDescriptionSection createRuleDescriptionSection(String sectionKey, String description, @Nullable String contextKey) {
- Context context = Optional.ofNullable(contextKey).map(key -> new Context(contextKey, contextKey + randomAlphanumeric(10))).orElse(null);
- return RuleDescriptionSection.builder().sectionKey(sectionKey)
- .htmlContent(description)
- .context(context)
- .build();
- }
-
- private static void assertSectionExists(RuleDescriptionSection apiSection, Set<RuleDescriptionSectionDto> sectionDtos) {
- sectionDtos.stream()
- .filter(sectionDto -> sectionDto.getKey().equals(apiSection.getKey()) && sectionDto.getContent().equals(apiSection.getHtmlContent()))
- .filter(sectionDto -> isSameContext(apiSection.getContext(), sectionDto.getContext()))
- .findAny()
- .orElseThrow(() -> new AssertionError(format("Impossible to find a section dto matching the API section %s", apiSection.getKey())));
- }
-
- private static boolean isSameContext(Optional<Context> apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
- if (apiContext.isEmpty() && contextDto == null) {
- return true;
- }
- return apiContext.filter(context -> isSameContext(context, contextDto)).isPresent();
- }
-
- private static boolean isSameContext(Context apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
- if (contextDto == null) {
- return false;
- }
- return Objects.equals(apiContext.getKey(), contextDto.getKey()) && Objects.equals(apiContext.getDisplayName(), contextDto.getDisplayName());
- }
-
- @Test
- public void rule_previously_created_as_adhoc_becomes_none_adhoc() {
- RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake").setIsExternal(true).setIsAdHoc(true));
- system.setNow(DATE2.getTime());
- execute(context -> {
- NewRepository repo = context.createExternalRepository("fake", rule.getLanguage());
- repo.createRule(rule.getRuleKey())
- .setName(rule.getName())
- .setHtmlDescription(rule.getDefaultRuleDescriptionSection().getContent());
- repo.done();
- });
-
- RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
- assertThat(reloaded.isAdHoc()).isFalse();
- }
-
- @Test
- public void remove_no_more_defined_external_rule() {
- RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
- .setStatus(READY)
- .setIsExternal(true)
- .setIsAdHoc(false));
-
- execute();
-
- RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
- assertThat(reloaded.getStatus()).isEqualTo(REMOVED);
- }
-
- @Test
- public void do_not_remove_no_more_defined_ad_hoc_rule() {
- RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
- .setStatus(READY)
- .setIsExternal(true)
- .setIsAdHoc(true));
-
- execute();
-
- RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
- assertThat(reloaded.getStatus()).isEqualTo(READY);
- }
-
- @Test
- public void disable_then_enable_rule() {
- // Install rule
- system.setNow(DATE1.getTime());
- execute(new FakeRepositoryV1());
-
- // Uninstall rule
- system.setNow(DATE2.getTime());
- execute();
-
- RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getStatus()).isEqualTo(REMOVED);
- assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isZero();
-
- // Re-install rule
- system.setNow(DATE3.getTime());
- execute(new FakeRepositoryV1());
-
- rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
- assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isOne();
- }
-
- @Test
- public void do_not_update_rules_when_no_changes() {
- execute(new FakeRepositoryV1());
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
-
- system.setNow(DATE2.getTime());
- execute(new FakeRepositoryV1());
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
- assertThat(rule1.getUpdatedAt()).isEqualTo(DATE1.getTime());
- }
-
- @Test
- public void do_not_update_already_removed_rules() {
- execute(new FakeRepositoryV1());
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
-
- RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
- RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
- assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
-
- assertThat(rule2.getStatus()).isEqualTo(READY);
-
- system.setNow(DATE2.getTime());
- execute(new FakeRepositoryV2());
-
- // On MySQL, need to update a rule otherwise rule2 will be seen as READY, but why ???
- dbClient.ruleDao().update(db.getSession(), rule1);
- db.getSession().commit();
-
- // rule2 is removed
- rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
- assertThat(rule2.getStatus()).isEqualTo(REMOVED);
-
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
-
- system.setNow(DATE3.getTime());
- execute(new FakeRepositoryV2());
- db.getSession().commit();
-
- // -> rule2 is still removed, but not update at DATE3
- rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
- assertThat(rule2.getStatus()).isEqualTo(REMOVED);
- assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
-
- assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
- }
-
- @Test
- public void mass_insert() {
- execute(new BigRepository());
- assertThat(db.countRowsOfTable("rules")).isEqualTo(BigRepository.SIZE);
- assertThat(db.countRowsOfTable("rules_parameters")).isEqualTo(BigRepository.SIZE * 20);
- assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).hasSize(BigRepository.SIZE);
- }
-
- @Test
- public void manage_repository_extensions() {
- execute(new FindbugsRepository(), new FbContribRepository());
- List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
- assertThat(rules).hasSize(2);
- for (RuleDto rule : rules) {
- assertThat(rule.getRepositoryKey()).isEqualTo("findbugs");
- }
- }
-
- @Test
- public void remove_system_tags_when_plugin_does_not_provide_any() {
- // Rule already exists in DB, with some system tags
- db.rules().insert(new RuleDto()
- .setRuleKey("rule1")
- .setRepositoryKey("findbugs")
- .setName("Rule One")
- .setType(RuleType.CODE_SMELL)
- .setScope(Scope.ALL)
- .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule one description"))
- .setDescriptionFormat(RuleDto.Format.HTML)
- .setSystemTags(newHashSet("tag1", "tag2")));
- db.getSession().commit();
-
- // Synchronize rule without tag
- execute(new FindbugsRepository());
-
- List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
- assertThat(rules).hasSize(1).extracting(RuleDto::getKey, RuleDto::getSystemTags)
- .containsOnly(tuple(RuleKey.of("findbugs", "rule1"), emptySet()));
- }
-
- @Test
- public void rules_that_deprecate_previous_rule_must_be_recorded() {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "rule1");
- repo.done();
- });
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey")
- .addDeprecatedRuleKey("fake", "rule1")
- .addDeprecatedRuleKey("fake", "rule2");
- repo.done();
- });
-
- List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
- Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
- assertThat(rules).hasSize(1);
- assertThat(deprecatedRuleKeys).hasSize(2);
- }
-
- @Test
- public void rules_that_remove_deprecated_key_must_remove_records() {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "rule1");
- repo.done();
- });
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey")
- .addDeprecatedRuleKey("fake", "rule1")
- .addDeprecatedRuleKey("fake", "rule2");
- repo.done();
- });
-
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
- Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
- assertThat(deprecatedRuleKeys).hasSize(2);
-
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey");
- repo.done();
- });
-
- assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
- deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
- assertThat(deprecatedRuleKeys).isEmpty();
- }
-
- @Test
- public void declaring_two_rules_with_same_deprecated_RuleKey_should_throw_ISE() {
- assertThatThrownBy(() -> {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey1")
- .addDeprecatedRuleKey("fake", "old");
- createRule(repo, "newKey2")
- .addDeprecatedRuleKey("fake", "old");
- repo.done();
- });
- })
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("The following deprecated rule keys are declared at least twice [fake:old]");
- }
-
- @Test
- public void declaring_a_rule_with_a_deprecated_RuleKey_still_used_should_throw_ISE() {
- assertThatThrownBy(() -> {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey1");
- createRule(repo, "newKey2")
- .addDeprecatedRuleKey("fake", "newKey1");
- repo.done();
- });
- })
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("The following rule keys are declared both as deprecated and used key [fake:newKey1]");
- }
-
- @Test
- public void updating_the_deprecated_to_a_new_ruleKey_should_throw_an_ISE() {
- // On this new rule add a deprecated key
- execute(context -> createRule(context, "javascript", "javascript", "s103",
- r -> r.addDeprecatedRuleKey("javascript", "linelength")));
-
- assertThatThrownBy(() -> {
- // This rule should have been moved to another repository
- execute(context -> createRule(context, "javascript", "sonarjs", "s103",
- r -> r.addDeprecatedRuleKey("javascript", "linelength")));
- })
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("An incorrect state of deprecated rule keys has been detected.\n " +
- "The deprecated rule key [javascript:linelength] was previously deprecated by [javascript:s103]. [javascript:s103] should be a deprecated key of [sonarjs:s103],");
- }
-
- @Test
- public void deprecate_rule_that_deprecated_another_rule() {
- execute(context -> createRule(context, "javascript", "javascript", "s103"));
- execute(context -> createRule(context, "javascript", "javascript", "s104",
- r -> r.addDeprecatedRuleKey("javascript", "s103")));
-
- // This rule should have been moved to another repository
- execute(context -> createRule(context, "javascript", "sonarjs", "s105",
- r -> r.addDeprecatedRuleKey("javascript", "s103")
- .addDeprecatedRuleKey("javascript", "s104")));
- }
-
- @Test
- public void declaring_a_rule_with_an_existing_RuleKey_still_used_should_throw_IAE() {
- assertThatThrownBy(() -> {
- execute(context -> {
- NewRepository repo = context.createRepository("fake", "java");
- createRule(repo, "newKey1");
- createRule(repo, "newKey1");
- repo.done();
- });
- })
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("The rule 'newKey1' of repository 'fake' is declared several times");
- }
-
- @Test
- public void removed_rule_should_appear_in_changelog() {
- //GIVEN
- QProfileDto qProfileDto = db.qualityProfiles().insert();
- RuleDto ruleDto = db.rules().insert(RULE_KEY1);
- db.qualityProfiles().activateRule(qProfileDto, ruleDto);
- ActiveRuleChange arChange = new ActiveRuleChange(DEACTIVATED, ActiveRuleDto.createFor(qProfileDto, ruleDto), ruleDto);
- when(qProfileRules.deleteRule(any(DbSession.class), eq(ruleDto))).thenReturn(List.of(arChange));
- //WHEN
- execute(context -> context.createRepository("fake", "java").done());
- //THEN
- List<QProfileChangeDto> qProfileChangeDtos = dbClient.qProfileChangeDao().selectByQuery(db.getSession(), new QProfileChangeQuery(qProfileDto.getKee()));
- assertThat(qProfileChangeDtos).extracting(QProfileChangeDto::getRulesProfileUuid, QProfileChangeDto::getChangeType)
- .contains(tuple(qProfileDto.getRulesProfileUuid(), "DEACTIVATED"));
- }
-
- @Test
- public void removed_rule_should_be_deleted_when_renamed_repository() {
- //GIVEN
- RuleDto removedRuleDto = db.rules().insert(RuleKey.of("old_repo", "removed_rule"));
- RuleDto renamedRuleDto = db.rules().insert(RuleKey.of("old_repo", "renamed_rule"));
- //WHEN
- execute(context -> createRule(context, "java", "new_repo", renamedRuleDto.getRuleKey(),
- rule -> rule.addDeprecatedRuleKey(renamedRuleDto.getRepositoryKey(), renamedRuleDto.getRuleKey())));
- //THEN
- verify(qProfileRules).deleteRule(any(DbSession.class), eq(removedRuleDto));
- }
-
- private void execute(RulesDefinition... defs) {
- ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
- when(pluginRepository.getPluginKey(any(RulesDefinition.class))).thenReturn(FAKE_PLUGIN_KEY);
- RuleDefinitionsLoader loader = new RuleDefinitionsLoader(pluginRepository, defs);
- Languages languages = mock(Languages.class);
- when(languages.get(any())).thenReturn(mock(Language.class));
- reset(webServerRuleFinder);
-
- RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, metadataIndex,
- rulesKeyVerifier, startupRuleUpdater, newRuleCreator);
- task.start();
- // Execute a commit to refresh session state as the task is using its own session
- db.getSession().commit();
-
- verify(webServerRuleFinder).startCaching();
- }
-
- private NewRule createRule(NewRepository repo, String key) {
- return repo.createRule(key)
- .setName(key + " name")
- .setHtmlDescription("Description of " + key)
- .setSeverity(BLOCKER)
- .setInternalKey("config1")
- .setTags("tag1", "tag2", "tag3")
- .setType(RuleType.CODE_SMELL)
- .setStatus(RuleStatus.BETA);
- }
-
- @SafeVarargs
- private void createRule(RulesDefinition.Context context, String language, String repositoryKey, String ruleKey, Consumer<NewRule>... consumers) {
- NewRepository repo = context.createRepository(repositoryKey, language);
- NewRule newRule = repo.createRule(ruleKey)
- .setName(ruleKey)
- .setHtmlDescription("Description of One")
- .setSeverity(BLOCKER)
- .setType(RuleType.CODE_SMELL)
- .setStatus(RuleStatus.BETA);
-
- Arrays.stream(consumers).forEach(c -> c.accept(newRule));
- repo.done();
- }
-
- private void verifyIndicesMarkedAsInitialized() {
- verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_RULE, true);
- verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_ACTIVE_RULE, true);
- reset(metadataIndex);
- }
-
- private void verifyIndicesNotMarkedAsInitialized() {
- verifyNoInteractions(metadataIndex);
- }
-
- private RuleParamDto getParam(List<RuleParamDto> params, String key) {
- for (RuleParamDto param : params) {
- if (param.getName().equals(key)) {
- return param;
- }
- }
- return null;
- }
-
- static class FakeRepositoryV1 implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("fake", "java");
- NewRule rule1 = repo.createRule(RULE_KEY1.rule())
- .setName("One")
- .setHtmlDescription("Description of One")
- .setSeverity(BLOCKER)
- .setInternalKey("config1")
- .setTags("tag1", "tag2", "tag3")
- .setScope(RuleScope.ALL)
- .setType(RuleType.CODE_SMELL)
- .setStatus(RuleStatus.BETA)
- .setGapDescription("java.S115.effortToFix")
- .addEducationPrincipleKeys("concept1", "concept2", "concept3")
- .addDefaultImpact(SoftwareQuality.RELIABILITY, Severity.HIGH);
- rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("5d", "10h"));
-
- rule1.createParam("param1").setDescription("parameter one").setDefaultValue("default1");
- rule1.createParam("param2").setDescription("parameter two").setDefaultValue("default2");
-
- repo.createRule(HOTSPOT_RULE_KEY.rule())
- .setName("Hotspot")
- .setHtmlDescription("Minimal hotspot")
- .setType(RuleType.SECURITY_HOTSPOT)
- .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
- .addCwe(1, 123, 863);
-
- repo.createRule(RULE_KEY2.rule())
- .setName("Two")
- .setHtmlDescription("Minimal rule")
- .setCleanCodeAttribute(CleanCodeAttribute.EFFICIENT);
- repo.done();
- }
- }
-
- /**
- * FakeRepositoryV1 with some changes
- */
- static class FakeRepositoryV2 implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("fake", "java");
-
- // almost all the attributes of rule1 are changed
- NewRule rule1 = repo.createRule(RULE_KEY1.rule())
- .setName("One v2")
- .setHtmlDescription("Description of One v2")
- .setSeverity(INFO)
- .setInternalKey("config1 v2")
- // tag2 and tag3 removed, tag4 added
- .setTags("tag1", "tag4")
- .setType(RuleType.BUG)
- .setStatus(READY)
- .setGapDescription("java.S115.effortToFix.v2")
- .addEducationPrincipleKeys("concept1", "concept4");
- rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("6d", "2h"));
- rule1.createParam("param1").setDescription("parameter one v2").setDefaultValue("default1 v2");
- rule1.createParam("param2").setDescription("parameter two v2").setDefaultValue("default2 v2");
-
- // rule2 is dropped, rule3 is new
- repo.createRule(RULE_KEY3.rule())
- .setName("Three")
- .setHtmlDescription("Rule Three");
-
- repo.done();
- }
- }
-
- static class FakeRepositoryV3 implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("fake", "java");
- // rule 3 is dropped
- repo.createRule(RULE_KEY3.rule())
- .setName("Three")
- .setMarkdownDescription("Rule Three V2");
-
- repo.done();
- }
- }
-
- static class ExternalRuleRepository implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createExternalRepository("eslint", "js");
- repo.createRule(RULE_KEY1.rule())
- .setName("One")
- .setHtmlDescription("Description of One")
- .setSeverity(BLOCKER)
- .setInternalKey("config1")
- .setTags("tag1", "tag2", "tag3")
- .setScope(RuleScope.ALL)
- .setType(RuleType.CODE_SMELL)
- .setStatus(RuleStatus.BETA)
- .addEducationPrincipleKeys("concept1", "concept2", "concept3");
-
- repo.createRule(EXTERNAL_HOTSPOT_RULE_KEY.rule())
- .setName("Hotspot")
- .setHtmlDescription("Minimal hotspot")
- .setType(RuleType.SECURITY_HOTSPOT)
- .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
- .addCwe(1, 123, 863);
-
- repo.done();
- }
- }
-
- static class BigRepository implements RulesDefinition {
- static final int SIZE = 500;
-
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("big", "java");
- for (int i = 0; i < SIZE; i++) {
- NewRule rule = repo.createRule("rule" + i)
- .setName("name of " + i)
- .setHtmlDescription("description of " + i);
- for (int j = 0; j < 20; j++) {
- rule.createParam("param" + j);
- }
-
- }
- repo.done();
- }
- }
-
- static class FindbugsRepository implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewRepository repo = context.createRepository("findbugs", "java");
- repo.createRule("rule1")
- .setName("Rule One")
- .setHtmlDescription("Description of Rule One");
- repo.done();
- }
- }
-
- static class FbContribRepository implements RulesDefinition {
- @Override
- public void define(Context context) {
- NewExtendedRepository repo = context.createRepository("findbugs", "java");
- repo.createRule("rule2")
- .setName("Rule Two")
- .setHtmlDescription("Description of Rule Two");
- repo.done();
- }
- }
-}
--- /dev/null
+/*
+ * 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
@Override
public BulkChangeResult bulkDeactivateAndCommit(DbSession dbSession, QProfileDto profile, RuleQuery ruleQuery) {
verifyNotBuiltIn(profile);
- BulkChangeResult bulkChangeResult = doBulk(dbSession, profile, ruleQuery, (context, ruleDto) ->
- ruleActivator.deactivate(dbSession, context, ruleDto.getUuid(), false));
+ BulkChangeResult bulkChangeResult = doBulk(dbSession, profile, ruleQuery, (context, ruleDto) -> ruleActivator.deactivate(dbSession, context, ruleDto.getUuid(), false));
qualityProfileChangeEventService.distributeRuleChangeEvent(List.of(profile), bulkChangeResult.getChanges(), profile.getLanguage());
import org.sonar.server.rule.LegacyHotspotRuleDescriptionSectionsGenerator;
import org.sonar.server.rule.LegacyIssueRuleDescriptionSectionsGenerator;
import org.sonar.server.rule.registration.NewRuleCreator;
+import org.sonar.server.rule.registration.QualityProfileChangesUpdater;
import org.sonar.server.rule.registration.RulesKeyVerifier;
import org.sonar.server.rule.registration.RulesRegistrant;
import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
LegacyHotspotRuleDescriptionSectionsGenerator.class,
LegacyIssueRuleDescriptionSectionsGenerator.class,
RulesRegistrant.class,
+ QualityProfileChangesUpdater.class,
NewRuleCreator.class,
RulesKeyVerifier.class,
StartupRuleUpdater.class,