]> source.dussan.org Git - sonarqube.git/blob
2889d00e22414597958ce8bd3ceff6356fa6abbd
[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.server.pushapi.issues;
21
22 import java.nio.charset.StandardCharsets;
23 import java.util.Deque;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
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.component.ProjectData;
39 import org.sonar.db.issue.IssueDto;
40 import org.sonar.db.project.ProjectDto;
41 import org.sonar.db.pushevent.PushEventDto;
42 import org.sonar.db.rule.RuleDto;
43 import org.sonarqube.ws.Common;
44
45 import static org.assertj.core.api.Assertions.assertThat;
46 import static org.assertj.core.api.Assertions.tuple;
47 import static org.sonar.api.issue.DefaultTransitions.ACCEPT;
48 import static org.sonar.api.issue.DefaultTransitions.CONFIRM;
49 import static org.sonar.api.issue.DefaultTransitions.FALSE_POSITIVE;
50 import static org.sonar.api.issue.DefaultTransitions.REOPEN;
51 import static org.sonar.api.issue.DefaultTransitions.RESOLVE;
52 import static org.sonar.api.issue.DefaultTransitions.UNCONFIRM;
53 import static org.sonar.api.issue.DefaultTransitions.WONT_FIX;
54 import static org.sonar.api.rules.RuleType.CODE_SMELL;
55 import static org.sonar.db.component.ComponentTesting.newFileDto;
56 import static org.sonarqube.ws.Common.Severity.BLOCKER;
57 import static org.sonarqube.ws.Common.Severity.CRITICAL;
58 import static org.sonarqube.ws.Common.Severity.MAJOR;
59
60 public class IssueChangeEventServiceImplTest {
61
62   @Rule
63   public DbTester db = DbTester.create();
64
65   public final IssueChangeEventServiceImpl underTest = new IssueChangeEventServiceImpl(db.getDbClient());
66
67   @Test
68   public void distributeIssueChangeEvent_whenSingleIssueChange_shouldChangeSeverity() {
69     ProjectData projectData = db.components().insertPublicProject();
70     RuleDto rule = db.rules().insert();
71     IssueDto issue = db.issues().insert(rule, projectData.getMainBranchDto(), projectData.getMainBranchComponent(), i -> i.setSeverity(MAJOR.name()));
72
73     assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, BLOCKER.name(), null, null, null, 1);
74   }
75
76   @Test
77   public void distributeIssueChangeEvent_whenSingleIssueChange_shouldChangeType() {
78     ProjectData projectData = db.components().insertPublicProject();
79     RuleDto rule = db.rules().insert();
80     IssueDto issue = db.issues().insert(rule, projectData.getMainBranchDto(), projectData.getMainBranchComponent(), i -> i.setSeverity(MAJOR.name()));
81
82     assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, null, Common.RuleType.BUG.name(), null, null, 1);
83   }
84
85   @Test
86   public void distributeIssueChangeEvent_whenSingleIssueChange_shouldExecuteTransitionChanges() {
87     ProjectData projectData = db.components().insertPublicProject();
88     ProjectDto project = projectData.getProjectDto();
89     BranchDto mainBranch = projectData.getMainBranchDto();
90     RuleDto rule = db.rules().insert();
91     IssueDto issue = db.issues().insert(rule, mainBranch, projectData.getMainBranchComponent(), i -> i.setSeverity(MAJOR.name()));
92
93     assertPushEventIsPersisted(project, mainBranch, issue, null, null, ACCEPT, true, 1);
94     assertPushEventIsPersisted(project, mainBranch, issue, null, null, WONT_FIX, true, 2);
95     assertPushEventIsPersisted(project, mainBranch, issue, null, null, REOPEN, false, 3);
96     assertPushEventIsPersisted(project, mainBranch, issue, null, null, FALSE_POSITIVE, true, 4);
97     assertPushEventIsPersisted(project, mainBranch, issue, null, null, REOPEN, false, 5);
98     assertPushEventIsPersisted(project, mainBranch, issue, null, null, RESOLVE, false, 6);
99     assertPushEventIsPersisted(project, mainBranch, issue, null, null, REOPEN, false, 7);
100     assertNoIssueDistribution(project, mainBranch, issue, null, null, CONFIRM, 8);
101     assertNoIssueDistribution(project, mainBranch, issue, null, null, UNCONFIRM, 9);
102   }
103
104   @Test
105   public void distributeIssueChangeEvent_whenSingleIssueChangeOnABranch_shouldChangeSeverity() {
106     ProjectData projectData = db.components().insertPublicProject();
107     BranchDto featureBranch = db.components().insertProjectBranch(projectData.getProjectDto(), b -> b.setKey("feature1"));
108     ComponentDto branchComponent = db.components().insertFile(featureBranch);
109     RuleDto rule = db.rules().insert();
110     IssueDto issue = db.issues().insert(rule, featureBranch, branchComponent, i -> i.setSeverity(MAJOR.name()));
111     assertPushEventIsPersisted(projectData.getProjectDto(), featureBranch, issue, BLOCKER.name(), null, null, null, 1);
112   }
113
114   @Test
115   public void distributeIssueChangeEvent_whenSingleIssueChange_shouldExecuteSeveralChanges() {
116     ProjectData projectData = db.components().insertPublicProject();
117     RuleDto rule = db.rules().insert();
118     IssueDto issue = db.issues().insert(rule, projectData.getMainBranchDto(), projectData.getMainBranchComponent(), i -> i.setSeverity(MAJOR.name()));
119
120     assertPushEventIsPersisted(projectData.getProjectDto(), projectData.getMainBranchDto(), issue, BLOCKER.name(), Common.RuleType.BUG.name(), ACCEPT, true, 1);
121   }
122
123   @Test
124   public void distributeIssueChangeEvent_whenBulkIssueChange_shouldDistributesEvents() {
125     RuleDto rule = db.rules().insert();
126
127     ProjectData projectData1 = db.components().insertPublicProject();
128     ProjectDto project1 = projectData1.getProjectDto();
129     BranchDto branch1 = projectData1.getMainBranchDto();
130     ComponentDto componentDto1 = projectData1.getMainBranchComponent();
131     IssueDto issue1 = db.issues().insert(rule, branch1, componentDto1, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG));
132
133     ProjectData projectData2 = db.components().insertPublicProject();
134     ProjectDto project2 = projectData2.getProjectDto();
135     BranchDto branch2 = projectData2.getMainBranchDto();
136     ComponentDto componentDto2 = projectData2.getMainBranchComponent();
137     IssueDto issue2 = db.issues().insert(rule, branch2, componentDto2, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG));
138
139     ProjectData projectData3 = db.components().insertPublicProject();
140     ProjectDto project3 = projectData3.getProjectDto();
141     BranchDto branch3 = projectData3.getMainBranchDto();
142     ComponentDto componentDto3 = projectData3.getMainBranchComponent();
143     IssueDto issue3 = db.issues().insert(rule, branch3, componentDto3, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG));
144
145     DefaultIssue defaultIssue1 = issue1.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs()
146       .setDiff("resolution", null, null)
147       .setDiff("severity", MAJOR.name(), CRITICAL.name())
148       .setDiff("type", RuleType.BUG.name(), CODE_SMELL.name()));
149     DefaultIssue defaultIssue2 = issue2.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs()
150       .setDiff("resolution", "OPEN", "FALSE-POSITIVE")
151       .setDiff("severity", MAJOR.name(), CRITICAL.name())
152       .setDiff("type", RuleType.BUG.name(), CODE_SMELL.name()));
153
154     Set<DefaultIssue> issues = Set.of(defaultIssue1, defaultIssue2, issue3.toDefaultIssue());
155     Map<String, ComponentDto> projectsByUuid = new HashMap<>();
156     projectsByUuid.put(componentDto1.branchUuid(), componentDto1);
157     projectsByUuid.put(componentDto2.branchUuid(), componentDto2);
158     projectsByUuid.put(componentDto3.branchUuid(), componentDto3);
159     Map<String, BranchDto> branchesByProjectUuid = new HashMap<>();
160     branchesByProjectUuid.put(componentDto1.branchUuid(), branch1);
161     branchesByProjectUuid.put(componentDto2.branchUuid(), branch2);
162     branchesByProjectUuid.put(componentDto3.branchUuid(), branch3);
163
164     underTest.distributeIssueChangeEvent(issues, projectsByUuid, branchesByProjectUuid);
165
166     Deque<PushEventDto> issueChangedEvents = db.getDbClient().pushEventDao()
167       .selectChunkByProjectUuids(db.getSession(), Set.of(project1.getUuid(), project2.getUuid()),
168         1l, null, 3);
169
170     assertThat(issueChangedEvents).hasSize(2);
171
172     assertThat(issueChangedEvents)
173       .extracting(PushEventDto::getName, PushEventDto::getProjectUuid)
174       .containsExactlyInAnyOrder(
175         tuple("IssueChanged", project1.getUuid()),
176         tuple("IssueChanged", project2.getUuid()));
177
178     Optional<PushEventDto> project1Event = issueChangedEvents.stream().filter(e -> e.getProjectUuid().equals(project1.getUuid())).findFirst();
179     Optional<PushEventDto> project2Event = issueChangedEvents.stream().filter(e -> e.getProjectUuid().equals(project2.getUuid())).findFirst();
180
181     assertThat(project1Event).isPresent();
182     assertThat(project2Event).isPresent();
183
184     String firstPayload = new String(project1Event.get().getPayload(), StandardCharsets.UTF_8);
185     assertThat(firstPayload)
186       .contains("\"userSeverity\":\"" + CRITICAL.name() + "\"",
187         "\"userType\":\"" + CODE_SMELL.name() + "\"",
188         "\"resolved\":" + false);
189
190     String secondPayload = new String(project2Event.get().getPayload(), StandardCharsets.UTF_8);
191     assertThat(secondPayload)
192       .contains("\"userSeverity\":\"" + CRITICAL.name() + "\"",
193         "\"userType\":\"" + CODE_SMELL.name() + "\"",
194         "\"resolved\":" + true);
195   }
196
197   @Test
198   public void distributeIssueChangeEvent_whenPullRequestIssues_shouldNotDistributeEvents() {
199     RuleDto rule = db.rules().insert();
200
201     ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
202     ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setKey("myBranch1")
203       .setBranchType(BranchType.PULL_REQUEST)
204       .setMergeBranchUuid(project.uuid()));
205     BranchDto branch1 = db.getDbClient().branchDao().selectByUuid(db.getSession(), pullRequest.uuid()).get();
206     ComponentDto file = db.components().insertComponent(newFileDto(pullRequest));
207     IssueDto issue1 = db.issues().insert(rule, pullRequest, file, i -> i.setSeverity(MAJOR.name()).setType(RuleType.BUG));
208
209     DefaultIssue defaultIssue1 = issue1.toDefaultIssue().setCurrentChangeWithoutAddChange(new FieldDiffs()
210       .setDiff("resolution", null, null)
211       .setDiff("severity", MAJOR.name(), CRITICAL.name())
212       .setDiff("type", RuleType.BUG.name(), CODE_SMELL.name()));
213
214     Set<DefaultIssue> issues = Set.of(defaultIssue1);
215     Map<String, ComponentDto> projectsByUuid = new HashMap<>();
216     projectsByUuid.put(project.branchUuid(), project);
217     Map<String, BranchDto> branchesByProjectUuid = new HashMap<>();
218     branchesByProjectUuid.put(project.branchUuid(), branch1);
219
220     underTest.distributeIssueChangeEvent(issues, projectsByUuid, branchesByProjectUuid);
221
222     Deque<PushEventDto> events = db.getDbClient().pushEventDao()
223       .selectChunkByProjectUuids(db.getSession(), Set.of(project.uuid()), 1l, null, 20);
224     assertThat(events).isEmpty();
225   }
226
227   private void assertNoIssueDistribution(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity,
228     @Nullable String type, @Nullable String transition, int page) {
229     underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, type, transition, branch, project.getKey());
230
231     Deque<PushEventDto> events = db.getDbClient().pushEventDao()
232       .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, page);
233     assertThat(events).hasSizeLessThan(page);
234   }
235
236   private void assertPushEventIsPersisted(ProjectDto project, BranchDto branch, IssueDto issue, @Nullable String severity,
237     @Nullable String type, @Nullable String transition, Boolean resolved, int page) {
238     underTest.distributeIssueChangeEvent(issue.toDefaultIssue(), severity, type, transition, branch, project.getKey());
239
240     Deque<PushEventDto> events = db.getDbClient().pushEventDao()
241       .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, page);
242     assertThat(events).isNotEmpty();
243     assertThat(events).extracting(PushEventDto::getName, PushEventDto::getProjectUuid)
244       .contains(tuple("IssueChanged", project.getUuid()));
245
246     String payload = new String(events.getLast().getPayload(), StandardCharsets.UTF_8);
247     if (severity != null) {
248       assertThat(payload).contains("\"userSeverity\":\"" + severity + "\"");
249     }
250
251     if (type != null) {
252       assertThat(payload).contains("\"userType\":\"" + type + "\"");
253     }
254
255     if (resolved != null) {
256       assertThat(payload).contains("\"resolved\":" + resolved);
257     }
258   }
259
260 }