]> source.dussan.org Git - sonarqube.git/blob
659d2206448f3268df566d1fbbfbe7ac54e0cecc
[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.almintegration.ws.bitbucketserver;
21
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Optional;
26 import org.junit.Before;
27 import org.junit.BeforeClass;
28 import org.junit.Rule;
29 import org.junit.Test;
30 import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient;
31 import org.sonar.alm.client.bitbucketserver.Branch;
32 import org.sonar.alm.client.bitbucketserver.BranchesList;
33 import org.sonar.alm.client.bitbucketserver.Project;
34 import org.sonar.alm.client.bitbucketserver.Repository;
35 import org.sonar.api.server.ws.WebService;
36 import org.sonar.api.utils.System2;
37 import org.sonar.core.platform.EditionProvider;
38 import org.sonar.core.platform.PlatformEditionProvider;
39 import org.sonar.core.util.SequenceUuidFactory;
40 import org.sonar.db.DbTester;
41 import org.sonar.db.alm.pat.AlmPatDto;
42 import org.sonar.db.alm.setting.AlmSettingDto;
43 import org.sonar.db.component.BranchDto;
44 import org.sonar.db.newcodeperiod.NewCodePeriodDto;
45 import org.sonar.db.project.CreationMethod;
46 import org.sonar.db.project.ProjectDto;
47 import org.sonar.db.user.UserDto;
48 import org.sonar.server.almintegration.ws.ImportHelper;
49 import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
50 import org.sonar.server.component.ComponentUpdater;
51 import org.sonar.server.es.TestIndexers;
52 import org.sonar.server.exceptions.BadRequestException;
53 import org.sonar.server.exceptions.ForbiddenException;
54 import org.sonar.server.exceptions.NotFoundException;
55 import org.sonar.server.exceptions.UnauthorizedException;
56 import org.sonar.server.favorite.FavoriteUpdater;
57 import org.sonar.server.l18n.I18nRule;
58 import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver;
59 import org.sonar.server.permission.PermissionService;
60 import org.sonar.server.permission.PermissionTemplateService;
61 import org.sonar.server.permission.PermissionUpdater;
62 import org.sonar.server.project.DefaultBranchNameResolver;
63 import org.sonar.server.project.ProjectDefaultVisibility;
64 import org.sonar.server.project.Visibility;
65 import org.sonar.server.tester.UserSessionRule;
66 import org.sonar.server.ws.WsActionTester;
67 import org.sonarqube.ws.Projects;
68
69 import static java.util.Objects.requireNonNull;
70 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
71 import static org.apache.commons.lang.math.JVMRandom.nextLong;
72 import static org.assertj.core.api.Assertions.assertThat;
73 import static org.assertj.core.api.Assertions.assertThatThrownBy;
74 import static org.assertj.core.api.Assertions.tuple;
75 import static org.mockito.ArgumentMatchers.any;
76 import static org.mockito.Mockito.mock;
77 import static org.mockito.Mockito.verify;
78 import static org.mockito.Mockito.when;
79 import static org.sonar.db.alm.integration.pat.AlmPatsTesting.newAlmPatDto;
80 import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
81 import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS;
82 import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
83 import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
84 import static org.sonar.db.permission.GlobalPermission.SCAN;
85 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE;
86 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE;
87
88 public class ImportBitbucketServerProjectActionIT {
89   private static final String GENERATED_PROJECT_KEY = "TEST_PROJECT_KEY";
90
91   @Rule
92   public UserSessionRule userSession = UserSessionRule.standalone();
93   @Rule
94   public DbTester db = DbTester.create();
95   @Rule
96   public final I18nRule i18n = new I18nRule();
97
98   private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class);
99   private final BitbucketServerRestClient bitbucketServerRestClient = mock(BitbucketServerRestClient.class);
100   private final DefaultBranchNameResolver defaultBranchNameResolver = mock(DefaultBranchNameResolver.class);
101   private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class);
102   private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider);
103
104   private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), i18n, System2.INSTANCE,
105     mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestIndexers(), new SequenceUuidFactory(),
106     defaultBranchNameResolver, mock(PermissionUpdater.class), mock(PermissionService.class));
107
108   private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession);
109   private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class);
110   private final WsActionTester ws = new WsActionTester(new ImportBitbucketServerProjectAction(db.getDbClient(), userSession,
111     bitbucketServerRestClient, projectDefaultVisibility, componentUpdater, importHelper, projectKeyGenerator, newCodeDefinitionResolver, defaultBranchNameResolver));
112
113   private static BranchesList defaultBranchesList;
114
115   @BeforeClass
116   public static void beforeAll() {
117     Branch defaultBranch = new Branch("default", true);
118     defaultBranchesList = new BranchesList(Collections.singletonList(defaultBranch));
119   }
120
121   @Before
122   public void before() {
123     when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE);
124     when(projectKeyGenerator.generateUniqueProjectKey(any(), any())).thenReturn(GENERATED_PROJECT_KEY);
125     when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn(DEFAULT_MAIN_BRANCH_NAME);
126   }
127
128   @Test
129   public void import_project() {
130     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
131     Project project = getGsonBBSProject();
132     Repository repo = mockBitbucketServerRepo(project);
133
134     Projects.CreateWsResponse response = ws.newRequest()
135       .setParam("almSetting", almSetting.getKey())
136       .setParam("projectKey", "projectKey")
137       .setParam("repositorySlug", "repo-slug")
138       .executeProtobuf(Projects.CreateWsResponse.class);
139
140     Projects.CreateWsResponse.Project result = response.getProject();
141     assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY);
142     assertThat(result.getName()).isEqualTo(repo.getName());
143
144     ProjectDto projectDto = getProjectDto(result);
145     assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_API);
146
147     assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto)).isPresent();
148     verify(projectKeyGenerator).generateUniqueProjectKey(requireNonNull(project.getKey()), repo.getSlug());
149   }
150
151   @Test
152   public void importProject_whenCallIsNotFromBrowser_shouldFlagTheProjectAsCreatedFromApi() {
153     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
154     Project project = getGsonBBSProject();
155     mockBitbucketServerRepo(project);
156
157     Projects.CreateWsResponse response = ws.newRequest()
158       .setParam("almSetting", almSetting.getKey())
159       .setParam("projectKey", "projectKey")
160       .setParam("repositorySlug", "repo-slug")
161       .executeProtobuf(Projects.CreateWsResponse.class);
162
163     ProjectDto projectDto = getProjectDto(response.getProject());
164     assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_API);
165   }
166
167   @Test
168   public void importProject_whenCallIsFromBrowser_shouldFlagTheProjectAsCreatedFromBrowser() {
169     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
170     userSession.flagSessionAsGui();
171     Project project = getGsonBBSProject();
172     mockBitbucketServerRepo(project);
173
174     Projects.CreateWsResponse response = ws.newRequest()
175       .setParam("almSetting", almSetting.getKey())
176       .setParam("projectKey", "projectKey")
177       .setParam("repositorySlug", "repo-slug")
178       .executeProtobuf(Projects.CreateWsResponse.class);
179
180     ProjectDto projectDto = getProjectDto(response.getProject());
181     assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_BROWSER);
182   }
183
184   @Test
185   public void import_project_with_NCD_developer_edition_sets_project_NCD() {
186     when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
187
188     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
189     Project project = getGsonBBSProject();
190     Repository repo = mockBitbucketServerRepo(project);
191
192     Projects.CreateWsResponse response = ws.newRequest()
193       .setParam("almSetting", almSetting.getKey())
194       .setParam("projectKey", "projectKey")
195       .setParam("repositorySlug", "repo-slug")
196       .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS")
197       .setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30")
198       .executeProtobuf(Projects.CreateWsResponse.class);
199
200     Projects.CreateWsResponse.Project result = response.getProject();
201     assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY);
202     assertThat(result.getName()).isEqualTo(repo.getName());
203
204     ProjectDto projectDto = getProjectDto(result);
205     assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto)).isPresent();
206     verify(projectKeyGenerator).generateUniqueProjectKey(requireNonNull(project.getKey()), repo.getSlug());
207
208     assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.getUuid()))
209       .isPresent()
210       .get()
211       .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid)
212       .containsExactly(NUMBER_OF_DAYS, "30", null);
213   }
214
215   @Test
216   public void import_project_with_NCD_community_edition_sets_branch_NCD() {
217     when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY));
218
219     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
220     Project project = getGsonBBSProject();
221     mockBitbucketServerRepo(project);
222
223     Projects.CreateWsResponse response = ws.newRequest()
224       .setParam("almSetting", almSetting.getKey())
225       .setParam("projectKey", "projectKey")
226       .setParam("repositorySlug", "repo-slug")
227       .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS")
228       .setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30")
229       .executeProtobuf(Projects.CreateWsResponse.class);
230
231     Projects.CreateWsResponse.Project result = response.getProject();
232
233     ProjectDto projectDto = getProjectDto(result);
234     BranchDto branchDto = db.getDbClient().branchDao().selectMainBranchByProjectUuid(db.getSession(), projectDto.getUuid()).orElseThrow();
235
236     String projectUuid = projectDto.getUuid();
237     assertThat(db.getDbClient().newCodePeriodDao().selectByBranch(db.getSession(), projectUuid, branchDto.getUuid()))
238       .isPresent()
239       .get()
240       .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid)
241       .containsExactly(NUMBER_OF_DAYS, "30", branchDto.getUuid());
242   }
243
244   @Test
245   public void import_project_reference_branch_ncd_no_default_branch() {
246     when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
247     when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn("default-branch");
248
249     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
250     Project project = getGsonBBSProject();
251     mockBitbucketServerRepo(project, new BranchesList());
252
253     Projects.CreateWsResponse response = ws.newRequest()
254       .setParam("almSetting", almSetting.getKey())
255       .setParam("projectKey", "projectKey")
256       .setParam("repositorySlug", "repo-slug")
257       .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "REFERENCE_BRANCH")
258       .executeProtobuf(Projects.CreateWsResponse.class);
259
260     Projects.CreateWsResponse.Project result = response.getProject();
261
262     ProjectDto projectDto = getProjectDto(result);
263
264     String projectUuid = projectDto.getUuid();
265     assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectUuid))
266       .isPresent()
267       .get()
268       .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue)
269       .containsExactly(REFERENCE_BRANCH, "default-branch");
270   }
271
272   @Test
273   public void import_project_reference_branch_ncd() {
274     when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
275
276     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
277     Project project = getGsonBBSProject();
278     mockBitbucketServerRepo(project);
279
280     Projects.CreateWsResponse response = ws.newRequest()
281       .setParam("almSetting", almSetting.getKey())
282       .setParam("projectKey", "projectKey")
283       .setParam("repositorySlug", "repo-slug")
284       .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "REFERENCE_BRANCH")
285       .executeProtobuf(Projects.CreateWsResponse.class);
286
287     Projects.CreateWsResponse.Project result = response.getProject();
288
289     ProjectDto projectDto = getProjectDto(result);
290
291     String projectUuid = projectDto.getUuid();
292     assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectUuid))
293       .isPresent()
294       .get()
295       .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue)
296       .containsExactly(REFERENCE_BRANCH, "default");
297   }
298
299   @Test
300   public void fail_project_already_exist() {
301     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
302     Project project = getGsonBBSProject();
303     mockBitbucketServerRepo(project);
304     db.components().insertPublicProject(p -> p.setKey(GENERATED_PROJECT_KEY)).getMainBranchComponent();
305
306     assertThatThrownBy(() -> {
307
308       ws.newRequest()
309         .setParam("almSetting", almSetting.getKey())
310         .setParam("projectKey", "projectKey")
311         .setParam("repositorySlug", "repo-slug")
312         .execute();
313     })
314       .isInstanceOf(BadRequestException.class)
315       .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", GENERATED_PROJECT_KEY, GENERATED_PROJECT_KEY);
316   }
317
318   @Test
319   public void fail_when_not_logged_in() {
320     assertThatThrownBy(() -> {
321       ws.newRequest()
322         .setParam("almSetting", "sdgfdshfjztutz")
323         .setParam("projectKey", "projectKey")
324         .setParam("repositorySlug", "repo-slug")
325         .execute();
326     })
327       .isInstanceOf(UnauthorizedException.class);
328   }
329
330   @Test
331   public void fail_when_missing_project_creator_permission() {
332     UserDto user = db.users().insertUser();
333     userSession.logIn(user).addPermission(SCAN);
334
335     assertThatThrownBy(() -> {
336       ws.newRequest()
337         .setParam("almSetting", "sdgfdshfjztutz")
338         .setParam("projectKey", "projectKey")
339         .setParam("repositorySlug", "repo-slug")
340         .execute();
341     })
342       .isInstanceOf(ForbiddenException.class)
343       .hasMessage("Insufficient privileges");
344   }
345
346   @Test
347   public void check_pat_is_missing() {
348     UserDto user = db.users().insertUser();
349     userSession.logIn(user).addPermission(PROVISION_PROJECTS);
350     AlmSettingDto almSetting = db.almSettings().insertGitHubAlmSetting();
351
352     assertThatThrownBy(() -> {
353       ws.newRequest()
354         .setParam("almSetting", almSetting.getKey())
355         .execute();
356     })
357       .isInstanceOf(IllegalArgumentException.class)
358       .hasMessage("personal access token for '" + almSetting.getKey() + "' is missing");
359   }
360
361   @Test
362   public void fail_check_alm_setting_not_found() {
363     UserDto user = db.users().insertUser();
364     userSession.logIn(user).addPermission(PROVISION_PROJECTS);
365     AlmPatDto almPatDto = newAlmPatDto();
366     db.getDbClient().almPatDao().insert(db.getSession(), almPatDto, user.getLogin(), null);
367
368     assertThatThrownBy(() -> {
369       ws.newRequest()
370         .setParam("almSetting", "testKey")
371         .execute();
372     })
373       .isInstanceOf(NotFoundException.class)
374       .hasMessage("DevOps Platform Setting 'testKey' not found");
375   }
376
377   @Test
378   public void fail_when_no_creation_project_permission() {
379     UserDto user = db.users().insertUser();
380     userSession.logIn(user);
381
382     assertThatThrownBy(() -> {
383       ws.newRequest()
384         .setParam("almSetting", "anyvalue")
385         .execute();
386     })
387       .isInstanceOf(ForbiddenException.class)
388       .hasMessage("Insufficient privileges");
389   }
390
391   @Test
392   public void handle_givenNoDefaultBranchFound_doNotUpdateDefaultBranchName() {
393     BranchesList branchesList = new BranchesList();
394     Branch branch = new Branch("not_a_master", false);
395     branchesList.addBranch(branch);
396
397     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
398     Project project = getGsonBBSProject();
399     mockBitbucketServerRepo(project, branchesList);
400
401     Projects.CreateWsResponse response = ws.newRequest()
402       .setParam("almSetting", almSetting.getKey())
403       .setParam("projectKey", "projectKey")
404       .setParam("repositorySlug", "repo-slug")
405       .executeProtobuf(Projects.CreateWsResponse.class);
406
407     Projects.CreateWsResponse.Project result = response.getProject();
408
409     ProjectDto projectDto = getProjectDto(result);
410     Collection<BranchDto> branchDtos = db.getDbClient().branchDao().selectByProject(db.getSession(), projectDto);
411     List<BranchDto> collect = branchDtos.stream().filter(BranchDto::isMain).toList();
412     String mainBranchName = collect.iterator().next().getKey();
413     assertThat(mainBranchName).isEqualTo(DEFAULT_MAIN_BRANCH_NAME);
414   }
415
416   @Test
417   public void handle_givenDefaultBranchNamedDefault_updateDefaultBranchNameToDefault() {
418     BranchesList branchesList = new BranchesList();
419     Branch branch = new Branch("default", true);
420     branchesList.addBranch(branch);
421
422     AlmSettingDto almSetting = configureUserAndPatAndAlmSettings();
423     Project project = getGsonBBSProject();
424     mockBitbucketServerRepo(project, branchesList);
425
426     Projects.CreateWsResponse response = ws.newRequest()
427       .setParam("almSetting", almSetting.getKey())
428       .setParam("projectKey", "projectKey")
429       .setParam("repositorySlug", "repo-slug")
430       .executeProtobuf(Projects.CreateWsResponse.class);
431
432     Projects.CreateWsResponse.Project result = response.getProject();
433
434     ProjectDto projectDto = getProjectDto(result);
435     Collection<BranchDto> branchDtos = db.getDbClient().branchDao().selectByProject(db.getSession(), projectDto);
436     List<BranchDto> collect = branchDtos.stream().filter(BranchDto::isMain).toList();
437     String mainBranchName = collect.iterator().next().getKey();
438     assertThat(mainBranchName).isEqualTo("default");
439   }
440
441   @Test
442   public void definition() {
443     WebService.Action def = ws.getDef();
444
445     assertThat(def.since()).isEqualTo("8.2");
446     assertThat(def.isPost()).isTrue();
447     assertThat(def.params())
448       .extracting(WebService.Param::key, WebService.Param::isRequired)
449       .containsExactlyInAnyOrder(
450         tuple("almSetting", true),
451         tuple("repositorySlug", true),
452         tuple("projectKey", true),
453         tuple(PARAM_NEW_CODE_DEFINITION_TYPE, false),
454         tuple(PARAM_NEW_CODE_DEFINITION_VALUE, false));
455   }
456
457   private AlmSettingDto configureUserAndPatAndAlmSettings() {
458     UserDto user = db.users().insertUser();
459     userSession.logIn(user).addPermission(PROVISION_PROJECTS);
460     AlmSettingDto almSetting = db.almSettings().insertGitHubAlmSetting();
461     db.almPats().insert(dto -> {
462       dto.setAlmSettingUuid(almSetting.getUuid());
463       dto.setUserUuid(user.getUuid());
464     });
465     return almSetting;
466   }
467
468   private Repository mockBitbucketServerRepo(Project project) {
469     return mockBitbucketServerRepo(project, defaultBranchesList);
470   }
471
472   private Repository mockBitbucketServerRepo(Project project, BranchesList branchesList) {
473     Repository bbsResult = new Repository();
474     bbsResult.setProject(project);
475     bbsResult.setSlug(randomAlphanumeric(5));
476     bbsResult.setName(randomAlphanumeric(5));
477     bbsResult.setId(nextLong(100));
478     when(bitbucketServerRestClient.getRepo(any(), any(), any(), any())).thenReturn(bbsResult);
479     when(bitbucketServerRestClient.getBranches(any(), any(), any(), any())).thenReturn(branchesList);
480     return bbsResult;
481   }
482
483   private Project getGsonBBSProject() {
484     return new Project()
485       .setKey(randomAlphanumeric(5))
486       .setId(nextLong(100))
487       .setName(randomAlphanumeric(5));
488   }
489
490   private ProjectDto getProjectDto(Projects.CreateWsResponse.Project result) {
491     Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
492     assertThat(projectDto).isPresent();
493     return projectDto.orElseThrow();
494   }
495
496 }