You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SearchEventsActionNewIssuesIT.java 16KB

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