]> source.dussan.org Git - sonarqube.git/blob
3d6d1e6cfc8b6176600152312d0d602e44376142
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.ce.queue;
21
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;
26 import java.util.Map;
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;
56
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;
78
79 /**
80  * Tests of {@link ReportSubmitter} when branch support is installed.
81  */
82 @RunWith(DataProviderRunner.class)
83 public class BranchReportSubmitterTest {
84
85   @Rule
86   public final UserSessionRule userSession = UserSessionRule.standalone();
87   @Rule
88   public final DbTester db = DbTester.create(System2.INSTANCE);
89
90   private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class);
91
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));
98
99   private final ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), branchSupport,
100     projectDefaultVisibility);
101
102   @Before
103   public void before() {
104     when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PUBLIC);
105   }
106
107   @Test
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);
114
115     underTest.submit(project.getKey(), project.name(), emptyMap(), reportInput);
116
117     verifyNoInteractions(branchSupportDelegate);
118   }
119
120   @Test
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();
131
132     underTest.submit(project.getKey(), project.name(), randomCharacteristics, reportInput);
133
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);
141   }
142
143   @Test
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();
156
157     underTest.submit(existingProject.getKey(), existingProject.name(), randomCharacteristics, reportInput);
158
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);
167   }
168
169   @Test
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);
176
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);
187
188     underTest.submit(nonExistingProject.getKey(), nonExistingProject.name(), randomCharacteristics, reportInput);
189
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));
197   }
198
199   @Test
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);
208
209     try {
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);
214     }
215   }
216
217   @Test
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);
229
230     String name = nonExistingProject.name();
231     assertThatThrownBy(() -> underTest.submit(nonExistingProjectDbKey, name, randomCharacteristics, reportInput))
232       .isInstanceOf(ForbiddenException.class)
233       .hasMessage("Insufficient privileges");
234   }
235
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);
240   }
241
242   private String mockSuccessfulPrepareSubmitCall() {
243     String taskUuid = randomAlphabetic(12);
244     when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(taskUuid));
245     return taskUuid;
246   }
247
248   private void verifyQueueSubmit(ComponentDto project, ComponentDto branch, UserDto user, Map<String, String> characteristics, String taskUuid) {
249     ArgumentCaptor<CeTaskSubmit> captor = ArgumentCaptor.forClass(CeTaskSubmit.class);
250
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());
260   }
261
262   private static BranchSupport.ComponentKey createComponentKeyOfBranch(String projectKey) {
263     return createComponentKeyOfBranch(projectKey, randomAlphabetic(5));
264   }
265
266   private static BranchSupport.ComponentKey createComponentKeyOfBranch(String projectKey, String branchKey) {
267     BranchSupport.ComponentKey componentKey = mockComponentKey(projectKey);
268     when(componentKey.getBranchName()).thenReturn(Optional.of(branchKey));
269     return componentKey;
270   }
271
272   private static BranchSupport.ComponentKey mockComponentKey(String key) {
273     BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class);
274     when(componentKey.getKey()).thenReturn(key);
275     return componentKey;
276   }
277
278   private static ImmutableMap<String, String> randomNonEmptyMap() {
279     return IntStream.range(0, 1 + new Random().nextInt(5))
280       .boxed()
281       .collect(uniqueIndex(i -> "key_" + i, i -> "val_" + i));
282   }
283
284 }