]> source.dussan.org Git - sonarqube.git/blob
0eccab91c481ab88fb9267ab9e3a1cc494edc179
[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       .setIssueCreationTime(963L)
223       .setIssueUpdateTime(852L)
224       .setIssueCloseTime(741L);
225
226     // fields tested separately and/or required to match SQL request
227     issueDto
228       .setType(RuleType.CODE_SMELL)
229       .setLocations(Locations.newBuilder().addFlow(DbIssues.Flow.newBuilder()).build())
230       .setRuleUuid(readyRuleDto.getUuid())
231       .setStatus(STATUS_OPEN).setProjectUuid(SOME_PROJECT_UUID);
232
233     insertIssue(issueDto);
234
235     underTest.execute(new TestComputationStepContext());
236
237     ProjectDump.Issue issue = getWrittenIssue();
238
239     assertThat(issue.getUuid()).isEqualTo(issueDto.getKey());
240     assertThat(issue.getComponentRef()).isEqualTo(componentRef);
241     assertThat(issue.getType()).isEqualTo(issueDto.getType());
242     assertThat(issue.getMessage()).isEqualTo(issueDto.getMessage());
243     assertThat(issue.getLine()).isEqualTo(issueDto.getLine());
244     assertThat(issue.getChecksum()).isEqualTo(issueDto.getChecksum());
245     assertThat(issue.getStatus()).isEqualTo(issueDto.getStatus());
246     assertThat(issue.getResolution()).isEqualTo(issueDto.getResolution());
247     assertThat(issue.getSeverity()).isEqualTo(issueDto.getSeverity());
248     assertThat(issue.getManualSeverity()).isEqualTo(issueDto.isManualSeverity());
249     assertThat(issue.getGap()).isEqualTo(issueDto.getGap());
250     assertThat(issue.getEffort()).isEqualTo(issueDto.getEffort());
251     assertThat(issue.getAssignee()).isEqualTo(issueDto.getAssigneeUuid());
252     assertThat(issue.getAuthor()).isEqualTo(issueDto.getAuthorLogin());
253     assertThat(issue.getTags()).isEqualTo(issueDto.getTagsString());
254     assertThat(issue.getIssueCreatedAt()).isEqualTo(issueDto.getIssueCreationTime());
255     assertThat(issue.getIssueUpdatedAt()).isEqualTo(issueDto.getIssueUpdateTime());
256     assertThat(issue.getIssueClosedAt()).isEqualTo(issueDto.getIssueCloseTime());
257     assertThat(issue.getLocations()).isNotEmpty();
258   }
259
260   @Test
261   public void verify_mapping_of_nullable_numerical_fields_to_defaultValue() {
262     insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
263
264     underTest.execute(new TestComputationStepContext());
265
266     ProjectDump.Issue issue = getWrittenIssue();
267
268     assertThat(issue.getLine()).isEqualTo(DumpElement.ISSUES.NO_LINE);
269     assertThat(issue.getGap()).isEqualTo(DumpElement.ISSUES.NO_GAP);
270     assertThat(issue.getEffort()).isEqualTo(DumpElement.ISSUES.NO_EFFORT);
271     assertThat(issue.getIssueCreatedAt()).isEqualTo(DumpElement.NO_DATETIME);
272     assertThat(issue.getIssueUpdatedAt()).isEqualTo(DumpElement.NO_DATETIME);
273     assertThat(issue.getIssueClosedAt()).isEqualTo(DumpElement.NO_DATETIME);
274   }
275
276   @Test
277   public void ruleRef_is_ref_provided_by_RuleRepository() {
278
279     IssueDto issueDto = insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
280
281     underTest.execute(new TestComputationStepContext());
282
283     ProjectDump.Issue issue = getWrittenIssue();
284     assertThat(issue.getRuleRef())
285       .isEqualTo(ruleRepository.register(issueDto.getRuleUuid(), readyRuleDto.getKey()).getRef());
286   }
287
288   @Test
289   public void locations_is_not_set_in_protobuf_if_null_in_DB() {
290     insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
291
292     underTest.execute(new TestComputationStepContext());
293
294     assertThat(getWrittenIssue().getLocations()).isEmpty();
295   }
296
297   @Test
298   public void execute_fails_with_ISE_if_locations_cannot_be_parsed_to_protobuf() throws URISyntaxException, IOException {
299     byte[] rubbishBytes = getRubbishBytes();
300     String uuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(rubbishBytes)).getKey();
301
302     assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
303       .isInstanceOf(IllegalStateException.class)
304       .hasMessage("Issue export failed after processing 0 issues successfully");
305   }
306
307   @Test
308   public void execute_logs_number_total_exported_issue_count_when_successful() {
309     insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
310     insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
311     insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
312
313     underTest.execute(new TestComputationStepContext());
314
315     assertThat(logTester.logs(LoggerLevel.DEBUG)).containsExactly("3 issues exported");
316   }
317
318   @Test
319   public void execute_throws_ISE_with_number_of_successful_exports_before_failure() throws URISyntaxException, IOException {
320     insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
321     insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
322     insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(getRubbishBytes())).getKey();
323
324     assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
325       .isInstanceOf(IllegalStateException.class)
326       .hasMessage("Issue export failed after processing 2 issues successfully");
327   }
328
329   private byte[] getRubbishBytes() throws IOException, URISyntaxException {
330     return FileUtils.readFileToByteArray(new File(getClass().getResource("rubbish_data.txt").toURI()));
331   }
332
333   private ProjectDump.Issue getWrittenIssue() {
334     return dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES).get(0);
335   }
336
337 //  private void expectExportFailure() {
338 //    expectExportFailure(0);
339 //  }
340
341 //  private void expectExportFailure(int i) {
342 //    expectedException.expect(IllegalStateException.class);
343 //    expectedException.expectMessage("Issue export failed after processing " + i + " issues successfully");
344 //  }
345
346   private int issueUuidGenerator = 1;
347
348   private IssueDto insertIssue(RuleDto ruleDto, String componentUuid, String status) {
349     IssueDto dto = createBaseIssueDto(ruleDto, componentUuid, status);
350     return insertIssue(dto);
351   }
352
353   private IssueDto insertIssue(IssueDto dto) {
354     dbClient.issueDao().insert(dbSession, dto);
355     dbSession.commit();
356     return dto;
357   }
358
359   private ProjectDto createProject() {
360     ComponentDto projectDto = dbTester.components().insertPrivateProject(c -> c.setDbKey(PROJECT_KEY).setUuid(SOME_PROJECT_UUID));
361     dbTester.commit();
362     return dbTester.components().getProjectDto(projectDto);
363   }
364
365   private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid) {
366     return createBaseIssueDto(ruleDto, componentUuid, STATUS_OPEN);
367   }
368
369   private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid, String status) {
370     return new IssueDto()
371       .setKee("issue_uuid_" + issueUuidGenerator++)
372       .setComponentUuid(componentUuid)
373       .setProjectUuid(SOME_PROJECT_UUID)
374       .setRuleUuid(ruleDto.getUuid())
375       .setCreatedAt(System2.INSTANCE.now())
376       .setStatus(status);
377   }
378
379   private RuleDto insertRule(String ruleKey1) {
380     RuleDto dto = new RuleDto().setRepositoryKey(SOME_REPO).setScope(Scope.MAIN).setRuleKey(ruleKey1).setStatus(RuleStatus.READY);
381     dbTester.rules().insert(dto);
382     dbSession.commit();
383     return dto;
384   }
385 }