3 * Copyright (C) 2009-2023 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.server.pushapi.issues;
22 import java.nio.charset.StandardCharsets;
23 import java.util.Deque;
24 import java.util.HashMap;
26 import java.util.Optional;
28 import javax.annotation.Nullable;
29 import org.junit.Rule;
30 import org.junit.Test;
31 import org.sonar.api.rules.RuleType;
32 import org.sonar.core.issue.DefaultIssue;
33 import org.sonar.core.issue.FieldDiffs;
34 import org.sonar.db.DbTester;
35 import org.sonar.db.component.BranchDto;
36 import org.sonar.db.component.BranchType;
37 import org.sonar.db.component.ComponentDto;
38 import org.sonar.db.issue.IssueDto;
39 import org.sonar.db.project.ProjectDto;
40 import org.sonar.db.pushevent.PushEventDto;
41 import org.sonar.db.rule.RuleDto;
42 import org.sonarqube.ws.Common;
44 import static org.assertj.core.api.Assertions.assertThat;
45 import static org.assertj.core.api.Assertions.tuple;
46 import static org.sonar.api.issue.DefaultTransitions.CONFIRM;
47 import static org.sonar.api.issue.DefaultTransitions.FALSE_POSITIVE;
48 import static org.sonar.api.issue.DefaultTransitions.REOPEN;
49 import static org.sonar.api.issue.DefaultTransitions.RESOLVE;
50 import static org.sonar.api.issue.DefaultTransitions.UNCONFIRM;
51 import static org.sonar.api.issue.DefaultTransitions.WONT_FIX;
52 import static org.sonar.api.rules.RuleType.CODE_SMELL;
53 import static org.sonar.db.component.ComponentTesting.newFileDto;
54 import static org.sonarqube.ws.Common.Severity.BLOCKER;
55 import static org.sonarqube.ws.Common.Severity.CRITICAL;
56 import static org.sonarqube.ws.Common.Severity.MAJOR;
58 public class IssueChangeEventServiceImplTest {
61 public DbTester db = DbTester.create();
63 public final IssueChangeEventServiceImpl underTest = new IssueChangeEventServiceImpl(db.getDbClient());
66 public void distributeIssueChangeEvent_singleIssueChange_severityChange() {
67 ComponentDto componentDto = db.components().insertPublicProject().getMainBranchComponent();
68 ProjectDto project = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto.uuid()).get();
69 BranchDto branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.getUuid()).get();
70 RuleDto rule = db.rules().insert();
71 IssueDto issue = db.issues().insert(rule, project, componentDto, i -> i.setSeverity(MAJOR.name()));
73 assertPushEventIsPersisted(project, branch, issue, BLOCKER.name(), null, null, null, 1);
77 public void distributeIssueChangeEvent_singleIssueChange_typeChange() {
78 ComponentDto componentDto = db.components().insertPublicProject().getMainBranchComponent();
79 ProjectDto project = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto.uuid()).get();
80 BranchDto branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.getUuid()).get();
81 RuleDto rule = db.rules().insert();
82 IssueDto issue = db.issues().insert(rule, project, componentDto, i -> i.setSeverity(MAJOR.name()));
84 assertPushEventIsPersisted(project, branch, issue, null, Common.RuleType.BUG.name(), null, null, 1);
88 public void distributeIssueChangeEvent_singleIssueChange_transitionChanges() {
89 ComponentDto componentDto = db.components().insertPublicProject().getMainBranchComponent();
90 ProjectDto project = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto.uuid()).get();
91 BranchDto branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.getUuid()).get();
92 RuleDto rule = db.rules().insert();
93 IssueDto issue = db.issues().insert(rule, project, componentDto, i -> i.setSeverity(MAJOR.name()));
95 assertPushEventIsPersisted(project, branch, issue, null, null, WONT_FIX, true, 1);
96 assertPushEventIsPersisted(project, branch, issue, null, null, REOPEN, false, 2);
97 assertPushEventIsPersisted(project, branch, issue, null, null, FALSE_POSITIVE, true, 3);
98 assertPushEventIsPersisted(project, branch, issue, null, null, REOPEN, false, 4);
99 assertPushEventIsPersisted(project, branch, issue, null, null, RESOLVE, false, 5);
100 assertPushEventIsPersisted(project, branch, issue, null, null, REOPEN, false, 6);
101 assertNoIssueDistribution(project, branch, issue, null, null, CONFIRM, 7);
102 assertNoIssueDistribution(project, branch, issue, null, null, UNCONFIRM, 8);
106 public void distributeIssueChangeEvent_singleIssueChange_severalChanges() {
107 ComponentDto componentDto = db.components().insertPublicProject().getMainBranchComponent();
108 ProjectDto project = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto.uuid()).get();
109 BranchDto branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.getUuid()).get();
110 RuleDto rule = db.rules().insert();
111 IssueDto issue = db.issues().insert(rule, project, componentDto, i -> i.setSeverity(MAJOR.name()));
113 assertPushEventIsPersisted(project, branch, issue, BLOCKER.name(), Common.RuleType.BUG.name(), WONT_FIX, true, 1);
117 public void distributeIssueChangeEvent_bulkIssueChange() {
118 RuleDto rule = db.rules().insert();
120 ComponentDto componentDto1 = db.components().insertPublicProject().getMainBranchComponent();
121 ProjectDto project1 = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto1.uuid()).get();
122 BranchDto branch1 = db.getDbClient().branchDao().selectByUuid(db.getSession(), project1.getUuid()).get();
123 IssueDto issue1 = db.issues().insert(rule, project1, componentDto1, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG));
125 ComponentDto componentDto2 = db.components().insertPublicProject().getMainBranchComponent();
126 ProjectDto project2 = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto2.uuid()).get();
127 BranchDto branch2 = db.getDbClient().branchDao().selectByUuid(db.getSession(), project2.getUuid()).get();
128 IssueDto issue2 = db.issues().insert(rule, project2, componentDto2, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG));
130 ComponentDto componentDto3 = db.components().insertPublicProject().getMainBranchComponent();
131 ProjectDto project3 = db.getDbClient().projectDao().selectByUuid(db.getSession(), componentDto3.uuid()).get();
132 BranchDto branch3 = db.getDbClient().branchDao().selectByUuid(db.getSession(), project3.getUuid()).get();
133 IssueDto issue3 = db.issues().insert(rule, project3, componentDto3, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG));
135 DefaultIssue defaultIssue1 = issue1.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs()
136 .setDiff("resolution", null, null)
137 .setDiff("severity", MAJOR.name(), CRITICAL.name())
138 .setDiff("type", RuleType.BUG.name(), CODE_SMELL.name()));
139 DefaultIssue defaultIssue2 = issue2.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs()
140 .setDiff("resolution", "OPEN", "FALSE-POSITIVE")
141 .setDiff("severity", MAJOR.name(), CRITICAL.name())
142 .setDiff("type", RuleType.BUG.name(), CODE_SMELL.name()));
144 Set<DefaultIssue> issues = Set.of(defaultIssue1, defaultIssue2, issue3.toDefaultIssue());
145 Map<String, ComponentDto> projectsByUuid = new HashMap<>();
146 projectsByUuid.put(componentDto1.branchUuid(), componentDto1);
147 projectsByUuid.put(componentDto2.branchUuid(), componentDto2);
148 projectsByUuid.put(componentDto3.branchUuid(), componentDto3);
149 Map<String, BranchDto> branchesByProjectUuid = new HashMap<>();
150 branchesByProjectUuid.put(componentDto1.branchUuid(), branch1);
151 branchesByProjectUuid.put(componentDto2.branchUuid(), branch2);
152 branchesByProjectUuid.put(componentDto3.branchUuid(), branch3);
154 underTest.distributeIssueChangeEvent(issues, projectsByUuid, branchesByProjectUuid);
156 Deque<PushEventDto> issueChangedEvents = db.getDbClient().pushEventDao()
157 .selectChunkByProjectUuids(db.getSession(), Set.of(project1.getUuid(), project2.getUuid()),
160 assertThat(issueChangedEvents).hasSize(2);
162 assertThat(issueChangedEvents)
163 .extracting(PushEventDto::getName, PushEventDto::getProjectUuid)
164 .containsExactlyInAnyOrder(
165 tuple("IssueChanged", project1.getUuid()),
166 tuple("IssueChanged", project2.getUuid()));
168 Optional<PushEventDto> project1Event = issueChangedEvents.stream().filter(e -> e.getProjectUuid().equals(project1.getUuid())).findFirst();
169 Optional<PushEventDto> project2Event = issueChangedEvents.stream().filter(e -> e.getProjectUuid().equals(project2.getUuid())).findFirst();
171 assertThat(project1Event).isPresent();
172 assertThat(project2Event).isPresent();
174 String firstPayload = new String(project1Event.get().getPayload(), StandardCharsets.UTF_8);
175 assertThat(firstPayload)
176 .contains("\"userSeverity\":\"" + CRITICAL.name() + "\"",
177 "\"userType\":\"" + CODE_SMELL.name() + "\"",
178 "\"resolved\":" + false);
180 String secondPayload = new String(project2Event.get().getPayload(), StandardCharsets.UTF_8);
181 assertThat(secondPayload)
182 .contains("\"userSeverity\":\"" + CRITICAL.name() + "\"",
183 "\"userType\":\"" + CODE_SMELL.name() + "\"",
184 "\"resolved\":" + true);
188 public void doNotDistributeIssueChangeEvent_forPullRequestIssues() {
189 RuleDto rule = db.rules().insert();
191 ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
192 ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setKey("myBranch1")
193 .setBranchType(BranchType.PULL_REQUEST)
194 .setMergeBranchUuid(project.uuid()));
195 BranchDto branch1 = db.getDbClient().branchDao().selectByUuid(db.getSession(), pullRequest.uuid()).get();
196 ComponentDto file = db.components().insertComponent(newFileDto(pullRequest));
197 IssueDto issue1 = db.issues().insert(rule, pullRequest, file, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG));
199 DefaultIssue defaultIssue1 = issue1.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs()
200 .setDiff("resolution", null, null)
201 .setDiff("severity", MAJOR.name(), CRITICAL.name())
202 .setDiff("type", RuleType.BUG.name(), CODE_SMELL.name()));
204 Set<DefaultIssue> issues = Set.of(defaultIssue1);
205 Map<String, ComponentDto> projectsByUuid = new HashMap<>();
206 projectsByUuid.put(project.branchUuid(), project);
207 Map<String, BranchDto> branchesByProjectUuid = new HashMap<>();
208 branchesByProjectUuid.put(project.branchUuid(), branch1);
210 underTest.distributeIssueChangeEvent(issues, projectsByUuid, branchesByProjectUuid);
212 Deque<PushEventDto> events = db.getDbClient().pushEventDao()
213 .selectChunkByProjectUuids(db.getSession(), Set.of(project.uuid()), 1l, null, 20);
214 assertThat(events).isEmpty();
217 private void assertNoIssueDistribution(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity,
218 @Nullable String type, @Nullable String transition, int page) {
219 underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, type, transition, branch, project.getKey());
221 Deque<PushEventDto> events = db.getDbClient().pushEventDao()
222 .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, page);
223 assertThat(events).hasSizeLessThan(page);
226 private void assertPushEventIsPersisted(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity,
227 @Nullable String type, @Nullable String transition, Boolean resolved, int page) {
228 underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, type, transition, branch, project.getKey());
230 Deque<PushEventDto> events = db.getDbClient().pushEventDao()
231 .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, page);
232 assertThat(events).isNotEmpty();
233 assertThat(events).extracting(PushEventDto::getName, PushEventDto::getProjectUuid)
234 .contains(tuple("IssueChanged", project.getUuid()));
236 String payload = new String(events.getLast().getPayload(), StandardCharsets.UTF_8);
237 if (severity != null) {
238 assertThat(payload).contains("\"userSeverity\":\"" + severity + "\"");
242 assertThat(payload).contains("\"userType\":\"" + type + "\"");
245 if (resolved != null) {
246 assertThat(payload).contains("\"resolved\":" + resolved);