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.

ShowActionTest.java 52KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  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.hotspot.ws;
  21. import com.google.common.collect.ImmutableSet;
  22. import com.google.common.collect.Sets;
  23. import com.tngtech.java.junit.dataprovider.DataProvider;
  24. import com.tngtech.java.junit.dataprovider.DataProviderRunner;
  25. import com.tngtech.java.junit.dataprovider.UseDataProvider;
  26. import java.util.Arrays;
  27. import java.util.Collections;
  28. import java.util.List;
  29. import java.util.Random;
  30. import java.util.Set;
  31. import java.util.function.Consumer;
  32. import java.util.function.Supplier;
  33. import java.util.stream.Collectors;
  34. import java.util.stream.IntStream;
  35. import java.util.stream.Stream;
  36. import javax.annotation.Nullable;
  37. import org.assertj.core.groups.Tuple;
  38. import org.junit.Rule;
  39. import org.junit.Test;
  40. import org.junit.runner.RunWith;
  41. import org.mockito.ArgumentMatcher;
  42. import org.mockito.Mockito;
  43. import org.sonar.api.issue.Issue;
  44. import org.sonar.api.rules.RuleType;
  45. import org.sonar.api.utils.System2;
  46. import org.sonar.api.web.UserRole;
  47. import org.sonar.core.util.UuidFactory;
  48. import org.sonar.core.util.UuidFactoryFast;
  49. import org.sonar.db.DbClient;
  50. import org.sonar.db.DbSession;
  51. import org.sonar.db.DbTester;
  52. import org.sonar.db.component.BranchType;
  53. import org.sonar.db.component.ComponentDto;
  54. import org.sonar.db.issue.IssueDto;
  55. import org.sonar.db.protobuf.DbCommons;
  56. import org.sonar.db.protobuf.DbIssues;
  57. import org.sonar.db.rule.RuleDescriptionSectionDto;
  58. import org.sonar.db.rule.RuleDto;
  59. import org.sonar.db.rule.RuleTesting;
  60. import org.sonar.db.user.UserDto;
  61. import org.sonar.db.user.UserTesting;
  62. import org.sonar.server.es.EsTester;
  63. import org.sonar.server.exceptions.ForbiddenException;
  64. import org.sonar.server.exceptions.NotFoundException;
  65. import org.sonar.server.issue.AvatarResolver;
  66. import org.sonar.server.issue.AvatarResolverImpl;
  67. import org.sonar.server.issue.IssueChangeWSSupport;
  68. import org.sonar.server.issue.IssueChangeWSSupport.FormattingContext;
  69. import org.sonar.server.issue.IssueChangeWSSupport.Load;
  70. import org.sonar.server.issue.TextRangeResponseFormatter;
  71. import org.sonar.server.issue.ws.UserResponseFormatter;
  72. import org.sonar.server.security.SecurityStandards;
  73. import org.sonar.server.security.SecurityStandards.SQCategory;
  74. import org.sonar.server.tester.UserSessionRule;
  75. import org.sonar.server.ws.TestRequest;
  76. import org.sonar.server.ws.WsActionTester;
  77. import org.sonarqube.ws.Common;
  78. import org.sonarqube.ws.Common.Changelog.Diff;
  79. import org.sonarqube.ws.Common.Location;
  80. import org.sonarqube.ws.Common.User;
  81. import org.sonarqube.ws.Hotspots;
  82. import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
  83. import static org.assertj.core.api.Assertions.assertThat;
  84. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  85. import static org.assertj.core.api.Assertions.tuple;
  86. import static org.mockito.ArgumentMatchers.any;
  87. import static org.mockito.ArgumentMatchers.anySet;
  88. import static org.mockito.ArgumentMatchers.argThat;
  89. import static org.mockito.ArgumentMatchers.eq;
  90. import static org.mockito.Mockito.verify;
  91. import static org.mockito.Mockito.when;
  92. import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
  93. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
  94. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
  95. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.INTRODUCTION_SECTION_KEY;
  96. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY;
  97. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;
  98. import static org.sonar.db.component.ComponentTesting.newFileDto;
  99. import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
  100. import static org.sonar.db.rule.RuleDto.Format.HTML;
  101. import static org.sonar.db.rule.RuleDto.Format.MARKDOWN;
  102. @RunWith(DataProviderRunner.class)
  103. public class ShowActionTest {
  104. private static final Random RANDOM = new Random();
  105. @Rule
  106. public DbTester dbTester = DbTester.create(System2.INSTANCE);
  107. @Rule
  108. public EsTester es = EsTester.create();
  109. @Rule
  110. public UserSessionRule userSessionRule = UserSessionRule.standalone();
  111. private final DbClient dbClient = dbTester.getDbClient();
  112. private final AvatarResolver avatarResolver = new AvatarResolverImpl();
  113. private final HotspotWsResponseFormatter responseFormatter = new HotspotWsResponseFormatter();
  114. private final IssueChangeWSSupport issueChangeSupport = Mockito.mock(IssueChangeWSSupport.class);
  115. private final HotspotWsSupport hotspotWsSupport = new HotspotWsSupport(dbClient, userSessionRule, System2.INSTANCE);
  116. private final UserResponseFormatter userFormatter = new UserResponseFormatter(new AvatarResolverImpl());
  117. private final TextRangeResponseFormatter textRangeFormatter = new TextRangeResponseFormatter();
  118. private final ShowAction underTest = new ShowAction(dbClient, hotspotWsSupport, responseFormatter, textRangeFormatter, userFormatter, issueChangeSupport);
  119. private final WsActionTester actionTester = new WsActionTester(underTest);
  120. private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
  121. @Test
  122. public void ws_is_public() {
  123. assertThat(actionTester.getDef().isInternal()).isFalse();
  124. }
  125. @Test
  126. public void fails_with_IAE_if_parameter_hotspot_is_missing() {
  127. TestRequest request = actionTester.newRequest();
  128. assertThatThrownBy(request::execute)
  129. .isInstanceOf(IllegalArgumentException.class)
  130. .hasMessage("The 'hotspot' parameter is missing");
  131. }
  132. @Test
  133. public void fails_with_NotFoundException_if_hotspot_does_not_exist() {
  134. String key = randomAlphabetic(12);
  135. TestRequest request = actionTester.newRequest()
  136. .setParam("hotspot", key);
  137. assertThatThrownBy(request::execute)
  138. .isInstanceOf(NotFoundException.class)
  139. .hasMessage("Hotspot '%s' does not exist", key);
  140. }
  141. @Test
  142. @UseDataProvider("ruleTypesButHotspot")
  143. public void fails_with_NotFoundException_if_issue_is_not_a_hotspot(RuleType ruleType) {
  144. ComponentDto project = dbTester.components().insertPublicProject();
  145. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  146. RuleDto rule = newRule(ruleType);
  147. IssueDto notAHotspot = dbTester.issues().insertIssue(rule, project, file, i -> i.setType(ruleType));
  148. TestRequest request = newRequest(notAHotspot);
  149. assertThatThrownBy(request::execute)
  150. .isInstanceOf(NotFoundException.class)
  151. .hasMessage("Hotspot '%s' does not exist", notAHotspot.getKey());
  152. }
  153. @DataProvider
  154. public static Object[][] ruleTypesButHotspot() {
  155. return Arrays.stream(RuleType.values())
  156. .filter(t -> t != SECURITY_HOTSPOT)
  157. .map(t -> new Object[] {t})
  158. .toArray(Object[][]::new);
  159. }
  160. @Test
  161. public void fails_with_NotFoundException_if_issue_is_hotspot_is_closed() {
  162. ComponentDto project = dbTester.components().insertPublicProject();
  163. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  164. RuleDto rule = newRule(SECURITY_HOTSPOT);
  165. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setStatus(Issue.STATUS_CLOSED));
  166. TestRequest request = newRequest(hotspot);
  167. assertThatThrownBy(request::execute)
  168. .isInstanceOf(NotFoundException.class)
  169. .hasMessage("Hotspot '%s' does not exist", hotspot.getKey());
  170. }
  171. @Test
  172. public void fails_with_ForbiddenException_if_project_is_private_and_not_allowed() {
  173. ComponentDto project = dbTester.components().insertPrivateProject();
  174. userSessionRule.registerComponents(project);
  175. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  176. RuleDto rule = newRule(SECURITY_HOTSPOT);
  177. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  178. TestRequest request = newRequest(hotspot);
  179. assertThatThrownBy(request::execute)
  180. .isInstanceOf(ForbiddenException.class)
  181. .hasMessage("Insufficient privileges");
  182. }
  183. @Test
  184. public void succeeds_on_hotspot_with_flow() {
  185. ComponentDto project = dbTester.components().insertPublicProject();
  186. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  187. ComponentDto anotherFile = dbTester.components().insertComponent(newFileDto(project));
  188. DbIssues.Locations.Builder locations = DbIssues.Locations.newBuilder().addFlow(DbIssues.Flow.newBuilder().addAllLocation(Arrays.asList(
  189. DbIssues.Location.newBuilder()
  190. .setComponentId(file.uuid())
  191. .setMsg("FLOW MESSAGE")
  192. .setTextRange(DbCommons.TextRange.newBuilder()
  193. .setStartLine(1)
  194. .setEndLine(1)
  195. .setStartOffset(0)
  196. .setEndOffset(12)
  197. .build())
  198. .build(),
  199. DbIssues.Location.newBuilder()
  200. .setComponentId(anotherFile.uuid())
  201. .setMsg("ANOTHER FLOW MESSAGE")
  202. .setTextRange(DbCommons.TextRange.newBuilder()
  203. .setStartLine(1)
  204. .setEndLine(1)
  205. .setStartOffset(0)
  206. .setEndOffset(12)
  207. .build())
  208. .build(),
  209. DbIssues.Location.newBuilder()
  210. .setMsg("FLOW MESSAGE WITHOUT FILE UUID")
  211. .setTextRange(DbCommons.TextRange.newBuilder()
  212. .setStartLine(1)
  213. .setEndLine(1)
  214. .setStartOffset(0)
  215. .setEndOffset(12)
  216. .build())
  217. .build())));
  218. RuleDto rule = newRule(SECURITY_HOTSPOT);
  219. var hotspot = dbTester.issues().insertHotspot(rule, project, file, i -> i.setLocations(locations.build()));
  220. mockChangelogAndCommentsFormattingContext();
  221. userSessionRule.registerComponents(project);
  222. Hotspots.ShowWsResponse response = newRequest(hotspot)
  223. .executeProtobuf(Hotspots.ShowWsResponse.class);
  224. assertThat(response.getKey()).isEqualTo(hotspot.getKey());
  225. assertThat(response.getFlowsCount()).isEqualTo(1);
  226. assertThat(response.getFlows(0).getLocationsList())
  227. .extracting(Location::getMsg, Location::getComponent)
  228. .containsExactlyInAnyOrder(
  229. tuple("FLOW MESSAGE", file.getKey()),
  230. tuple("ANOTHER FLOW MESSAGE", anotherFile.getKey()),
  231. tuple("FLOW MESSAGE WITHOUT FILE UUID", file.getKey()));
  232. }
  233. @Test
  234. public void succeeds_on_public_project() {
  235. ComponentDto project = dbTester.components().insertPublicProject();
  236. userSessionRule.registerComponents(project);
  237. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  238. RuleDto rule = newRule(SECURITY_HOTSPOT);
  239. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  240. mockChangelogAndCommentsFormattingContext();
  241. Hotspots.ShowWsResponse response = newRequest(hotspot)
  242. .executeProtobuf(Hotspots.ShowWsResponse.class);
  243. assertThat(response.getKey()).isEqualTo(hotspot.getKey());
  244. }
  245. @Test
  246. public void succeeds_on_private_project_with_permission() {
  247. ComponentDto project = dbTester.components().insertPrivateProject();
  248. userSessionRule.registerComponents(project);
  249. userSessionRule.logIn().addProjectPermission(UserRole.USER, project);
  250. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  251. RuleDto rule = newRule(SECURITY_HOTSPOT);
  252. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  253. mockChangelogAndCommentsFormattingContext();
  254. Hotspots.ShowWsResponse response = newRequest(hotspot)
  255. .executeProtobuf(Hotspots.ShowWsResponse.class);
  256. assertThat(response.getKey()).isEqualTo(hotspot.getKey());
  257. }
  258. @Test
  259. public void return_canChangeStatus_false_on_public_project_when_anonymous() {
  260. ComponentDto project = dbTester.components().insertPublicProject();
  261. userSessionRule.registerComponents(project);
  262. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  263. RuleDto rule = newRule(SECURITY_HOTSPOT);
  264. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  265. mockChangelogAndCommentsFormattingContext();
  266. Hotspots.ShowWsResponse response = newRequest(hotspot)
  267. .executeProtobuf(Hotspots.ShowWsResponse.class);
  268. assertThat(response.getCanChangeStatus()).isFalse();
  269. }
  270. @Test
  271. @UseDataProvider("allPublicProjectPermissionsButSECURITYHOTSPOT_ADMIN")
  272. public void return_canChangeStatus_false_on_public_project_when_authenticated_without_SECURITYHOTSPOT_ADMIN_permission(@Nullable String permission) {
  273. ComponentDto project = dbTester.components().insertPublicProject();
  274. userSessionRule.logIn().registerComponents(project);
  275. if (permission != null) {
  276. userSessionRule.addProjectPermission(permission, project);
  277. }
  278. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  279. RuleDto rule = newRule(SECURITY_HOTSPOT);
  280. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  281. mockChangelogAndCommentsFormattingContext();
  282. Hotspots.ShowWsResponse response = newRequest(hotspot)
  283. .executeProtobuf(Hotspots.ShowWsResponse.class);
  284. assertThat(response.getCanChangeStatus()).isFalse();
  285. }
  286. @Test
  287. @UseDataProvider("allPublicProjectPermissionsButSECURITYHOTSPOT_ADMIN")
  288. public void return_canChangeStatus_true_on_public_project_when_authenticated_with_SECURITYHOTSPOT_ADMIN_permission(@Nullable String permission) {
  289. ComponentDto project = dbTester.components().insertPublicProject();
  290. userSessionRule.registerComponents(project)
  291. .addProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN, project);
  292. if (permission != null) {
  293. userSessionRule.addProjectPermission(permission, project);
  294. }
  295. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  296. RuleDto rule = newRule(SECURITY_HOTSPOT);
  297. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  298. mockChangelogAndCommentsFormattingContext();
  299. Hotspots.ShowWsResponse response = newRequest(hotspot)
  300. .executeProtobuf(Hotspots.ShowWsResponse.class);
  301. assertThat(response.getCanChangeStatus()).isTrue();
  302. }
  303. @DataProvider
  304. public static Object[][] allPublicProjectPermissionsButSECURITYHOTSPOT_ADMIN() {
  305. return new Object[][] {
  306. {null}, // no permission
  307. {UserRole.ADMIN},
  308. {UserRole.SCAN},
  309. {UserRole.ISSUE_ADMIN}
  310. };
  311. }
  312. @Test
  313. @UseDataProvider("allPrivateProjectPermissionsButSECURITYHOTSPOT_ADMIN_and_USER")
  314. public void return_canChangeStatus_false_on_private_project_without_SECURITYHOTSPOT_ADMIN_permission(@Nullable String permission) {
  315. ComponentDto project = dbTester.components().insertPrivateProject();
  316. userSessionRule
  317. .registerComponents(project)
  318. .logIn()
  319. .addProjectPermission(UserRole.USER, project);
  320. if (permission != null) {
  321. userSessionRule.addProjectPermission(permission, project);
  322. }
  323. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  324. RuleDto rule = newRule(SECURITY_HOTSPOT);
  325. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  326. mockChangelogAndCommentsFormattingContext();
  327. Hotspots.ShowWsResponse response = newRequest(hotspot)
  328. .executeProtobuf(Hotspots.ShowWsResponse.class);
  329. assertThat(response.getCanChangeStatus()).isFalse();
  330. }
  331. @Test
  332. @UseDataProvider("allPrivateProjectPermissionsButSECURITYHOTSPOT_ADMIN_and_USER")
  333. public void return_canChangeStatus_false_on_private_project_with_SECURITYHOTSPOT_ADMIN_permission(@Nullable String permission) {
  334. ComponentDto project = dbTester.components().insertPrivateProject();
  335. userSessionRule
  336. .registerComponents(project)
  337. .logIn()
  338. .addProjectPermission(UserRole.USER, project)
  339. .addProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN, project);
  340. if (permission != null) {
  341. userSessionRule.addProjectPermission(permission, project);
  342. }
  343. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  344. RuleDto rule = newRule(SECURITY_HOTSPOT);
  345. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  346. mockChangelogAndCommentsFormattingContext();
  347. Hotspots.ShowWsResponse response = newRequest(hotspot)
  348. .executeProtobuf(Hotspots.ShowWsResponse.class);
  349. assertThat(response.getCanChangeStatus()).isTrue();
  350. }
  351. @DataProvider
  352. public static Object[][] allPrivateProjectPermissionsButSECURITYHOTSPOT_ADMIN_and_USER() {
  353. return new Object[][] {
  354. {null}, // only USER permission
  355. {UserRole.CODEVIEWER},
  356. {UserRole.ADMIN},
  357. {UserRole.SCAN},
  358. {UserRole.ISSUE_ADMIN}
  359. };
  360. }
  361. @Test
  362. @UseDataProvider("statusAndResolutionCombinations")
  363. public void returns_status_and_resolution(String status, @Nullable String resolution) {
  364. ComponentDto project = dbTester.components().insertPrivateProject();
  365. userSessionRule.registerComponents(project);
  366. userSessionRule.logIn().addProjectPermission(UserRole.USER, project);
  367. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  368. RuleDto rule = newRule(SECURITY_HOTSPOT);
  369. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setStatus(status).setResolution(resolution));
  370. mockChangelogAndCommentsFormattingContext();
  371. Hotspots.ShowWsResponse response = newRequest(hotspot)
  372. .executeProtobuf(Hotspots.ShowWsResponse.class);
  373. assertThat(response.getStatus()).isEqualTo(status);
  374. if (resolution == null) {
  375. assertThat(response.hasResolution()).isFalse();
  376. } else {
  377. assertThat(response.getResolution()).isEqualTo(resolution);
  378. }
  379. }
  380. @DataProvider
  381. public static Object[][] statusAndResolutionCombinations() {
  382. return new Object[][] {
  383. {Issue.STATUS_TO_REVIEW, null},
  384. {Issue.STATUS_REVIEWED, Issue.RESOLUTION_FIXED},
  385. {Issue.STATUS_REVIEWED, Issue.RESOLUTION_SAFE}
  386. };
  387. }
  388. @Test
  389. public void dispatch_description_sections_of_advanced_rule_in_relevant_field() {
  390. ComponentDto project = dbTester.components().insertPrivateProject();
  391. userSessionRule.registerComponents(project);
  392. userSessionRule.logIn().addProjectPermission(UserRole.USER, project);
  393. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  394. RuleDescriptionSectionDto introductionSection = generateSectionWithKey(INTRODUCTION_SECTION_KEY);
  395. RuleDescriptionSectionDto rootCauseSection = generateSectionWithKey(ROOT_CAUSE_SECTION_KEY);
  396. RuleDescriptionSectionDto assesTheProblemSection = generateSectionWithKey(ASSESS_THE_PROBLEM_SECTION_KEY);
  397. RuleDescriptionSectionDto resourcesSection = generateSectionWithKey(RESOURCES_SECTION_KEY);
  398. RuleDescriptionSectionDto howToFixSection = generateSectionWithKey(HOW_TO_FIX_SECTION_KEY);
  399. RuleDescriptionSectionDto dummySection = generateSectionWithKey("dummySection");
  400. RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT,
  401. r -> r.addRuleDescriptionSectionDto(introductionSection)
  402. .addRuleDescriptionSectionDto(rootCauseSection)
  403. .addRuleDescriptionSectionDto(assesTheProblemSection)
  404. .addRuleDescriptionSectionDto(resourcesSection)
  405. .addRuleDescriptionSectionDto(howToFixSection)
  406. .addRuleDescriptionSectionDto(dummySection)
  407. .setDescriptionFormat(HTML));
  408. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  409. mockChangelogAndCommentsFormattingContext();
  410. Hotspots.ShowWsResponse response = newRequest(hotspot)
  411. .executeProtobuf(Hotspots.ShowWsResponse.class);
  412. assertThat(response.getRule().getVulnerabilityDescription()).isEqualTo(rootCauseSection.getContent());
  413. assertThat(response.getRule().getRiskDescription()).isEqualTo(assesTheProblemSection.getContent());
  414. assertThat(response.getRule().getFixRecommendations()).isEqualTo(howToFixSection.getContent());
  415. }
  416. @Test
  417. public void fallbacks_to_default_section_in_case_of_legacy_rule() {
  418. ComponentDto project = dbTester.components().insertPrivateProject();
  419. userSessionRule.registerComponents(project);
  420. userSessionRule.logIn().addProjectPermission(UserRole.USER, project);
  421. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  422. RuleDescriptionSectionDto introductionSection = generateSectionWithKey(DEFAULT_KEY);
  423. RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT,
  424. r -> r.addRuleDescriptionSectionDto(introductionSection)
  425. .setDescriptionFormat(HTML));
  426. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  427. mockChangelogAndCommentsFormattingContext();
  428. Hotspots.ShowWsResponse response = newRequest(hotspot)
  429. .executeProtobuf(Hotspots.ShowWsResponse.class);
  430. assertThat(response.getRule().getVulnerabilityDescription()).isEqualTo(introductionSection.getContent());
  431. assertThat(response.getRule().getRiskDescription()).isEmpty();
  432. assertThat(response.getRule().getFixRecommendations()).isEmpty();
  433. }
  434. @Test
  435. public void ignore_default_section_if_root_cause_provided() {
  436. ComponentDto project = dbTester.components().insertPrivateProject();
  437. userSessionRule.registerComponents(project);
  438. userSessionRule.logIn().addProjectPermission(UserRole.USER, project);
  439. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  440. RuleDescriptionSectionDto introductionSection = generateSectionWithKey(INTRODUCTION_SECTION_KEY);
  441. RuleDescriptionSectionDto rootCauseSection = generateSectionWithKey(ROOT_CAUSE_SECTION_KEY);
  442. RuleDescriptionSectionDto assesTheProblemSection = generateSectionWithKey(ASSESS_THE_PROBLEM_SECTION_KEY);
  443. RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT,
  444. r -> r.addRuleDescriptionSectionDto(introductionSection)
  445. .addRuleDescriptionSectionDto(rootCauseSection)
  446. .addRuleDescriptionSectionDto(assesTheProblemSection)
  447. .setDescriptionFormat(HTML));
  448. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  449. mockChangelogAndCommentsFormattingContext();
  450. Hotspots.ShowWsResponse response = newRequest(hotspot)
  451. .executeProtobuf(Hotspots.ShowWsResponse.class);
  452. assertThat(response.getRule().getVulnerabilityDescription()).isEqualTo(rootCauseSection.getContent());
  453. assertThat(response.getRule().getRiskDescription()).isEqualTo(assesTheProblemSection.getContent());
  454. assertThat(response.getRule().getFixRecommendations()).isEmpty();
  455. }
  456. private RuleDescriptionSectionDto generateSectionWithKey(String assessTheProblemSectionKey) {
  457. return RuleDescriptionSectionDto.builder()
  458. .uuid(uuidFactory.create())
  459. .key(assessTheProblemSectionKey)
  460. .content(randomAlphabetic(200))
  461. .build();
  462. }
  463. @Test
  464. public void returns_html_description_for_custom_rules() {
  465. ComponentDto project = dbTester.components().insertPrivateProject();
  466. userSessionRule.registerComponents(project);
  467. userSessionRule.logIn().addProjectPermission(UserRole.USER, project);
  468. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  469. String description = "== Title\n<div>line1\nline2</div>";
  470. RuleDescriptionSectionDto sectionDto = RuleDescriptionSectionDto.builder()
  471. .uuid(uuidFactory.create())
  472. .key(ASSESS_THE_PROBLEM_SECTION_KEY)
  473. .content(description)
  474. .build();
  475. RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT,
  476. r -> r.setTemplateUuid("123")
  477. .addRuleDescriptionSectionDto(sectionDto)
  478. .setDescriptionFormat(MARKDOWN));
  479. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  480. mockChangelogAndCommentsFormattingContext();
  481. Hotspots.ShowWsResponse response = newRequest(hotspot)
  482. .executeProtobuf(Hotspots.ShowWsResponse.class);
  483. assertThat(response.getRule().getRiskDescription()).isEqualTo("<h2>Title</h2>&lt;div&gt;line1<br/>line2&lt;/div&gt;");
  484. }
  485. @Test
  486. public void handles_null_description_for_custom_rules() {
  487. ComponentDto project = dbTester.components().insertPrivateProject();
  488. userSessionRule.registerComponents(project);
  489. userSessionRule.logIn().addProjectPermission(UserRole.USER, project);
  490. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  491. RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, r -> r.setTemplateUuid("123"));
  492. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  493. mockChangelogAndCommentsFormattingContext();
  494. Hotspots.ShowWsResponse response = newRequest(hotspot)
  495. .executeProtobuf(Hotspots.ShowWsResponse.class);
  496. assertThat(response.getRule().getRiskDescription()).isNullOrEmpty();
  497. }
  498. @Test
  499. public void returns_hotspot_component_and_rule() {
  500. ComponentDto project = dbTester.components().insertPublicProject();
  501. userSessionRule.registerComponents(project);
  502. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  503. RuleDto rule = newRule(SECURITY_HOTSPOT);
  504. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  505. mockChangelogAndCommentsFormattingContext();
  506. Hotspots.ShowWsResponse response = newRequest(hotspot)
  507. .executeProtobuf(Hotspots.ShowWsResponse.class);
  508. assertThat(response.getKey()).isEqualTo(hotspot.getKey());
  509. verifyComponent(response.getComponent(), file, null, null);
  510. verifyComponent(response.getProject(), project, null, null);
  511. verifyRule(response.getRule(), rule);
  512. assertThat(response.hasTextRange()).isFalse();
  513. }
  514. @Test
  515. public void returns_no_textRange_when_locations_have_none() {
  516. ComponentDto project = dbTester.components().insertPublicProject();
  517. userSessionRule.registerComponents(project);
  518. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  519. RuleDto rule = newRule(SECURITY_HOTSPOT);
  520. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file,
  521. t -> t.setLocations(DbIssues.Locations.newBuilder().build()));
  522. mockChangelogAndCommentsFormattingContext();
  523. Hotspots.ShowWsResponse response = newRequest(hotspot)
  524. .executeProtobuf(Hotspots.ShowWsResponse.class);
  525. assertThat(response.hasTextRange()).isFalse();
  526. }
  527. @Test
  528. @UseDataProvider("randomTextRangeValues")
  529. public void returns_textRange(int startLine, int endLine, int startOffset, int endOffset) {
  530. ComponentDto project = dbTester.components().insertPublicProject();
  531. userSessionRule.registerComponents(project);
  532. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  533. RuleDto rule = newRule(SECURITY_HOTSPOT);
  534. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file,
  535. t -> t.setLocations(DbIssues.Locations.newBuilder()
  536. .setTextRange(DbCommons.TextRange.newBuilder()
  537. .setStartLine(startLine)
  538. .setEndLine(endLine)
  539. .setStartOffset(startOffset)
  540. .setEndOffset(endOffset)
  541. .build())
  542. .build()));
  543. mockChangelogAndCommentsFormattingContext();
  544. Hotspots.ShowWsResponse response = newRequest(hotspot)
  545. .executeProtobuf(Hotspots.ShowWsResponse.class);
  546. assertThat(response.hasTextRange()).isTrue();
  547. Common.TextRange textRange = response.getTextRange();
  548. assertThat(textRange.getStartLine()).isEqualTo(startLine);
  549. assertThat(textRange.getEndLine()).isEqualTo(endLine);
  550. assertThat(textRange.getStartOffset()).isEqualTo(startOffset);
  551. assertThat(textRange.getEndOffset()).isEqualTo(endOffset);
  552. }
  553. @Test
  554. public void returns_no_assignee_when_user_does_not_exist() {
  555. ComponentDto project = dbTester.components().insertPublicProject();
  556. userSessionRule.registerComponents(project);
  557. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  558. RuleDto rule = newRule(SECURITY_HOTSPOT);
  559. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setAssigneeUuid(randomAlphabetic(10)));
  560. mockChangelogAndCommentsFormattingContext();
  561. Hotspots.ShowWsResponse response = newRequest(hotspot)
  562. .executeProtobuf(Hotspots.ShowWsResponse.class);
  563. assertThat(response.hasAssignee()).isFalse();
  564. }
  565. @Test
  566. public void returns_assignee_details_when_user_exists() {
  567. ComponentDto project = dbTester.components().insertPublicProject();
  568. userSessionRule.registerComponents(project);
  569. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  570. RuleDto rule = newRule(SECURITY_HOTSPOT);
  571. UserDto assignee = dbTester.users().insertUser();
  572. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setAssigneeUuid(assignee.getUuid()));
  573. mockChangelogAndCommentsFormattingContext();
  574. Hotspots.ShowWsResponse response = newRequest(hotspot)
  575. .executeProtobuf(Hotspots.ShowWsResponse.class);
  576. assertThat(response.getAssignee()).isEqualTo(assignee.getLogin());
  577. assertThat(response.getUsersList()).hasSize(1);
  578. User wsAssignee = response.getUsersList().iterator().next();
  579. assertThat(wsAssignee.getLogin()).isEqualTo(assignee.getLogin());
  580. assertThat(wsAssignee.getName()).isEqualTo(assignee.getName());
  581. assertThat(wsAssignee.getActive()).isEqualTo(assignee.isActive());
  582. assertThat(wsAssignee.getAvatar()).isEqualTo(avatarResolver.create(assignee));
  583. }
  584. @Test
  585. public void returns_no_avatar_if_assignee_has_no_email() {
  586. ComponentDto project = dbTester.components().insertPublicProject();
  587. userSessionRule.registerComponents(project);
  588. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  589. RuleDto rule = newRule(SECURITY_HOTSPOT);
  590. UserDto assignee = dbTester.users().insertUser(t -> t.setEmail(null));
  591. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setAssigneeUuid(assignee.getUuid()));
  592. mockChangelogAndCommentsFormattingContext();
  593. Hotspots.ShowWsResponse response = newRequest(hotspot)
  594. .executeProtobuf(Hotspots.ShowWsResponse.class);
  595. assertThat(response.getUsersList()).hasSize(1);
  596. assertThat(response.getUsersList().iterator().next().hasAvatar()).isFalse();
  597. }
  598. @Test
  599. public void returns_inactive_when_assignee_is_inactive() {
  600. ComponentDto project = dbTester.components().insertPublicProject();
  601. userSessionRule.registerComponents(project);
  602. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  603. RuleDto rule = newRule(SECURITY_HOTSPOT);
  604. UserDto assignee = dbTester.users().insertUser(t -> t.setActive(false));
  605. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setAssigneeUuid(assignee.getUuid()));
  606. mockChangelogAndCommentsFormattingContext();
  607. Hotspots.ShowWsResponse response = newRequest(hotspot)
  608. .executeProtobuf(Hotspots.ShowWsResponse.class);
  609. assertThat(response.getUsersList()).hasSize(1);
  610. assertThat(response.getUsersList().iterator().next().getActive()).isFalse();
  611. }
  612. @Test
  613. public void returns_author_login_when_user_does_not_exist() {
  614. ComponentDto project = dbTester.components().insertPublicProject();
  615. userSessionRule.registerComponents(project);
  616. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  617. RuleDto rule = newRule(SECURITY_HOTSPOT);
  618. String authorLogin = randomAlphabetic(10);
  619. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setAuthorLogin(authorLogin));
  620. mockChangelogAndCommentsFormattingContext();
  621. Hotspots.ShowWsResponse response = newRequest(hotspot)
  622. .executeProtobuf(Hotspots.ShowWsResponse.class);
  623. assertThat(response.getUsersList()).isEmpty();
  624. assertThat(response.getAuthor()).isEqualTo(authorLogin);
  625. }
  626. @Test
  627. public void returns_author_details_when_user_exists() {
  628. ComponentDto project = dbTester.components().insertPublicProject();
  629. userSessionRule.registerComponents(project);
  630. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  631. RuleDto rule = newRule(SECURITY_HOTSPOT);
  632. UserDto author = dbTester.users().insertUser();
  633. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setAuthorLogin(author.getLogin()));
  634. mockChangelogAndCommentsFormattingContext();
  635. Hotspots.ShowWsResponse response = newRequest(hotspot)
  636. .executeProtobuf(Hotspots.ShowWsResponse.class);
  637. assertThat(response.getAuthor()).isEqualTo(author.getLogin());
  638. User wsAuthorFromList = response.getUsersList().iterator().next();
  639. assertThat(wsAuthorFromList.getLogin()).isEqualTo(author.getLogin());
  640. assertThat(wsAuthorFromList.getName()).isEqualTo(author.getName());
  641. assertThat(wsAuthorFromList.getActive()).isEqualTo(author.isActive());
  642. assertThat(wsAuthorFromList.getAvatar()).isEqualTo(avatarResolver.create(author));
  643. }
  644. @Test
  645. public void returns_no_avatar_if_author_has_no_email() {
  646. ComponentDto project = dbTester.components().insertPublicProject();
  647. userSessionRule.registerComponents(project);
  648. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  649. RuleDto rule = newRule(SECURITY_HOTSPOT);
  650. UserDto author = dbTester.users().insertUser(t -> t.setEmail(null));
  651. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setAuthorLogin(author.getLogin()));
  652. mockChangelogAndCommentsFormattingContext();
  653. Hotspots.ShowWsResponse response = newRequest(hotspot)
  654. .executeProtobuf(Hotspots.ShowWsResponse.class);
  655. assertThat(response.getUsersList()).hasSize(1);
  656. assertThat(response.getUsersList().iterator().next().hasAvatar()).isFalse();
  657. }
  658. @Test
  659. public void returns_inactive_if_author_is_inactive() {
  660. ComponentDto project = dbTester.components().insertPublicProject();
  661. userSessionRule.registerComponents(project);
  662. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  663. RuleDto rule = newRule(SECURITY_HOTSPOT);
  664. UserDto author = dbTester.users().insertUser(t -> t.setActive(false));
  665. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, t -> t.setAuthorLogin(author.getLogin()));
  666. mockChangelogAndCommentsFormattingContext();
  667. Hotspots.ShowWsResponse response = newRequest(hotspot)
  668. .executeProtobuf(Hotspots.ShowWsResponse.class);
  669. assertThat(response.getUsersList()).hasSize(1);
  670. assertThat(response.getUsersList().iterator().next().getActive()).isFalse();
  671. }
  672. @DataProvider
  673. public static Object[][] randomTextRangeValues() {
  674. int startLine = RANDOM.nextInt(200);
  675. int endLine = RANDOM.nextInt(200);
  676. int startOffset = RANDOM.nextInt(200);
  677. int endOffset = RANDOM.nextInt(200);
  678. return new Object[][] {
  679. {startLine, endLine, startOffset, endOffset}
  680. };
  681. }
  682. @Test
  683. public void returns_textRange_missing_fields() {
  684. ComponentDto project = dbTester.components().insertPublicProject();
  685. userSessionRule.registerComponents(project);
  686. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  687. RuleDto rule = newRule(SECURITY_HOTSPOT);
  688. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file,
  689. t -> t.setLocations(DbIssues.Locations.newBuilder()
  690. .setTextRange(DbCommons.TextRange.newBuilder().build())
  691. .build()));
  692. mockChangelogAndCommentsFormattingContext();
  693. Hotspots.ShowWsResponse response = newRequest(hotspot)
  694. .executeProtobuf(Hotspots.ShowWsResponse.class);
  695. assertThat(response.hasTextRange()).isTrue();
  696. Common.TextRange textRange = response.getTextRange();
  697. assertThat(textRange.hasStartLine()).isFalse();
  698. assertThat(textRange.hasEndLine()).isFalse();
  699. assertThat(textRange.hasStartOffset()).isFalse();
  700. assertThat(textRange.hasEndOffset()).isFalse();
  701. }
  702. @Test
  703. @UseDataProvider("allSQCategoryAndVulnerabilityProbability")
  704. public void returns_securityCategory_and_vulnerabilityProbability_of_rule(Set<String> standards,
  705. SQCategory expected) {
  706. ComponentDto project = dbTester.components().insertPublicProject();
  707. userSessionRule.registerComponents(project);
  708. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  709. RuleDto rule = newRule(SECURITY_HOTSPOT, t -> t.setSecurityStandards(standards));
  710. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file,
  711. t -> t.setLocations(DbIssues.Locations.newBuilder()
  712. .setTextRange(DbCommons.TextRange.newBuilder().build())
  713. .build()));
  714. mockChangelogAndCommentsFormattingContext();
  715. Hotspots.ShowWsResponse response = newRequest(hotspot)
  716. .executeProtobuf(Hotspots.ShowWsResponse.class);
  717. Hotspots.Rule wsRule = response.getRule();
  718. assertThat(wsRule.getSecurityCategory()).isEqualTo(expected.getKey());
  719. assertThat(wsRule.getVulnerabilityProbability()).isEqualTo(expected.getVulnerability().name());
  720. }
  721. @DataProvider
  722. public static Object[][] allSQCategoryAndVulnerabilityProbability() {
  723. Stream<Object[]> allButOthers = SecurityStandards.CWES_BY_SQ_CATEGORY
  724. .entrySet()
  725. .stream()
  726. .map(t -> new Object[] {
  727. t.getValue().stream().map(s -> "cwe:" + s).collect(Collectors.toSet()),
  728. t.getKey()
  729. });
  730. Stream<Object[]> others = Stream.of(
  731. new Object[] {Collections.emptySet(), SQCategory.OTHERS},
  732. new Object[] {ImmutableSet.of("foo", "bar", "acme"), SQCategory.OTHERS});
  733. return Stream.concat(allButOthers, others)
  734. .toArray(Object[][]::new);
  735. }
  736. @Test
  737. public void returns_project_twice_when_hotspot_on_project() {
  738. ComponentDto project = dbTester.components().insertPublicProject();
  739. userSessionRule.registerComponents(project);
  740. RuleDto rule = newRule(SECURITY_HOTSPOT);
  741. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, project,
  742. t -> t.setLocations(DbIssues.Locations.newBuilder()
  743. .setTextRange(DbCommons.TextRange.newBuilder().build())
  744. .build()));
  745. mockChangelogAndCommentsFormattingContext();
  746. Hotspots.ShowWsResponse response = newRequest(hotspot)
  747. .executeProtobuf(Hotspots.ShowWsResponse.class);
  748. verifyComponent(response.getProject(), project, null, null);
  749. verifyComponent(response.getComponent(), project, null, null);
  750. }
  751. @Test
  752. public void returns_branch_but_no_pullRequest_on_component_and_project_on_non_main_branch() {
  753. ComponentDto project = dbTester.components().insertPublicProject();
  754. ComponentDto branch = dbTester.components().insertProjectBranch(project);
  755. ComponentDto file = dbTester.components().insertComponent(newFileDto(branch));
  756. userSessionRule.registerComponents(project);
  757. RuleDto rule = newRule(SECURITY_HOTSPOT);
  758. IssueDto hotspot = dbTester.issues().insertHotspot(rule, branch, file,
  759. t -> t.setLocations(DbIssues.Locations.newBuilder()
  760. .setTextRange(DbCommons.TextRange.newBuilder().build())
  761. .build()));
  762. mockChangelogAndCommentsFormattingContext();
  763. Hotspots.ShowWsResponse response = newRequest(hotspot)
  764. .executeProtobuf(Hotspots.ShowWsResponse.class);
  765. verifyComponent(response.getProject(), branch, branch.getBranch(), null);
  766. verifyComponent(response.getComponent(), file, branch.getBranch(), null);
  767. }
  768. @Test
  769. public void returns_pullRequest_but_no_branch_on_component_and_project_on_pullRequest() {
  770. ComponentDto project = dbTester.components().insertPublicProject();
  771. ComponentDto pullRequest = dbTester.components().insertProjectBranch(project,
  772. t -> t.setBranchType(BranchType.PULL_REQUEST));
  773. ComponentDto file = dbTester.components().insertComponent(newFileDto(pullRequest));
  774. userSessionRule.registerComponents(project);
  775. RuleDto rule = newRule(SECURITY_HOTSPOT);
  776. IssueDto hotspot = dbTester.issues().insertHotspot(rule, pullRequest, file,
  777. t -> t.setLocations(DbIssues.Locations.newBuilder()
  778. .setTextRange(DbCommons.TextRange.newBuilder().build())
  779. .build()));
  780. mockChangelogAndCommentsFormattingContext();
  781. Hotspots.ShowWsResponse response = newRequest(hotspot)
  782. .executeProtobuf(Hotspots.ShowWsResponse.class);
  783. verifyComponent(response.getProject(), pullRequest, null, pullRequest.getPullRequest());
  784. verifyComponent(response.getComponent(), file, null, pullRequest.getPullRequest());
  785. }
  786. @Test
  787. public void returns_hotspot_changelog_and_comments() {
  788. ComponentDto project = dbTester.components().insertPublicProject();
  789. userSessionRule.registerComponents(project);
  790. RuleDto rule = newRule(SECURITY_HOTSPOT);
  791. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  792. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file,
  793. t -> t.setLocations(DbIssues.Locations.newBuilder()
  794. .setTextRange(DbCommons.TextRange.newBuilder().build())
  795. .build()));
  796. List<Common.Changelog> changelog = IntStream.range(0, 1 + new Random().nextInt(12))
  797. .mapToObj(i -> Common.Changelog.newBuilder().setUser("u" + i).build())
  798. .collect(Collectors.toList());
  799. List<Common.Comment> comments = IntStream.range(0, 1 + new Random().nextInt(12))
  800. .mapToObj(i -> Common.Comment.newBuilder().setKey("u" + i).build())
  801. .collect(Collectors.toList());
  802. FormattingContext formattingContext = mockChangelogAndCommentsFormattingContext();
  803. when(issueChangeSupport.formatChangelog(any(), any())).thenReturn(changelog.stream());
  804. when(issueChangeSupport.formatComments(any(), any(), any())).thenReturn(comments.stream());
  805. Hotspots.ShowWsResponse response = newRequest(hotspot)
  806. .executeProtobuf(Hotspots.ShowWsResponse.class);
  807. assertThat(response.getChangelogList())
  808. .extracting(Common.Changelog::getUser)
  809. .containsExactly(changelog.stream().map(Common.Changelog::getUser).toArray(String[]::new));
  810. assertThat(response.getCommentList())
  811. .extracting(Common.Comment::getKey)
  812. .containsExactly(comments.stream().map(Common.Comment::getKey).toArray(String[]::new));
  813. verify(issueChangeSupport).newFormattingContext(any(DbSession.class),
  814. argThat(new IssueDtoSetArgumentMatcher(hotspot)),
  815. eq(Load.ALL),
  816. eq(Collections.emptySet()), eq(ImmutableSet.of(project, file)));
  817. verify(issueChangeSupport).formatChangelog(argThat(new IssueDtoArgumentMatcher(hotspot)), eq(formattingContext));
  818. verify(issueChangeSupport).formatComments(argThat(new IssueDtoArgumentMatcher(hotspot)), any(Common.Comment.Builder.class), eq(formattingContext));
  819. }
  820. @Test
  821. public void returns_user_details_of_users_from_ChangelogAndComments() {
  822. ComponentDto project = dbTester.components().insertPublicProject();
  823. userSessionRule.registerComponents(project);
  824. RuleDto rule = newRule(SECURITY_HOTSPOT);
  825. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  826. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
  827. FormattingContext formattingContext = mockChangelogAndCommentsFormattingContext();
  828. Set<UserDto> changeLogAndCommentsUsers = IntStream.range(0, 1 + RANDOM.nextInt(14))
  829. .mapToObj(i -> UserTesting.newUserDto())
  830. .collect(Collectors.toSet());
  831. when(formattingContext.getUsers()).thenReturn(changeLogAndCommentsUsers);
  832. Hotspots.ShowWsResponse response = newRequest(hotspot)
  833. .executeProtobuf(Hotspots.ShowWsResponse.class);
  834. assertThat(response.getUsersList())
  835. .extracting(User::getLogin, User::getName, User::getActive)
  836. .containsExactlyInAnyOrder(
  837. changeLogAndCommentsUsers.stream()
  838. .map(t -> tuple(t.getLogin(), t.getName(), t.isActive()))
  839. .toArray(Tuple[]::new));
  840. }
  841. @Test
  842. public void returns_user_of_users_from_ChangelogAndComments_and_assignee_and_author() {
  843. ComponentDto project = dbTester.components().insertPublicProject();
  844. userSessionRule.registerComponents(project);
  845. RuleDto rule = newRule(SECURITY_HOTSPOT);
  846. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  847. UserDto author = dbTester.users().insertUser();
  848. UserDto assignee = dbTester.users().insertUser();
  849. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file,
  850. t -> t.setAuthorLogin(author.getLogin())
  851. .setAssigneeUuid(assignee.getUuid()));
  852. FormattingContext formattingContext = mockChangelogAndCommentsFormattingContext();
  853. Set<UserDto> changeLogAndCommentsUsers = IntStream.range(0, 1 + RANDOM.nextInt(14))
  854. .mapToObj(i -> UserTesting.newUserDto())
  855. .collect(Collectors.toSet());
  856. when(formattingContext.getUsers()).thenReturn(changeLogAndCommentsUsers);
  857. Hotspots.ShowWsResponse response = newRequest(hotspot)
  858. .executeProtobuf(Hotspots.ShowWsResponse.class);
  859. assertThat(response.getUsersList())
  860. .extracting(User::getLogin, User::getName, User::getActive)
  861. .containsExactlyInAnyOrder(
  862. Stream.concat(
  863. Stream.of(author, assignee),
  864. changeLogAndCommentsUsers.stream())
  865. .map(t -> tuple(t.getLogin(), t.getName(), t.isActive()))
  866. .toArray(Tuple[]::new));
  867. }
  868. @Test
  869. public void do_not_duplicate_user_if_author_assignee_ChangeLogComment_user() {
  870. ComponentDto project = dbTester.components().insertPublicProject();
  871. userSessionRule.registerComponents(project);
  872. RuleDto rule = newRule(SECURITY_HOTSPOT);
  873. ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
  874. UserDto author = dbTester.users().insertUser();
  875. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file,
  876. t -> t.setAuthorLogin(author.getLogin())
  877. .setAssigneeUuid(author.getUuid()));
  878. FormattingContext formattingContext = mockChangelogAndCommentsFormattingContext();
  879. when(formattingContext.getUsers()).thenReturn(ImmutableSet.of(author));
  880. Hotspots.ShowWsResponse response = newRequest(hotspot)
  881. .executeProtobuf(Hotspots.ShowWsResponse.class);
  882. assertThat(response.getUsersList())
  883. .extracting(User::getLogin, User::getName, User::getActive)
  884. .containsOnly(tuple(author.getLogin(), author.getName(), author.isActive()));
  885. }
  886. @Test
  887. public void verify_response_example() {
  888. ComponentDto project = dbTester.components().insertPublicProject(componentDto -> componentDto
  889. .setName("test-project")
  890. .setLongName("test-project")
  891. .setDbKey("com.sonarsource:test-project"));
  892. userSessionRule.registerComponents(project)
  893. .addProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN, project);
  894. ComponentDto file = dbTester.components().insertComponent(
  895. newFileDto(project)
  896. .setDbKey("com.sonarsource:test-project:src/main/java/com/sonarsource/FourthClass.java")
  897. .setName("FourthClass.java")
  898. .setLongName("src/main/java/com/sonarsource/FourthClass.java")
  899. .setPath("src/main/java/com/sonarsource/FourthClass.java"));
  900. UserDto author = dbTester.users().insertUser(u -> u.setLogin("joe")
  901. .setName("Joe"));
  902. long time = 1577976190000L;
  903. RuleDto rule = newRule(SECURITY_HOTSPOT, r -> r.setRuleKey("S4787")
  904. .setRepositoryKey("java")
  905. .setName("rule-name")
  906. .setSecurityStandards(Sets.newHashSet(SQCategory.WEAK_CRYPTOGRAPHY.getKey())));
  907. IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file, h -> h
  908. .setAssigneeUuid("assignee-uuid")
  909. .setAuthorLogin("joe")
  910. .setMessage("message")
  911. .setLine(10)
  912. .setChecksum("a227e508d6646b55a086ee11d63b21e9")
  913. .setIssueCreationTime(time)
  914. .setIssueUpdateTime(time)
  915. .setAuthorLogin(author.getLogin())
  916. .setAssigneeUuid(author.getUuid())
  917. .setKee("AW9mgJw6eFC3pGl94Wrf"));
  918. List<Common.Changelog> changelog = IntStream.range(0, 3)
  919. .mapToObj(i -> Common.Changelog.newBuilder()
  920. .setUser("joe")
  921. .setCreationDate("2020-01-02T14:44:55+0100")
  922. .addDiffs(Diff.newBuilder().setKey("diff-key-" + i).setNewValue("new-value-" + i).setOldValue("old-value-" + i))
  923. .setIsUserActive(true)
  924. .setUserName("Joe")
  925. .setAvatar("my-avatar")
  926. .build())
  927. .collect(Collectors.toList());
  928. List<Common.Comment> comments = IntStream.range(0, 3)
  929. .mapToObj(i -> Common.Comment.newBuilder()
  930. .setKey("comment-" + i)
  931. .setHtmlText("html text " + i)
  932. .setLogin("Joe")
  933. .setMarkdown("markdown " + i)
  934. .setCreatedAt("2020-01-02T14:47:47+0100")
  935. .build())
  936. .collect(Collectors.toList());
  937. mockChangelogAndCommentsFormattingContext();
  938. when(issueChangeSupport.formatChangelog(any(), any())).thenReturn(changelog.stream());
  939. when(issueChangeSupport.formatComments(any(), any(), any())).thenReturn(comments.stream());
  940. assertThat(actionTester.getDef().responseExampleAsString()).isNotNull();
  941. newRequest(hotspot)
  942. .execute()
  943. .assertJson(actionTester.getDef().responseExampleAsString());
  944. }
  945. private FormattingContext mockChangelogAndCommentsFormattingContext() {
  946. FormattingContext formattingContext = Mockito.mock(FormattingContext.class);
  947. when(issueChangeSupport.newFormattingContext(any(), any(), any(), anySet(), anySet())).thenReturn(formattingContext);
  948. return formattingContext;
  949. }
  950. private void verifyRule(Hotspots.Rule wsRule, RuleDto dto) {
  951. assertThat(wsRule.getKey()).isEqualTo(dto.getKey().toString());
  952. assertThat(wsRule.getName()).isEqualTo(dto.getName());
  953. assertThat(wsRule.getSecurityCategory()).isEqualTo(SQCategory.OTHERS.getKey());
  954. assertThat(wsRule.getVulnerabilityProbability()).isEqualTo(SQCategory.OTHERS.getVulnerability().name());
  955. }
  956. private static void verifyComponent(Hotspots.Component wsComponent, ComponentDto dto, @Nullable String branch, @Nullable String pullRequest) {
  957. assertThat(wsComponent.getKey()).isEqualTo(dto.getKey());
  958. if (dto.path() == null) {
  959. assertThat(wsComponent.hasPath()).isFalse();
  960. } else {
  961. assertThat(wsComponent.getPath()).isEqualTo(dto.path());
  962. }
  963. assertThat(wsComponent.getQualifier()).isEqualTo(dto.qualifier());
  964. assertThat(wsComponent.getName()).isEqualTo(dto.name());
  965. assertThat(wsComponent.getLongName()).isEqualTo(dto.longName());
  966. if (branch == null) {
  967. assertThat(wsComponent.hasBranch()).isFalse();
  968. } else {
  969. assertThat(wsComponent.getBranch()).isEqualTo(branch);
  970. }
  971. if (pullRequest == null) {
  972. assertThat(wsComponent.hasPullRequest()).isFalse();
  973. } else {
  974. assertThat(wsComponent.getPullRequest()).isEqualTo(pullRequest);
  975. }
  976. }
  977. private TestRequest newRequest(IssueDto hotspot) {
  978. return actionTester.newRequest()
  979. .setParam("hotspot", hotspot.getKey());
  980. }
  981. private RuleDto newRule(RuleType ruleType) {
  982. return newRule(ruleType, t -> {
  983. });
  984. }
  985. private RuleDto newRule(RuleType ruleType, Consumer<RuleDto> populate) {
  986. return newRule(ruleType, RuleTesting::newRule, populate);
  987. }
  988. private RuleDto newRuleWithoutSection(RuleType ruleType, Consumer<RuleDto> populate) {
  989. return newRule(ruleType, RuleTesting::newRuleWithoutDescriptionSection, populate);
  990. }
  991. private RuleDto newRule(RuleType ruleType, Supplier<RuleDto> ruleDefinitionDtoSupplier, Consumer<RuleDto> populate) {
  992. RuleDto ruleDefinitionDto = ruleDefinitionDtoSupplier.get().setType(ruleType);
  993. populate.accept(ruleDefinitionDto);
  994. dbTester.rules().insert(ruleDefinitionDto);
  995. return ruleDefinitionDto;
  996. }
  997. private static class IssueDtoSetArgumentMatcher implements ArgumentMatcher<Set<IssueDto>> {
  998. private final IssueDto expected;
  999. private IssueDtoSetArgumentMatcher(IssueDto expected) {
  1000. this.expected = expected;
  1001. }
  1002. @Override
  1003. public boolean matches(Set<IssueDto> argument) {
  1004. return argument != null && argument.size() == 1 && argument.iterator().next().getKey().equals(expected.getKey());
  1005. }
  1006. @Override
  1007. public String toString() {
  1008. return "Set<IssueDto>[" + expected.getKey() + "]";
  1009. }
  1010. }
  1011. private static class IssueDtoArgumentMatcher implements ArgumentMatcher<IssueDto> {
  1012. private final IssueDto expected;
  1013. private IssueDtoArgumentMatcher(IssueDto expected) {
  1014. this.expected = expected;
  1015. }
  1016. @Override
  1017. public boolean matches(IssueDto argument) {
  1018. return argument != null && argument.getKey().equals(expected.getKey());
  1019. }
  1020. @Override
  1021. public String toString() {
  1022. return "IssueDto[key=" + expected.getKey() + "]";
  1023. }
  1024. }
  1025. }