3 * Copyright (C) 2009-2020 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.projectanalysis.step;
22 import java.util.Arrays;
23 import java.util.Date;
24 import java.util.List;
25 import org.junit.After;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.junit.rules.TemporaryFolder;
30 import org.mockito.ArgumentCaptor;
31 import org.sonar.api.rule.RuleKey;
32 import org.sonar.api.rules.RuleType;
33 import org.sonar.api.utils.System2;
34 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
35 import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
36 import org.sonar.ce.task.projectanalysis.issue.AdHocRuleCreator;
37 import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
38 import org.sonar.ce.task.projectanalysis.issue.RuleRepositoryImpl;
39 import org.sonar.ce.task.projectanalysis.issue.UpdateConflictResolver;
40 import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
41 import org.sonar.ce.task.step.ComputationStep;
42 import org.sonar.ce.task.step.TestComputationStepContext;
43 import org.sonar.core.issue.DefaultIssue;
44 import org.sonar.core.issue.DefaultIssueComment;
45 import org.sonar.core.issue.FieldDiffs;
46 import org.sonar.db.DbClient;
47 import org.sonar.db.DbSession;
48 import org.sonar.db.DbTester;
49 import org.sonar.db.component.ComponentDto;
50 import org.sonar.db.issue.IssueChangeDto;
51 import org.sonar.db.issue.IssueDto;
52 import org.sonar.db.issue.IssueMapper;
53 import org.sonar.db.organization.OrganizationDto;
54 import org.sonar.db.rule.RuleDefinitionDto;
55 import org.sonar.db.rule.RuleTesting;
56 import org.sonar.scanner.protocol.output.ScannerReport;
57 import org.sonar.server.issue.IssueStorage;
59 import static java.util.Collections.singletonList;
60 import static org.assertj.core.api.Assertions.assertThat;
61 import static org.assertj.core.data.MapEntry.entry;
62 import static org.mockito.ArgumentMatchers.any;
63 import static org.mockito.ArgumentMatchers.anyString;
64 import static org.mockito.ArgumentMatchers.eq;
65 import static org.mockito.Mockito.mock;
66 import static org.mockito.Mockito.verify;
67 import static org.mockito.Mockito.when;
68 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
69 import static org.sonar.api.issue.Issue.STATUS_CLOSED;
70 import static org.sonar.api.issue.Issue.STATUS_OPEN;
71 import static org.sonar.api.rule.Severity.BLOCKER;
72 import static org.sonar.db.component.ComponentTesting.newFileDto;
74 public class PersistIssuesStepTest extends BaseStepTest {
76 private static final long NOW = 1_400_000_000_000L;
79 public TemporaryFolder temp = new TemporaryFolder();
81 public DbTester db = DbTester.create(System2.INSTANCE);
83 public BatchReportReaderRule reportReader = new BatchReportReaderRule();
85 public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
86 .setOrganizationUuid("org-1", "qg-uuid-1");
88 private System2 system2 = mock(System2.class);
89 private DbSession session = db.getSession();
90 private DbClient dbClient = db.getDbClient();
91 private UpdateConflictResolver conflictResolver = mock(UpdateConflictResolver.class);
92 private ProtoIssueCache protoIssueCache;
93 private ComputationStep underTest;
95 private AdHocRuleCreator adHocRuleCreator = mock(AdHocRuleCreator.class);
98 protected ComputationStep step() {
103 public void setup() throws Exception {
104 protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
105 reportReader.setMetadata(ScannerReport.Metadata.getDefaultInstance());
107 underTest = new PersistIssuesStep(dbClient, system2, conflictResolver, new RuleRepositoryImpl(adHocRuleCreator, dbClient, analysisMetadataHolder), protoIssueCache,
112 public void tearDown() {
117 public void insert_copied_issue() {
118 RuleDefinitionDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
119 db.rules().insert(rule);
120 OrganizationDto organizationDto = db.organizations().insert();
121 ComponentDto project = db.components().insertPrivateProject(organizationDto);
122 ComponentDto file = db.components().insertComponent(newFileDto(project, null));
123 when(system2.now()).thenReturn(NOW);
125 protoIssueCache.newAppender().append(new DefaultIssue()
127 .setType(RuleType.CODE_SMELL)
128 .setRuleKey(rule.getKey())
129 .setComponentUuid(file.uuid())
130 .setComponentKey(file.getKey())
131 .setProjectUuid(project.uuid())
132 .setProjectKey(project.getKey())
133 .setSeverity(BLOCKER)
134 .setStatus(STATUS_OPEN)
137 .setType(RuleType.BUG)
138 .setCreationDate(new Date(NOW))
140 .addComment(new DefaultIssueComment()
142 .setIssueKey("ISSUE")
143 .setUserUuid("john_uuid")
144 .setMarkdownText("Some text")
145 .setCreatedAt(new Date(NOW))
146 .setUpdatedAt(new Date(NOW))
150 .setIssueKey("ISSUE")
151 .setUserUuid("john_uuid")
152 .setDiff("technicalDebt", null, 1L)
153 .setCreationDate(new Date(NOW))))
156 TestComputationStepContext context = new TestComputationStepContext();
157 underTest.execute(context);
159 IssueDto result = dbClient.issueDao().selectOrFailByKey(session, "ISSUE");
160 assertThat(result.getKey()).isEqualTo("ISSUE");
161 assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
162 assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
163 assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
164 assertThat(result.getSeverity()).isEqualTo(BLOCKER);
165 assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
166 assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
168 List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList("ISSUE"));
169 assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE);
170 assertThat(context.getStatistics().getAll()).contains(
171 entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
175 public void insert_merged_issue() {
176 RuleDefinitionDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
177 db.rules().insert(rule);
178 OrganizationDto organizationDto = db.organizations().insert();
179 ComponentDto project = db.components().insertPrivateProject(organizationDto);
180 ComponentDto file = db.components().insertComponent(newFileDto(project, null));
181 when(system2.now()).thenReturn(NOW);
183 protoIssueCache.newAppender().append(new DefaultIssue()
185 .setType(RuleType.CODE_SMELL)
186 .setRuleKey(rule.getKey())
187 .setComponentUuid(file.uuid())
188 .setComponentKey(file.getKey())
189 .setProjectUuid(project.uuid())
190 .setProjectKey(project.getKey())
191 .setSeverity(BLOCKER)
192 .setStatus(STATUS_OPEN)
195 .setType(RuleType.BUG)
196 .setCreationDate(new Date(NOW))
198 .addComment(new DefaultIssueComment()
200 .setIssueKey("ISSUE")
201 .setUserUuid("john_uuid")
202 .setMarkdownText("Some text")
203 .setUpdatedAt(new Date(NOW))
204 .setCreatedAt(new Date(NOW))
206 .setCurrentChange(new FieldDiffs()
207 .setIssueKey("ISSUE")
208 .setUserUuid("john_uuid")
209 .setDiff("technicalDebt", null, 1L)
210 .setCreationDate(new Date(NOW))))
213 TestComputationStepContext context = new TestComputationStepContext();
214 underTest.execute(context);
216 IssueDto result = dbClient.issueDao().selectOrFailByKey(session, "ISSUE");
217 assertThat(result.getKey()).isEqualTo("ISSUE");
218 assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
219 assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
220 assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
221 assertThat(result.getSeverity()).isEqualTo(BLOCKER);
222 assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
223 assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
225 List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList("ISSUE"));
226 assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE);
227 assertThat(context.getStatistics().getAll()).contains(
228 entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
232 public void update_conflicting_issue() {
233 RuleDefinitionDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
234 db.rules().insert(rule);
235 OrganizationDto organizationDto = db.organizations().insert();
236 ComponentDto project = db.components().insertPrivateProject(organizationDto);
237 ComponentDto file = db.components().insertComponent(newFileDto(project, null));
238 IssueDto issue = db.issues().insert(rule, project, file,
239 i -> i.setStatus(STATUS_OPEN)
241 .setCreatedAt(NOW - 1_000_000_000L)
242 // simulate the issue has been updated after the analysis ran
243 .setUpdatedAt(NOW + 1_000_000_000L));
244 issue = dbClient.issueDao().selectByKey(db.getSession(), issue.getKey()).get();
245 DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
246 when(system2.now()).thenReturn(NOW);
248 DefaultIssue defaultIssue = issue.toDefaultIssue()
249 .setStatus(STATUS_CLOSED)
250 .setResolution(RESOLUTION_FIXED)
254 issueCacheAppender.append(defaultIssue).close();
256 TestComputationStepContext context = new TestComputationStepContext();
257 underTest.execute(context);
259 ArgumentCaptor<IssueDto> issueDtoCaptor = ArgumentCaptor.forClass(IssueDto.class);
260 verify(conflictResolver).resolve(eq(defaultIssue), issueDtoCaptor.capture(), any(IssueMapper.class));
261 assertThat(issueDtoCaptor.getValue().getId()).isEqualTo(issue.getId());
262 assertThat(context.getStatistics().getAll()).contains(
263 entry("inserts", "0"), entry("updates", "1"), entry("merged", "1"));
268 public void insert_new_issue() {
269 RuleDefinitionDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
270 db.rules().insert(rule);
271 OrganizationDto organizationDto = db.organizations().insert();
272 ComponentDto project = db.components().insertPrivateProject(organizationDto);
273 ComponentDto file = db.components().insertComponent(newFileDto(project, null));
276 protoIssueCache.newAppender().append(new DefaultIssue()
278 .setType(RuleType.CODE_SMELL)
279 .setRuleKey(rule.getKey())
280 .setComponentUuid(file.uuid())
281 .setComponentKey(file.getKey())
282 .setProjectUuid(project.uuid())
283 .setProjectKey(project.getKey())
284 .setSeverity(BLOCKER)
285 .setStatus(STATUS_OPEN)
286 .setCreationDate(new Date(NOW))
288 .setType(RuleType.BUG)).close();
290 TestComputationStepContext context = new TestComputationStepContext();
291 underTest.execute(context);
293 IssueDto result = dbClient.issueDao().selectOrFailByKey(session, "ISSUE");
294 assertThat(result.getKey()).isEqualTo("ISSUE");
295 assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
296 assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
297 assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
298 assertThat(result.getSeverity()).isEqualTo(BLOCKER);
299 assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
300 assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
301 assertThat(context.getStatistics().getAll()).contains(
302 entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
306 public void close_issue() {
307 ComponentDto project = db.components().insertPrivateProject();
308 ComponentDto file = db.components().insertComponent(newFileDto(project));
309 RuleDefinitionDto rule = db.rules().insert();
310 IssueDto issue = db.issues().insert(rule, project, file,
311 i -> i.setStatus(STATUS_OPEN)
313 .setCreatedAt(NOW - 1_000_000_000L)
314 .setUpdatedAt(NOW - 1_000_000_000L));
315 DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
317 issueCacheAppender.append(
318 issue.toDefaultIssue()
319 .setStatus(STATUS_CLOSED)
320 .setResolution(RESOLUTION_FIXED)
326 TestComputationStepContext context = new TestComputationStepContext();
327 underTest.execute(context);
329 IssueDto issueReloaded = db.getDbClient().issueDao().selectByKey(db.getSession(), issue.getKey()).get();
330 assertThat(issueReloaded.getStatus()).isEqualTo(STATUS_CLOSED);
331 assertThat(issueReloaded.getResolution()).isEqualTo(RESOLUTION_FIXED);
332 assertThat(context.getStatistics().getAll()).contains(
333 entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
337 public void add_comment() {
338 ComponentDto project = db.components().insertPrivateProject();
339 ComponentDto file = db.components().insertComponent(newFileDto(project));
340 RuleDefinitionDto rule = db.rules().insert();
341 IssueDto issue = db.issues().insert(rule, project, file,
342 i -> i.setStatus(STATUS_OPEN)
344 .setCreatedAt(NOW - 1_000_000_000L)
345 .setUpdatedAt(NOW - 1_000_000_000L));
346 DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
348 issueCacheAppender.append(
349 issue.toDefaultIssue()
350 .setStatus(STATUS_CLOSED)
351 .setResolution(RESOLUTION_FIXED)
355 .addComment(new DefaultIssueComment()
357 .setIssueKey(issue.getKey())
358 .setUserUuid("john_uuid")
359 .setMarkdownText("Some text")
360 .setCreatedAt(new Date(NOW))
361 .setUpdatedAt(new Date(NOW))
365 TestComputationStepContext context = new TestComputationStepContext();
366 underTest.execute(context);
368 IssueChangeDto issueChangeDto = db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singletonList(issue.getKey())).get(0);
369 assertThat(issueChangeDto)
370 .extracting(IssueChangeDto::getChangeType, IssueChangeDto::getUserUuid, IssueChangeDto::getChangeData, IssueChangeDto::getIssueKey,
371 IssueChangeDto::getIssueChangeCreationDate)
372 .containsOnly(IssueChangeDto.TYPE_COMMENT, "john_uuid", "Some text", issue.getKey(), NOW);
373 assertThat(context.getStatistics().getAll()).contains(
374 entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
378 public void add_change() {
379 ComponentDto project = db.components().insertPrivateProject();
380 ComponentDto file = db.components().insertComponent(newFileDto(project));
381 RuleDefinitionDto rule = db.rules().insert();
382 IssueDto issue = db.issues().insert(rule, project, file,
383 i -> i.setStatus(STATUS_OPEN)
385 .setCreatedAt(NOW - 1_000_000_000L)
386 .setUpdatedAt(NOW - 1_000_000_000L));
387 DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
389 issueCacheAppender.append(
390 issue.toDefaultIssue()
391 .setStatus(STATUS_CLOSED)
392 .setResolution(RESOLUTION_FIXED)
396 .setCurrentChange(new FieldDiffs()
397 .setIssueKey("ISSUE")
398 .setUserUuid("john_uuid")
399 .setDiff("technicalDebt", null, 1L)
400 .setCreationDate(new Date(NOW))))
403 TestComputationStepContext context = new TestComputationStepContext();
404 underTest.execute(context);
406 IssueChangeDto issueChangeDto = db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singletonList(issue.getKey())).get(0);
407 assertThat(issueChangeDto)
408 .extracting(IssueChangeDto::getChangeType, IssueChangeDto::getUserUuid, IssueChangeDto::getChangeData, IssueChangeDto::getIssueKey,
409 IssueChangeDto::getIssueChangeCreationDate)
410 .containsOnly(IssueChangeDto.TYPE_FIELD_CHANGE, "john_uuid", "technicalDebt=1", issue.getKey(), NOW);
411 assertThat(context.getStatistics().getAll()).contains(
412 entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));