3 * Copyright (C) 2009-2022 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.Random;
30 import org.apache.commons.io.FileUtils;
31 import org.junit.After;
32 import org.junit.Before;
33 import org.junit.Rule;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.sonar.api.issue.Issue;
37 import org.sonar.api.rule.RuleKey;
38 import org.sonar.api.rule.RuleStatus;
39 import org.sonar.api.rules.RuleType;
40 import org.sonar.api.utils.System2;
41 import org.sonar.api.utils.log.LogTester;
42 import org.sonar.api.utils.log.LoggerLevel;
43 import org.sonar.ce.task.projectexport.component.ComponentRepositoryImpl;
44 import org.sonar.ce.task.projectexport.component.MutableComponentRepository;
45 import org.sonar.ce.task.projectexport.rule.RuleRepository;
46 import org.sonar.ce.task.projectexport.rule.RuleRepositoryImpl;
47 import org.sonar.ce.task.projectexport.steps.DumpElement;
48 import org.sonar.ce.task.projectexport.steps.FakeDumpWriter;
49 import org.sonar.ce.task.projectexport.steps.ProjectHolder;
50 import org.sonar.ce.task.step.TestComputationStepContext;
51 import org.sonar.db.DbClient;
52 import org.sonar.db.DbSession;
53 import org.sonar.db.DbTester;
54 import org.sonar.db.component.BranchDto;
55 import org.sonar.db.component.BranchType;
56 import org.sonar.db.component.ComponentDto;
57 import org.sonar.db.issue.IssueDto;
58 import org.sonar.db.project.ProjectDto;
59 import org.sonar.db.protobuf.DbIssues;
60 import org.sonar.db.protobuf.DbIssues.Locations;
61 import org.sonar.db.rule.RuleDto;
62 import org.sonar.db.rule.RuleDto.Scope;
64 import static com.google.common.collect.Lists.newArrayList;
65 import static org.assertj.core.api.Assertions.assertThat;
66 import static org.assertj.core.api.Assertions.assertThatThrownBy;
67 import static org.mockito.Mockito.mock;
68 import static org.mockito.Mockito.when;
69 import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
70 import static org.sonar.api.issue.Issue.STATUS_OPEN;
71 import static org.sonar.api.issue.Issue.STATUS_REOPENED;
72 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
74 @RunWith(DataProviderRunner.class)
75 public class ExportIssuesStepTest {
76 private static final String SOME_PROJECT_UUID = "project uuid";
77 private static final String PROJECT_KEY = "projectkey";
78 private static final String SOME_REPO = "rule repo";
79 private static final String READY_RULE_KEY = "rule key 1";
82 public DbTester dbTester = DbTester.create(System2.INSTANCE);
84 public LogTester logTester = new LogTester();
86 private final DbClient dbClient = dbTester.getDbClient();
87 private final DbSession dbSession = dbClient.openSession(false);
88 private final ProjectHolder projectHolder = mock(ProjectHolder.class);
89 private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
90 private final RuleRepository ruleRepository = new RuleRepositoryImpl();
91 private final MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
93 private final ExportIssuesStep underTest = new ExportIssuesStep(dbClient, projectHolder, dumpWriter, ruleRepository, componentRepository);
95 private RuleDto readyRuleDto;
99 ProjectDto project = createProject();
100 when(projectHolder.projectDto()).thenReturn(project);
101 when(projectHolder.branches()).thenReturn(newArrayList(
102 new BranchDto().setBranchType(BranchType.BRANCH).setKey("master").setProjectUuid(SOME_PROJECT_UUID).setUuid(SOME_PROJECT_UUID)));
104 // adds a random number of Rules to db and repository so that READY_RULE_KEY does always get id=ref=1
105 for (int i = 0; i < new Random().nextInt(150); i++) {
106 RuleKey ruleKey = RuleKey.of("repo_" + i, "key_" + i);
107 RuleDto ruleDto = insertRule(ruleKey.toString());
108 ruleRepository.register(ruleDto.getUuid(), ruleKey);
110 this.readyRuleDto = insertRule(READY_RULE_KEY);
111 componentRepository.register(12, SOME_PROJECT_UUID, false);
115 public void tearDown() {
120 public void getDescription_is_set() {
121 assertThat(underTest.getDescription()).isEqualTo("Export issues");
125 public void execute_written_writes_no_issues_when_project_has_no_issues() {
126 underTest.execute(new TestComputationStepContext());
128 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
132 public void execute_written_writes_no_issues_when_project_has_only_CLOSED_issues() {
133 insertIssue(readyRuleDto, SOME_PROJECT_UUID, Issue.STATUS_CLOSED);
135 underTest.execute(new TestComputationStepContext());
137 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
141 public void execute_fails_with_ISE_if_componentUuid_is_not_set() {
142 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setComponentUuid(null));
144 assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
145 .isInstanceOf(IllegalStateException.class)
146 .hasMessage("Issue export failed after processing 0 issues successfully")
147 .hasRootCauseInstanceOf(NullPointerException.class)
148 .hasRootCauseMessage("uuid can not be null");
152 public static Object[][] allStatusesButCLOSED() {
153 return new Object[][] {
162 @UseDataProvider("allStatusesButCLOSED")
163 public void execute_writes_issues_with_any_status_but_CLOSED(String status) {
164 String uuid = insertIssue(readyRuleDto, SOME_PROJECT_UUID, status).getKey();
166 underTest.execute(new TestComputationStepContext());
168 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
169 .extracting(ProjectDump.Issue::getUuid)
174 public void execute_writes_issues_from_any_component_in_project_are_written() {
175 componentRepository.register(13, "module uuid", false);
176 componentRepository.register(14, "dir uuid", false);
177 componentRepository.register(15, "file uuid", false);
178 String projectIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID)).getKey();
179 String moduleIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "module uuid")).getKey();
180 String dirIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "dir uuid")).getKey();
181 String fileIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "file uuid")).getKey();
183 underTest.execute(new TestComputationStepContext());
185 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
186 .extracting(ProjectDump.Issue::getUuid)
187 .containsOnly(projectIssueUuid, moduleIssueUuid, dirIssueUuid, fileIssueUuid);
191 public void execute_ignores_issues_of_other_projects() {
192 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setProjectUuid("other project"));
193 String projectIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID)).getKey();
195 underTest.execute(new TestComputationStepContext());
197 assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
198 .extracting(ProjectDump.Issue::getUuid)
199 .containsOnly(projectIssueUuid);
203 public void verify_field_by_field_mapping() {
204 String componentUuid = "component uuid";
205 long componentRef = 5454;
206 componentRepository.register(componentRef, componentUuid, false);
207 IssueDto issueDto = new IssueDto()
208 .setKee("issue uuid")
209 .setComponentUuid(componentUuid)
213 .setChecksum("checksum")
214 .setResolution("resolution")
215 .setSeverity("severity")
216 .setManualSeverity(true)
219 .setAssigneeUuid("assignee-uuid")
220 .setAuthorLogin("author")
221 .setTagsString("tags")
222 .setIssueAttributes("attributes")
223 .setIssueCreationTime(963L)
224 .setIssueUpdateTime(852L)
225 .setIssueCloseTime(741L);
227 // fields tested separately and/or required to match SQL request
229 .setType(RuleType.CODE_SMELL)
230 .setLocations(Locations.newBuilder().addFlow(DbIssues.Flow.newBuilder()).build())
231 .setRuleUuid(readyRuleDto.getUuid())
232 .setStatus(STATUS_OPEN).setProjectUuid(SOME_PROJECT_UUID);
234 insertIssue(issueDto);
236 underTest.execute(new TestComputationStepContext());
238 ProjectDump.Issue issue = getWrittenIssue();
240 assertThat(issue.getUuid()).isEqualTo(issueDto.getKey());
241 assertThat(issue.getComponentRef()).isEqualTo(componentRef);
242 assertThat(issue.getType()).isEqualTo(issueDto.getType());
243 assertThat(issue.getMessage()).isEqualTo(issueDto.getMessage());
244 assertThat(issue.getLine()).isEqualTo(issueDto.getLine());
245 assertThat(issue.getChecksum()).isEqualTo(issueDto.getChecksum());
246 assertThat(issue.getStatus()).isEqualTo(issueDto.getStatus());
247 assertThat(issue.getResolution()).isEqualTo(issueDto.getResolution());
248 assertThat(issue.getSeverity()).isEqualTo(issueDto.getSeverity());
249 assertThat(issue.getManualSeverity()).isEqualTo(issueDto.isManualSeverity());
250 assertThat(issue.getGap()).isEqualTo(issueDto.getGap());
251 assertThat(issue.getEffort()).isEqualTo(issueDto.getEffort());
252 assertThat(issue.getAssignee()).isEqualTo(issueDto.getAssigneeUuid());
253 assertThat(issue.getAuthor()).isEqualTo(issueDto.getAuthorLogin());
254 assertThat(issue.getTags()).isEqualTo(issueDto.getTagsString());
255 assertThat(issue.getAttributes()).isEqualTo(issueDto.getIssueAttributes());
256 assertThat(issue.getIssueCreatedAt()).isEqualTo(issueDto.getIssueCreationTime());
257 assertThat(issue.getIssueUpdatedAt()).isEqualTo(issueDto.getIssueUpdateTime());
258 assertThat(issue.getIssueClosedAt()).isEqualTo(issueDto.getIssueCloseTime());
259 assertThat(issue.getLocations()).isNotEmpty();
263 public void verify_mapping_of_nullable_numerical_fields_to_defaultValue() {
264 insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
266 underTest.execute(new TestComputationStepContext());
268 ProjectDump.Issue issue = getWrittenIssue();
270 assertThat(issue.getLine()).isEqualTo(DumpElement.ISSUES.NO_LINE);
271 assertThat(issue.getGap()).isEqualTo(DumpElement.ISSUES.NO_GAP);
272 assertThat(issue.getEffort()).isEqualTo(DumpElement.ISSUES.NO_EFFORT);
273 assertThat(issue.getIssueCreatedAt()).isEqualTo(DumpElement.NO_DATETIME);
274 assertThat(issue.getIssueUpdatedAt()).isEqualTo(DumpElement.NO_DATETIME);
275 assertThat(issue.getIssueClosedAt()).isEqualTo(DumpElement.NO_DATETIME);
279 public void ruleRef_is_ref_provided_by_RuleRepository() {
281 IssueDto issueDto = insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
283 underTest.execute(new TestComputationStepContext());
285 ProjectDump.Issue issue = getWrittenIssue();
286 assertThat(issue.getRuleRef())
287 .isEqualTo(ruleRepository.register(issueDto.getRuleUuid(), readyRuleDto.getKey()).getRef());
291 public void locations_is_not_set_in_protobuf_if_null_in_DB() {
292 insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
294 underTest.execute(new TestComputationStepContext());
296 assertThat(getWrittenIssue().getLocations()).isEmpty();
300 public void execute_fails_with_ISE_if_locations_cannot_be_parsed_to_protobuf() throws URISyntaxException, IOException {
301 byte[] rubbishBytes = getRubbishBytes();
302 String uuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(rubbishBytes)).getKey();
304 assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
305 .isInstanceOf(IllegalStateException.class)
306 .hasMessage("Issue export failed after processing 0 issues successfully");
310 public void execute_logs_number_total_exported_issue_count_when_successful() {
311 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
312 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
313 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
315 underTest.execute(new TestComputationStepContext());
317 assertThat(logTester.logs(LoggerLevel.DEBUG)).containsExactly("3 issues exported");
321 public void execute_throws_ISE_with_number_of_successful_exports_before_failure() throws URISyntaxException, IOException {
322 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
323 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
324 insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(getRubbishBytes())).getKey();
326 assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
327 .isInstanceOf(IllegalStateException.class)
328 .hasMessage("Issue export failed after processing 2 issues successfully");
331 private byte[] getRubbishBytes() throws IOException, URISyntaxException {
332 return FileUtils.readFileToByteArray(new File(getClass().getResource("rubbish_data.txt").toURI()));
335 private ProjectDump.Issue getWrittenIssue() {
336 return dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES).get(0);
339 // private void expectExportFailure() {
340 // expectExportFailure(0);
343 // private void expectExportFailure(int i) {
344 // expectedException.expect(IllegalStateException.class);
345 // expectedException.expectMessage("Issue export failed after processing " + i + " issues successfully");
348 private int issueUuidGenerator = 1;
350 private IssueDto insertIssue(RuleDto ruleDto, String componentUuid, String status) {
351 IssueDto dto = createBaseIssueDto(ruleDto, componentUuid, status);
352 return insertIssue(dto);
355 private IssueDto insertIssue(IssueDto dto) {
356 dbClient.issueDao().insert(dbSession, dto);
361 private ProjectDto createProject() {
362 ComponentDto projectDto = dbTester.components().insertPrivateProject(c -> c.setDbKey(PROJECT_KEY).setUuid(SOME_PROJECT_UUID));
364 return dbTester.components().getProjectDto(projectDto);
367 private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid) {
368 return createBaseIssueDto(ruleDto, componentUuid, STATUS_OPEN);
371 private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid, String status) {
372 return new IssueDto()
373 .setKee("issue_uuid_" + issueUuidGenerator++)
374 .setComponentUuid(componentUuid)
375 .setProjectUuid(SOME_PROJECT_UUID)
376 .setRuleUuid(ruleDto.getUuid())
377 .setCreatedAt(System2.INSTANCE.now())
381 private RuleDto insertRule(String ruleKey1) {
382 RuleDto dto = new RuleDto().setRepositoryKey(SOME_REPO).setScope(Scope.MAIN).setRuleKey(ruleKey1).setStatus(RuleStatus.READY);
383 dbTester.rules().insert(dto.getDefinition());