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.

PullActionIT.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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.hotspot.ws;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.util.ArrayList;
  24. import java.util.Date;
  25. import java.util.List;
  26. import java.util.function.Consumer;
  27. import java.util.stream.Stream;
  28. import org.junit.Before;
  29. import org.junit.Rule;
  30. import org.junit.Test;
  31. import org.sonar.api.issue.Issue;
  32. import org.sonar.api.resources.Qualifiers;
  33. import org.sonar.api.utils.System2;
  34. import org.sonar.db.DbTester;
  35. import org.sonar.db.component.BranchDto;
  36. import org.sonar.db.component.ComponentDto;
  37. import org.sonar.db.component.ProjectData;
  38. import org.sonar.db.component.ResourceTypesRule;
  39. import org.sonar.db.issue.IssueDbTester;
  40. import org.sonar.db.issue.IssueDto;
  41. import org.sonar.db.project.ProjectDto;
  42. import org.sonar.db.protobuf.DbCommons;
  43. import org.sonar.db.protobuf.DbIssues;
  44. import org.sonar.db.rule.RuleDto;
  45. import org.sonar.db.user.UserDto;
  46. import org.sonar.server.component.ComponentFinder;
  47. import org.sonar.server.exceptions.ForbiddenException;
  48. import org.sonar.server.exceptions.NotFoundException;
  49. import org.sonar.server.tester.UserSessionRule;
  50. import org.sonar.server.ws.TestRequest;
  51. import org.sonar.server.ws.TestResponse;
  52. import org.sonar.server.ws.WsActionTester;
  53. import org.sonarqube.ws.Common;
  54. import org.sonarqube.ws.Hotspots;
  55. import org.sonarqube.ws.Issues;
  56. import static java.lang.String.format;
  57. import static org.assertj.core.api.Assertions.assertThat;
  58. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  59. import static org.mockito.Mockito.mock;
  60. import static org.mockito.Mockito.when;
  61. import static org.sonar.api.web.UserRole.USER;
  62. import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
  63. import static org.sonar.db.component.ComponentTesting.newFileDto;
  64. public class PullActionIT {
  65. private static final long NOW = 10_000_000_000L;
  66. private static final long PAST = 1_000_000_000L;
  67. private static final String DEFAULT_BRANCH = DEFAULT_MAIN_BRANCH_NAME;
  68. @Rule
  69. public UserSessionRule userSession = UserSessionRule.standalone();
  70. @Rule
  71. public DbTester db = DbTester.create(System2.INSTANCE);
  72. private final System2 system2 = mock(System2.class);
  73. private final PullHotspotsActionProtobufObjectGenerator pullActionProtobufObjectGenerator = new PullHotspotsActionProtobufObjectGenerator();
  74. private final ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT);
  75. private final ComponentFinder componentFinder = new ComponentFinder(db.getDbClient(), resourceTypes);
  76. private final IssueDbTester issueDbTester = new IssueDbTester(db);
  77. private final PullAction underTest = new PullAction(system2, componentFinder, db.getDbClient(), userSession,
  78. pullActionProtobufObjectGenerator);
  79. private final WsActionTester tester = new WsActionTester(underTest);
  80. private ProjectDto project;
  81. private ComponentDto correctMainBranch, incorrectMainBranch;
  82. private ComponentDto correctFile, incorrectFile;
  83. @Before
  84. public void before() {
  85. when(system2.now()).thenReturn(NOW);
  86. ProjectData projectData = db.components().insertPrivateProject();
  87. correctMainBranch = projectData.getMainBranchComponent();
  88. project = projectData.getProjectDto();
  89. correctFile = db.components().insertComponent(newFileDto(correctMainBranch));
  90. ProjectData incorrectProjectData = db.components().insertPrivateProject();
  91. incorrectMainBranch = incorrectProjectData.getMainBranchComponent();
  92. incorrectFile = db.components().insertComponent(newFileDto(incorrectMainBranch));
  93. }
  94. @Test
  95. public void wsExecution_whenMissingParams_shouldThrowIllegalArgumentException() {
  96. TestRequest request = tester.newRequest();
  97. assertThatThrownBy(() -> request.executeProtobuf(Issues.IssuesPullQueryTimestamp.class))
  98. .isInstanceOf(IllegalArgumentException.class);
  99. }
  100. @Test
  101. public void wsExecution_whenNotExistingProjectKey_shouldThrowException() {
  102. TestRequest request = tester.newRequest()
  103. .setParam("projectKey", "projectKey")
  104. .setParam("branchName", DEFAULT_BRANCH);
  105. assertThatThrownBy(request::execute)
  106. .isInstanceOf(NotFoundException.class)
  107. .hasMessage("Project 'projectKey' not found");
  108. }
  109. @Test
  110. public void wsExecution_whenValidProjectKeyWithoutPermissionsTo_shouldThrowException() {
  111. userSession.logIn();
  112. TestRequest request = tester.newRequest()
  113. .setParam("projectKey", correctMainBranch.getKey())
  114. .setParam("branchName", DEFAULT_BRANCH);
  115. assertThatThrownBy(request::execute)
  116. .isInstanceOf(ForbiddenException.class)
  117. .hasMessage("Insufficient privileges");
  118. }
  119. @Test
  120. public void wsExecution_whenNotExistingBranchKey_shouldThrowException() {
  121. DbCommons.TextRange textRange = DbCommons.TextRange.newBuilder()
  122. .setStartLine(1)
  123. .setEndLine(2)
  124. .setStartOffset(3)
  125. .setEndOffset(4)
  126. .build();
  127. DbIssues.Locations.Builder mainLocation = DbIssues.Locations.newBuilder()
  128. .setChecksum("hash")
  129. .setTextRange(textRange);
  130. RuleDto rule = db.rules().insertIssueRule(r -> r.setRepositoryKey("java").setRuleKey("S1000"));
  131. IssueDto issueDto = issueDbTester.insertIssue(rule, p -> p.setSeverity("MINOR")
  132. .setManualSeverity(true)
  133. .setMessage("message")
  134. .setCreatedAt(NOW)
  135. .setStatus(Issue.STATUS_RESOLVED)
  136. .setLocations(mainLocation.build())
  137. .setType(Common.RuleType.BUG.getNumber()));
  138. loginWithBrowsePermission(issueDto);
  139. TestRequest request = tester.newRequest()
  140. .setParam("projectKey", issueDto.getProjectKey())
  141. .setParam("branchName", "non-existent-branch");
  142. assertThatThrownBy(request::execute)
  143. .isInstanceOf(NotFoundException.class)
  144. .hasMessage(format("Branch 'non-existent-branch' in project '%s' not found", issueDto.getProjectKey()));
  145. }
  146. @Test
  147. public void wsExecution_whenValidProjectKeyAndOneHotspotOnBranch_shouldReturnOneHotspot() throws IOException {
  148. DbCommons.TextRange textRange = DbCommons.TextRange.newBuilder()
  149. .setStartLine(1)
  150. .setEndLine(2)
  151. .setStartOffset(3)
  152. .setEndOffset(4)
  153. .build();
  154. DbIssues.Locations.Builder mainLocation = DbIssues.Locations.newBuilder()
  155. .setChecksum("hash")
  156. .setTextRange(textRange);
  157. UserDto assignee = db.users().insertUser();
  158. IssueDto issueDto = issueDbTester.insertHotspot(p -> p.setSeverity("MINOR")
  159. .setMessage("message")
  160. .setAssigneeUuid(assignee.getUuid())
  161. .setIssueCreationTime(NOW)
  162. .setStatus(Issue.STATUS_TO_REVIEW)
  163. .setLocations(mainLocation.build()));
  164. loginWithBrowsePermission(issueDto);
  165. TestResponse response = tester.newRequest()
  166. .setParam("projectKey", issueDto.getProjectKey())
  167. .setParam("branchName", DEFAULT_BRANCH)
  168. .execute();
  169. List<Hotspots.HotspotLite> issues = readAllIssues(response);
  170. assertThat(issues).hasSize(1);
  171. Hotspots.TextRange expectedTextRange = Hotspots.TextRange.newBuilder()
  172. .setStartLine(1)
  173. .setEndLine(2)
  174. .setStartLineOffset(3)
  175. .setEndLineOffset(4)
  176. .setHash("hash")
  177. .build();
  178. Hotspots.HotspotLite expectedHotspotLite = Hotspots.HotspotLite.newBuilder()
  179. .setKey(issueDto.getKey())
  180. .setFilePath(issueDto.getFilePath())
  181. .setVulnerabilityProbability("LOW")
  182. .setStatus(Issue.STATUS_TO_REVIEW)
  183. .setMessage("message")
  184. .setCreationDate(NOW)
  185. .setTextRange(expectedTextRange)
  186. .setRuleKey(issueDto.getRuleKey().toString())
  187. .setAssignee(assignee.getLogin())
  188. .build();
  189. Hotspots.HotspotLite issueLite = issues.get(0);
  190. assertThat(issueLite).isEqualTo(expectedHotspotLite);
  191. }
  192. @Test
  193. public void wsExecution_whenHotspotOnAnotherBranchThanMain_shouldReturnOneIssue() throws IOException {
  194. ProjectData projectData = db.components().insertPrivateProjectWithCustomBranch("develop");
  195. ProjectDto project = projectData.getProjectDto();
  196. ComponentDto developBranch = projectData.getMainBranchComponent();
  197. ComponentDto developFile = db.components().insertComponent(newFileDto(developBranch));
  198. List<String> hotspotKeys = generateHotspots(developBranch, developFile, 1);
  199. loginWithBrowsePermission(project);
  200. TestRequest request = tester.newRequest()
  201. .setParam("projectKey", developBranch.getKey())
  202. .setParam("branchName", "develop");
  203. TestResponse response = request.execute();
  204. List<Hotspots.HotspotLite> issues = readAllIssues(response);
  205. assertThat(issues).hasSize(1)
  206. .extracting(Hotspots.HotspotLite::getKey)
  207. .containsExactlyInAnyOrderElementsOf(hotspotKeys);
  208. }
  209. @Test
  210. public void wsExecution_whenIncrementalModeThen_shouldReturnClosedIssues() throws IOException {
  211. IssueDto toReviewHotspot = issueDbTester.insertHotspot(p -> p.setSeverity("MINOR")
  212. .setMessage("toReviewHotspot")
  213. .setCreatedAt(NOW)
  214. .setStatus(Issue.STATUS_TO_REVIEW));
  215. issueDbTester.insertHotspot(p -> p.setSeverity("MINOR")
  216. .setMessage("closedIssue")
  217. .setCreatedAt(NOW)
  218. .setStatus(Issue.STATUS_CLOSED)
  219. .setComponentUuid(toReviewHotspot.getComponentUuid())
  220. .setProjectUuid(toReviewHotspot.getProjectUuid())
  221. .setIssueUpdateTime(PAST)
  222. .setIssueCreationTime(PAST));
  223. loginWithBrowsePermission(toReviewHotspot);
  224. TestRequest request = tester.newRequest()
  225. .setParam("projectKey", toReviewHotspot.getProjectKey())
  226. .setParam("branchName", DEFAULT_BRANCH)
  227. .setParam("changedSince", PAST + "");
  228. TestResponse response = request.execute();
  229. List<Hotspots.HotspotLite> issues = readAllIssues(response);
  230. assertThat(issues).hasSize(2);
  231. }
  232. @Test
  233. public void wsExecution_whenDifferentHotspotsInTheTable_shouldReturnOnlyThatBelongToSelectedProject() throws IOException {
  234. loginWithBrowsePermission(project);
  235. List<String> correctIssueKeys = generateHotspots(correctMainBranch, correctFile, 10);
  236. List<String> incorrectIssueKeys = generateHotspots(incorrectMainBranch, incorrectFile, 5);
  237. TestRequest request = tester.newRequest()
  238. .setParam("projectKey", correctMainBranch.getKey())
  239. .setParam("branchName", DEFAULT_BRANCH);
  240. TestResponse response = request.execute();
  241. List<Hotspots.HotspotLite> issues = readAllIssues(response);
  242. assertThat(issues)
  243. .hasSize(10)
  244. .extracting(Hotspots.HotspotLite::getKey)
  245. .containsExactlyInAnyOrderElementsOf(correctIssueKeys)
  246. .doesNotContainAnyElementsOf(incorrectIssueKeys);
  247. }
  248. @Test
  249. public void wsExecution_whenNoIssuesBelongToTheProject_shouldReturnZeroIssues() throws IOException {
  250. loginWithBrowsePermission(project);
  251. generateHotspots(incorrectMainBranch, incorrectFile, 5);
  252. TestRequest request = tester.newRequest()
  253. .setParam("projectKey", correctMainBranch.getKey())
  254. .setParam("branchName", DEFAULT_BRANCH);
  255. TestResponse response = request.execute();
  256. List<Hotspots.HotspotLite> issues = readAllIssues(response);
  257. assertThat(issues).isEmpty();
  258. }
  259. @Test
  260. public void wsExecution_whenLanguagesParam_shouldReturnOneIssue() throws IOException {
  261. loginWithBrowsePermission(project);
  262. RuleDto javaRule = db.rules().insert(r -> r.setLanguage("java"));
  263. IssueDto javaIssue = issueDbTester.insertHotspot(p -> p.setSeverity("MINOR")
  264. .setMessage("openIssue")
  265. .setCreatedAt(NOW)
  266. .setRule(javaRule)
  267. .setRuleUuid(javaRule.getUuid())
  268. .setStatus(Issue.STATUS_TO_REVIEW)
  269. .setLanguage("java")
  270. .setProject(correctMainBranch)
  271. .setComponent(correctFile));
  272. TestRequest request = tester.newRequest()
  273. .setParam("projectKey", correctMainBranch.getKey())
  274. .setParam("branchName", DEFAULT_BRANCH)
  275. .setParam("languages", "java");
  276. TestResponse response = request.execute();
  277. List<Hotspots.HotspotLite> issues = readAllIssues(response);
  278. assertThat(issues).hasSize(1)
  279. .extracting(Hotspots.HotspotLite::getKey)
  280. .containsExactly(javaIssue.getKey());
  281. }
  282. @Test
  283. public void wsExecution_whenChangedSinceParam_shouldReturnMatchingIssue() throws IOException {
  284. loginWithBrowsePermission(project);
  285. RuleDto javaRule = db.rules().insert(r -> r.setLanguage("java"));
  286. IssueDto issueBefore = issueDbTester.insertHotspot(p -> p.setSeverity("MINOR")
  287. .setMessage("openIssue")
  288. .setCreatedAt(NOW)
  289. .setIssueUpdateDate(new Date(NOW))
  290. .setRule(javaRule)
  291. .setRuleUuid(javaRule.getUuid())
  292. .setStatus(Issue.STATUS_TO_REVIEW)
  293. .setLanguage("java")
  294. .setProject(correctMainBranch)
  295. .setComponent(correctFile));
  296. IssueDto issueAfter = issueDbTester.insertHotspot(p -> p.setSeverity("MINOR")
  297. .setMessage("openIssue")
  298. .setCreatedAt(NOW)
  299. .setIssueUpdateDate(new Date(NOW + 1L))
  300. .setRule(javaRule)
  301. .setRuleUuid(javaRule.getUuid())
  302. .setStatus(Issue.STATUS_TO_REVIEW)
  303. .setLanguage("java")
  304. .setProject(correctMainBranch)
  305. .setComponent(correctFile));
  306. TestRequest request = tester.newRequest()
  307. .setParam("projectKey", correctMainBranch.getKey())
  308. .setParam("branchName", DEFAULT_BRANCH)
  309. .setParam("languages", "java")
  310. .setParam("changedSince", String.valueOf(issueBefore.getIssueUpdateTime() + 1L));
  311. TestResponse response = request.execute();
  312. List<Hotspots.HotspotLite> issues = readAllIssues(response);
  313. assertThat(issues).extracting(Hotspots.HotspotLite::getKey)
  314. .doesNotContain(issueBefore.getKey())
  315. .containsExactly(issueAfter.getKey());
  316. }
  317. @Test
  318. public void wsExecution_whenWrongLanguageSet_shouldReturnZeroIssues() throws IOException {
  319. loginWithBrowsePermission(project);
  320. RuleDto javascriptRule = db.rules().insert(r -> r.setLanguage("javascript"));
  321. issueDbTester.insertHotspot(p -> p.setSeverity("MINOR")
  322. .setMessage("openIssue")
  323. .setCreatedAt(NOW)
  324. .setRule(javascriptRule)
  325. .setRuleUuid(javascriptRule.getUuid())
  326. .setStatus(Issue.STATUS_TO_REVIEW)
  327. .setProject(correctMainBranch)
  328. .setComponent(correctFile));
  329. TestRequest request = tester.newRequest()
  330. .setParam("projectKey", correctMainBranch.getKey())
  331. .setParam("branchName", DEFAULT_BRANCH)
  332. .setParam("languages", "java");
  333. TestResponse response = request.execute();
  334. List<Hotspots.HotspotLite> issues = readAllIssues(response);
  335. assertThat(issues).isEmpty();
  336. }
  337. private List<String> generateHotspots(ComponentDto project, ComponentDto file, int numberOfIssues) {
  338. Consumer<IssueDto> consumer = i -> i.setProject(project)
  339. .setComponentUuid(file.uuid())
  340. .setStatus(Issue.STATUS_TO_REVIEW);
  341. return Stream.generate(() -> issueDbTester.insertHotspot(consumer))
  342. .limit(numberOfIssues)
  343. .map(IssueDto::getKey)
  344. .toList();
  345. }
  346. private List<Hotspots.HotspotLite> readAllIssues(TestResponse response) throws IOException {
  347. List<Hotspots.HotspotLite> issues = new ArrayList<>();
  348. InputStream inputStream = response.getInputStream();
  349. Hotspots.HotspotPullQueryTimestamp hotspotPullQueryTimestamp = Hotspots.HotspotPullQueryTimestamp.parseDelimitedFrom(inputStream);
  350. assertThat(hotspotPullQueryTimestamp).isNotNull();
  351. assertThat(hotspotPullQueryTimestamp.getQueryTimestamp()).isEqualTo(NOW);
  352. while (inputStream.available() > 0) {
  353. issues.add(Hotspots.HotspotLite.parseDelimitedFrom(inputStream));
  354. }
  355. return issues;
  356. }
  357. private void loginWithBrowsePermission(IssueDto issueDto) {
  358. BranchDto branchDto = db.getDbClient().branchDao().selectByUuid(db.getSession(), issueDto.getProjectUuid()).get();
  359. ProjectDto projectDto = db.getDbClient().projectDao().selectByUuid(db.getSession(), branchDto.getProjectUuid()).get();
  360. loginWithBrowsePermission(projectDto);
  361. }
  362. private void loginWithBrowsePermission(ProjectDto project) {
  363. UserDto user = db.users().insertUser("john");
  364. userSession.logIn(user).addProjectPermission(USER, project);
  365. }
  366. }