]> source.dussan.org Git - sonarqube.git/blob
7314720e63cd1249c1b726df2d8a2d23ea3b9712
[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.server.developers.ws;
21
22 import java.io.UnsupportedEncodingException;
23 import java.net.URLEncoder;
24 import java.util.Date;
25 import java.util.stream.Stream;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.sonar.api.platform.Server;
29 import org.sonar.api.rules.RuleType;
30 import org.sonar.db.DbTester;
31 import org.sonar.db.ce.CeActivityDto;
32 import org.sonar.db.ce.CeQueueDto;
33 import org.sonar.db.ce.CeTaskTypes;
34 import org.sonar.db.component.BranchType;
35 import org.sonar.db.component.ComponentDto;
36 import org.sonar.db.component.SnapshotDto;
37 import org.sonar.db.rule.RuleDto;
38 import org.sonar.server.es.EsTester;
39 import org.sonar.server.issue.index.IssueIndex;
40 import org.sonar.server.issue.index.IssueIndexSyncProgressChecker;
41 import org.sonar.server.issue.index.IssueIndexer;
42 import org.sonar.server.issue.index.IssueIteratorFactory;
43 import org.sonar.server.tester.UserSessionRule;
44 import org.sonar.server.ws.WsActionTester;
45 import org.sonarqube.ws.Developers.SearchEventsWsResponse;
46 import org.sonarqube.ws.Developers.SearchEventsWsResponse.Event;
47
48 import static java.lang.String.format;
49 import static java.nio.charset.StandardCharsets.UTF_8;
50 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
51 import static org.apache.commons.lang.math.RandomUtils.nextInt;
52 import static org.apache.commons.lang.math.RandomUtils.nextLong;
53 import static org.assertj.core.api.Assertions.assertThat;
54 import static org.assertj.core.api.Assertions.tuple;
55 import static org.mockito.Mockito.mock;
56 import static org.mockito.Mockito.when;
57 import static org.sonar.api.utils.DateUtils.formatDateTime;
58 import static org.sonar.api.web.UserRole.USER;
59 import static org.sonar.db.component.BranchType.BRANCH;
60 import static org.sonar.server.developers.ws.SearchEventsAction.PARAM_FROM;
61 import static org.sonar.server.developers.ws.SearchEventsAction.PARAM_PROJECTS;
62
63 public class SearchEventsActionNewIssuesTest {
64
65   private static final RuleType[] RULE_TYPES_EXCEPT_HOTSPOT = Stream.of(RuleType.values())
66     .filter(r -> r != RuleType.SECURITY_HOTSPOT)
67     .toArray(RuleType[]::new);
68
69   @Rule
70   public DbTester db = DbTester.create();
71   @Rule
72   public EsTester es = EsTester.create();
73   @Rule
74   public UserSessionRule userSession = UserSessionRule.standalone();
75
76   private Server server = mock(Server.class);
77
78   private IssueIndex issueIndex = new IssueIndex(es.client(), null, null, null);
79   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
80   private IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = mock(IssueIndexSyncProgressChecker.class);
81   private WsActionTester ws = new WsActionTester(new SearchEventsAction(db.getDbClient(), userSession, server, issueIndex,
82     issueIndexSyncProgressChecker));
83
84   @Test
85   public void issue_event() {
86     userSession.logIn();
87     when(server.getPublicRootUrl()).thenReturn("https://sonarcloud.io");
88     ComponentDto project = db.components().insertPrivateProject();
89     userSession.addProjectPermission(USER, project);
90     SnapshotDto analysis = insertAnalysis(project, 1_500_000_000_000L);
91     insertIssue(project, analysis);
92     insertIssue(project, analysis);
93     // will be ignored
94     insertSecurityHotspot(project, analysis);
95     issueIndexer.indexAllIssues();
96
97     long from = analysis.getCreatedAt() - 1_000_000L;
98     SearchEventsWsResponse result = ws.newRequest()
99       .setParam(PARAM_PROJECTS, project.getKey())
100       .setParam(PARAM_FROM, formatDateTime(from))
101       .executeProtobuf(SearchEventsWsResponse.class);
102
103     assertThat(result.getEventsList())
104       .extracting(Event::getCategory, Event::getProject, Event::getMessage, Event::getLink, Event::getDate)
105       .containsOnly(
106         tuple("NEW_ISSUES", project.getKey(), format("You have 2 new issues on project '%s'", project.name()),
107           format("https://sonarcloud.io/project/issues?id=%s&createdAfter=%s&assignees=%s&resolved=false", project.getKey(), encode(formatDateTime(from + 1_000L)),
108             userSession.getLogin()),
109           formatDateTime(analysis.getCreatedAt())));
110   }
111
112   @Test
113   public void many_issues_events() {
114     userSession.logIn();
115     long from = 1_500_000_000_000L;
116     ComponentDto project = db.components().insertPrivateProject(p -> p.setName("SonarQube"));
117     userSession.addProjectPermission(USER, project);
118     SnapshotDto analysis = insertAnalysis(project, from);
119     insertIssue(project, analysis);
120     insertIssue(project, analysis);
121     issueIndexer.indexAllIssues();
122     String fromDate = formatDateTime(from - 1_000L);
123
124     SearchEventsWsResponse result = ws.newRequest()
125       .setParam(PARAM_PROJECTS, project.getKey())
126       .setParam(PARAM_FROM, fromDate)
127       .executeProtobuf(SearchEventsWsResponse.class);
128
129     assertThat(result.getEventsList()).extracting(Event::getCategory, Event::getMessage, Event::getProject, Event::getDate)
130       .containsExactly(tuple("NEW_ISSUES", "You have 2 new issues on project 'SonarQube'", project.getKey(),
131         formatDateTime(from)));
132   }
133
134   @Test
135   public void does_not_return_old_issue() {
136     userSession.logIn();
137     ComponentDto project = db.components().insertPrivateProject();
138     userSession.addProjectPermission(USER, project);
139     SnapshotDto analysis = insertAnalysis(project, 1_500_000_000_000L);
140     db.issues().insert(db.rules().insert(), project, project, i -> i.setIssueCreationDate(new Date(analysis.getCreatedAt() - 10_000L)));
141     issueIndexer.indexAllIssues();
142
143     SearchEventsWsResponse result = ws.newRequest()
144       .setParam(PARAM_PROJECTS, project.getKey())
145       .setParam(PARAM_FROM, formatDateTime(analysis.getCreatedAt() - 1_000L))
146       .executeProtobuf(SearchEventsWsResponse.class);
147
148     assertThat(result.getEventsList()).isEmpty();
149   }
150
151   @Test
152   public void return_link_to_issue_search_for_new_issues_event() {
153     userSession.logIn("my_login");
154     ComponentDto project = db.components().insertPrivateProject(p -> p.setKey("my_project"));
155     userSession.addProjectPermission(USER, project);
156     SnapshotDto analysis = insertAnalysis(project, 1_400_000_000_000L);
157     insertIssue(project, analysis);
158     issueIndexer.indexAllIssues();
159     when(server.getPublicRootUrl()).thenReturn("https://sonarcloud.io");
160
161     SearchEventsWsResponse result = ws.newRequest()
162       .setParam(PARAM_PROJECTS, project.getKey())
163       .setParam(PARAM_FROM, formatDateTime(analysis.getCreatedAt() - 1_000L))
164       .executeProtobuf(SearchEventsWsResponse.class);
165
166     assertThat(result.getEventsList()).extracting(Event::getLink)
167       .containsExactly("https://sonarcloud.io/project/issues?id=my_project&createdAfter=" + encode(formatDateTime(analysis.getCreatedAt())) + "&assignees=my_login&resolved=false");
168   }
169
170   @Test
171   public void branch_issues_events() {
172     userSession.logIn().setSystemAdministrator();
173     when(server.getPublicRootUrl()).thenReturn("https://sonarcloud.io");
174     ComponentDto project = db.components().insertPrivateProject();
175     userSession.addProjectPermission(USER, project);
176     ComponentDto branch1 = db.components().insertProjectBranch(project, b -> b.setBranchType(BRANCH).setKey("branch1"));
177     SnapshotDto branch1Analysis = insertAnalysis(branch1, 1_500_000_000_000L);
178     insertIssue(branch1, branch1Analysis);
179     insertIssue(branch1, branch1Analysis);
180     ComponentDto branch2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setKey("branch"));
181     SnapshotDto branch2Analysis = insertAnalysis(branch2, 1_300_000_000_000L);
182     insertIssue(branch2, branch2Analysis);
183     issueIndexer.indexAllIssues();
184
185     long from = 1_000_000_000_000L;
186     SearchEventsWsResponse result = ws.newRequest()
187       .setParam(PARAM_PROJECTS, project.getKey())
188       .setParam(PARAM_FROM, formatDateTime(from))
189       .executeProtobuf(SearchEventsWsResponse.class);
190
191     assertThat(result.getEventsList())
192       .extracting(Event::getCategory, Event::getProject, Event::getMessage, Event::getLink, Event::getDate)
193       .containsOnly(
194         tuple("NEW_ISSUES", project.getKey(), format("You have 2 new issues on project '%s' on branch '%s'", project.name(), branch1.getBranch()),
195           format("https://sonarcloud.io/project/issues?id=%s&createdAfter=%s&assignees=%s&resolved=false&branch=%s", branch1.getKey(), encode(formatDateTime(from + 1_000L)),
196             userSession.getLogin(), branch1.getBranch()),
197           formatDateTime(branch1Analysis.getCreatedAt())),
198         tuple("NEW_ISSUES", project.getKey(), format("You have 1 new issue on project '%s' on branch '%s'", project.name(), branch2.getBranch()),
199           format("https://sonarcloud.io/project/issues?id=%s&createdAfter=%s&assignees=%s&resolved=false&branch=%s", branch2.getKey(), encode(formatDateTime(from + 1_000L)),
200             userSession.getLogin(), branch2.getBranch()),
201           formatDateTime(branch2Analysis.getCreatedAt())));
202   }
203
204   @Test
205   public void pull_request_issues_events() {
206     userSession.logIn().setSystemAdministrator();
207     when(server.getPublicRootUrl()).thenReturn("https://sonarcloud.io");
208     ComponentDto project = db.components().insertPrivateProject();
209     userSession.addProjectPermission(USER, project);
210     ComponentDto nonMainBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BRANCH).setKey("nonMain"));
211     SnapshotDto nonMainBranchAnalysis = insertAnalysis(nonMainBranch, 1_500_000_000_000L);
212     insertIssue(nonMainBranch, nonMainBranchAnalysis);
213     insertIssue(nonMainBranch, nonMainBranchAnalysis);
214     String pullRequestKey = "42";
215     ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST).setKey(pullRequestKey));
216     SnapshotDto pullRequestAnalysis = insertAnalysis(pullRequest, 1_300_000_000_000L);
217     insertIssue(pullRequest, pullRequestAnalysis);
218     issueIndexer.indexAllIssues();
219
220     long from = 1_000_000_000_000L;
221     SearchEventsWsResponse result = ws.newRequest()
222       .setParam(PARAM_PROJECTS, project.getKey())
223       .setParam(PARAM_FROM, formatDateTime(from))
224       .executeProtobuf(SearchEventsWsResponse.class);
225
226     assertThat(result.getEventsList())
227       .extracting(Event::getCategory, Event::getProject, Event::getMessage, Event::getLink, Event::getDate)
228       .containsOnly(
229         tuple("NEW_ISSUES", project.getKey(), format("You have 2 new issues on project '%s' on branch '%s'", project.name(), nonMainBranch.getBranch()),
230           format("https://sonarcloud.io/project/issues?id=%s&createdAfter=%s&assignees=%s&resolved=false&branch=%s", nonMainBranch.getKey(), encode(formatDateTime(from + 1_000L)),
231             userSession.getLogin(), nonMainBranch.getBranch()),
232           formatDateTime(nonMainBranchAnalysis.getCreatedAt())),
233         tuple("NEW_ISSUES", project.getKey(), format("You have 1 new issue on project '%s' on pull request '%s'", project.name(), pullRequestKey),
234           format("https://sonarcloud.io/project/issues?id=%s&createdAfter=%s&assignees=%s&resolved=false&pullRequest=%s", pullRequest.getKey(),
235             encode(formatDateTime(from + 1_000L)),
236             userSession.getLogin(), pullRequestKey),
237           formatDateTime(pullRequestAnalysis.getCreatedAt())));
238   }
239
240   @Test
241   public void encode_link() {
242     userSession.logIn("rågnar").setSystemAdministrator();
243     long from = 1_500_000_000_000L;
244     ComponentDto project = db.components().insertPrivateProject(p -> p.setKey("M&M's"));
245     userSession.addProjectPermission(USER, project);
246     SnapshotDto analysis = insertAnalysis(project, from);
247     insertIssue(project, analysis);
248     issueIndexer.indexAllIssues();
249     when(server.getPublicRootUrl()).thenReturn("http://sonarcloud.io");
250
251     String fromDate = formatDateTime(from - 1_000L);
252     SearchEventsWsResponse result = ws.newRequest()
253       .setParam(PARAM_PROJECTS, project.getKey())
254       .setParam(PARAM_FROM, fromDate)
255       .executeProtobuf(SearchEventsWsResponse.class);
256
257     assertThat(result.getEventsList()).extracting(Event::getLink)
258       .containsExactly("http://sonarcloud.io/project/issues?id=M%26M%27s&createdAfter=" + encode(formatDateTime(from)) + "&assignees=r%C3%A5gnar&resolved=false");
259   }
260
261   private String encode(String text) {
262     try {
263       return URLEncoder.encode(text, UTF_8.name());
264     } catch (UnsupportedEncodingException e) {
265       throw new IllegalStateException(format("Cannot encode %s", text), e);
266     }
267   }
268
269   private void insertIssue(ComponentDto component, SnapshotDto analysis) {
270     RuleDto rule = db.rules().insert(r -> r.setType(randomRuleTypeExceptHotspot()));
271     db.issues().insert(rule, component, component,
272       i -> i.setIssueCreationDate(new Date(analysis.getCreatedAt()))
273         .setAssigneeUuid(userSession.getUuid())
274         .setType(randomRuleTypeExceptHotspot()));
275   }
276
277   private void insertSecurityHotspot(ComponentDto component, SnapshotDto analysis) {
278     RuleDto rule = db.rules().insert(r -> r.setType(RuleType.SECURITY_HOTSPOT));
279     db.issues().insert(rule, component, component,
280       i -> i.setIssueCreationDate(new Date(analysis.getCreatedAt()))
281         .setAssigneeUuid(userSession.getUuid())
282         .setType(RuleType.SECURITY_HOTSPOT));
283   }
284
285   private SnapshotDto insertAnalysis(ComponentDto project, long analysisDate) {
286     SnapshotDto analysis = db.components().insertSnapshot(project, s -> s.setCreatedAt(analysisDate));
287     insertActivity(project, analysis, CeActivityDto.Status.SUCCESS);
288     return analysis;
289   }
290
291   private CeActivityDto insertActivity(ComponentDto project, SnapshotDto analysis, CeActivityDto.Status status) {
292     CeQueueDto queueDto = new CeQueueDto();
293     queueDto.setTaskType(CeTaskTypes.REPORT);
294     String mainBranchProjectUuid = project.getMainBranchProjectUuid();
295     queueDto.setComponentUuid(mainBranchProjectUuid == null ? project.uuid() : mainBranchProjectUuid);
296     queueDto.setUuid(randomAlphanumeric(40));
297     queueDto.setCreatedAt(nextLong());
298     CeActivityDto activityDto = new CeActivityDto(queueDto);
299     activityDto.setStatus(status);
300     activityDto.setExecutionTimeMs(nextLong());
301     activityDto.setExecutedAt(nextLong());
302     activityDto.setAnalysisUuid(analysis.getUuid());
303     db.getDbClient().ceActivityDao().insert(db.getSession(), activityDto);
304     db.commit();
305     return activityDto;
306   }
307
308   private RuleType randomRuleTypeExceptHotspot() {
309     return RULE_TYPES_EXCEPT_HOTSPOT[nextInt(RULE_TYPES_EXCEPT_HOTSPOT.length)];
310   }
311 }