aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorPierre Guillot <pierre.guillot@sonarsource.com>2024-12-10 15:29:09 +0100
committerSteve Marion <steve.marion@sonarsource.com>2024-12-18 11:13:21 +0100
commitd639a965bce7acafb004906cd07a8f0b5f7af993 (patch)
tree647cd646abddb12dfeeef7e637aa33b4658f1049 /server
parent451c1c2e4856ec3df87f86189fcdb25b31794027 (diff)
downloadsonarqube-d639a965bce7acafb004906cd07a8f0b5f7af993.tar.gz
sonarqube-d639a965bce7acafb004906cd07a8f0b5f7af993.zip
SONAR-22998 fetch active rules with a dedicated endpoint
Co-authored-by: Julien HENRY <julien.henry@sonarsource.com>
Diffstat (limited to 'server')
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/ActiveRuleDaoIT.java74
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java15
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java51
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java7
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java6
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml39
-rw-r--r--server/sonar-server-common/src/it/java/org/sonar/server/rule/ActiveRuleServiceIT.java206
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleRestReponse.java123
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleService.java177
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/rule/CachingRuleFinder.java1
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java1
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/ActiveRulesController.java47
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesController.java38
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandler.java27
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImpl.java47
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java20
-rw-r--r--server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesControllerTest.java107
-rw-r--r--server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImplTest.java71
19 files changed, 1034 insertions, 27 deletions
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/ActiveRuleDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/ActiveRuleDaoIT.java
index b229d7895b6..eeebb9d5b9e 100644
--- a/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/ActiveRuleDaoIT.java
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/qualityprofile/ActiveRuleDaoIT.java
@@ -62,6 +62,7 @@ import static org.sonar.api.rule.Severity.MAJOR;
import static org.sonar.db.qualityprofile.ActiveRuleDto.INHERITED;
import static org.sonar.db.qualityprofile.ActiveRuleDto.OVERRIDES;
import static org.sonar.db.qualityprofile.ActiveRuleDto.createFor;
+import static org.sonar.db.rule.RuleDto.deserializeSecurityStandardsString;
class ActiveRuleDaoIT {
@@ -113,6 +114,19 @@ class ActiveRuleDaoIT {
}
@Test
+ void selectAllParamsByProfileUuids() {
+ ActiveRuleDto activeRule = createFor(profile1, rule1).setSeverity(BLOCKER);
+ underTest.insert(dbSession, activeRule);
+ ActiveRuleParamDto activeRuleParam1 = ActiveRuleParamDto.createFor(rule1Param1);
+ underTest.insertParam(dbSession, activeRule, activeRuleParam1);
+ ActiveRuleParamDto activeRuleParam2 = ActiveRuleParamDto.createFor(rule1Param2);
+ underTest.insertParam(dbSession, activeRule, activeRuleParam2);
+ dbSession.commit();
+
+ assertThat(underTest.selectAllParamsByProfileUuids(dbSession, List.of(profile1.getKee()))).hasSize(2);
+ }
+
+ @Test
void selectByKey() {
ActiveRuleDto activeRule = createFor(profile1, rule1).setSeverity(BLOCKER);
underTest.insert(dbSession, activeRule);
@@ -184,6 +198,50 @@ class ActiveRuleDaoIT {
}
@Test
+ void selectByProfileUuids_ignores_removed_rules() {
+ ActiveRuleDto activeRule = createFor(profile1, removedRule).setSeverity(BLOCKER);
+ underTest.insert(dbSession, activeRule);
+
+ assertThat(underTest.selectByProfileUuids(dbSession, List.of(profile1.getKee()))).isEmpty();
+ }
+
+ @Test
+ void selectByProfileUuids_exclude_other_profiles() {
+ ActiveRuleDto activeRule = createFor(profile1, rule1).setSeverity(BLOCKER);
+ underTest.insert(dbSession, activeRule);
+ ActiveRuleDto activeRule2 = createFor(profile2, rule2).setSeverity(BLOCKER);
+ underTest.insert(dbSession, activeRule2);
+
+ assertThat(underTest.selectByProfileUuids(dbSession, List.of(profile1.getKee()))).hasSize(1);
+ }
+
+ @Test
+ void selectByProfileUuids_returns_all_fields() {
+ ActiveRuleDto activeRule = createFor(profile1, rule1).setSeverity(BLOCKER);
+ underTest.insert(dbSession, activeRule);
+
+ List<OrgActiveRuleDto> actualActiveRule = underTest.selectByProfileUuids(dbSession, List.of(profile1.getKee()));
+ // verify data from the "rules" table
+ assertThat(actualActiveRule)
+ .extracting(OrgActiveRuleDto::getRuleKey, org -> deserializeSecurityStandardsString(org.getSecurityStandards()), OrgActiveRuleDto::isExternal, OrgActiveRuleDto::getName,
+ OrgActiveRuleDto::getConfigKey, OrgActiveRuleDto::getTemplateUuid, OrgActiveRuleDto::getLanguage)
+ .containsExactly(
+ tuple(rule1.getKey(), rule1.getSecurityStandards(), rule1.isExternal(), rule1.getName(), rule1.getConfigKey(), rule1.getTemplateUuid(), rule1.getLanguage()));
+
+ // verify data from "active_rules" table
+ assertThat(actualActiveRule)
+ .extracting(OrgActiveRuleDto::getUuid, OrgActiveRuleDto::getProfileUuid, OrgActiveRuleDto::getRuleUuid, OrgActiveRuleDto::getSeverity, OrgActiveRuleDto::getInheritance,
+ OrgActiveRuleDto::isPrioritizedRule, OrgActiveRuleDto::getCreatedAt, OrgActiveRuleDto::getUpdatedAt)
+ .containsExactly(tuple(activeRule.getUuid(), activeRule.getProfileUuid(), activeRule.getRuleUuid(), activeRule.getSeverity(), activeRule.getInheritance(),
+ activeRule.isPrioritizedRule(), activeRule.getCreatedAt(), activeRule.getUpdatedAt()));
+
+ // verify data from "rules_profiles" and "org_qprofiles"
+ assertThat(actualActiveRule)
+ .extracting(o -> o.getKey().getRuleProfileUuid(), OrgActiveRuleDto::getOrgProfileUuid)
+ .containsExactly(tuple(activeRule.getKey().getRuleProfileUuid(), profile1.getKee()));
+ }
+
+ @Test
void selectByTypeAndProfileUuids() {
RuleDto rule1 = db.rules().insert(r -> r.setType(RuleType.VULNERABILITY.getDbConstant()));
ActiveRuleDto activeRule1 = createFor(profile1, rule1).setSeverity(BLOCKER);
@@ -192,8 +250,8 @@ class ActiveRuleDaoIT {
assertThat(underTest.selectByTypeAndProfileUuids(dbSession, singletonList(RuleType.VULNERABILITY.getDbConstant()),
singletonList(profile1.getKee())))
- .extracting(OrgActiveRuleDto::getOrgProfileUuid, OrgActiveRuleDto::getRuleUuid)
- .contains(tuple(profile1.getKee(), rule1.getUuid()));
+ .extracting(OrgActiveRuleDto::getOrgProfileUuid, OrgActiveRuleDto::getRuleUuid)
+ .contains(tuple(profile1.getKee(), rule1.getUuid()));
}
@Test
@@ -205,7 +263,7 @@ class ActiveRuleDaoIT {
assertThat(underTest.selectByTypeAndProfileUuids(dbSession, singletonList(RuleType.VULNERABILITY.getDbConstant()),
singletonList(profile1.getKee())))
- .isEmpty();
+ .isEmpty();
}
@Test
@@ -219,14 +277,14 @@ class ActiveRuleDaoIT {
underTest.selectByTypeAndProfileUuids(dbSession,
singletonList(RuleType.VULNERABILITY.getDbConstant()),
singletonList(profile1.getKee())))
- .extracting(OrgActiveRuleDto::getOrgProfileUuid, OrgActiveRuleDto::getRuleUuid)
- .contains(tuple(profile1.getKee(), rule1.getUuid()));
+ .extracting(OrgActiveRuleDto::getOrgProfileUuid, OrgActiveRuleDto::getRuleUuid)
+ .contains(tuple(profile1.getKee(), rule1.getUuid()));
assertThat(
underTest.selectByTypeAndProfileUuids(dbSession,
asList(RuleType.CODE_SMELL.getDbConstant(), RuleType.SECURITY_HOTSPOT.getDbConstant(), RuleType.BUG.getDbConstant()),
singletonList(profile1.getKee())))
- .isEmpty();
+ .isEmpty();
}
@Test
@@ -684,10 +742,10 @@ class ActiveRuleDaoIT {
ActiveRuleCountQuery.Builder builder = ActiveRuleCountQuery.builder();
assertThat(underTest.countActiveRulesByQuery(dbSession,
builder.setProfiles(asList(profile1, profile2)).setInheritance(OVERRIDES).build()))
- .containsOnly(entry(profile1.getKee(), 1L), entry(profile2.getKee(), 1L));
+ .containsOnly(entry(profile1.getKee(), 1L), entry(profile2.getKee(), 1L));
assertThat(underTest.countActiveRulesByQuery(dbSession,
builder.setProfiles(asList(profile1, profile2)).setInheritance(INHERITED).build()))
- .containsOnly(entry(profile2.getKee(), 1L));
+ .containsOnly(entry(profile2.getKee(), 1L));
}
@Test
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java
index e085a3cc315..76c9ae977ef 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDao.java
@@ -84,8 +84,15 @@ public class ActiveRuleDao implements Dao {
/**
* Active rule on removed rule are NOT returned
*/
- public List<OrgActiveRuleDto> selectByProfileUuid(DbSession dbSession, String uuid) {
- return mapper(dbSession).selectByProfileUuid(uuid);
+ public List<OrgActiveRuleDto> selectByProfileUuid(DbSession dbSession, String profileUuid) {
+ return selectByProfileUuids(dbSession, List.of(profileUuid));
+ }
+
+ /**
+ * Active rule on removed rule are NOT returned
+ */
+ public List<OrgActiveRuleDto> selectByProfileUuids(DbSession dbSession, Collection<String> profileUuids) {
+ return mapper(dbSession).selectByProfileUuids(profileUuids);
}
public List<OrgActiveRuleDto> selectByTypeAndProfileUuids(DbSession dbSession, List<Integer> types, List<String> uuids) {
@@ -162,6 +169,10 @@ public class ActiveRuleDao implements Dao {
return executeLargeInputs(activeRuleUuids, mapper(dbSession)::selectParamsByActiveRuleUuids);
}
+ public List<ActiveRuleParamDto> selectAllParamsByProfileUuids(final DbSession dbSession, Collection<String> profileUuids) {
+ return mapper(dbSession).selectAllParamsByProfileUuids(profileUuids);
+ }
+
public ActiveRuleParamDto insertParam(DbSession dbSession, ActiveRuleDto activeRule, ActiveRuleParamDto activeRuleParam) {
checkArgument(activeRule.getUuid() != null, ACTIVE_RULE_IS_NOT_PERSISTED);
checkArgument(activeRuleParam.getUuid() == null, ACTIVE_RULE_PARAM_IS_ALREADY_PERSISTED);
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java
index 906cdb4a2c7..483ed6e34a9 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java
@@ -58,10 +58,14 @@ public class ActiveRuleDto {
// These fields do not exist in db, it's only retrieve by joins
private String repository;
private String ruleField;
- private String ruleProfileUuid;
private String securityStandards;
private boolean isExternal;
+ private String name;
+ private String configKey;
+ private String templateUuid;
+ private String language;
+
public ActiveRuleDto() {
// nothing to do here
}
@@ -69,12 +73,12 @@ public class ActiveRuleDto {
public ActiveRuleDto setKey(ActiveRuleKey key) {
this.repository = key.getRuleKey().repository();
this.ruleField = key.getRuleKey().rule();
- this.ruleProfileUuid = key.getRuleProfileUuid();
+ this.profileUuid = key.getRuleProfileUuid();
return this;
}
public ActiveRuleKey getKey() {
- return new ActiveRuleKey(ruleProfileUuid, RuleKey.of(repository, ruleField));
+ return new ActiveRuleKey(profileUuid, RuleKey.of(repository, ruleField));
}
public RuleKey getRuleKey() {
@@ -207,6 +211,43 @@ public class ActiveRuleDto {
return this;
}
+ public String getName() {
+ return name;
+ }
+
+ public ActiveRuleDto setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getConfigKey() {
+ return configKey;
+ }
+
+ public ActiveRuleDto setConfigKey(String configKey) {
+ this.configKey = configKey;
+ return this;
+ }
+
+ @CheckForNull
+ public String getTemplateUuid() {
+ return templateUuid;
+ }
+
+ public ActiveRuleDto setTemplateUuid(@Nullable String templateUuid) {
+ this.templateUuid = templateUuid;
+ return this;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public ActiveRuleDto setLanguage(String language) {
+ this.language = language;
+ return this;
+ }
+
public static ActiveRuleDto createFor(QProfileDto profile, RuleDto ruleDto) {
requireNonNull(profile.getRulesProfileUuid(), "Profile is not persisted");
requireNonNull(ruleDto.getUuid(), "Rule is not persisted");
@@ -214,6 +255,10 @@ public class ActiveRuleDto {
dto.setProfileUuid(profile.getRulesProfileUuid());
dto.setRuleUuid(ruleDto.getUuid());
dto.setKey(ActiveRuleKey.of(profile, ruleDto.getKey()));
+ dto.setName(ruleDto.getName());
+ dto.setConfigKey(ruleDto.getConfigKey());
+ dto.setTemplateUuid(ruleDto.getTemplateUuid());
+ dto.setLanguage(ruleDto.getLanguage());
dto.setImpacts(ruleDto.getDefaultImpactsMap());
return dto;
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java
index d2b2dddfdbe..af2d2dcaa98 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleMapper.java
@@ -52,7 +52,7 @@ public interface ActiveRuleMapper {
List<OrgActiveRuleDto> selectByRuleUuids(@Param("ruleUuids") List<String> partitionOfRuleUuids);
- List<OrgActiveRuleDto> selectByProfileUuid(String uuid);
+ List<OrgActiveRuleDto> selectByProfileUuids(@Param("profileUuids") Collection<String> profileUuids);
List<OrgActiveRuleDto> selectByTypeAndProfileUuids(@Param("types") List<Integer> types, @Param("profileUuids") List<String> uuids);
@@ -90,4 +90,6 @@ public interface ActiveRuleMapper {
void scrollByRuleProfileUuidForIndexing(@Param("ruleProfileUuid") String ruleProfileUuid, ResultHandler<IndexedActiveRuleDto> handler);
int countMissingRules(@Param("rulesProfileUuid") String rulesProfileUuid, @Param("compareToRulesProfileUuid") String compareToRulesProfileUuid);
+
+ List<ActiveRuleParamDto> selectAllParamsByProfileUuids(@Param("profileUuids") Collection<String> profileUuids);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java
index f9d0982e4b4..34d9bd3ea71 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java
@@ -168,7 +168,11 @@ public class QualityProfileDao implements Dao {
}
public List<QProfileDto> selectAssociatedToProjectAndLanguages(DbSession dbSession, ProjectDto project, Collection<String> languages) {
- return executeLargeInputs(languages, partition -> mapper(dbSession).selectAssociatedToProjectUuidAndLanguages(project.getUuid(), partition));
+ return selectAssociatedToProjectAndLanguages(dbSession, project.getUuid(), languages);
+ }
+
+ public List<QProfileDto> selectAssociatedToProjectAndLanguages(DbSession dbSession, String projectUuid, Collection<String> languages) {
+ return executeLargeInputs(languages, partition -> mapper(dbSession).selectAssociatedToProjectUuidAndLanguages(projectUuid, partition));
}
public List<QProfileDto> selectQProfilesByProjectUuid(DbSession dbSession, String projectUuid) {
@@ -251,6 +255,7 @@ public class QualityProfileDao implements Dao {
public List<ProjectQProfileLanguageAssociationDto> selectAllProjectAssociations(DbSession dbSession) {
return mapper(dbSession).selectAllProjectAssociations();
}
+
public Collection<String> selectUuidsOfCustomRulesProfiles(DbSession dbSession, String language, String name) {
return mapper(dbSession).selectUuidsOfCustomRuleProfiles(language, name);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java
index 6d1f544e076..722f131695b 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java
@@ -72,7 +72,7 @@ public class RuleDto {
private RuleDto.Format descriptionFormat = null;
private RuleStatus status = null;
- private Set<ImpactDto> defaultImpacts = new HashSet<>();
+ private final Set<ImpactDto> defaultImpacts = new HashSet<>();
private String name = null;
private String configKey = null;
@@ -375,7 +375,6 @@ public class RuleDto {
return this;
}
- @CheckForNull
public String getLanguage() {
return language;
}
@@ -650,13 +649,12 @@ public class RuleDto {
@Override
public boolean equals(Object obj) {
- if (!(obj instanceof RuleDto)) {
+ if (!(obj instanceof RuleDto other)) {
return false;
}
if (this == obj) {
return true;
}
- RuleDto other = (RuleDto) obj;
return Objects.equals(this.uuid, other.uuid);
}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml
index 10442858528..c6facb12ff6 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/ActiveRuleMapper.xml
@@ -26,14 +26,19 @@
a.inheritance as "inheritance",
a.impacts as "impactsString",
a.prioritized_rule as "prioritizedRule",
+ a.created_at as "createdAt",
+ a.updated_at as "updatedAt",
+
r.plugin_rule_key as "rulefield",
r.plugin_name as "repository",
r.security_standards as "securityStandards",
- rp.uuid as "ruleProfileUuid",
- a.created_at as "createdAt",
- a.updated_at as "updatedAt",
- oqp.uuid as "orgProfileUuid",
- r.is_external as "isExternal"
+ r.is_external as "isExternal",
+ r.name as "name",
+ r.plugin_config_key as "configKey",
+ r.template_uuid as "templateUuid",
+ r.language as "language",
+
+ oqp.uuid as "orgProfileUuid"
</sql>
<sql id="activeRuleKeyJoin">
@@ -113,14 +118,18 @@
and r.plugin_name = #{repository, jdbcType=VARCHAR}
</select>
- <select id="selectByProfileUuid" parameterType="string" resultType="org.sonar.db.qualityprofile.OrgActiveRuleDto">
+ <select id="selectByProfileUuids" parameterType="string" resultType="org.sonar.db.qualityprofile.OrgActiveRuleDto">
select
<include refid="orgActiveRuleColumns"/>
from active_rules a
inner join rules_profiles rp on rp.uuid = a.profile_uuid
inner join org_qprofiles oqp on oqp.rules_profile_uuid = rp.uuid
inner join rules r on r.uuid = a.rule_uuid and r.status != 'REMOVED'
- where oqp.uuid = #{id, jdbcType=VARCHAR}
+ where
+ oqp.uuid in
+ <foreach collection="profileUuids" item="profileUuid" separator="," open="(" close=")">
+ #{profileUuid, jdbcType=VARCHAR}
+ </foreach>
</select>
<select id="selectByTypeAndProfileUuids" parameterType="Map" resultType="org.sonar.db.qualityprofile.OrgActiveRuleDto">
@@ -306,6 +315,22 @@
</where>
</select>
+ <select id="selectAllParamsByProfileUuids" parameterType="map" resultType="ActiveRuleParam">
+ select
+ <include refid="activeRuleParamColumns"/>
+ from active_rule_parameters p
+ inner join active_rules ar on ar.uuid = p.active_rule_uuid
+ inner join rules_profiles rp on rp.uuid = ar.profile_uuid
+ inner join org_qprofiles oqp on oqp.rules_profile_uuid = rp.uuid
+ inner join rules r on r.uuid = ar.rule_uuid
+ <where>
+ <foreach collection="profileUuids" item="profileUuid" open="(" separator=" or " close=")">
+ oqp.uuid = #{profileUuid, jdbcType=VARCHAR}
+ </foreach>
+ and r.status != 'REMOVED'
+ </where>
+ </select>
+
<select id="countActiveRulesByQuery" resultType="KeyLongValue" parameterType="map">
select oqp.uuid as "key", count(ar.uuid) as "value"
from active_rules ar
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/rule/ActiveRuleServiceIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/rule/ActiveRuleServiceIT.java
new file mode 100644
index 00000000000..2d36a185236
--- /dev/null
+++ b/server/sonar-server-common/src/it/java/org/sonar/server/rule/ActiveRuleServiceIT.java
@@ -0,0 +1,206 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.rule;
+
+import java.util.List;
+import org.assertj.core.groups.Tuple;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ProjectData;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleKey;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.rule.ActiveRuleRestReponse.ActiveRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+
+class ActiveRuleServiceIT {
+
+ public static final Language XOO_LANGUAGE = mock(Language.class);
+ public static final Language XOO2_LANGUAGE = mock(Language.class);
+
+ public static final String XOO_LANGUAGE_KEY = "xoo";
+ public static final String XOO2_LANGUAGE_KEY = "xoo2";
+
+ static {
+ when(XOO_LANGUAGE.getKey()).thenReturn(XOO_LANGUAGE_KEY);
+ when(XOO2_LANGUAGE.getKey()).thenReturn(XOO2_LANGUAGE_KEY);
+ }
+
+ @RegisterExtension
+ DbTester db = DbTester.create(System2.INSTANCE);
+
+ private final ActiveRuleService activeRuleService = new ActiveRuleService(db.getDbClient(), new Languages(XOO_LANGUAGE, XOO2_LANGUAGE));
+
+ @Test
+ void buildDefaultActiveRules() {
+ QProfileDto qProfileDto1 = db.qualityProfiles().insert(qp -> qp.setLanguage(XOO_LANGUAGE_KEY));
+ db.qualityProfiles().setAsDefault(qProfileDto1);
+ RuleDto rule1 = db.rules().insert(r -> r.setLanguage(XOO_LANGUAGE_KEY), r -> r.setName("rule1"));
+ ActiveRuleDto activatedRule1 = db.qualityProfiles().activateRule(qProfileDto1, rule1);
+ RuleDto rule2 = db.rules().insert(r -> r.setLanguage(XOO_LANGUAGE_KEY), r -> r.setName("rule2"));
+ ActiveRuleDto activatedRule2 = db.qualityProfiles().activateRule(qProfileDto1, rule2);
+
+ QProfileDto qProfileDto2 = db.qualityProfiles().insert(qp -> qp.setLanguage(XOO2_LANGUAGE_KEY));
+ db.qualityProfiles().setAsDefault(qProfileDto2);
+ RuleDto rule3 = db.rules().insert(r -> r.setLanguage(XOO2_LANGUAGE_KEY), r -> r.setName("rule3"));
+ ActiveRuleDto activatedRule3 = db.qualityProfiles().activateRule(qProfileDto2, rule3);
+
+ List<ActiveRule> activeRules = activeRuleService.buildDefaultActiveRules();
+ assertThat(activeRules).isNotNull().hasSize(3)
+ .extracting(ActiveRule::ruleKey, ActiveRule::name, ActiveRule::severity,
+ ActiveRule::createdAt, ActiveRule::updatedAt, ActiveRule::internalKey,
+ ActiveRule::language, ActiveRule::templateRuleKey, ActiveRule::qProfileKey,
+ ActiveRule::deprecatedKeys, ActiveRule::params)
+ .containsExactlyInAnyOrder(
+ toTuple(activatedRule1, XOO_LANGUAGE_KEY, qProfileDto1, List.of()),
+ toTuple(activatedRule2, XOO_LANGUAGE_KEY, qProfileDto1, List.of()),
+ toTuple(activatedRule3, XOO2_LANGUAGE_KEY, qProfileDto2, List.of()));
+ }
+
+ @Test
+ void buildActiveRule_with_default_profile() {
+ QProfileDto qProfileDto1 = db.qualityProfiles().insert(qp -> qp.setLanguage(XOO_LANGUAGE_KEY));
+ db.qualityProfiles().setAsDefault(qProfileDto1);
+ RuleDto rule1 = db.rules().insert(r -> r.setLanguage(XOO_LANGUAGE_KEY), r -> r.setName("rule1"));
+ ActiveRuleDto activatedRule1 = db.qualityProfiles().activateRule(qProfileDto1, rule1);
+ RuleDto rule2 = db.rules().insert(r -> r.setLanguage(XOO_LANGUAGE_KEY), r -> r.setName("rule2"));
+ ActiveRuleDto activatedRule2 = db.qualityProfiles().activateRule(qProfileDto1, rule2);
+
+ QProfileDto qProfileDto2 = db.qualityProfiles().insert(qp -> qp.setLanguage(XOO2_LANGUAGE_KEY));
+ db.qualityProfiles().setAsDefault(qProfileDto2);
+ RuleDto rule3 = db.rules().insert(r -> r.setLanguage(XOO2_LANGUAGE_KEY), r -> r.setName("rule3"));
+ ActiveRuleDto activatedRule3 = db.qualityProfiles().activateRule(qProfileDto2, rule3);
+
+ var removedLanguageKey = "removed";
+ QProfileDto qProfileDtoRemovedLanguage = db.qualityProfiles().insert(qp -> qp.setLanguage(removedLanguageKey));
+ db.qualityProfiles().setAsDefault(qProfileDtoRemovedLanguage);
+ RuleDto ruleRemovedLanguage = db.rules().insert(r -> r.setLanguage(removedLanguageKey), r -> r.setName("ruleRemovedLanguage"));
+ db.qualityProfiles().activateRule(qProfileDtoRemovedLanguage, ruleRemovedLanguage);
+
+ List<ActiveRule> activeRules = activeRuleService.buildActiveRules("myProjectUuid");
+ assertThat(activeRules).isNotNull().hasSize(3)
+ .extracting(ActiveRule::ruleKey, ActiveRule::name, ActiveRule::severity,
+ ActiveRule::createdAt, ActiveRule::updatedAt, ActiveRule::internalKey,
+ ActiveRule::language, ActiveRule::templateRuleKey, ActiveRule::qProfileKey,
+ ActiveRule::deprecatedKeys, ActiveRule::params)
+ .containsExactlyInAnyOrder(
+ toTuple(activatedRule1, XOO_LANGUAGE_KEY, qProfileDto1, List.of()),
+ toTuple(activatedRule2, XOO_LANGUAGE_KEY, qProfileDto1, List.of()),
+ toTuple(activatedRule3, XOO2_LANGUAGE_KEY, qProfileDto2, List.of()));
+ }
+
+ @Test
+ void projectProfile_overrides_defaultProfile() {
+ QProfileDto qProfileDto1 = db.qualityProfiles().insert(qp -> qp.setLanguage(XOO_LANGUAGE_KEY));
+ db.qualityProfiles().setAsDefault(qProfileDto1);
+ RuleDto rule1 = db.rules().insert(r -> r.setLanguage(XOO_LANGUAGE_KEY), r -> r.setName("rule1"));
+ db.qualityProfiles().activateRule(qProfileDto1, rule1);
+
+ QProfileDto qProfileDto2 = db.qualityProfiles().insert(qp -> qp.setLanguage(XOO_LANGUAGE_KEY));
+ ProjectData project = db.components().insertPrivateProject();
+ db.qualityProfiles().associateWithProject(project.getProjectDto(), qProfileDto2);
+ ActiveRuleDto activatedRule2 = db.qualityProfiles().activateRule(qProfileDto2, rule1);
+
+ List<ActiveRule> activeRules = activeRuleService.buildActiveRules(project.projectUuid());
+ assertThat(activeRules).hasSize(1)
+ .extracting(ActiveRule::ruleKey, ActiveRule::name, ActiveRule::severity,
+ ActiveRule::createdAt, ActiveRule::updatedAt, ActiveRule::internalKey,
+ ActiveRule::language, ActiveRule::templateRuleKey, ActiveRule::qProfileKey,
+ ActiveRule::deprecatedKeys, ActiveRule::params)
+ .containsExactlyInAnyOrder(
+ toTuple(activatedRule2, XOO_LANGUAGE_KEY, qProfileDto2, List.of()));
+ }
+
+ @Test
+ void customRuleParam_overrides_defaultRuleParamValue() {
+ RuleDto rule = db.rules().insert(r -> r.setLanguage(XOO_LANGUAGE_KEY), r -> r.setName("rule"));
+ RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setDefaultValue("defaultValue"));
+ QProfileDto defaultQualityProfile = db.qualityProfiles().insert(qp -> qp.setLanguage(XOO_LANGUAGE_KEY));
+ db.qualityProfiles().setAsDefault(defaultQualityProfile);
+ db.qualityProfiles().activateRule(defaultQualityProfile, rule);
+
+ QProfileDto qProfileDto2 = db.qualityProfiles().insert(qp -> qp.setLanguage(XOO_LANGUAGE_KEY));
+ ProjectData project = db.components().insertPrivateProject();
+ db.qualityProfiles().associateWithProject(project.getProjectDto(), qProfileDto2);
+ ActiveRuleDto activatedRule2 = db.qualityProfiles().activateRule(qProfileDto2, rule);
+ db.getDbClient().activeRuleDao().insertParam(db.getSession(), activatedRule2, new ActiveRuleParamDto()
+ .setRulesParameterUuid(ruleParam.getUuid())
+ .setKey(ruleParam.getName())
+ .setValue("overriddenValue"));
+
+ db.commit();
+
+ List<ActiveRule> activeRules = activeRuleService.buildActiveRules(project.projectUuid());
+ assertThat(activeRules).hasSize(1)
+ .extracting(ActiveRule::ruleKey, ActiveRule::name, ActiveRule::severity,
+ ActiveRule::createdAt, ActiveRule::updatedAt, ActiveRule::internalKey,
+ ActiveRule::language, ActiveRule::templateRuleKey, ActiveRule::qProfileKey,
+ ActiveRule::deprecatedKeys, ActiveRule::params)
+ .containsExactlyInAnyOrder(
+ toTuple(activatedRule2, XOO_LANGUAGE_KEY, qProfileDto2, List.of(new ActiveRuleRestReponse.Param(ruleParam.getName(), "overriddenValue"))));
+ }
+
+ @Test
+ void returnsRuleTemplate() {
+ RuleDto templateRule = db.rules().insert(r -> r.setLanguage(XOO_LANGUAGE_KEY)
+ .setIsTemplate(true));
+ RuleDto rule = db.rules().insert(r -> r.setLanguage(XOO_LANGUAGE_KEY)
+ .setTemplateUuid(templateRule.getUuid()));
+ RuleParamDto ruleParam = db.rules().insertRuleParam(rule);
+ QProfileDto defaultQualityProfile = db.qualityProfiles().insert(qp -> qp.setLanguage(XOO_LANGUAGE_KEY));
+ db.qualityProfiles().setAsDefault(defaultQualityProfile);
+ ActiveRuleDto activeRule = db.qualityProfiles().activateRule(defaultQualityProfile, rule);
+ ProjectData project = db.components().insertPrivateProject();
+
+ List<ActiveRule> activeRules = activeRuleService.buildActiveRules(project.projectUuid());
+ assertThat(activeRules).hasSize(1)
+ .extracting(ActiveRule::ruleKey, ActiveRule::name, ActiveRule::severity,
+ ActiveRule::createdAt, ActiveRule::updatedAt, ActiveRule::internalKey,
+ ActiveRule::language, ActiveRule::templateRuleKey, ActiveRule::qProfileKey,
+ ActiveRule::deprecatedKeys, ActiveRule::params)
+ .containsExactlyInAnyOrder(
+ tuple(toRuleKey(activeRule.getKey()), activeRule.getName(), activeRule.getSeverityString(), formatDateTime(activeRule.getCreatedAt()),
+ formatDateTime(activeRule.getUpdatedAt()), activeRule.getConfigKey(), XOO_LANGUAGE_KEY, templateRule.getKey().toString(), defaultQualityProfile.getKee(), List.of(),
+ List.of(new ActiveRuleRestReponse.Param(ruleParam.getName(), ruleParam.getDefaultValue()))));
+
+ }
+
+ private Tuple toTuple(ActiveRuleDto activatedRule, String language, QProfileDto qProfileDto, List<ActiveRuleRestReponse.Param> expectedParams) {
+ return tuple(toRuleKey(activatedRule.getKey()), activatedRule.getName(), activatedRule.getSeverityString(), formatDateTime(activatedRule.getCreatedAt()),
+ formatDateTime(activatedRule.getUpdatedAt()), activatedRule.getConfigKey(), language, null, qProfileDto.getKee(), List.of(), expectedParams);
+ }
+
+ private ActiveRuleRestReponse.RuleKey toRuleKey(ActiveRuleKey key) {
+ return new ActiveRuleRestReponse.RuleKey(key.getRuleKey().repository(), key.getRuleKey().rule());
+ }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleRestReponse.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleRestReponse.java
new file mode 100644
index 00000000000..f802e7f004d
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleRestReponse.java
@@ -0,0 +1,123 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.rule;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import java.util.ArrayList;
+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;
+
+public class ActiveRuleRestReponse {
+
+ public record ActiveRule(
+ RuleKey ruleKey,
+ String name,
+ String severity,
+ String createdAt,
+ String updatedAt,
+ @JsonInclude(JsonInclude.Include.NON_NULL) @Nullable String internalKey,
+ String language,
+ @JsonInclude(JsonInclude.Include.NON_NULL) @Nullable String templateRuleKey,
+ String qProfileKey,
+ @JsonInclude(JsonInclude.Include.NON_EMPTY) List<RuleKey> deprecatedKeys,
+ @JsonInclude(JsonInclude.Include.NON_EMPTY) List<Param> params,
+ @JsonInclude(JsonInclude.Include.NON_EMPTY) Map<SoftwareQuality, Severity> impacts) {
+ }
+
+ public record RuleKey(String repository, String rule) {
+ }
+
+ public record Param(String key, String value) {
+ }
+
+ public static class Builder {
+ private RuleKey ruleKey;
+ private String name;
+ private String severity;
+ private String createdAt;
+ private String updatedAt;
+ private String internalKey;
+ private String language;
+ private String templateRuleKey;
+ private String qProfilKey;
+ private final List<RuleKey> deprecatedKeys = new ArrayList<>();
+ private final List<Param> params = new ArrayList<>();
+ private final Map<SoftwareQuality, Severity> impacts = new EnumMap<>(SoftwareQuality.class);
+
+ public void setRuleKey(RuleKey ruleKey) {
+ this.ruleKey = ruleKey;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setSeverity(String severity) {
+ this.severity = severity;
+ }
+
+ public void setCreatedAt(String createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public void setUpdatedAt(String updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ public void setInternalKey(String internalKey) {
+ this.internalKey = internalKey;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ public void setTemplateRuleKey(String templateRuleKey) {
+ this.templateRuleKey = templateRuleKey;
+ }
+
+ public void setQProfilKey(String qProfilKey) {
+ this.qProfilKey = qProfilKey;
+ }
+
+ public void setParams(List<Param> params) {
+ this.params.clear();
+ this.params.addAll(params);
+ }
+
+ public void addAllDeprecatedKeys(List<RuleKey> deprecatedKeys) {
+ this.deprecatedKeys.addAll(deprecatedKeys);
+ }
+
+ public void setImpacts(Map<SoftwareQuality, Severity> impacts) {
+ this.impacts.clear();
+ this.impacts.putAll(impacts);
+ }
+
+ public ActiveRule build() {
+ return new ActiveRule(ruleKey, name, severity, createdAt, updatedAt, internalKey, language, templateRuleKey, qProfilKey, deprecatedKeys, params, impacts);
+ }
+ }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleService.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleService.java
new file mode 100644
index 00000000000..5a7960a6010
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/ActiveRuleService.java
@@ -0,0 +1,177 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.rule;
+
+import com.google.common.base.Strings;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.OrgActiveRuleDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+
+import static java.util.Optional.ofNullable;
+
+public class ActiveRuleService {
+
+ private final DbClient dbClient;
+ private final Languages languages;
+
+ public ActiveRuleService(DbClient dbClient, Languages languages) {
+ this.dbClient = dbClient;
+ this.languages = languages;
+ }
+
+ public List<ActiveRuleRestReponse.ActiveRule> buildDefaultActiveRules() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ List<QProfileDto> qualityProfiles = dbClient.qualityProfileDao().selectDefaultProfiles(dbSession, getLanguageKeys());
+ return loadActiveRules(qualityProfiles, dbSession);
+ }
+ }
+
+ public List<ActiveRuleRestReponse.ActiveRule> buildActiveRules(String projectUuid) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ List<QProfileDto> qualityProfiles = getQualityProfiles(dbSession, projectUuid);
+ return loadActiveRules(qualityProfiles, dbSession);
+ }
+ }
+
+ private Set<String> getLanguageKeys() {
+ return Arrays.stream(languages.all()).map(Language::getKey).collect(Collectors.toSet());
+ }
+
+ private List<ActiveRuleRestReponse.ActiveRule> loadActiveRules(List<QProfileDto> qualityProfiles, DbSession dbSession) {
+
+ Map<String, QProfileDto> profilesByUuids = qualityProfiles.stream().collect(Collectors.toMap(QProfileDto::getKee, Function.identity()));
+
+ Map<String, List<RuleParamDto>> ruleParamsByRuleUuid = dbClient.ruleDao().selectAllRuleParams(dbSession).stream()
+ .collect(Collectors.groupingBy(RuleParamDto::getRuleUuid));
+
+ Map<String, List<ActiveRuleParamDto>> activeRuleParamsByActiveRuleUuid = dbClient.activeRuleDao().selectAllParamsByProfileUuids(dbSession, profilesByUuids.keySet()).stream()
+ .collect(Collectors.groupingBy(ActiveRuleParamDto::getActiveRuleUuid));
+
+ List<OrgActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByProfileUuids(dbSession, profilesByUuids.keySet());
+ Map<String, RuleDto> templateRulesByUuid = getTemplateRulesByUuid(dbSession, activeRuleDtos);
+ Map<String, List<DeprecatedRuleKeyDto>> deprecatedRuleKeysByRuleUuid = getDeprecatedRuleKeysByRuleUuid(dbSession);
+
+ return activeRuleDtos.stream()
+ .map(
+ activeRuleDto -> buildActiveRule(activeRuleDto, profilesByUuids, templateRulesByUuid, deprecatedRuleKeysByRuleUuid, ruleParamsByRuleUuid, activeRuleParamsByActiveRuleUuid))
+ .toList();
+ }
+
+ private static ActiveRuleRestReponse.ActiveRule buildActiveRule(OrgActiveRuleDto activeRuleDto,
+ Map<String, QProfileDto> profilesByUuids,
+ Map<String, RuleDto> templateRulesByUuid,
+ Map<String, List<DeprecatedRuleKeyDto>> deprecatedRuleKeysByRuleUuid,
+ Map<String, List<RuleParamDto>> ruleParamsByRuleUuid,
+ Map<String, List<ActiveRuleParamDto>> activeRuleParamsByActiveRuleUuid) {
+ ActiveRuleRestReponse.Builder activeRuleBuilder = new ActiveRuleRestReponse.Builder();
+ activeRuleBuilder.setRuleKey(toRuleKey(activeRuleDto.getRuleKey()));
+ activeRuleBuilder.setName(activeRuleDto.getName());
+ activeRuleBuilder.setSeverity(activeRuleDto.getSeverityString());
+ activeRuleBuilder.setCreatedAt(DateUtils.formatDateTime(activeRuleDto.getCreatedAt()));
+ activeRuleBuilder.setUpdatedAt(DateUtils.formatDateTime(activeRuleDto.getUpdatedAt()));
+ // same as RuleForIndexingDto mapping
+ activeRuleBuilder.setInternalKey(activeRuleDto.getConfigKey());
+ activeRuleBuilder.setQProfilKey(profilesByUuids.get(activeRuleDto.getOrgProfileUuid()).getKee());
+ activeRuleBuilder.setLanguage(activeRuleDto.getLanguage());
+ Optional<RuleDto> templateRule = ofNullable(templateRulesByUuid.get(activeRuleDto.getTemplateUuid()));
+ templateRule.ifPresent(template -> activeRuleBuilder.setTemplateRuleKey(template.getKey().toString()));
+
+ List<ActiveRuleRestReponse.RuleKey> deprecatedKeys = buildDeprecatedKeys(activeRuleDto, deprecatedRuleKeysByRuleUuid);
+ activeRuleBuilder.addAllDeprecatedKeys(deprecatedKeys);
+
+ List<RuleParamDto> ruleParams = ruleParamsByRuleUuid.get(activeRuleDto.getRuleUuid());
+ List<ActiveRuleParamDto> activeRuleParams = ofNullable(activeRuleParamsByActiveRuleUuid.get(activeRuleDto.getUuid())).orElse(List.of());
+ List<ActiveRuleRestReponse.Param> params = buildParams(ruleParams, activeRuleParams);
+ activeRuleBuilder.setParams(params);
+
+ activeRuleBuilder.setImpacts(activeRuleDto.getImpacts());
+
+ return activeRuleBuilder.build();
+ }
+
+ private static List<ActiveRuleRestReponse.Param> buildParams(@Nullable List<RuleParamDto> ruleParams, List<ActiveRuleParamDto> activeRuleParams) {
+ if (ruleParams == null) {
+ return List.of();
+ }
+ return ruleParams.stream()
+ .map(parameter -> {
+ Optional<ActiveRuleParamDto> activeRuleParamDto = activeRuleParams.stream()
+ .filter(arp -> arp.getRulesParameterUuid().equals(parameter.getUuid()))
+ .findAny();
+ String paramValue = activeRuleParamDto.map(ActiveRuleParamDto::getValue).orElse(parameter.getDefaultValue());
+ return new ActiveRuleRestReponse.Param(parameter.getName(), Strings.nullToEmpty(paramValue));
+ })
+ .toList();
+ }
+
+ private static List<ActiveRuleRestReponse.RuleKey> buildDeprecatedKeys(OrgActiveRuleDto activeRuleDto, Map<String, List<DeprecatedRuleKeyDto>> deprecatedRuleKeysByRuleUuid) {
+ List<DeprecatedRuleKeyDto> deprecatedRuleKeyDtos = deprecatedRuleKeysByRuleUuid.getOrDefault(activeRuleDto.getUuid(), List.of());
+ return deprecatedRuleKeyDtos.stream()
+ .map(ActiveRuleService::toRuleKey)
+ .toList();
+ }
+
+ private List<QProfileDto> getQualityProfiles(DbSession dbSession, String projectUuid) {
+ List<QProfileDto> defaultProfiles = dbClient.qualityProfileDao().selectDefaultProfiles(dbSession, getLanguageKeys());
+ Map<String, QProfileDto> projectProfiles = dbClient.qualityProfileDao().selectAssociatedToProjectAndLanguages(dbSession, projectUuid, getLanguageKeys())
+ .stream().collect(Collectors.toMap(QProfileDto::getLanguage, Function.identity()));
+
+ return defaultProfiles.stream()
+ .map(defaultProfile -> projectProfiles.getOrDefault(defaultProfile.getLanguage(), defaultProfile))
+ .toList();
+ }
+
+ private Map<String, RuleDto> getTemplateRulesByUuid(DbSession dbSession, List<OrgActiveRuleDto> activeRuleDtos) {
+ Set<String> templateRuleUuids = activeRuleDtos.stream().map(OrgActiveRuleDto::getTemplateUuid).filter(Objects::nonNull).collect(Collectors.toSet());
+ return dbClient.ruleDao().selectByUuids(dbSession, templateRuleUuids).stream().collect(Collectors.toMap(RuleDto::getUuid, p -> p));
+ }
+
+ private static ActiveRuleRestReponse.RuleKey toRuleKey(RuleKey ruleKey) {
+ return new ActiveRuleRestReponse.RuleKey(ruleKey.repository(), ruleKey.rule());
+ }
+
+ private static ActiveRuleRestReponse.RuleKey toRuleKey(DeprecatedRuleKeyDto r) {
+ return new ActiveRuleRestReponse.RuleKey(r.getOldRepositoryKey(), r.getOldRuleKey());
+ }
+
+ private Map<String, List<DeprecatedRuleKeyDto>> getDeprecatedRuleKeysByRuleUuid(DbSession dbSession) {
+ return dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream()
+ .collect(Collectors.groupingBy(DeprecatedRuleKeyDto::getRuleUuid));
+ }
+
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/CachingRuleFinder.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/CachingRuleFinder.java
index 4809218d3e2..3b6c7007df7 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/CachingRuleFinder.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/rule/CachingRuleFinder.java
@@ -20,7 +20,6 @@
package org.sonar.server.rule;
import com.google.common.collect.Ordering;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
index d77f2fda48a..1d053c5049f 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
@@ -51,6 +51,7 @@ public class WebApiEndpoints {
public static final String ANALYSIS_DOMAIN = "/analysis";
public static final String VERSION_ENDPOINT = ANALYSIS_DOMAIN + "/version";
public static final String JRE_ENDPOINT = ANALYSIS_DOMAIN + "/jres";
+ public static final String ACTIVE_RULES_ENDPOINT = ANALYSIS_DOMAIN + "/active_rules";
public static final String SCANNER_ENGINE_ENDPOINT = ANALYSIS_DOMAIN + "/engine";
private WebApiEndpoints() {
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/ActiveRulesController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/ActiveRulesController.java
new file mode 100644
index 00000000000..905420589a3
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/ActiveRulesController.java
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import java.util.List;
+import org.sonar.server.rule.ActiveRuleRestReponse;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import static org.sonar.server.v2.WebApiEndpoints.ACTIVE_RULES_ENDPOINT;
+import static org.springframework.http.HttpStatus.OK;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+
+@RequestMapping(value = ACTIVE_RULES_ENDPOINT, produces = APPLICATION_JSON_VALUE)
+@ResponseStatus(OK)
+@RestController
+public interface ActiveRulesController {
+
+ String PROJECT_KEY_PARAM_DESCRIPTION = "Project Key";
+
+ @GetMapping
+ @Operation(summary = "Get all active rules for a specific project", description = "Used by the scanner-engine to get all active rules for a given project.")
+ List<ActiveRuleRestReponse.ActiveRule> getActiveRules(@RequestParam(value = "projectKey") @Parameter(description = PROJECT_KEY_PARAM_DESCRIPTION) String projectKey);
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesController.java
new file mode 100644
index 00000000000..9a51bd5972b
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesController.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.controller;
+
+import java.util.List;
+import org.sonar.server.rule.ActiveRuleRestReponse;
+import org.sonar.server.v2.api.analysis.service.ActiveRulesHandler;
+
+public class DefaultActiveRulesController implements ActiveRulesController {
+
+ private final ActiveRulesHandler activeRulesHandler;
+
+ public DefaultActiveRulesController(ActiveRulesHandler activeRulesHandler) {
+ this.activeRulesHandler = activeRulesHandler;
+ }
+
+ @Override
+ public List<ActiveRuleRestReponse.ActiveRule> getActiveRules(String projectKey) {
+ return activeRulesHandler.getActiveRules(projectKey);
+ }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandler.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandler.java
new file mode 100644
index 00000000000..d14a66a6d7a
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandler.java
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.service;
+
+import java.util.List;
+import org.sonar.server.rule.ActiveRuleRestReponse;
+
+public interface ActiveRulesHandler {
+ List<ActiveRuleRestReponse.ActiveRule> getActiveRules(String projectKey);
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImpl.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImpl.java
new file mode 100644
index 00000000000..8b4562bc4f9
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImpl.java
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.service;
+
+import java.util.List;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.server.rule.ActiveRuleRestReponse;
+import org.sonar.server.rule.ActiveRuleService;
+
+public class ActiveRulesHandlerImpl implements ActiveRulesHandler {
+
+ private final DbClient dbClient;
+ private final ActiveRuleService activeRuleService;
+
+ public ActiveRulesHandlerImpl(DbClient dbClient, ActiveRuleService activeRuleService) {
+ this.dbClient = dbClient;
+ this.activeRuleService = activeRuleService;
+ }
+
+ @Override
+ public List<ActiveRuleRestReponse.ActiveRule> getActiveRules(String projectKey) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ return dbClient.projectDao().selectProjectByKey(dbSession, projectKey)
+ .map(projectDto -> activeRuleService.buildActiveRules(projectDto.getUuid()))
+ .orElse(activeRuleService.buildDefaultActiveRules());
+ }
+ }
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
index 20880ad7420..2ae1170b7af 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
@@ -48,16 +48,21 @@ import org.sonar.server.platform.ServerFileSystem;
import org.sonar.server.platform.db.migration.DatabaseMigrationState;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;
import org.sonar.server.qualitygate.QualityGateConditionsValidator;
+import org.sonar.server.rule.ActiveRuleService;
import org.sonar.server.rule.RuleDescriptionFormatter;
import org.sonar.server.setting.SettingsChangeNotifier;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;
+import org.sonar.server.v2.api.analysis.controller.ActiveRulesController;
+import org.sonar.server.v2.api.analysis.controller.DefaultActiveRulesController;
import org.sonar.server.v2.api.analysis.controller.DefaultJresController;
import org.sonar.server.v2.api.analysis.controller.DefaultScannerEngineController;
import org.sonar.server.v2.api.analysis.controller.DefaultVersionController;
import org.sonar.server.v2.api.analysis.controller.JresController;
import org.sonar.server.v2.api.analysis.controller.ScannerEngineController;
import org.sonar.server.v2.api.analysis.controller.VersionController;
+import org.sonar.server.v2.api.analysis.service.ActiveRulesHandler;
+import org.sonar.server.v2.api.analysis.service.ActiveRulesHandlerImpl;
import org.sonar.server.v2.api.analysis.service.JresHandler;
import org.sonar.server.v2.api.analysis.service.JresHandlerImpl;
import org.sonar.server.v2.api.analysis.service.ScannerEngineHandler;
@@ -227,4 +232,19 @@ public class PlatformLevel4WebConfig {
return new DefaultModeController(userSession, dbClient, configuration, settingsChangeNotifier, notificationManager, qualityGateConditionsValidator);
}
+ @Bean
+ public ActiveRuleService activeRuleService(DbClient dbClient, Languages languages) {
+ return new ActiveRuleService(dbClient, languages);
+ }
+
+ @Bean
+ public ActiveRulesHandler activeRulesHandler(DbClient dbClient, ActiveRuleService activeRuleService) {
+ return new ActiveRulesHandlerImpl(dbClient, activeRuleService);
+ }
+
+ @Bean
+ public ActiveRulesController activeRulesController(ActiveRulesHandler activeRulesHandler) {
+ return new DefaultActiveRulesController(activeRulesHandler);
+ }
+
}
diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesControllerTest.java
new file mode 100644
index 00000000000..c35ecfda28a
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultActiveRulesControllerTest.java
@@ -0,0 +1,107 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.controller;
+
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.server.rule.ActiveRuleRestReponse;
+import org.sonar.server.v2.api.analysis.service.ActiveRulesHandler;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.v2.WebApiEndpoints.ACTIVE_RULES_ENDPOINT;
+import static org.sonar.server.v2.api.ControllerTester.getMockMvc;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+class DefaultActiveRulesControllerTest {
+
+ private final ActiveRulesHandler handler = mock(ActiveRulesHandler.class);
+
+ private final MockMvc mockMvc = getMockMvc(new DefaultActiveRulesController(handler));
+
+ @Test
+ void getActiveRules_shouldReturnActiveRulesAsJson() throws Exception {
+ var minimalAr = new ActiveRuleRestReponse.ActiveRule(
+ new ActiveRuleRestReponse.RuleKey("xoo", "rule1"),
+ "Rule 1",
+ "MAJOR",
+ "2024-12-09",
+ "2024-12-09",
+ null,
+ "java",
+ null,
+ "qProfileKey",
+ List.of(),
+ List.of(),
+ Map.of());
+ var maximalAr = new ActiveRuleRestReponse.ActiveRule(
+ new ActiveRuleRestReponse.RuleKey("xoo", "rule1"),
+ "Rule 1",
+ "MAJOR",
+ "2024-12-09",
+ "2024-12-09",
+ "someInternalKey",
+ "java",
+ "templateKey",
+ "qProfileKey",
+ List.of(new ActiveRuleRestReponse.RuleKey("old", "rule")),
+ List.of(new ActiveRuleRestReponse.Param("key", "value")),
+ Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH));
+ when(handler.getActiveRules("someKey")).thenReturn(List.of(minimalAr, maximalAr));
+
+ String expectedJson = """
+ [
+ {
+ "ruleKey":{"repository":"xoo","rule":"rule1"},
+ "name":"Rule 1",
+ "severity":"MAJOR",
+ "createdAt":"2024-12-09",
+ "updatedAt":"2024-12-09",
+ "language":"java",
+ "qProfileKey":"qProfileKey"
+ },
+ {
+ "ruleKey":{"repository":"xoo","rule":"rule1"},
+ "name":"Rule 1","severity":"MAJOR",
+ "createdAt":"2024-12-09",
+ "updatedAt":"2024-12-09",
+ "internalKey":"someInternalKey",
+ "language":"java",
+ "templateRuleKey":"templateKey",
+ "qProfileKey":"qProfileKey",
+ "deprecatedKeys":[{"repository":"old","rule":"rule"}],
+ "params":[{"key":"key","value":"value"}],
+ "impacts":{"MAINTAINABILITY":"HIGH"}}
+ ]
+ """;
+
+ mockMvc.perform(get(ACTIVE_RULES_ENDPOINT + "?projectKey=someKey"))
+ .andExpectAll(
+ status().isOk(),
+ content().json(expectedJson, true));
+ }
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImplTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImplTest.java
new file mode 100644
index 00000000000..26c8983195a
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/ActiveRulesHandlerImplTest.java
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.service;
+
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.rule.ActiveRuleRestReponse;
+import org.sonar.server.rule.ActiveRuleService;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ActiveRulesHandlerImplTest {
+
+ private final DbClient dbClient = mock(DbClient.class, RETURNS_DEEP_STUBS);
+ private final DbSession dbSession = mock();
+ private final ActiveRuleService activeRuleService = mock(ActiveRuleService.class);
+
+ @BeforeEach
+ void setup() {
+ when(dbClient.openSession(false)).thenReturn(dbSession);
+ }
+
+ ActiveRulesHandlerImpl underTest = new ActiveRulesHandlerImpl(dbClient, activeRuleService);
+
+ @Test
+ void getActiveRules_returns_default_quality_profile_for_unknown_project() {
+ var defaultActiveRule1 = mock(ActiveRuleRestReponse.ActiveRule.class);
+ when(activeRuleService.buildDefaultActiveRules()).thenReturn(List.of(defaultActiveRule1));
+
+ List<ActiveRuleRestReponse.ActiveRule> result = underTest.getActiveRules("unknown-project");
+
+ assertThat(result).containsExactly(defaultActiveRule1);
+ }
+
+ @Test
+ void getActiveRules_returns_associated_quality_profile_for_known_project() {
+ when(dbClient.projectDao().selectProjectByKey(dbSession, "my-project")).thenReturn(Optional.of(new ProjectDto().setUuid("someProjectUuid")));
+ var activeRule1 = mock(ActiveRuleRestReponse.ActiveRule.class);
+ when(activeRuleService.buildActiveRules("someProjectUuid")).thenReturn(List.of(activeRule1));
+
+ List<ActiveRuleRestReponse.ActiveRule> result = underTest.getActiveRules("my-project");
+
+ assertThat(result).containsExactly(activeRule1);
+ }
+
+}