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();
206 String projectUuid = projectDto.get().getUuid();
207 assertThat(db.getDbClient().newCodePeriodDao().selectByBranch(db.getSession(), projectUuid, projectUuid))
210 .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid)
211 .containsExactly(NUMBER_OF_DAYS, "30", projectUuid);
215 public void importProject_reference_branch_ncd_no_default_branch() {
216 when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
217 when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn("default-branch");
219 AlmSettingDto githubAlmSetting = setupAlm();
220 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
222 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
223 "octocat/" + PROJECT_KEY_NAME,
224 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, null);
225 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
226 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
228 Projects.CreateWsResponse response = ws.newRequest()
229 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
230 .setParam(PARAM_ORGANIZATION, "octocat")
231 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
232 .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "reference_branch")
233 .executeProtobuf(Projects.CreateWsResponse.class);
235 Projects.CreateWsResponse.Project result = response.getProject();
237 Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
238 assertThat(projectDto).isPresent();
240 assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid()))
243 .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue)
244 .containsExactly(REFERENCE_BRANCH, "default-branch");
248 public void importProject_reference_branch_ncd() {
249 when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
251 AlmSettingDto githubAlmSetting = setupAlm();
252 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
254 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
255 "octocat/" + PROJECT_KEY_NAME,
256 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "mainBranch");
257 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
258 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
260 Projects.CreateWsResponse response = ws.newRequest()
261 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
262 .setParam(PARAM_ORGANIZATION, "octocat")
263 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
264 .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "reference_branch")
265 .executeProtobuf(Projects.CreateWsResponse.class);
267 Projects.CreateWsResponse.Project result = response.getProject();
269 Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
270 assertThat(projectDto).isPresent();
272 assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid()))
275 .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue)
276 .containsExactly(REFERENCE_BRANCH, "mainBranch");
280 public void importProject_ifProjectWithSameNameAlreadyExists_importSucceed() {
281 AlmSettingDto githubAlmSetting = setupAlm();
282 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
283 db.components().insertPublicProject(p -> p.setKey("Hello-World")).getMainBranchComponent();
285 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, "Hello-World", false, "Hello-World",
286 "https://github.sonarsource.com/api/v3/repos/octocat/Hello-World", "main");
287 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
288 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
290 Projects.CreateWsResponse response = ws.newRequest()
291 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
292 .setParam(PARAM_ORGANIZATION, "octocat")
293 .setParam(PARAM_REPOSITORY_KEY, "Hello-World")
294 .executeProtobuf(Projects.CreateWsResponse.class);
296 Projects.CreateWsResponse.Project result = response.getProject();
297 assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME);
298 assertThat(result.getName()).isEqualTo(repository.getName());
302 public void importProject_whenGithubProvisioningIsDisabled_shouldApplyPermissionTemplate() {
303 AlmSettingDto githubAlmSetting = setupAlm();
304 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
306 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
307 "octocat/" + PROJECT_KEY_NAME,
308 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch");
309 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
310 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
311 when(gitHubSettings.isProvisioningEnabled()).thenReturn(false);
314 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
315 .setParam(PARAM_ORGANIZATION, "octocat")
316 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
317 .executeProtobuf(Projects.CreateWsResponse.class);
319 ArgumentCaptor<EntityDto> projectDtoArgumentCaptor = ArgumentCaptor.forClass(EntityDto.class);
320 verify(permissionTemplateService).applyDefaultToNewComponent(any(DbSession.class), projectDtoArgumentCaptor.capture(), eq(userSession.getUuid()));
321 String projectKey = projectDtoArgumentCaptor.getValue().getKey();
322 assertThat(projectKey).isEqualTo(PROJECT_KEY_NAME);
327 public void importProject_whenGithubProvisioningIsEnabled_shouldNotApplyPermissionTemplate() {
328 AlmSettingDto githubAlmSetting = setupAlm();
329 db.almPats().insert(p -> p.setAlmSettingUuid(githubAlmSetting.getUuid()).setUserUuid(userSession.getUuid()));
331 GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
332 "octocat/" + PROJECT_KEY_NAME,
333 "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch");
334 when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
335 when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
336 when(gitHubSettings.isProvisioningEnabled()).thenReturn(true);
339 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
340 .setParam(PARAM_ORGANIZATION, "octocat")
341 .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
342 .executeProtobuf(Projects.CreateWsResponse.class);
344 verify(permissionTemplateService, never()).applyDefaultToNewComponent(any(), any(), any());
349 public void fail_when_not_logged_in() {
350 TestRequest request = ws.newRequest()
351 .setParam(PARAM_ALM_SETTING, "asdfghjkl")
352 .setParam(PARAM_ORGANIZATION, "test")
353 .setParam(PARAM_REPOSITORY_KEY, "test/repo");
354 assertThatThrownBy(request::execute)
355 .isInstanceOf(UnauthorizedException.class);
359 public void fail_when_missing_create_project_permission() {
360 TestRequest request = ws.newRequest();
361 assertThatThrownBy(request::execute)
362 .isInstanceOf(UnauthorizedException.class);
366 public void fail_when_almSetting_does_not_exist() {
367 UserDto user = db.users().insertUser();
368 userSession.logIn(user).addPermission(GlobalPermission.PROVISION_PROJECTS);
370 TestRequest request = ws.newRequest()
371 .setParam(PARAM_ALM_SETTING, "unknown")
372 .setParam(PARAM_ORGANIZATION, "test")
373 .setParam(PARAM_REPOSITORY_KEY, "test/repo");
374 assertThatThrownBy(request::execute)
375 .isInstanceOf(NotFoundException.class)
376 .hasMessage("DevOps Platform Setting 'unknown' not found");
380 public void fail_when_personal_access_token_doesnt_exist() {
381 AlmSettingDto githubAlmSetting = setupAlm();
383 TestRequest request = ws.newRequest()
384 .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
385 .setParam(PARAM_ORGANIZATION, "test")
386 .setParam(PARAM_REPOSITORY_KEY, "test/repo");
387 assertThatThrownBy(request::execute)
388 .isInstanceOf(IllegalArgumentException.class)
389 .hasMessage("No personal access token found");
393 public void definition() {
394 WebService.Action def = ws.getDef();
396 assertThat(def.since()).isEqualTo("8.4");
397 assertThat(def.isPost()).isTrue();
398 assertThat(def.params())
399 .extracting(WebService.Param::key, WebService.Param::isRequired)
400 .containsExactlyInAnyOrder(
401 tuple(PARAM_ALM_SETTING, true),
402 tuple(PARAM_ORGANIZATION, true),
403 tuple(PARAM_REPOSITORY_KEY, true),
404 tuple(PARAM_NEW_CODE_DEFINITION_TYPE, false),
405 tuple(PARAM_NEW_CODE_DEFINITION_VALUE, false));
408 private AlmSettingDto setupAlm() {
409 UserDto user = db.users().insertUser();
410 userSession.logIn(user).addPermission(GlobalPermission.PROVISION_PROJECTS);
412 return db.almSettings().insertGitHubAlmSetting(alm -> alm.setClientId("client_123").setClientSecret("client_secret_123"));