]> source.dussan.org Git - sonarqube.git/blob
3dae30abdb8e75dc862a8bb980c11379eb8e9cfb
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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.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;
68
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;
79
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();
87
88   @Rule
89   public DbTester dbTester = DbTester.create(System2.INSTANCE);
90   @Rule
91   public LogTester logTester = new LogTester();
92
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();
99
100   private final ExportIssuesStep underTest = new ExportIssuesStep(dbClient, projectHolder, dumpWriter, ruleRepository, componentRepository);
101
102   private RuleDto readyRuleDto;
103
104   @Before
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)));
111
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);
117     }
118     this.readyRuleDto = insertRule(READY_RULE_KEY);
119     componentRepository.register(12, SOME_PROJECT_UUID, false);
120   }
121
122   @After
123   public void tearDown() {
124     dbSession.close();
125   }
126
127   @Test
128   public void getDescription_is_set() {
129     assertThat(underTest.getDescription()).isEqualTo("Export issues");
130   }
131
132   @Test
133   public void execute_written_writes_no_issues_when_project_has_no_issues() {
134     underTest.execute(new TestComputationStepContext());
135
136     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
137   }
138
139   @Test
140   public void execute_written_writes_no_issues_when_project_has_only_CLOSED_issues() {
141     insertIssue(readyRuleDto, SOME_PROJECT_UUID, Issue.STATUS_CLOSED);
142
143     underTest.execute(new TestComputationStepContext());
144
145     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
146   }
147
148   @Test
149   public void execute_fails_with_ISE_if_componentUuid_is_not_set() {
150     insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setComponentUuid(null));
151
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");
157   }
158
159   @DataProvider
160   public static Object[][] allStatusesButCLOSED() {
161     return new Object[][] {
162       {STATUS_OPEN},
163       {STATUS_CONFIRMED},
164       {STATUS_REOPENED},
165       {STATUS_RESOLVED}
166     };
167   }
168
169   @Test
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();
173
174     underTest.execute(new TestComputationStepContext());
175
176     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
177       .extracting(ProjectDump.Issue::getUuid)
178       .containsOnly(uuid);
179   }
180
181   @Test
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();
190
191     underTest.execute(new TestComputationStepContext());
192
193     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
194       .extracting(ProjectDump.Issue::getUuid)
195       .containsOnly(projectIssueUuid, moduleIssueUuid, dirIssueUuid, fileIssueUuid);
196   }
197
198   @Test
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();
202
203     underTest.execute(new TestComputationStepContext());
204
205     assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
206       .extracting(ProjectDump.Issue::getUuid)
207       .containsOnly(projectIssueUuid);
208   }
209
210   @Test
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)
219       .setType(988)
220       .setMessage("msg")
221       .setMessageFormattings(messageFormattings)
222       .setLine(10)
223       .setChecksum("checksum")
224       .setResolution("resolution")
225       .setSeverity("severity")
226       .setManualSeverity(true)
227       .setGap(13.13d)
228       .setEffort(99L)
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"));
239
240     // fields tested separately and/or required to match SQL request
241     issueDto
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);
246
247     insertIssue(issueDto);
248
249     underTest.execute(new TestComputationStepContext());
250
251     ProjectDump.Issue issue = getWrittenIssue();
252
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());
278   }
279
280   @Test
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);
284     issueDto2
285       .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH))
286       .addImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.LOW));
287
288     insertIssue(issueDto);
289     insertIssue(issueDto2);
290
291     underTest.execute(new TestComputationStepContext());
292     List<ProjectDump.Issue> issuesInReport = dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES);
293
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);
297   }
298
299   @Test
300   public void verify_mapping_of_nullable_numerical_fields_to_defaultValue() {
301     insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
302
303     underTest.execute(new TestComputationStepContext());
304
305     ProjectDump.Issue issue = getWrittenIssue();
306
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();
314   }
315
316   @Test
317   public void ruleRef_is_ref_provided_by_RuleRepository() {
318
319     IssueDto issueDto = insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
320
321     underTest.execute(new TestComputationStepContext());
322
323     ProjectDump.Issue issue = getWrittenIssue();
324     assertThat(issue.getRuleRef())
325       .isEqualTo(ruleRepository.register(issueDto.getRuleUuid(), readyRuleDto.getKey()).ref());
326   }
327
328   @Test
329   public void locations_is_not_set_in_protobuf_if_null_in_DB() {
330     insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
331
332     underTest.execute(new TestComputationStepContext());
333
334     assertThat(getWrittenIssue().getLocations()).isEmpty();
335   }
336
337   @Test
338   public void message_formattings_is_empty_in_protobuf_if_null_in_DB() {
339     insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
340
341     underTest.execute(new TestComputationStepContext());
342
343     assertThat(getWrittenIssue().getMessageFormattingsList()).isEmpty();
344   }
345
346   @Test
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();
350
351     assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
352       .isInstanceOf(IllegalStateException.class)
353       .hasMessage("Issue export failed after processing 0 issues successfully");
354   }
355
356   @Test
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));
361
362     underTest.execute(new TestComputationStepContext());
363
364     assertThat(logTester.logs(Level.DEBUG)).contains("3 issues exported");
365   }
366
367   @Test
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();
372
373     assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
374       .isInstanceOf(IllegalStateException.class)
375       .hasMessage("Issue export failed after processing 2 issues successfully");
376   }
377
378   private byte[] getRubbishBytes() throws IOException, URISyntaxException {
379     return FileUtils.readFileToByteArray(new File(getClass().getResource("rubbish_data.txt").toURI()));
380   }
381
382   private ProjectDump.Issue getWrittenIssue() {
383     return dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES).get(0);
384   }
385
386   // private void expectExportFailure() {
387   // expectExportFailure(0);
388   // }
389
390   // private void expectExportFailure(int i) {
391   // expectedException.expect(IllegalStateException.class);
392   // expectedException.expectMessage("Issue export failed after processing " + i + " issues successfully");
393   // }
394
395   private int issueUuidGenerator = 1;
396
397   private IssueDto insertIssue(RuleDto ruleDto, String componentUuid, String status) {
398     IssueDto dto = createBaseIssueDto(ruleDto, componentUuid, status);
399     return insertIssue(dto);
400   }
401
402   private IssueDto insertIssue(IssueDto dto) {
403     dbClient.issueDao().insert(dbSession, dto);
404     dbSession.commit();
405     return dto;
406   }
407
408   private ProjectDto createProject() {
409     ComponentDto projectDto = dbTester.components().insertPrivateProject(c -> c.setKey(PROJECT_KEY).setUuid(SOME_PROJECT_UUID)).getMainBranchComponent();
410     dbTester.commit();
411     return dbTester.components().getProjectDtoByMainBranch(projectDto);
412   }
413
414   private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid) {
415     return createBaseIssueDto(ruleDto, componentUuid, STATUS_OPEN);
416   }
417
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())
425       .setStatus(status);
426   }
427
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);
431     dbSession.commit();
432     return dto;
433   }
434 }