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.issue;
22 import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
23 import com.tngtech.java.junit.dataprovider.DataProvider;
24 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
25 import com.tngtech.java.junit.dataprovider.UseDataProvider;
27 import java.io.IOException;
28 import java.net.URISyntaxException;
29 import java.util.List;
30 import java.util.Random;
31 import org.apache.commons.io.FileUtils;
32 import org.junit.After;
33 import org.junit.Before;
34 import org.junit.Rule;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 import org.slf4j.event.Level;
38 import org.sonar.api.issue.Issue;
39 import org.sonar.api.issue.impact.Severity;
40 import org.sonar.api.issue.impact.SoftwareQuality;
41 import org.sonar.api.rule.RuleKey;
42 import org.sonar.api.rule.RuleStatus;
43 import org.sonar.api.rules.RuleType;
44 import org.sonar.api.testfixtures.log.LogTester;
45 import org.sonar.api.utils.System2;
46 import org.sonar.ce.task.projectexport.component.ComponentRepositoryImpl;
47 import org.sonar.ce.task.projectexport.component.MutableComponentRepository;
48 import org.sonar.ce.task.projectexport.rule.RuleRepository;
49 import org.sonar.ce.task.projectexport.rule.RuleRepositoryImpl;
50 import org.sonar.ce.task.projectexport.steps.DumpElement;
51 import org.sonar.ce.task.projectexport.steps.FakeDumpWriter;
52 import org.sonar.ce.task.projectexport.steps.ProjectHolder;
53 import org.sonar.ce.task.step.TestComputationStepContext;
54 import org.sonar.db.DbClient;
55 import org.sonar.db.DbSession;
56 import org.sonar.db.DbTester;
57 import org.sonar.db.component.BranchDto;
58 import org.sonar.db.component.BranchType;
59 import org.sonar.db.component.ComponentDto;
60 import org.sonar.db.issue.ImpactDto;
61 import org.sonar.db.issue.IssueDto;
62 import org.sonar.db.project.ProjectDto;
63 import org.sonar.db.protobuf.DbIssues;
64 import org.sonar.db.protobuf.DbIssues.Locations;
65 import org.sonar.db.protobuf.DbIssues.MessageFormattingType;
66 import org.sonar.db.rule.RuleDto;
67 import org.sonar.db.rule.RuleDto.Scope;
69 import static com.google.common.collect.Lists.newArrayList;
70 import static org.assertj.core.api.Assertions.assertThat;
71 import static org.assertj.core.api.Assertions.assertThatThrownBy;
72 import static org.assertj.core.api.Assertions.tuple;
73 import static org.mockito.Mockito.mock;
74 import static org.mockito.Mockito.when;
75 import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
76 import static org.sonar.api.issue.Issue.STATUS_OPEN;
77 import static org.sonar.api.issue.Issue.STATUS_REOPENED;
78 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
80 @RunWith(DataProviderRunner.class)
81 public class ExportIssuesStepIT {
82 private static final String SOME_PROJECT_UUID = "project uuid";
83 private static final String PROJECT_KEY = "projectkey";
84 private static final String SOME_REPO = "rule repo";
85 private static final String READY_RULE_KEY = "rule key 1";
86 public static final DbIssues.MessageFormatting MESSAGE_FORMATTING = DbIssues.MessageFormatting.newBuilder().setStart(0).setEnd(4).setType(MessageFormattingType.CODE).build();
89 public DbTester dbTester = DbTester.create(System2.INSTANCE);
91 public LogTester logTester = new LogTester();
93 private final DbClient dbClient = dbTester.getDbClient();
94 private final DbSession dbSession = dbClient.openSession(false);
95 private final ProjectHolder projectHolder = mock(ProjectHolder.class);
96 private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
97 private final RuleRepository ruleRepository = new RuleRepositoryImpl();
98 private final MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
100 private final ExportIssuesStep underTest = new ExportIssuesStep(dbClient, projectHolder, dumpWriter, ruleRepository, componentRepository);
102 private RuleDto readyRuleDto;
105 public void setUp() {
106 logTester.setLevel(Level.DEBUG);
107 ProjectDto project = createProject();
108 when(projectHolder.projectDto()).thenReturn(project);
109 when(projectHolder.branches()).thenReturn(newArrayList(
110 new BranchDto().setBranchType(BranchType.BRANCH).setKey("master").setProjectUuid(SOME_PROJECT_UUID).setUuid(SOME_PROJECT_UUID)));
112 // adds a random number of Rules to db and repository so that READY_RULE_KEY does always get id=ref=1
113 for (int i = 0; i < new Random().nextInt(150); i++) {
114 RuleKey ruleKey = RuleKey.of("repo_" + i, "key_" + i);
115 RuleDto ruleDto = insertRule(ruleKey.toString());
116 ruleRepository.register(ruleDto.getUuid(), ruleKey);
118 this.readyRuleDto = insertRule(READY_RULE_KEY);
119 componentRepository.register(12, SOME_PROJECT_UUID, false);
123 public void tearDown() {
128 public void getDescription_is_set() {
129 assertThat(underTest.getDescription()).isEqualTo("Export issues");
133 public void execute_written_writes_no_issues_when_project_has_no_issues() {
134 underTest.execute(new TestComputationStepContext());
136 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
140 public void execute_written_writes_no_issues_when_project_has_only_CLOSED_issues() {
141 insertIssue(readyRuleDto, SOME_PROJECT_UUID, Issue.STATUS_CLOSED);
143 underTest.execute(new TestComputationStepContext());
145 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
149 public void execute_fails_with_ISE_if_componentUuid_is_not_set() {
150 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setComponentUuid(null));
152 assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
153 .isInstanceOf(IllegalStateException.class)
154 .hasMessage("Issue export failed after processing 0 issues successfully")
155 .hasRootCauseInstanceOf(NullPointerException.class)
156 .hasRootCauseMessage("uuid can not be null");
160 public static Object[][] allStatusesButCLOSED() {
161 return new Object[][] {
170 @UseDataProvider("allStatusesButCLOSED")
171 public void execute_writes_issues_with_any_status_but_CLOSED(String status) {
172 String uuid = insertIssue(readyRuleDto, SOME_PROJECT_UUID, status).getKey();
174 underTest.execute(new TestComputationStepContext());
176 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
177 .extracting(ProjectDump.Issue::getUuid)
182 public void execute_writes_issues_from_any_component_in_project_are_written() {
183 componentRepository.register(13, "module uuid", false);
184 componentRepository.register(14, "dir uuid", false);
185 componentRepository.register(15, "file uuid", false);
186 String projectIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID)).getKey();
187 String moduleIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "module uuid")).getKey();
188 String dirIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "dir uuid")).getKey();
189 String fileIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "file uuid")).getKey();
191 underTest.execute(new TestComputationStepContext());
193 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
194 .extracting(ProjectDump.Issue::getUuid)
195 .containsOnly(projectIssueUuid, moduleIssueUuid, dirIssueUuid, fileIssueUuid);
199 public void execute_ignores_issues_of_other_projects() {
200 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setProjectUuid("other project"));
201 String projectIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID)).getKey();
203 underTest.execute(new TestComputationStepContext());
205 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
206 .extracting(ProjectDump.Issue::getUuid)
207 .containsOnly(projectIssueUuid);
211 public void verify_field_by_field_mapping() {
212 String componentUuid = "component uuid";
213 long componentRef = 5454;
214 componentRepository.register(componentRef, componentUuid, false);
215 DbIssues.MessageFormattings messageFormattings = DbIssues.MessageFormattings.newBuilder().addMessageFormatting(MESSAGE_FORMATTING).build();
216 IssueDto issueDto = new IssueDto()
217 .setKee("issue uuid")
218 .setComponentUuid(componentUuid)
221 .setMessageFormattings(messageFormattings)
223 .setChecksum("checksum")
224 .setResolution("resolution")
225 .setSeverity("severity")
226 .setManualSeverity(true)
229 .setAssigneeUuid("assignee-uuid")
230 .setAuthorLogin("author")
231 .setTagsString("tags")
232 .setRuleDescriptionContextKey("test_rule_description_context_key")
233 .setIssueCreationTime(963L)
234 .setIssueUpdateTime(852L)
235 .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH))
236 .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.BLOCKER))
237 .setIssueCloseTime(741L)
238 .setCodeVariants(List.of("v1", "v2"));
240 // fields tested separately and/or required to match SQL request
242 .setType(RuleType.CODE_SMELL)
243 .setLocations(Locations.newBuilder().addFlow(DbIssues.Flow.newBuilder()).build())
244 .setRuleUuid(readyRuleDto.getUuid())
245 .setStatus(STATUS_OPEN).setProjectUuid(SOME_PROJECT_UUID);
247 insertIssue(issueDto);
249 underTest.execute(new TestComputationStepContext());
251 ProjectDump.Issue issue = getWrittenIssue();
253 assertThat(issue.getUuid()).isEqualTo(issueDto.getKey());
254 assertThat(issue.getComponentRef()).isEqualTo(componentRef);
255 assertThat(issue.getType()).isEqualTo(issueDto.getType());
256 assertThat(issue.getMessage()).isEqualTo(issueDto.getMessage());
257 assertThat(issue.getLine()).isEqualTo(issueDto.getLine());
258 assertThat(issue.getChecksum()).isEqualTo(issueDto.getChecksum());
259 assertThat(issue.getStatus()).isEqualTo(issueDto.getStatus());
260 assertThat(issue.getResolution()).isEqualTo(issueDto.getResolution());
261 assertThat(issue.getSeverity()).isEqualTo(issueDto.getSeverity());
262 assertThat(issue.getManualSeverity()).isEqualTo(issueDto.isManualSeverity());
263 assertThat(issue.getGap()).isEqualTo(issueDto.getGap());
264 assertThat(issue.getEffort()).isEqualTo(issueDto.getEffort());
265 assertThat(issue.getAssignee()).isEqualTo(issueDto.getAssigneeUuid());
266 assertThat(issue.getAuthor()).isEqualTo(issueDto.getAuthorLogin());
267 assertThat(issue.getTags()).isEqualTo(issueDto.getTagsString());
268 assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issue.getRuleDescriptionContextKey());
269 assertThat(issue.getIssueCreatedAt()).isEqualTo(issueDto.getIssueCreationTime());
270 assertThat(issue.getIssueUpdatedAt()).isEqualTo(issueDto.getIssueUpdateTime());
271 assertThat(issue.getIssueClosedAt()).isEqualTo(issueDto.getIssueCloseTime());
272 assertThat(issue.getLocations()).isNotEmpty();
273 assertThat(issue.getImpactsList()).extracting(ProjectDump.Impact::getSoftwareQuality, ProjectDump.Impact::getSeverity)
274 .containsOnly(tuple(ProjectDump.SoftwareQuality.MAINTAINABILITY, ProjectDump.Severity.HIGH), tuple(ProjectDump.SoftwareQuality.SECURITY, ProjectDump.Severity.BLOCKER));
275 assertThat(issue.getMessageFormattingsList())
276 .isEqualTo(ExportIssuesStep.dbToDumpMessageFormatting(messageFormattings.getMessageFormattingList()));
277 assertThat(issue.getCodeVariants()).isEqualTo(issueDto.getCodeVariantsString());
281 public void verify_two_issues_are_exported_including_one_without_software_quality() {
282 IssueDto issueDto = createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID);
283 IssueDto issueDto2 = createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID);
285 .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH))
286 .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.LOW));
288 insertIssue(issueDto);
289 insertIssue(issueDto2);
291 underTest.execute(new TestComputationStepContext());
292 List<ProjectDump.Issue> issuesInReport = dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES);
294 assertThat(issuesInReport).hasSize(2);
295 assertThat(issuesInReport).filteredOn(i -> i.getImpactsList().size() == 2).hasSize(1);
296 assertThat(issuesInReport).filteredOn(i -> i.getImpactsList().isEmpty()).hasSize(1);
300 public void verify_mapping_of_nullable_numerical_fields_to_defaultValue() {
301 insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
303 underTest.execute(new TestComputationStepContext());
305 ProjectDump.Issue issue = getWrittenIssue();
307 assertThat(issue.getLine()).isEqualTo(DumpElement.ISSUES.NO_LINE);
308 assertThat(issue.getGap()).isEqualTo(DumpElement.ISSUES.NO_GAP);
309 assertThat(issue.getEffort()).isEqualTo(DumpElement.ISSUES.NO_EFFORT);
310 assertThat(issue.getIssueCreatedAt()).isEqualTo(DumpElement.NO_DATETIME);
311 assertThat(issue.getIssueUpdatedAt()).isEqualTo(DumpElement.NO_DATETIME);
312 assertThat(issue.getIssueClosedAt()).isEqualTo(DumpElement.NO_DATETIME);
313 assertThat(issue.hasRuleDescriptionContextKey()).isFalse();
317 public void ruleRef_is_ref_provided_by_RuleRepository() {
319 IssueDto issueDto = insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
321 underTest.execute(new TestComputationStepContext());
323 ProjectDump.Issue issue = getWrittenIssue();
324 assertThat(issue.getRuleRef())
325 .isEqualTo(ruleRepository.register(issueDto.getRuleUuid(), readyRuleDto.getKey()).ref());
329 public void locations_is_not_set_in_protobuf_if_null_in_DB() {
330 insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
332 underTest.execute(new TestComputationStepContext());
334 assertThat(getWrittenIssue().getLocations()).isEmpty();
338 public void message_formattings_is_empty_in_protobuf_if_null_in_DB() {
339 insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
341 underTest.execute(new TestComputationStepContext());
343 assertThat(getWrittenIssue().getMessageFormattingsList()).isEmpty();
347 public void execute_fails_with_ISE_if_locations_cannot_be_parsed_to_protobuf() throws URISyntaxException, IOException {
348 byte[] rubbishBytes = getRubbishBytes();
349 String uuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(rubbishBytes)).getKey();
351 assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
352 .isInstanceOf(IllegalStateException.class)
353 .hasMessage("Issue export failed after processing 0 issues successfully");
357 public void execute_logs_number_total_exported_issue_count_when_successful() {
358 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
359 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
360 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
362 underTest.execute(new TestComputationStepContext());
364 assertThat(logTester.logs(Level.DEBUG)).contains("3 issues exported");
368 public void execute_throws_ISE_with_number_of_successful_exports_before_failure() throws URISyntaxException, IOException {
369 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
370 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
371 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(getRubbishBytes())).getKey();
373 assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
374 .isInstanceOf(IllegalStateException.class)
375 .hasMessage("Issue export failed after processing 2 issues successfully");
378 private byte[] getRubbishBytes() throws IOException, URISyntaxException {
379 return FileUtils.readFileToByteArray(new File(getClass().getResource("rubbish_data.txt").toURI()));
382 private ProjectDump.Issue getWrittenIssue() {
383 return dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES).get(0);
386 // private void expectExportFailure() {
387 // expectExportFailure(0);
390 // private void expectExportFailure(int i) {
391 // expectedException.expect(IllegalStateException.class);
392 // expectedException.expectMessage("Issue export failed after processing " + i + " issues successfully");
395 private int issueUuidGenerator = 1;
397 private IssueDto insertIssue(RuleDto ruleDto, String componentUuid, String status) {
398 IssueDto dto = createBaseIssueDto(ruleDto, componentUuid, status);
399 return insertIssue(dto);
402 private IssueDto insertIssue(IssueDto dto) {
403 dbClient.issueDao().insert(dbSession, dto);
408 private ProjectDto createProject() {
409 ComponentDto projectDto = dbTester.components().insertPrivateProject(c -> c.setKey(PROJECT_KEY).setUuid(SOME_PROJECT_UUID)).getMainBranchComponent();
411 return dbTester.components().getProjectDtoByMainBranch(projectDto);
414 private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid) {
415 return createBaseIssueDto(ruleDto, componentUuid, STATUS_OPEN);
418 private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid, String status) {
419 return new IssueDto()
420 .setKee("issue_uuid_" + issueUuidGenerator++)
421 .setComponentUuid(componentUuid)
422 .setProjectUuid(SOME_PROJECT_UUID)
423 .setRuleUuid(ruleDto.getUuid())
424 .setCreatedAt(System2.INSTANCE.now())
428 private RuleDto insertRule(String ruleKey1) {
429 RuleDto dto = new RuleDto().setRepositoryKey(SOME_REPO).setScope(Scope.MAIN).setRuleKey(ruleKey1).setStatus(RuleStatus.READY);
430 dbTester.rules().insert(dto);