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.almintegration.ws.github;
22 import java.util.Optional;
23 import org.junit.Before;
24 import org.junit.Rule;
25 import org.junit.Test;
26 import org.mockito.ArgumentCaptor;
27 import org.sonar.alm.client.github.GithubApplicationClient;
28 import org.sonar.alm.client.github.GithubApplicationClientImpl;
29 import org.sonar.api.server.ws.WebService;
30 import org.sonar.api.utils.System2;
31 import org.sonar.auth.github.GitHubSettings;
32 import org.sonar.core.i18n.I18n;
33 import org.sonar.core.platform.EditionProvider;
34 import org.sonar.core.platform.PlatformEditionProvider;
35 import org.sonar.core.util.SequenceUuidFactory;
36 import org.sonar.db.DbSession;
37 import org.sonar.db.DbTester;
38 import org.sonar.db.alm.setting.AlmSettingDto;
39 import org.sonar.db.component.BranchDto;
40 import org.sonar.db.entity.EntityDto;
41 import org.sonar.db.newcodeperiod.NewCodePeriodDto;
42 import org.sonar.db.permission.GlobalPermission;
43 import org.sonar.db.project.ProjectDto;
44 import org.sonar.db.user.UserDto;
45 import org.sonar.server.almintegration.ws.ImportHelper;
46 import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
47 import org.sonar.server.component.ComponentUpdater;
48 import org.sonar.server.es.TestIndexers;
49 import org.sonar.server.exceptions.NotFoundException;
50 import org.sonar.server.exceptions.UnauthorizedException;
51 import org.sonar.server.favorite.FavoriteUpdater;
52 import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver;
53 import org.sonar.server.permission.PermissionTemplateService;
54 import org.sonar.server.project.DefaultBranchNameResolver;
55 import org.sonar.server.project.ProjectDefaultVisibility;
56 import org.sonar.server.project.Visibility;
57 import org.sonar.server.tester.UserSessionRule;
58 import org.sonar.server.ws.TestRequest;
59 import org.sonar.server.ws.WsActionTester;
60 import org.sonarqube.ws.Projects;
62 import static org.assertj.core.api.Assertions.assertThat;
63 import static org.assertj.core.api.Assertions.assertThatThrownBy;
64 import static org.assertj.core.api.Assertions.tuple;
65 import static org.mockito.ArgumentMatchers.any;
66 import static org.mockito.ArgumentMatchers.eq;
67 import static org.mockito.Mockito.mock;
68 import static org.mockito.Mockito.never;
69 import static org.mockito.Mockito.verify;
70 import static org.mockito.Mockito.when;
71 import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
72 import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS;
73 import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
74 import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING;
75 import static org.sonar.server.almintegration.ws.github.ImportGithubProjectAction.PARAM_ORGANIZATION;
76 import static org.sonar.server.almintegration.ws.github.ImportGithubProjectAction.PARAM_REPOSITORY_KEY;
77 import static org.sonar.server.tester.UserSessionRule.standalone;
78 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE;
79 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE;
81 public class ImportGithubProjectActionIT {
83 private static final String PROJECT_KEY_NAME = "PROJECT_NAME";
86 public UserSessionRule userSession = standalone();
88 private final System2 system2 = mock(System2.class);
89 private final GithubApplicationClientImpl appClient = mock(GithubApplicationClientImpl.class);
90 private final DefaultBranchNameResolver defaultBranchNameResolver = mock(DefaultBranchNameResolver.class);
93 public DbTester db = DbTester.create(system2);
94 private final PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
95 private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), System2.INSTANCE,
96 permissionTemplateService, new FavoriteUpdater(db.getDbClient()), new TestIndexers(), new SequenceUuidFactory(),
97 defaultBranchNameResolver, true);
99 private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession);
100 private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class);
101 private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class);
102 private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class);
104 private final GitHubSettings gitHubSettings = mock(GitHubSettings.class);
105 private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider);
107 private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(db.getDbClient(), userSession,
108 projectDefaultVisibility, appClient, componentUpdater, importHelper, projectKeyGenerator, newCodeDefinitionResolver,
109 defaultBranchNameResolver, gitHubSettings));
112 public void before() {
113 when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE);
114 when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn(DEFAULT_MAIN_BRANCH_NAME);
118 public void importProject_ifProjectWithSameNameDoesNotExist_importSucceed() {
119 AlmSettingDto githubAlmSetting = setupAlm();
120 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
122 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
123 "octocat/" + PROJECT_KEY_NAME,
124 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch");
125 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
126 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
128 Projects.CreateWsResponse response = ws.newRequest()
129 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
130 .setParam(PARAM_ORGANIZATION, "octocat")
131 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
132 .executeProtobuf(Projects.CreateWsResponse.class);
134 Projects.CreateWsResponse.Project result = response.getProject();
135 assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME);
136 assertThat(result.getName()).isEqualTo(repository.getName());
138 Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
139 assertThat(projectDto).isPresent();
140 assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.get())).isPresent();
141 Optional<BranchDto> mainBranch = db.getDbClient().branchDao().selectByProject(db.getSession(), projectDto.get()).stream().filter(BranchDto::isMain).findAny();
142 assertThat(mainBranch).isPresent();
143 assertThat(mainBranch.get().getKey()).isEqualTo("default-branch");
148 public void importProject_withNCD_developer_edition() {
149 when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
151 AlmSettingDto githubAlmSetting = setupAlm();
152 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
154 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
155 "octocat/" + PROJECT_KEY_NAME,
156 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch");
157 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
158 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
160 Projects.CreateWsResponse response = ws.newRequest()
161 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
162 .setParam(PARAM_ORGANIZATION, "octocat")
163 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
164 .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS")
165 .setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30")
166 .executeProtobuf(Projects.CreateWsResponse.class);
168 Projects.CreateWsResponse.Project result = response.getProject();
170 Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
171 assertThat(projectDto).isPresent();
173 assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid()))
176 .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid)
177 .containsExactly(NUMBER_OF_DAYS, "30", null);
181 public void importProject_withNCD_community_edition() {
182 when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY));
184 AlmSettingDto githubAlmSetting = setupAlm();
185 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
187 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
188 "octocat/" + PROJECT_KEY_NAME,
189 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch");
190 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
191 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
193 Projects.CreateWsResponse response = ws.newRequest()
194 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
195 .setParam(PARAM_ORGANIZATION, "octocat")
196 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
197 .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS")
198 .setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30")
199 .executeProtobuf(Projects.CreateWsResponse.class);
201 Projects.CreateWsResponse.Project result = response.getProject();
203 Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
204 assertThat(projectDto).isPresent();
205 BranchDto branchDto = db.getDbClient().branchDao().selectMainBranchByProjectUuid(db.getSession(), projectDto.get().getUuid()).orElseThrow();
207 String projectUuid = projectDto.get().getUuid();
208 assertThat(db.getDbClient().newCodePeriodDao().selectByBranch(db.getSession(), projectUuid, branchDto.getUuid()))
211 .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid)
212 .containsExactly(NUMBER_OF_DAYS, "30", branchDto.getUuid());
216 public void importProject_reference_branch_ncd_no_default_branch() {
217 when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
218 when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn("default-branch");
220 AlmSettingDto githubAlmSetting = setupAlm();
221 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
223 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
224 "octocat/" + PROJECT_KEY_NAME,
225 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, null);
226 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
227 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
229 Projects.CreateWsResponse response = ws.newRequest()
230 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
231 .setParam(PARAM_ORGANIZATION, "octocat")
232 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
233 .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "reference_branch")
234 .executeProtobuf(Projects.CreateWsResponse.class);
236 Projects.CreateWsResponse.Project result = response.getProject();
238 Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
239 assertThat(projectDto).isPresent();
241 assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid()))
244 .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue)
245 .containsExactly(REFERENCE_BRANCH, "default-branch");
249 public void importProject_reference_branch_ncd() {
250 when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
252 AlmSettingDto githubAlmSetting = setupAlm();
253 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
255 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
256 "octocat/" + PROJECT_KEY_NAME,
257 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "mainBranch");
258 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
259 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
261 Projects.CreateWsResponse response = ws.newRequest()
262 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
263 .setParam(PARAM_ORGANIZATION, "octocat")
264 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
265 .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "reference_branch")
266 .executeProtobuf(Projects.CreateWsResponse.class);
268 Projects.CreateWsResponse.Project result = response.getProject();
270 Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
271 assertThat(projectDto).isPresent();
273 assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid()))
276 .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue)
277 .containsExactly(REFERENCE_BRANCH, "mainBranch");
281 public void importProject_ifProjectWithSameNameAlreadyExists_importSucceed() {
282 AlmSettingDto githubAlmSetting = setupAlm();
283 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
284 db.components().insertPublicProject(p -> p.setKey("Hello-World")).getMainBranchComponent();
286 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, "Hello-World", false, "Hello-World",
287 "https://github.sonarsource.com/api/v3/repos/octocat/Hello-World", "main");
288 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
289 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
291 Projects.CreateWsResponse response = ws.newRequest()
292 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
293 .setParam(PARAM_ORGANIZATION, "octocat")
294 .setParam(PARAM_REPOSITORY_KEY, "Hello-World")
295 .executeProtobuf(Projects.CreateWsResponse.class);
297 Projects.CreateWsResponse.Project result = response.getProject();
298 assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME);
299 assertThat(result.getName()).isEqualTo(repository.getName());
303 public void importProject_whenGithubProvisioningIsDisabled_shouldApplyPermissionTemplate() {
304 AlmSettingDto githubAlmSetting = setupAlm();
305 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
307 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
308 "octocat/" + PROJECT_KEY_NAME,
309 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch");
310 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
311 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
312 when(gitHubSettings.isProvisioningEnabled()).thenReturn(false);
315 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
316 .setParam(PARAM_ORGANIZATION, "octocat")
317 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
318 .executeProtobuf(Projects.CreateWsResponse.class);
320 ArgumentCaptor<EntityDto> projectDtoArgumentCaptor = ArgumentCaptor.forClass(EntityDto.class);
321 verify(permissionTemplateService).applyDefaultToNewComponent(any(DbSession.class), projectDtoArgumentCaptor.capture(), eq(userSession.getUuid()));
322 String projectKey = projectDtoArgumentCaptor.getValue().getKey();
323 assertThat(projectKey).isEqualTo(PROJECT_KEY_NAME);
328 public void importProject_whenGithubProvisioningIsEnabled_shouldNotApplyPermissionTemplate() {
329 AlmSettingDto githubAlmSetting = setupAlm();
330 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
332 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
333 "octocat/" + PROJECT_KEY_NAME,
334 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch");
335 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
336 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
337 when(gitHubSettings.isProvisioningEnabled()).thenReturn(true);
340 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
341 .setParam(PARAM_ORGANIZATION, "octocat")
342 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
343 .executeProtobuf(Projects.CreateWsResponse.class);
345 verify(permissionTemplateService, never()).applyDefaultToNewComponent(any(), any(), any());
350 public void fail_when_not_logged_in() {
351 TestRequest request = ws.newRequest()
352 .setParam(PARAM_ALM_SETTING, "asdfghjkl")
353 .setParam(PARAM_ORGANIZATION, "test")
354 .setParam(PARAM_REPOSITORY_KEY, "test/repo");
355 assertThatThrownBy(request::execute)
356 .isInstanceOf(UnauthorizedException.class);
360 public void fail_when_missing_create_project_permission() {
361 TestRequest request = ws.newRequest();
362 assertThatThrownBy(request::execute)
363 .isInstanceOf(UnauthorizedException.class);
367 public void fail_when_almSetting_does_not_exist() {
368 UserDto user = db.users().insertUser();
369 userSession.logIn(user).addPermission(GlobalPermission.PROVISION_PROJECTS);
371 TestRequest request = ws.newRequest()
372 .setParam(PARAM_ALM_SETTING, "unknown")
373 .setParam(PARAM_ORGANIZATION, "test")
374 .setParam(PARAM_REPOSITORY_KEY, "test/repo");
375 assertThatThrownBy(request::execute)
376 .isInstanceOf(NotFoundException.class)
377 .hasMessage("DevOps Platform Setting 'unknown' not found");
381 public void fail_when_personal_access_token_doesnt_exist() {
382 AlmSettingDto githubAlmSetting = setupAlm();
384 TestRequest request = ws.newRequest()
385 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
386 .setParam(PARAM_ORGANIZATION, "test")
387 .setParam(PARAM_REPOSITORY_KEY, "test/repo");
388 assertThatThrownBy(request::execute)
389 .isInstanceOf(IllegalArgumentException.class)
390 .hasMessage("No personal access token found");
394 public void definition() {
395 WebService.Action def = ws.getDef();
397 assertThat(def.since()).isEqualTo("8.4");
398 assertThat(def.isPost()).isTrue();
399 assertThat(def.params())
400 .extracting(WebService.Param::key, WebService.Param::isRequired)
401 .containsExactlyInAnyOrder(
402 tuple(PARAM_ALM_SETTING, true),
403 tuple(PARAM_ORGANIZATION, true),
404 tuple(PARAM_REPOSITORY_KEY, true),
405 tuple(PARAM_NEW_CODE_DEFINITION_TYPE, false),
406 tuple(PARAM_NEW_CODE_DEFINITION_VALUE, false));
409 private AlmSettingDto setupAlm() {
410 UserDto user = db.users().insertUser();
411 userSession.logIn(user).addPermission(GlobalPermission.PROVISION_PROJECTS);
413 return db.almSettings().insertGitHubAlmSetting(alm -> alm.setClientId("client_123").setClientSecret("client_secret_123"));