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