]> source.dussan.org Git - sonarqube.git/blob
8b39c16f368ec153debc9a7c67a2f7e8302c78db
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2022 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.ce.task.projectexport.issue;
21
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;
26 import java.io.File;
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;
63
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;
73
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";
80
81   @Rule
82   public DbTester dbTester = DbTester.create(System2.INSTANCE);
83   @Rule
84   public LogTester logTester = new LogTester();
85
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();
92
93   private final ExportIssuesStep underTest = new ExportIssuesStep(dbClient, projectHolder, dumpWriter, ruleRepository, componentRepository);
94
95   private RuleDto readyRuleDto;
96
97   @Before
98   public void setUp() {
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)));
103
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);
109     }
110     this.readyRuleDto = insertRule(READY_RULE_KEY);
111     componentRepository.register(12, SOME_PROJECT_UUID, false);
112   }
113
114   @After
115   public void tearDown() {
116     dbSession.close();
117   }
118
119   @Test
120   public void getDescription_is_set() {
121     assertThat(underTest.getDescription()).isEqualTo("Export issues");
122   }
123
124   @Test
125   public void execute_written_writes_no_issues_when_project_has_no_issues() {
126     underTest.execute(new TestComputationStepContext());
127
128     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
129   }
130
131   @Test
132   public void execute_written_writes_no_issues_when_project_has_only_CLOSED_issues() {
133     insertIssue(readyRuleDto, SOME_PROJECT_UUID, Issue.STATUS_CLOSED);
134
135     underTest.execute(new TestComputationStepContext());
136
137     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
138   }
139
140   @Test
141   public void execute_fails_with_ISE_if_componentUuid_is_not_set() {
142     insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setComponentUuid(null));
143
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");
149   }
150
151   @DataProvider
152   public static Object[][] allStatusesButCLOSED() {
153     return new Object[][] {
154       {STATUS_OPEN},
155       {STATUS_CONFIRMED},
156       {STATUS_REOPENED},
157       {STATUS_RESOLVED}
158     };
159   }
160
161   @Test
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();
165
166     underTest.execute(new TestComputationStepContext());
167
168     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
169       .extracting(ProjectDump.Issue::getUuid)
170       .containsOnly(uuid);
171   }
172
173   @Test
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();
182
183     underTest.execute(new TestComputationStepContext());
184
185     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
186       .extracting(ProjectDump.Issue::getUuid)
187       .containsOnly(projectIssueUuid, moduleIssueUuid, dirIssueUuid, fileIssueUuid);
188   }
189
190   @Test
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();
194
195     underTest.execute(new TestComputationStepContext());
196
197     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
198       .extracting(ProjectDump.Issue::getUuid)
199       .containsOnly(projectIssueUuid);
200   }
201
202   @Test
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)
210       .setType(988)
211       .setMessage("msg")
212       .setLine(10)
213       .setChecksum("checksum")
214       .setResolution("resolution")
215       .setSeverity("severity")
216       .setManualSeverity(true)
217       .setGap(13.13d)
218       .setEffort(99L)
219       .setAssigneeUuid("assignee-uuid")
220       .setAuthorLogin("author")
221       .setTagsString("tags")
222       .setIssueAttributes("attributes")
223       .setIssueCreationTime(963L)
224       .setIssueUpdateTime(852L)
225       .setIssueCloseTime(741L);
226
227     // fields tested separately and/or required to match SQL request
228     issueDto
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);
233
234     insertIssue(issueDto);
235
236     underTest.execute(new TestComputationStepContext());
237
238     ProjectDump.Issue issue = getWrittenIssue();
239
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();
260   }
261
262   @Test
263   public void verify_mapping_of_nullable_numerical_fields_to_defaultValue() {
264     insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
265
266     underTest.execute(new TestComputationStepContext());
267
268     ProjectDump.Issue issue = getWrittenIssue();
269
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);
276   }
277
278   @Test
279   public void ruleRef_is_ref_provided_by_RuleRepository() {
280
281     IssueDto issueDto = insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
282
283     underTest.execute(new TestComputationStepContext());
284
285     ProjectDump.Issue issue = getWrittenIssue();
286     assertThat(issue.getRuleRef())
287       .isEqualTo(ruleRepository.register(issueDto.getRuleUuid(), readyRuleDto.getKey()).getRef());
288   }
289
290   @Test
291   public void locations_is_not_set_in_protobuf_if_null_in_DB() {
292     insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
293
294     underTest.execute(new TestComputationStepContext());
295
296     assertThat(getWrittenIssue().getLocations()).isEmpty();
297   }
298
299   @Test
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();
303
304     assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
305       .isInstanceOf(IllegalStateException.class)
306       .hasMessage("Issue export failed after processing 0 issues successfully");
307   }
308
309   @Test
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));
314
315     underTest.execute(new TestComputationStepContext());
316
317     assertThat(logTester.logs(LoggerLevel.DEBUG)).containsExactly("3 issues exported");
318   }
319
320   @Test
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();
325
326     assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
327       .isInstanceOf(IllegalStateException.class)
328       .hasMessage("Issue export failed after processing 2 issues successfully");
329   }
330
331   private byte[] getRubbishBytes() throws IOException, URISyntaxException {
332     return FileUtils.readFileToByteArray(new File(getClass().getResource("rubbish_data.txt").toURI()));
333   }
334
335   private ProjectDump.Issue getWrittenIssue() {
336     return dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES).get(0);
337   }
338
339 //  private void expectExportFailure() {
340 //    expectExportFailure(0);
341 //  }
342
343 //  private void expectExportFailure(int i) {
344 //    expectedException.expect(IllegalStateException.class);
345 //    expectedException.expectMessage("Issue export failed after processing " + i + " issues successfully");
346 //  }
347
348   private int issueUuidGenerator = 1;
349
350   private IssueDto insertIssue(RuleDto ruleDto, String componentUuid, String status) {
351     IssueDto dto = createBaseIssueDto(ruleDto, componentUuid, status);
352     return insertIssue(dto);
353   }
354
355   private IssueDto insertIssue(IssueDto dto) {
356     dbClient.issueDao().insert(dbSession, dto);
357     dbSession.commit();
358     return dto;
359   }
360
361   private ProjectDto createProject() {
362     ComponentDto projectDto = dbTester.components().insertPrivateProject(c -> c.setDbKey(PROJECT_KEY).setUuid(SOME_PROJECT_UUID));
363     dbTester.commit();
364     return dbTester.components().getProjectDto(projectDto);
365   }
366
367   private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid) {
368     return createBaseIssueDto(ruleDto, componentUuid, STATUS_OPEN);
369   }
370
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())
378       .setStatus(status);
379   }
380
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());
384     dbSession.commit();
385     return dto;
386   }
387 }