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.

BranchReportSubmitterTest.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 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.ce.queue;
  21. import com.google.common.collect.ImmutableMap;
  22. import com.tngtech.java.junit.dataprovider.DataProvider;
  23. import com.tngtech.java.junit.dataprovider.DataProviderRunner;
  24. import java.io.InputStream;
  25. import java.nio.charset.StandardCharsets;
  26. import java.util.Map;
  27. import java.util.Optional;
  28. import java.util.Random;
  29. import java.util.function.BiConsumer;
  30. import java.util.stream.IntStream;
  31. import org.apache.commons.io.IOUtils;
  32. import org.junit.Rule;
  33. import org.junit.Test;
  34. import org.junit.rules.ExpectedException;
  35. import org.junit.runner.RunWith;
  36. import org.mockito.stubbing.Answer;
  37. import org.sonar.api.utils.System2;
  38. import org.sonar.ce.queue.CeQueue;
  39. import org.sonar.ce.queue.CeQueueImpl;
  40. import org.sonar.ce.queue.CeTaskSubmit;
  41. import org.sonar.db.DbSession;
  42. import org.sonar.db.DbTester;
  43. import org.sonar.db.ce.CeTaskTypes;
  44. import org.sonar.db.component.BranchDto;
  45. import org.sonar.db.component.BranchType;
  46. import org.sonar.db.component.ComponentDto;
  47. import org.sonar.db.component.ComponentTesting;
  48. import org.sonar.db.organization.OrganizationDto;
  49. import org.sonar.db.user.UserDto;
  50. import org.sonar.server.component.ComponentUpdater;
  51. import org.sonar.server.exceptions.ForbiddenException;
  52. import org.sonar.server.favorite.FavoriteUpdater;
  53. import org.sonar.server.permission.PermissionTemplateService;
  54. import org.sonar.server.tester.UserSessionRule;
  55. import static java.util.Collections.emptyMap;
  56. import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
  57. import static org.assertj.core.api.Assertions.assertThat;
  58. import static org.junit.Assert.fail;
  59. import static org.mockito.ArgumentMatchers.any;
  60. import static org.mockito.ArgumentMatchers.argThat;
  61. import static org.mockito.ArgumentMatchers.eq;
  62. import static org.mockito.ArgumentMatchers.same;
  63. import static org.mockito.Mockito.mock;
  64. import static org.mockito.Mockito.spy;
  65. import static org.mockito.Mockito.times;
  66. import static org.mockito.Mockito.verify;
  67. import static org.mockito.Mockito.verifyNoMoreInteractions;
  68. import static org.mockito.Mockito.verifyZeroInteractions;
  69. import static org.mockito.Mockito.when;
  70. import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
  71. import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
  72. import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
  73. import static org.sonar.db.component.ComponentTesting.newBranchDto;
  74. import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
  75. import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
  76. import static org.sonar.db.permission.OrganizationPermission.SCAN;
  77. /**
  78. * Tests of {@link ReportSubmitter} when branch support is installed.
  79. */
  80. @RunWith(DataProviderRunner.class)
  81. public class BranchReportSubmitterTest {
  82. @Rule
  83. public ExpectedException expectedException = ExpectedException.none();
  84. @Rule
  85. public UserSessionRule userSession = UserSessionRule.standalone();
  86. @Rule
  87. public DbTester db = DbTester.create(System2.INSTANCE);
  88. private CeQueue queue = mock(CeQueueImpl.class);
  89. private ComponentUpdater componentUpdater = mock(ComponentUpdater.class);
  90. private PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
  91. private FavoriteUpdater favoriteUpdater = mock(FavoriteUpdater.class);
  92. private BranchSupportDelegate branchSupportDelegate = mock(BranchSupportDelegate.class);
  93. private BranchSupport branchSupport = spy(new BranchSupport(branchSupportDelegate));
  94. private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), branchSupport);
  95. @Test
  96. public void submit_does_not_use_delegate_if_characteristics_are_empty() {
  97. OrganizationDto organization = db.organizations().insert();
  98. ComponentDto project = db.components().insertMainBranch(organization);
  99. UserDto user = db.users().insertUser();
  100. userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
  101. mockSuccessfulPrepareSubmitCall();
  102. InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
  103. underTest.submit(organization.getKey(), project.getDbKey(), project.name(), emptyMap(), reportInput);
  104. verifyZeroInteractions(branchSupportDelegate);
  105. }
  106. @Test
  107. public void submit_a_report_on_existing_branch() {
  108. OrganizationDto organization = db.organizations().insert();
  109. ComponentDto project = db.components().insertMainBranch(organization);
  110. ComponentDto branch = db.components().insertProjectBranch(project);
  111. UserDto user = db.users().insertUser();
  112. userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
  113. Map<String, String> randomCharacteristics = randomNonEmptyMap();
  114. BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(branch);
  115. when(branchSupportDelegate.createComponentKey(project.getDbKey(), randomCharacteristics))
  116. .thenReturn(componentKey);
  117. InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
  118. String taskUuid = mockSuccessfulPrepareSubmitCall();
  119. underTest.submit(organization.getKey(), project.getDbKey(), project.name(), randomCharacteristics, reportInput);
  120. verifyZeroInteractions(permissionTemplateService);
  121. verifyZeroInteractions(favoriteUpdater);
  122. verify(branchSupport, times(0)).createBranchComponent(any(), any(), any(), any(), any());
  123. verify(branchSupportDelegate).createComponentKey(project.getDbKey(), randomCharacteristics);
  124. verify(branchSupportDelegate, times(0)).createBranchComponent(any(), any(), any(), any(), any());
  125. verifyNoMoreInteractions(branchSupportDelegate);
  126. verifyQueueSubmit(project, branch, user, randomCharacteristics, taskUuid);
  127. }
  128. @Test
  129. public void submit_a_report_on_missing_branch_but_existing_project() {
  130. OrganizationDto organization = db.organizations().insert();
  131. ComponentDto existingProject = db.components().insertMainBranch(organization);
  132. BranchDto exitingProjectMainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), existingProject.uuid()).get();
  133. UserDto user = db.users().insertUser();
  134. userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, existingProject);
  135. Map<String, String> randomCharacteristics = randomNonEmptyMap();
  136. ComponentDto createdBranch = createButDoNotInsertBranch(existingProject);
  137. BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(createdBranch);
  138. when(branchSupportDelegate.createComponentKey(existingProject.getDbKey(), randomCharacteristics))
  139. .thenReturn(componentKey);
  140. when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(existingProject), eq(exitingProjectMainBranch)))
  141. .thenReturn(createdBranch);
  142. InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
  143. String taskUuid = mockSuccessfulPrepareSubmitCall();
  144. underTest.submit(organization.getKey(), existingProject.getDbKey(), existingProject.name(), randomCharacteristics, reportInput);
  145. verifyZeroInteractions(permissionTemplateService);
  146. verifyZeroInteractions(favoriteUpdater);
  147. verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(existingProject), eq(exitingProjectMainBranch));
  148. verify(branchSupportDelegate).createComponentKey(existingProject.getDbKey(), randomCharacteristics);
  149. verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(existingProject), eq(exitingProjectMainBranch));
  150. verifyNoMoreInteractions(branchSupportDelegate);
  151. verify(componentUpdater, times(0)).commitAndIndex(any(), any());
  152. verifyQueueSubmit(existingProject, createdBranch, user, randomCharacteristics, taskUuid);
  153. }
  154. @Test
  155. public void submit_report_on_missing_branch_of_missing_project_provisions_project_when_org_PROVISION_PROJECT_perm() {
  156. OrganizationDto organization = db.organizations().insert();
  157. ComponentDto nonExistingProject = newPrivateProjectDto(organization);
  158. UserDto user = db.users().insertUser();
  159. userSession.logIn(user)
  160. .addPermission(PROVISION_PROJECTS, organization)
  161. .addPermission(SCAN, organization);
  162. Map<String, String> randomCharacteristics = randomNonEmptyMap();
  163. ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
  164. BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(createdBranch);
  165. when(branchSupportDelegate.createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics))
  166. .thenReturn(componentKey);
  167. when(componentUpdater.createWithoutCommit(any(), any(), eq(user.getId())))
  168. .thenAnswer((Answer<ComponentDto>) invocation -> db.components().insertMainBranch(nonExistingProject));
  169. when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject), any()))
  170. .thenReturn(createdBranch);
  171. when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(nonExistingProject.getKey())))
  172. .thenReturn(true);
  173. String taskUuid = mockSuccessfulPrepareSubmitCall();
  174. InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
  175. underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), nonExistingProject.name(), randomCharacteristics, reportInput);
  176. BranchDto exitingProjectMainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), nonExistingProject.uuid()).get();
  177. verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject), eq(exitingProjectMainBranch));
  178. verify(branchSupportDelegate).createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics);
  179. verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject), eq(exitingProjectMainBranch));
  180. verifyNoMoreInteractions(branchSupportDelegate);
  181. verifyQueueSubmit(nonExistingProject, createdBranch, user, randomCharacteristics, taskUuid);
  182. verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(nonExistingProject));
  183. }
  184. @Test
  185. public void submit_fails_if_branch_support_delegate_createComponentKey_throws_an_exception() {
  186. OrganizationDto organization = db.organizations().insert();
  187. ComponentDto project = db.components().insertMainBranch(organization);
  188. UserDto user = db.users().insertUser();
  189. userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
  190. Map<String, String> randomCharacteristics = randomNonEmptyMap();
  191. InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
  192. RuntimeException expected = new RuntimeException("Faking an exception thrown by branchSupportDelegate");
  193. when(branchSupportDelegate.createComponentKey(any(), any())).thenThrow(expected);
  194. try {
  195. underTest.submit(organization.getKey(), project.getDbKey(), project.name(), randomCharacteristics, reportInput);
  196. fail("exception should have been thrown");
  197. } catch (Exception e) {
  198. assertThat(e).isSameAs(expected);
  199. }
  200. }
  201. @DataProvider
  202. public static Object[][] permissionsAllowingProjectProvisioning() {
  203. BiConsumer<ComponentDto, UserSessionRule> noProjectPerm = (cpt, userSession) -> {
  204. };
  205. BiConsumer<OrganizationDto, UserSessionRule> noOrgPerm = (cpt, userSession) -> {
  206. };
  207. BiConsumer<ComponentDto, UserSessionRule> provisionOnProject = (cpt, userSession) -> userSession.addProjectPermission(PROVISIONING, cpt);
  208. BiConsumer<OrganizationDto, UserSessionRule> provisionOnOrganization = (cpt, userSession) -> userSession.addPermission(PROVISION_PROJECTS, cpt);
  209. return new Object[][] {
  210. {provisionOnProject, noOrgPerm},
  211. {noProjectPerm, provisionOnOrganization},
  212. {provisionOnProject, provisionOnOrganization}
  213. };
  214. }
  215. @Test
  216. public void submit_report_on_missing_branch_of_missing_project_fails_with_ForbiddenException_if_only_scan_permission() {
  217. OrganizationDto organization = db.organizations().insert();
  218. ComponentDto nonExistingProject = newPrivateProjectDto(organization);
  219. UserDto user = db.users().insertUser();
  220. userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, nonExistingProject);
  221. Map<String, String> randomCharacteristics = randomNonEmptyMap();
  222. ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
  223. BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(createdBranch);
  224. when(branchSupportDelegate.createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics))
  225. .thenReturn(componentKey);
  226. when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject), any()))
  227. .thenReturn(createdBranch);
  228. InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
  229. expectedException.expect(ForbiddenException.class);
  230. expectedException.expectMessage("Insufficient privileges");
  231. underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), nonExistingProject.name(), randomCharacteristics, reportInput);
  232. }
  233. private static ComponentDto createButDoNotInsertBranch(ComponentDto project) {
  234. BranchType randomBranchType = BranchType.values()[new Random().nextInt(BranchType.values().length)];
  235. BranchDto branchDto = newBranchDto(project.projectUuid(), randomBranchType);
  236. return ComponentTesting.newProjectBranch(project, branchDto);
  237. }
  238. private String mockSuccessfulPrepareSubmitCall() {
  239. String taskUuid = randomAlphabetic(12);
  240. when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(taskUuid));
  241. return taskUuid;
  242. }
  243. private void verifyQueueSubmit(ComponentDto project, ComponentDto branch, UserDto user, Map<String, String> characteristics, String taskUuid) {
  244. verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT)
  245. && submit.getComponent().filter(cpt -> cpt.getUuid().equals(branch.uuid()) && cpt.getMainComponentUuid().equals(project.uuid())).isPresent()
  246. && submit.getSubmitterUuid().equals(user.getUuid())
  247. && submit.getCharacteristics().equals(characteristics)
  248. && submit.getUuid().equals(taskUuid)));
  249. }
  250. private static BranchSupport.ComponentKey createComponentKeyOfBranch(ComponentDto branch) {
  251. BranchSupport.ComponentKey mainComponentKey = mockComponentKey(branch.getKey(), branch.getKey());
  252. when(mainComponentKey.getMainBranchComponentKey()).thenReturn(mainComponentKey);
  253. BranchSupport.ComponentKey componentKey = mockComponentKey(branch.getKey(), branch.getDbKey());
  254. when(componentKey.getBranch()).thenReturn(Optional.ofNullable(branch).map(b -> new BranchSupport.Branch(b.name(), BranchType.LONG)));
  255. when(componentKey.getMainBranchComponentKey()).thenReturn(mainComponentKey);
  256. return componentKey;
  257. }
  258. private static BranchSupport.ComponentKey mockComponentKey(String key, String dbKey) {
  259. BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class);
  260. when(componentKey.getKey()).thenReturn(key);
  261. when(componentKey.getDbKey()).thenReturn(dbKey);
  262. return componentKey;
  263. }
  264. private static ImmutableMap<String, String> randomNonEmptyMap() {
  265. return IntStream.range(0, 1 + new Random().nextInt(5))
  266. .boxed()
  267. .collect(uniqueIndex(i -> "key_" + i, i -> "val_" + i));
  268. }
  269. }