3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.ce.task.projectexport.rule;
22 import com.google.common.collect.ImmutableList;
23 import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
24 import java.util.Date;
25 import java.util.List;
28 import java.util.stream.Collectors;
29 import org.apache.commons.lang3.RandomStringUtils;
30 import org.junit.Before;
31 import org.junit.Rule;
32 import org.junit.Test;
33 import org.slf4j.event.Level;
34 import org.sonar.api.issue.impact.SoftwareQuality;
35 import org.sonar.api.rule.RuleKey;
36 import org.sonar.api.rule.RuleStatus;
37 import org.sonar.api.rule.Severity;
38 import org.sonar.api.rules.CleanCodeAttribute;
39 import org.sonar.api.rules.RuleType;
40 import org.sonar.api.testfixtures.log.LogTester;
41 import org.sonar.api.utils.System2;
42 import org.sonar.ce.task.projectexport.steps.DumpElement;
43 import org.sonar.ce.task.projectexport.steps.FakeDumpWriter;
44 import org.sonar.ce.task.projectexport.steps.ProjectHolder;
45 import org.sonar.ce.task.step.TestComputationStepContext;
46 import org.sonar.db.DbTester;
47 import org.sonar.db.component.BranchDto;
48 import org.sonar.db.component.BranchType;
49 import org.sonar.db.component.ComponentDto;
50 import org.sonar.db.component.ProjectData;
51 import org.sonar.db.issue.ImpactDto;
52 import org.sonar.db.issue.IssueDto;
53 import org.sonar.db.project.ProjectDto;
54 import org.sonar.db.rule.RuleDto;
56 import static org.assertj.core.api.Assertions.assertThat;
57 import static org.assertj.core.api.Assertions.assertThatThrownBy;
58 import static org.mockito.Mockito.mock;
59 import static org.mockito.Mockito.when;
61 public class ExportAdHocRulesStepIT {
62 private static final String PROJECT_UUID = "some-uuid";
63 private static final List<BranchDto> BRANCHES = ImmutableList.of(
64 new BranchDto().setBranchType(BranchType.PULL_REQUEST).setProjectUuid(PROJECT_UUID).setKey("pr-1").setUuid("pr-1-uuid").setMergeBranchUuid("master").setIsMain(false),
65 new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-2").setUuid("branch-2-uuid").setMergeBranchUuid("master")
66 .setExcludeFromPurge(true).setIsMain(false),
67 new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-3").setUuid("branch-3-uuid").setMergeBranchUuid("master")
68 .setExcludeFromPurge(false).setIsMain(false));
71 public LogTester logTester = new LogTester();
73 public DbTester dbTester = DbTester.create(System2.INSTANCE);
75 private int issueUuidGenerator = 1;
76 private ComponentDto mainBranch;
77 private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
78 private final ProjectHolder projectHolder = mock(ProjectHolder.class);
79 private final ExportAdHocRulesStep underTest = new ExportAdHocRulesStep(dbTester.getDbClient(), projectHolder, dumpWriter);
83 logTester.setLevel(Level.DEBUG);
84 ProjectDto project = createProject();
85 when(projectHolder.projectDto()).thenReturn(project);
89 public void export_zero_ad_hoc_rules() {
90 underTest.execute(new TestComputationStepContext());
92 List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
93 assertThat(exportedRules).isEmpty();
94 assertThat(logTester.logs(Level.DEBUG)).contains("0 ad-hoc rules exported");
98 public void execute_only_exports_ad_hoc_rules_that_reference_project_issue() {
99 String differentProject = "diff-proj-uuid";
100 RuleDto rule1 = insertAddHocRule("rule-1");
101 RuleDto rule2 = insertAddHocRule("rule-2");
102 insertAddHocRule("rule-3");
103 insertIssue(rule1, differentProject, differentProject);
104 insertIssue(rule2, mainBranch.uuid(), mainBranch.uuid());
106 underTest.execute(new TestComputationStepContext());
108 List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
109 assertThat(exportedRules).hasSize(1);
110 assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule2);
111 assertThat(logTester.logs(Level.DEBUG)).contains("1 ad-hoc rules exported");
115 public void execute_only_exports_rules_that_are_ad_hoc() {
116 RuleDto rule1 = insertStandardRule("rule-1");
117 RuleDto rule2 = insertExternalRule("rule-2");
118 RuleDto rule3 = insertAddHocRule("rule-3");
119 insertIssue(rule1, mainBranch.uuid(), mainBranch.uuid());
120 insertIssue(rule2, mainBranch.uuid(), mainBranch.uuid());
121 insertIssue(rule3, mainBranch.uuid(), mainBranch.uuid());
123 underTest.execute(new TestComputationStepContext());
125 List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
126 assertThat(exportedRules).hasSize(1);
127 assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule3);
128 assertThat(logTester.logs(Level.DEBUG)).contains("1 ad-hoc rules exported");
132 public void execute_exports_ad_hoc_rules_that_are_referenced_by_issues_on_branches_excluded_from_purge() {
133 when(projectHolder.branches()).thenReturn(BRANCHES);
134 RuleDto rule1 = insertAddHocRule("rule-1");
135 RuleDto rule2 = insertAddHocRule("rule-2");
136 RuleDto rule3 = insertAddHocRule("rule-3");
137 insertIssue(rule1, "branch-1-uuid", "branch-1-uuid");
138 insertIssue(rule2, "branch-2-uuid", "branch-2-uuid");
139 insertIssue(rule3, "branch-3-uuid", "branch-3-uuid");
141 underTest.execute(new TestComputationStepContext());
143 List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
144 assertThat(exportedRules).hasSize(1);
145 assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule2);
146 assertThat(logTester.logs(Level.DEBUG)).contains("1 ad-hoc rules exported");
150 public void execute_throws_ISE_with_number_of_successful_exports_before_failure() {
151 RuleDto rule1 = insertAddHocRule("rule-1");
152 RuleDto rule2 = insertAddHocRule("rule-2");
153 RuleDto rule3 = insertAddHocRule("rule-3");
154 insertIssue(rule1, mainBranch.uuid(), mainBranch.uuid());
155 insertIssue(rule2, mainBranch.uuid(), mainBranch.uuid());
156 insertIssue(rule3, mainBranch.uuid(), mainBranch.uuid());
157 dumpWriter.failIfMoreThan(2, DumpElement.AD_HOC_RULES);
159 assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
160 .isInstanceOf(IllegalStateException.class)
161 .hasMessage("Ad-hoc rules export failed after processing 2 rules successfully");
165 public void execute_shouldReturnCorrectAdhocRules_whenMultipleIssuesForSameRule() {
166 RuleDto rule1 = insertAddHocRule("rule-1");
167 insertIssue(rule1, mainBranch.uuid(), mainBranch.uuid());
168 insertIssue(rule1, mainBranch.uuid(), mainBranch.uuid());
169 insertIssue(rule1, mainBranch.uuid(), mainBranch.uuid());
171 underTest.execute(new TestComputationStepContext());
173 List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
174 assertThat(exportedRules).hasSize(1);
175 assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule1);
179 public void getDescription() {
180 assertThat(underTest.getDescription()).isEqualTo("Export ad-hoc rules");
184 public void execute_shouldMapFieldWithEmptyString_whenNameOrPluginKeyAreNull() {
185 RuleKey ruleKey = RuleKey.of("plugin1", "partiallyInit");
186 RuleDto partiallyInitRuleDto = insertAdHocRuleWithoutNameAndPluginKeyAndAdHocInformations(ruleKey);
187 insertIssue(partiallyInitRuleDto, mainBranch.uuid(), mainBranch.uuid());
189 underTest.execute(new TestComputationStepContext());
191 List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
192 assertThat(exportedRules).hasSize(1);
193 ProjectDump.AdHocRule adHocRule = exportedRules.iterator().next();
194 assertThat(adHocRule.getName()).isEmpty();
195 assertThat(adHocRule.getPluginKey()).isEmpty();
196 ProjectDump.AdHocRule.RuleMetadata adHocRuleMetadata = adHocRule.getMetadata();
197 assertThat(adHocRuleMetadata.getAdHocDescription()).isEmpty();
198 assertThat(adHocRuleMetadata.getAdHocName()).isEmpty();
199 assertThat(adHocRuleMetadata.getAdHocSeverity()).isEmpty();
200 assertThat(adHocRuleMetadata.getAdHocType()).isZero();
204 private RuleDto insertAdHocRuleWithoutNameAndPluginKeyAndAdHocInformations(RuleKey ruleKey) {
205 RuleDto partiallyInitRuleDto = new RuleDto()
206 .setIsExternal(false)
208 .setCleanCodeAttribute(CleanCodeAttribute.CONVENTIONAL)
209 .addDefaultImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(org.sonar.api.issue.impact.Severity.MEDIUM))
210 .addDefaultImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(org.sonar.api.issue.impact.Severity.BLOCKER))
212 .setScope(RuleDto.Scope.ALL)
213 .setStatus(RuleStatus.READY);
215 dbTester.rules().insert(partiallyInitRuleDto);
218 return dbTester.getDbClient().ruleDao().selectByKey(dbTester.getSession(), ruleKey)
219 .orElseThrow(() -> new RuntimeException("insertAdHocRule failed"));
222 private ProjectDto createProject() {
223 Date createdAt = new Date();
224 ProjectData projectData = dbTester.components().insertPublicProject(PROJECT_UUID);
225 mainBranch = projectData.getMainBranchComponent();
226 BRANCHES.forEach(branch -> dbTester.components().insertProjectBranch(projectData.getProjectDto(), branch).setCreatedAt(createdAt));
228 return projectData.getProjectDto();
231 private void insertIssue(RuleDto ruleDto, String branchUuid, String componentUuid) {
232 IssueDto dto = createBaseIssueDto(ruleDto, branchUuid, componentUuid);
236 private void insertIssue(IssueDto dto) {
237 dbTester.getDbClient().issueDao().insert(dbTester.getSession(), dto);
241 private IssueDto createBaseIssueDto(RuleDto ruleDto, String branchUuid, String componentUuid) {
242 return new IssueDto()
243 .setKee("issue_uuid_" + issueUuidGenerator++)
244 .setComponentUuid(componentUuid)
245 .setProjectUuid(branchUuid)
246 .setRuleUuid(ruleDto.getUuid())
250 private RuleDto insertExternalRule(String ruleName) {
251 RuleDto ruleDto = new RuleDto()
254 return insertRule(ruleName, ruleDto);
257 private RuleDto insertAddHocRule(String ruleName) {
258 RuleDto ruleDto = new RuleDto()
259 .setIsExternal(false)
261 .setCleanCodeAttribute(CleanCodeAttribute.CONVENTIONAL)
262 .addDefaultImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(org.sonar.api.issue.impact.Severity.MEDIUM))
263 .addDefaultImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(org.sonar.api.issue.impact.Severity.HIGH))
264 .setAdHocName("ad_hoc_rule" + RandomStringUtils.randomAlphabetic(10))
265 .setAdHocType(RuleType.VULNERABILITY)
266 .setAdHocSeverity(Severity.CRITICAL)
267 .setAdHocDescription("ad hoc description: " + RandomStringUtils.randomAlphanumeric(100));
268 return insertRule(ruleName, ruleDto);
271 private RuleDto insertStandardRule(String ruleName) {
272 RuleDto ruleDto = new RuleDto()
273 .setIsExternal(false)
275 return insertRule(ruleName, ruleDto);
278 private RuleDto insertRule(String ruleName, RuleDto partiallyInitRuleDto) {
279 RuleKey ruleKey = RuleKey.of("plugin1", ruleName);
281 .setName("ruleName" + RandomStringUtils.randomAlphanumeric(10))
283 .setPluginKey("pluginKey" + RandomStringUtils.randomAlphanumeric(10))
284 .setStatus(RuleStatus.READY)
285 .setScope(RuleDto.Scope.ALL);
287 dbTester.rules().insert(partiallyInitRuleDto);
289 return dbTester.getDbClient().ruleDao().selectByKey(dbTester.getSession(), ruleKey)
290 .orElseThrow(() -> new RuntimeException("insertAdHocRule failed"));
293 private static void assertProtobufAdHocRuleIsCorrectlyBuilt(ProjectDump.AdHocRule protobufAdHocRule, RuleDto source) {
294 assertThat(protobufAdHocRule.getName()).isEqualTo(source.getName());
295 assertThat(protobufAdHocRule.getRef()).isEqualTo(source.getUuid());
296 assertThat(protobufAdHocRule.getPluginKey()).isEqualTo(source.getPluginKey());
297 assertThat(protobufAdHocRule.getPluginRuleKey()).isEqualTo(source.getRuleKey());
298 assertThat(protobufAdHocRule.getPluginName()).isEqualTo(source.getRepositoryKey());
299 assertThat(protobufAdHocRule.getName()).isEqualTo(source.getName());
300 assertThat(protobufAdHocRule.getStatus()).isEqualTo(source.getStatus().name());
301 assertThat(protobufAdHocRule.getType()).isEqualTo(source.getType());
302 assertThat(protobufAdHocRule.getScope()).isEqualTo(source.getScope().name());
303 assertThat(protobufAdHocRule.getCleanCodeAttribute()).isEqualTo(source.getCleanCodeAttribute().name());
304 assertThat(toImpactMap(protobufAdHocRule.getImpactsList())).isEqualTo(toImpactMap(source.getDefaultImpacts()));
305 assertProtobufAdHocRuleIsCorrectlyBuilt(protobufAdHocRule.getMetadata(), source);
308 private static Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> toImpactMap(Set<ImpactDto> defaultImpacts) {
309 return defaultImpacts
310 .stream().collect(Collectors.toMap(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity));
313 private static Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> toImpactMap(List<ProjectDump.Impact> impactsList) {
314 return impactsList.stream()
315 .collect(Collectors.toMap(i -> SoftwareQuality.valueOf(i.getSoftwareQuality().name()),
316 i -> org.sonar.api.issue.impact.Severity.valueOf(i.getSeverity().name())));
319 private static void assertProtobufAdHocRuleIsCorrectlyBuilt(ProjectDump.AdHocRule.RuleMetadata metadata, RuleDto expected) {
320 assertThat(metadata.getAdHocName()).isEqualTo(expected.getAdHocName());
321 assertThat(metadata.getAdHocDescription()).isEqualTo(expected.getAdHocDescription());
322 assertThat(metadata.getAdHocSeverity()).isEqualTo(expected.getAdHocSeverity());
323 assertThat(metadata.getAdHocType()).isEqualTo(expected.getAdHocType());