]> source.dussan.org Git - sonarqube.git/blob
b8e93f8d6afa84725a4d0a2ea6d33b02a30047aa
[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.util.Date;
23 import java.util.stream.IntStream;
24 import java.util.stream.Stream;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.sonar.api.platform.Server;
28 import org.sonar.api.rules.RuleType;
29 import org.sonar.api.server.ws.WebService;
30 import org.sonar.api.server.ws.WebService.Param;
31 import org.sonar.api.web.UserRole;
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.ComponentDto;
37 import org.sonar.db.component.SnapshotDto;
38 import org.sonar.db.event.EventDto;
39 import org.sonar.server.es.EsTester;
40 import org.sonar.server.exceptions.UnauthorizedException;
41 import org.sonar.server.issue.index.IssueIndex;
42 import org.sonar.server.issue.index.IssueIndexSyncProgressChecker;
43 import org.sonar.server.issue.index.IssueIndexer;
44 import org.sonar.server.issue.index.IssueIteratorFactory;
45 import org.sonar.server.projectanalysis.ws.EventCategory;
46 import org.sonar.server.tester.UserSessionRule;
47 import org.sonar.server.ws.KeyExamples;
48 import org.sonar.server.ws.WsActionTester;
49 import org.sonarqube.ws.Developers.SearchEventsWsResponse;
50 import org.sonarqube.ws.Developers.SearchEventsWsResponse.Event;
51
52 import static java.lang.String.format;
53 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
54 import static org.apache.commons.lang.math.RandomUtils.nextInt;
55 import static org.apache.commons.lang.math.RandomUtils.nextLong;
56 import static org.assertj.core.api.Assertions.assertThat;
57 import static org.assertj.core.api.Assertions.assertThatThrownBy;
58 import static org.assertj.core.api.Assertions.tuple;
59 import static org.mockito.ArgumentMatchers.any;
60 import static org.mockito.ArgumentMatchers.argThat;
61 import static org.mockito.Mockito.mock;
62 import static org.mockito.Mockito.verify;
63 import static org.mockito.Mockito.when;
64 import static org.sonar.api.utils.DateUtils.formatDateTime;
65 import static org.sonar.api.web.UserRole.USER;
66 import static org.sonar.db.event.EventTesting.newEvent;
67 import static org.sonar.server.developers.ws.SearchEventsAction.PARAM_FROM;
68 import static org.sonar.server.developers.ws.SearchEventsAction.PARAM_PROJECTS;
69 import static org.sonar.test.JsonAssert.assertJson;
70
71 public class SearchEventsActionTest {
72
73   private static final RuleType[] RULE_TYPES_EXCEPT_HOTSPOT = Stream.of(RuleType.values())
74     .filter(r -> r != RuleType.SECURITY_HOTSPOT)
75     .toArray(RuleType[]::new);
76
77   @Rule
78   public DbTester db = DbTester.create();
79   @Rule
80   public EsTester es = EsTester.create();
81   @Rule
82   public UserSessionRule userSession = UserSessionRule.standalone().logIn();
83   private Server server = mock(Server.class);
84   private IssueIndex issueIndex = new IssueIndex(es.client(), null, null, null);
85   private IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = mock(IssueIndexSyncProgressChecker.class);
86   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
87   private WsActionTester ws = new WsActionTester(new SearchEventsAction(db.getDbClient(), userSession, server, issueIndex,
88     issueIndexSyncProgressChecker));
89
90   @Test
91   public void definition() {
92     WebService.Action definition = ws.getDef();
93
94     assertThat(definition.key()).isEqualTo("search_events");
95     assertThat(definition.description()).isNotEmpty();
96     assertThat(definition.isPost()).isFalse();
97     assertThat(definition.isInternal()).isTrue();
98     assertThat(definition.since()).isEqualTo("1.0");
99     assertThat(definition.description()).isNotEmpty();
100     assertThat(definition.responseExampleAsString()).isNotEmpty();
101     assertThat(definition.params()).extracting(Param::key).containsOnly("projects", "from");
102     Param projects = definition.param("projects");
103     assertThat(projects.isRequired()).isTrue();
104     assertThat(projects.exampleValue()).isEqualTo("my_project,another_project");
105     assertThat(definition.param("from").isRequired()).isTrue();
106   }
107
108   @Test
109   public void json_example() {
110     ComponentDto project = db.components().insertPrivateProject(p -> p.setName("My Project").setKey(KeyExamples.KEY_PROJECT_EXAMPLE_001));
111     userSession.addProjectPermission(USER, project);
112     SnapshotDto analysis = insertAnalysis(project, 1_500_000_000_000L);
113     EventDto e1 = db.events().insertEvent(newQualityGateEvent(analysis).setName("Failed").setDate(analysis.getCreatedAt()));
114     IntStream.range(0, 15).forEach(x -> insertIssue(project, analysis));
115     issueIndexer.indexAllIssues();
116     when(server.getPublicRootUrl()).thenReturn("https://sonarcloud.io");
117
118     String result = ws.newRequest()
119       .setParam(PARAM_PROJECTS, project.getKey())
120       .setParam(PARAM_FROM, formatDateTime(analysis.getCreatedAt() - 1_000L))
121       .execute().getInput();
122
123     assertJson(result).ignoreFields("date", "link").isSimilarTo(ws.getDef().responseExampleAsString());
124   }
125
126   @Test
127   public void events() {
128     when(server.getPublicRootUrl()).thenReturn("https://sonarcloud.io");
129     ComponentDto project = db.components().insertPrivateProject();
130     userSession.addProjectPermission(USER, project);
131     ComponentDto branch = db.components().insertProjectBranch(project);
132     SnapshotDto projectAnalysis = insertAnalysis(project, 1_500_000_000_000L);
133     db.events().insertEvent(newQualityGateEvent(projectAnalysis).setDate(projectAnalysis.getCreatedAt()).setName("Passed"));
134     insertIssue(project, projectAnalysis);
135     insertIssue(project, projectAnalysis);
136     SnapshotDto branchAnalysis = insertAnalysis(branch, 1_501_000_000_000L);
137     db.events().insertEvent(newQualityGateEvent(branchAnalysis).setDate(branchAnalysis.getCreatedAt()).setName("Failed"));
138     insertIssue(branch, branchAnalysis);
139     issueIndexer.indexAllIssues();
140
141     SearchEventsWsResponse result = ws.newRequest()
142       .setParam(PARAM_PROJECTS, project.getKey())
143       .setParam(PARAM_FROM, formatDateTime(1_499_000_000_000L))
144       .executeProtobuf(SearchEventsWsResponse.class);
145
146     assertThat(result.getEventsList())
147       .extracting(Event::getCategory, Event::getProject, Event::getMessage)
148       .containsOnly(
149         tuple("QUALITY_GATE", project.getKey(), format("Quality Gate status of project '%s' changed to 'Passed'", project.name())),
150         tuple("QUALITY_GATE", project.getKey(), format("Quality Gate status of project '%s' on branch '%s' changed to 'Failed'", project.name(), branch.getBranch())),
151         tuple("NEW_ISSUES", project.getKey(), format("You have 2 new issues on project '%s'", project.name())),
152         tuple("NEW_ISSUES", project.getKey(), format("You have 1 new issue on project '%s' on branch '%s'", project.name(), branch.getBranch())));
153     verify(issueIndexSyncProgressChecker).checkIfAnyComponentsNeedIssueSync(any(), argThat(arg -> arg.contains(project.getKey())));
154   }
155
156   @Test
157   public void does_not_return_old_events() {
158     ComponentDto project = db.components().insertPrivateProject();
159     userSession.addProjectPermission(USER, project);
160     SnapshotDto analysis = insertAnalysis(project, 1_500_000_000_000L);
161     insertIssue(project, analysis);
162     db.events().insertEvent(newQualityGateEvent(analysis).setDate(analysis.getCreatedAt()).setName("Passed"));
163     SnapshotDto oldAnalysis = insertAnalysis(project, 1_400_000_000_000L);
164     insertIssue(project, oldAnalysis);
165     db.events().insertEvent(newQualityGateEvent(oldAnalysis).setDate(oldAnalysis.getCreatedAt()).setName("Failed"));
166     issueIndexer.indexAllIssues();
167
168     SearchEventsWsResponse result = ws.newRequest()
169       .setParam(PARAM_PROJECTS, project.getKey())
170       .setParam(PARAM_FROM, formatDateTime(analysis.getCreatedAt() - 1450_000_000_000L))
171       .executeProtobuf(SearchEventsWsResponse.class);
172
173     assertThat(result.getEventsList())
174       .extracting(Event::getCategory, Event::getDate)
175       .containsOnly(
176         tuple("NEW_ISSUES", formatDateTime(analysis.getCreatedAt())),
177         tuple("QUALITY_GATE", formatDateTime(analysis.getCreatedAt())));
178   }
179
180   @Test
181   public void empty_response_for_empty_list_of_projects() {
182     SearchEventsWsResponse result = ws.newRequest()
183       .setParam(PARAM_PROJECTS, "")
184       .setParam(PARAM_FROM, "")
185       .executeProtobuf(SearchEventsWsResponse.class);
186
187     assertThat(result.getEventsList()).isEmpty();
188   }
189
190   @Test
191   public void does_not_return_events_of_project_for_which_the_current_user_has_no_browse_permission() {
192     ComponentDto project1 = db.components().insertPrivateProject();
193     userSession.addProjectPermission(UserRole.CODEVIEWER, project1);
194     userSession.addProjectPermission(UserRole.ISSUE_ADMIN, project1);
195
196     ComponentDto project2 = db.components().insertPrivateProject();
197     userSession.addProjectPermission(USER, project2);
198
199     SnapshotDto a1 = insertAnalysis(project1, 1_500_000_000_000L);
200     EventDto e1 = db.events().insertEvent(newQualityGateEvent(a1).setDate(a1.getCreatedAt()));
201     insertIssue(project1, a1);
202     SnapshotDto a2 = insertAnalysis(project2, 1_500_000_000_000L);
203     EventDto e2 = db.events().insertEvent(newQualityGateEvent(a2).setDate(a2.getCreatedAt()));
204     insertIssue(project2, a2);
205     issueIndexer.indexAllIssues();
206
207     String stringFrom = formatDateTime(a1.getCreatedAt() - 1_000L);
208     SearchEventsWsResponse result = ws.newRequest()
209       .setParam(PARAM_PROJECTS, String.join(",", project1.getKey(), project2.getKey()))
210       .setParam(PARAM_FROM, String.join(",", stringFrom, stringFrom))
211       .executeProtobuf(SearchEventsWsResponse.class);
212
213     assertThat(result.getEventsList())
214       .extracting(Event::getCategory, Event::getProject)
215       .containsOnly(
216         tuple("NEW_ISSUES", project2.getKey()),
217         tuple(EventCategory.QUALITY_GATE.name(), project2.getKey()));
218   }
219
220   @Test
221   public void empty_response_if_project_key_is_unknown() {
222     long from = 1_500_000_000_000L;
223     SearchEventsWsResponse result = ws.newRequest()
224       .setParam(PARAM_PROJECTS, "unknown")
225       .setParam(PARAM_FROM, formatDateTime(from - 1_000L))
226       .executeProtobuf(SearchEventsWsResponse.class);
227
228     assertThat(result.getEventsList()).isEmpty();
229   }
230
231   @Test
232   public void fail_when_not_loggued() {
233     userSession.anonymous();
234     ComponentDto project = db.components().insertPrivateProject();
235
236     assertThatThrownBy(() -> {
237       ws.newRequest()
238         .setParam(PARAM_PROJECTS, project.getKey())
239         .setParam(PARAM_FROM, formatDateTime(1_000L))
240         .execute();
241     })
242       .isInstanceOf(UnauthorizedException.class);
243   }
244
245   @Test
246   public void fail_if_date_format_is_not_valid() {
247     assertThatThrownBy(() -> {
248       ws.newRequest()
249         .setParam(PARAM_PROJECTS, "foo")
250         .setParam(PARAM_FROM, "wat")
251         .executeProtobuf(SearchEventsWsResponse.class);
252     })
253       .isInstanceOf(IllegalArgumentException.class)
254       .hasMessage("'wat' cannot be parsed as either a date or date+time");
255   }
256
257   private static EventDto newQualityGateEvent(SnapshotDto analysis) {
258     return newEvent(analysis).setCategory(EventCategory.QUALITY_GATE.getLabel());
259   }
260
261   private CeActivityDto insertActivity(ComponentDto project, SnapshotDto analysis, CeActivityDto.Status status) {
262     CeQueueDto queueDto = new CeQueueDto();
263     queueDto.setTaskType(CeTaskTypes.REPORT);
264     String mainBranchProjectUuid = project.getMainBranchProjectUuid();
265     queueDto.setComponentUuid(mainBranchProjectUuid == null ? project.uuid() : mainBranchProjectUuid);
266     queueDto.setUuid(randomAlphanumeric(40));
267     queueDto.setCreatedAt(nextLong());
268     CeActivityDto activityDto = new CeActivityDto(queueDto);
269     activityDto.setStatus(status);
270     activityDto.setExecutionTimeMs(nextLong());
271     activityDto.setExecutedAt(nextLong());
272     activityDto.setAnalysisUuid(analysis.getUuid());
273     db.getDbClient().ceActivityDao().insert(db.getSession(), activityDto);
274     db.commit();
275     return activityDto;
276   }
277
278   private void insertIssue(ComponentDto component, SnapshotDto analysis) {
279     db.issues().insert(db.rules().insert(), component, component,
280       i -> i.setIssueCreationDate(new Date(analysis.getCreatedAt()))
281         .setAssigneeUuid(userSession.getUuid())
282         .setType(randomRuleTypeExceptHotspot()));
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 RuleType randomRuleTypeExceptHotspot() {
292     return RULE_TYPES_EXCEPT_HOTSPOT[nextInt(RULE_TYPES_EXCEPT_HOTSPOT.length)];
293   }
294 }