3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.ce.queue;
22 import com.google.common.collect.ImmutableMap;
23 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
24 import java.io.InputStream;
25 import java.nio.charset.StandardCharsets;
27 import java.util.Optional;
28 import java.util.Random;
29 import java.util.stream.IntStream;
30 import org.apache.commons.io.IOUtils;
31 import org.junit.Before;
32 import org.junit.Rule;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.mockito.ArgumentCaptor;
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.user.UserDto;
49 import org.sonar.server.component.ComponentUpdater;
50 import org.sonar.server.exceptions.ForbiddenException;
51 import org.sonar.server.favorite.FavoriteUpdater;
52 import org.sonar.server.permission.PermissionTemplateService;
53 import org.sonar.server.project.ProjectDefaultVisibility;
54 import org.sonar.server.project.Visibility;
55 import org.sonar.server.tester.UserSessionRule;
57 import static java.util.Collections.emptyMap;
58 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
59 import static org.assertj.core.api.Assertions.assertThat;
60 import static org.assertj.core.api.Assertions.assertThatThrownBy;
61 import static org.junit.Assert.fail;
62 import static org.mockito.ArgumentMatchers.any;
63 import static org.mockito.ArgumentMatchers.eq;
64 import static org.mockito.ArgumentMatchers.same;
65 import static org.mockito.Mockito.mock;
66 import static org.mockito.Mockito.spy;
67 import static org.mockito.Mockito.times;
68 import static org.mockito.Mockito.verify;
69 import static org.mockito.Mockito.verifyNoInteractions;
70 import static org.mockito.Mockito.verifyNoMoreInteractions;
71 import static org.mockito.Mockito.when;
72 import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
73 import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
74 import static org.sonar.db.component.ComponentTesting.newBranchDto;
75 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
76 import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
77 import static org.sonar.db.permission.GlobalPermission.SCAN;
80 * Tests of {@link ReportSubmitter} when branch support is installed.
82 @RunWith(DataProviderRunner.class)
83 public class BranchReportSubmitterTest {
86 public final UserSessionRule userSession = UserSessionRule.standalone();
88 public final DbTester db = DbTester.create(System2.INSTANCE);
90 private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class);
92 private final CeQueue queue = mock(CeQueueImpl.class);
93 private final ComponentUpdater componentUpdater = mock(ComponentUpdater.class);
94 private final PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
95 private final FavoriteUpdater favoriteUpdater = mock(FavoriteUpdater.class);
96 private final BranchSupportDelegate branchSupportDelegate = mock(BranchSupportDelegate.class);
97 private final BranchSupport branchSupport = spy(new BranchSupport(branchSupportDelegate));
99 private final ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), branchSupport,
100 projectDefaultVisibility);
103 public void before() {
104 when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PUBLIC);
108 public void submit_does_not_use_delegate_if_characteristics_are_empty() {
109 ComponentDto project = db.components().insertPublicProject();
110 UserDto user = db.users().insertUser();
111 userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
112 mockSuccessfulPrepareSubmitCall();
113 InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
115 underTest.submit(project.getKey(), project.name(), emptyMap(), reportInput);
117 verifyNoInteractions(branchSupportDelegate);
121 public void submit_a_report_on_existing_branch() {
122 ComponentDto project = db.components().insertPublicProject();
123 ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("branch1"));
124 UserDto user = db.users().insertUser();
125 userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
126 Map<String, String> randomCharacteristics = randomNonEmptyMap();
127 BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(project.getKey(), "branch1");
128 when(branchSupportDelegate.createComponentKey(project.getKey(), randomCharacteristics)).thenReturn(componentKey);
129 InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
130 String taskUuid = mockSuccessfulPrepareSubmitCall();
132 underTest.submit(project.getKey(), project.name(), randomCharacteristics, reportInput);
134 verifyNoInteractions(permissionTemplateService);
135 verifyNoInteractions(favoriteUpdater);
136 verify(branchSupport, times(0)).createBranchComponent(any(), any(), any(), any());
137 verify(branchSupportDelegate).createComponentKey(project.getKey(), randomCharacteristics);
138 verify(branchSupportDelegate, times(0)).createBranchComponent(any(), any(), any(), any());
139 verifyNoMoreInteractions(branchSupportDelegate);
140 verifyQueueSubmit(project, branch, user, randomCharacteristics, taskUuid);
144 public void submit_a_report_on_missing_branch_but_existing_project() {
145 ComponentDto existingProject = db.components().insertPublicProject();
146 BranchDto exitingProjectMainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), existingProject.uuid()).get();
147 UserDto user = db.users().insertUser();
148 userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, existingProject);
149 Map<String, String> randomCharacteristics = randomNonEmptyMap();
150 ComponentDto createdBranch = createButDoNotInsertBranch(existingProject);
151 BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(existingProject.getKey(), "branch1");
152 when(branchSupportDelegate.createComponentKey(existingProject.getKey(), randomCharacteristics)).thenReturn(componentKey);
153 when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(existingProject), eq(exitingProjectMainBranch))).thenReturn(createdBranch);
154 InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
155 String taskUuid = mockSuccessfulPrepareSubmitCall();
157 underTest.submit(existingProject.getKey(), existingProject.name(), randomCharacteristics, reportInput);
159 verifyNoInteractions(permissionTemplateService);
160 verifyNoInteractions(favoriteUpdater);
161 verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(existingProject), eq(exitingProjectMainBranch));
162 verify(branchSupportDelegate).createComponentKey(existingProject.getKey(), randomCharacteristics);
163 verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(existingProject), eq(exitingProjectMainBranch));
164 verifyNoMoreInteractions(branchSupportDelegate);
165 verify(componentUpdater, times(0)).commitAndIndex(any(), any());
166 verifyQueueSubmit(existingProject, createdBranch, user, randomCharacteristics, taskUuid);
170 public void submit_report_on_missing_branch_of_missing_project_provisions_project_when_PROVISION_PROJECT_perm() {
171 ComponentDto nonExistingProject = newPrivateProjectDto();
172 UserDto user = db.users().insertUser();
173 userSession.logIn(user)
174 .addPermission(PROVISION_PROJECTS)
175 .addPermission(SCAN);
177 Map<String, String> randomCharacteristics = randomNonEmptyMap();
178 ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
179 BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(nonExistingProject.getKey());
180 when(branchSupportDelegate.createComponentKey(nonExistingProject.getKey(), randomCharacteristics)).thenReturn(componentKey);
181 when(componentUpdater.createWithoutCommit(any(), any(), eq(user.getUuid()), eq(user.getLogin()), any()))
182 .thenAnswer((Answer<ComponentDto>) invocation -> db.components().insertPrivateProject(nonExistingProject));
183 when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(nonExistingProject), any())).thenReturn(createdBranch);
184 when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(nonExistingProject.getKey()))).thenReturn(true);
185 String taskUuid = mockSuccessfulPrepareSubmitCall();
186 InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
188 underTest.submit(nonExistingProject.getKey(), nonExistingProject.name(), randomCharacteristics, reportInput);
190 BranchDto exitingProjectMainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), nonExistingProject.uuid()).get();
191 verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(nonExistingProject), eq(exitingProjectMainBranch));
192 verify(branchSupportDelegate).createComponentKey(nonExistingProject.getKey(), randomCharacteristics);
193 verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(nonExistingProject), eq(exitingProjectMainBranch));
194 verifyNoMoreInteractions(branchSupportDelegate);
195 verifyQueueSubmit(nonExistingProject, createdBranch, user, randomCharacteristics, taskUuid);
196 verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(nonExistingProject));
200 public void submit_fails_if_branch_support_delegate_createComponentKey_throws_an_exception() {
201 ComponentDto project = db.components().insertPublicProject();
202 UserDto user = db.users().insertUser();
203 userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
204 Map<String, String> randomCharacteristics = randomNonEmptyMap();
205 InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
206 RuntimeException expected = new RuntimeException("Faking an exception thrown by branchSupportDelegate");
207 when(branchSupportDelegate.createComponentKey(any(), any())).thenThrow(expected);
210 underTest.submit(project.getKey(), project.name(), randomCharacteristics, reportInput);
211 fail("exception should have been thrown");
212 } catch (Exception e) {
213 assertThat(e).isSameAs(expected);
218 public void submit_report_on_missing_branch_of_missing_project_fails_with_ForbiddenException_if_only_scan_permission() {
219 ComponentDto nonExistingProject = newPrivateProjectDto();
220 UserDto user = db.users().insertUser();
221 userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, nonExistingProject);
222 Map<String, String> randomCharacteristics = randomNonEmptyMap();
223 ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
224 BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(nonExistingProject.getKey());
225 String nonExistingProjectDbKey = nonExistingProject.getKey();
226 when(branchSupportDelegate.createComponentKey(nonExistingProjectDbKey, randomCharacteristics)).thenReturn(componentKey);
227 when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(nonExistingProject), any())).thenReturn(createdBranch);
228 InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
230 String name = nonExistingProject.name();
231 assertThatThrownBy(() -> underTest.submit(nonExistingProjectDbKey, name, randomCharacteristics, reportInput))
232 .isInstanceOf(ForbiddenException.class)
233 .hasMessage("Insufficient privileges");
236 private static ComponentDto createButDoNotInsertBranch(ComponentDto project) {
237 BranchType randomBranchType = BranchType.values()[new Random().nextInt(BranchType.values().length)];
238 BranchDto branchDto = newBranchDto(project.branchUuid(), randomBranchType);
239 return ComponentTesting.newBranchComponent(project, branchDto);
242 private String mockSuccessfulPrepareSubmitCall() {
243 String taskUuid = randomAlphabetic(12);
244 when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(taskUuid));
248 private void verifyQueueSubmit(ComponentDto project, ComponentDto branch, UserDto user, Map<String, String> characteristics, String taskUuid) {
249 ArgumentCaptor<CeTaskSubmit> captor = ArgumentCaptor.forClass(CeTaskSubmit.class);
251 verify(queue).submit(captor.capture());
252 CeTaskSubmit ceTask = captor.getValue();
253 assertThat(ceTask.getUuid()).isEqualTo(taskUuid);
254 assertThat(ceTask.getSubmitterUuid()).isEqualTo(user.getUuid());
255 assertThat(ceTask.getCharacteristics()).isEqualTo(characteristics);
256 assertThat(ceTask.getType()).isEqualTo(CeTaskTypes.REPORT);
257 assertThat(ceTask.getComponent()).isPresent();
258 assertThat(ceTask.getComponent().get().getUuid()).isEqualTo(branch.uuid());
259 assertThat(ceTask.getComponent().get().getMainComponentUuid()).isEqualTo(project.uuid());
262 private static BranchSupport.ComponentKey createComponentKeyOfBranch(String projectKey) {
263 return createComponentKeyOfBranch(projectKey, randomAlphabetic(5));
266 private static BranchSupport.ComponentKey createComponentKeyOfBranch(String projectKey, String branchKey) {
267 BranchSupport.ComponentKey componentKey = mockComponentKey(projectKey);
268 when(componentKey.getBranchName()).thenReturn(Optional.of(branchKey));
272 private static BranchSupport.ComponentKey mockComponentKey(String key) {
273 BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class);
274 when(componentKey.getKey()).thenReturn(key);
278 private static ImmutableMap<String, String> randomNonEmptyMap() {
279 return IntStream.range(0, 1 + new Random().nextInt(5))
281 .collect(uniqueIndex(i -> "key_" + i, i -> "val_" + i));