You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GithubProjectCreatorFactoryTest.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 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.common.almsettings.github;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Optional;
  24. import org.junit.Test;
  25. import org.junit.runner.RunWith;
  26. import org.mockito.Answers;
  27. import org.mockito.InjectMocks;
  28. import org.mockito.Mock;
  29. import org.mockito.junit.MockitoJUnitRunner;
  30. import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
  31. import org.sonar.alm.client.github.GithubPermissionConverter;
  32. import org.sonar.auth.github.AppInstallationToken;
  33. import org.sonar.auth.github.GitHubSettings;
  34. import org.sonar.auth.github.client.GithubApplicationClient;
  35. import org.sonar.auth.github.security.AccessToken;
  36. import org.sonar.auth.github.security.UserAccessToken;
  37. import org.sonar.db.DbClient;
  38. import org.sonar.db.DbSession;
  39. import org.sonar.db.alm.pat.AlmPatDto;
  40. import org.sonar.db.alm.setting.ALM;
  41. import org.sonar.db.alm.setting.AlmSettingDto;
  42. import org.sonar.server.common.almintegration.ProjectKeyGenerator;
  43. import org.sonar.server.common.almsettings.DevOpsProjectCreator;
  44. import org.sonar.server.common.almsettings.DevOpsProjectDescriptor;
  45. import org.sonar.server.common.permission.PermissionUpdater;
  46. import org.sonar.server.common.permission.UserPermissionChange;
  47. import org.sonar.server.common.project.ProjectCreator;
  48. import org.sonar.server.exceptions.BadConfigurationException;
  49. import org.sonar.server.management.ManagedProjectService;
  50. import org.sonar.server.permission.PermissionService;
  51. import org.sonar.server.project.ProjectDefaultVisibility;
  52. import org.sonar.server.user.UserSession;
  53. import static java.lang.String.format;
  54. import static org.assertj.core.api.Assertions.assertThat;
  55. import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
  56. import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
  57. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  58. import static org.mockito.ArgumentMatchers.any;
  59. import static org.mockito.ArgumentMatchers.eq;
  60. import static org.mockito.Mockito.mock;
  61. import static org.mockito.Mockito.when;
  62. import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_PROJECT_IDENTIFIER;
  63. import static org.sonar.core.ce.CeTaskCharacteristics.DEVOPS_PLATFORM_URL;
  64. @RunWith(MockitoJUnitRunner.class)
  65. public class GithubProjectCreatorFactoryTest {
  66. private static final String PROJECT_NAME = "projectName";
  67. private static final String ORGANIZATION_NAME = "orgname";
  68. private static final String GITHUB_REPO_FULL_NAME = ORGANIZATION_NAME + "/" + PROJECT_NAME;
  69. private static final String GITHUB_API_URL = "https://api.toto.com";
  70. private static final DevOpsProjectDescriptor GITHUB_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME, null);
  71. private static final Map<String, String> VALID_GITHUB_PROJECT_COORDINATES = Map.of(
  72. DEVOPS_PLATFORM_URL, GITHUB_PROJECT_DESCRIPTOR.url(),
  73. DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.repositoryIdentifier());
  74. private static final long APP_INSTALLATION_ID = 534534534543L;
  75. private static final String USER_ACCESS_TOKEN = "userPat";
  76. @Mock
  77. private DbSession dbSession;
  78. @Mock
  79. private GithubGlobalSettingsValidator githubGlobalSettingsValidator;
  80. @Mock
  81. private GithubApplicationClient githubApplicationClient;
  82. @Mock(answer = Answers.RETURNS_DEEP_STUBS)
  83. private DbClient dbClient;
  84. @Mock
  85. private UserSession userSession;
  86. @Mock(answer = Answers.RETURNS_DEEP_STUBS)
  87. private ProjectDefaultVisibility projectDefaultVisibility;
  88. @Mock
  89. private ProjectKeyGenerator projectKeyGenerator;
  90. @Mock
  91. private GitHubSettings gitHubSettings;
  92. @Mock
  93. private GithubPermissionConverter githubPermissionConverter;
  94. @Mock
  95. private AppInstallationToken appInstallationToken;
  96. @Mock
  97. private AppInstallationToken authAppInstallationToken;
  98. @Mock
  99. private PermissionService permissionService;
  100. @Mock
  101. private PermissionUpdater<UserPermissionChange> permissionUpdater;
  102. @Mock
  103. private ManagedProjectService managedProjectService;
  104. @Mock
  105. private ProjectCreator projectCreator;
  106. @InjectMocks
  107. private GithubProjectCreatorFactory githubProjectCreatorFactory;
  108. @Test
  109. public void getDevOpsProjectCreator_whenNoCharacteristics_shouldReturnEmpty() {
  110. Optional<DevOpsProjectCreator> devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, Map.of());
  111. assertThat(devOpsProjectCreator).isEmpty();
  112. }
  113. @Test
  114. public void getDevOpsProjectCreator_whenValidCharacteristicsButNoAlmSettingDao_shouldReturnEmpty() {
  115. Optional<DevOpsProjectCreator> devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES);
  116. assertThat(devOpsProjectCreator).isEmpty();
  117. }
  118. @Test
  119. public void getDevOpsProjectCreator_whenValidCharacteristicsButInvalidAlmSettingDto_shouldThrow() {
  120. AlmSettingDto almSettingDto = mockAlmSettingDto(true);
  121. IllegalArgumentException error = new IllegalArgumentException("error happened");
  122. when(githubGlobalSettingsValidator.validate(almSettingDto)).thenThrow(error);
  123. assertThatIllegalArgumentException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES))
  124. .isSameAs(error);
  125. }
  126. @Test
  127. public void getDevOpsProjectCreator_whenAppHasNoAccessToRepo_shouldReturnEmpty() {
  128. mockAlmSettingDto(true);
  129. when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.empty());
  130. Optional<DevOpsProjectCreator> devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES);
  131. assertThat(devOpsProjectCreator).isEmpty();
  132. }
  133. @Test
  134. public void getDevOpsProjectCreator_whenNotPossibleToGenerateToken_shouldThrow() {
  135. AlmSettingDto almSettingDto = mockAlmSettingDto(true);
  136. when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.of(APP_INSTALLATION_ID));
  137. when(githubGlobalSettingsValidator.validate(almSettingDto)).thenReturn(mock());
  138. when(githubApplicationClient.createAppInstallationToken(any(), eq(APP_INSTALLATION_ID))).thenReturn(Optional.empty());
  139. assertThatIllegalStateException().isThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES))
  140. .withMessage("Error while generating token for GitHub Api Url null (installation id: 534534534543)");
  141. }
  142. @Test
  143. public void getDevOpsProjectCreator_whenOneValidAlmSetting_shouldInstantiateDevOpsProjectCreator() {
  144. AlmSettingDto almSettingDto = mockAlmSettingDto(true);
  145. mockSuccessfulGithubInteraction();
  146. DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow();
  147. GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, false, appInstallationToken);
  148. assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator);
  149. }
  150. @Test
  151. public void getDevOpsProjectCreator_whenOneValidAlmSettingAndPublicByDefaultAndAutoProvisioningEnabled_shouldInstantiateDevOpsProjectCreatorAndDefineAnAuthAppToken() {
  152. AlmSettingDto almSettingDto = mockAlmSettingDto(true);
  153. mockSuccessfulGithubInteraction();
  154. when(projectDefaultVisibility.get(any()).isPrivate()).thenReturn(true);
  155. mockValidGitHubSettings();
  156. long authAppInstallationId = 32;
  157. when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.of(authAppInstallationId));
  158. when(githubApplicationClient.createAppInstallationToken(any(), eq(authAppInstallationId))).thenReturn(Optional.of(authAppInstallationToken));
  159. DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow();
  160. GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(almSettingDto, true, appInstallationToken);
  161. assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator);
  162. }
  163. @Test
  164. public void getDevOpsProjectCreator_whenOneMatchingAndOneNotMatchingAlmSetting_shouldInstantiateDevOpsProjectCreator() {
  165. AlmSettingDto matchingAlmSettingDto = mockAlmSettingDto(true);
  166. AlmSettingDto notMatchingAlmSettingDto = mockAlmSettingDto(false);
  167. when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(notMatchingAlmSettingDto, matchingAlmSettingDto));
  168. mockSuccessfulGithubInteraction();
  169. DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(dbSession, VALID_GITHUB_PROJECT_COORDINATES).orElseThrow();
  170. GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(matchingAlmSettingDto, false, appInstallationToken);
  171. assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator);
  172. }
  173. @Test
  174. public void getDevOpsProjectCreatorFromImport_shouldInstantiateDevOpsProjectCreator() {
  175. AlmSettingDto mockAlmSettingDto = mockAlmSettingDto(true);
  176. mockAlmPatDto(mockAlmSettingDto);
  177. mockSuccessfulGithubInteraction();
  178. DevOpsProjectCreator devOpsProjectCreator = githubProjectCreatorFactory.getDevOpsProjectCreator(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR).orElseThrow();
  179. GithubProjectCreator expectedGithubProjectCreator = getExpectedGithubProjectCreator(mockAlmSettingDto, false, new UserAccessToken(USER_ACCESS_TOKEN));
  180. assertThat(devOpsProjectCreator).usingRecursiveComparison().isEqualTo(expectedGithubProjectCreator);
  181. }
  182. @Test
  183. public void getDevOpsProjectCreatorFromImport_whenGitHubConfigDoesNotAllowAccessToRepo_shouldThrow() {
  184. AlmSettingDto mockAlmSettingDto = mockAlmSettingDto(false);
  185. mockAlmPatDto(mockAlmSettingDto);
  186. mockValidGitHubSettings();
  187. when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.empty());
  188. assertThatThrownBy(() -> githubProjectCreatorFactory.getDevOpsProjectCreator(mockAlmSettingDto, GITHUB_PROJECT_DESCRIPTOR))
  189. .isInstanceOf(BadConfigurationException.class)
  190. .hasMessage(format("GitHub auto-provisioning is activated. However the repo %s is not in the scope of the authentication application. "
  191. + "The permissions can't be checked, and the project can not be created.",
  192. GITHUB_REPO_FULL_NAME));
  193. }
  194. private void mockValidGitHubSettings() {
  195. when(gitHubSettings.appId()).thenReturn("4324");
  196. when(gitHubSettings.privateKey()).thenReturn("privateKey");
  197. when(gitHubSettings.apiURL()).thenReturn(GITHUB_API_URL);
  198. when(gitHubSettings.isProvisioningEnabled()).thenReturn(true);
  199. }
  200. private void mockSuccessfulGithubInteraction() {
  201. when(githubApplicationClient.getInstallationId(any(), eq(GITHUB_REPO_FULL_NAME))).thenReturn(Optional.of(APP_INSTALLATION_ID));
  202. when(githubApplicationClient.createAppInstallationToken(any(), eq(APP_INSTALLATION_ID))).thenReturn(Optional.of(appInstallationToken));
  203. }
  204. private GithubProjectCreator getExpectedGithubProjectCreator(AlmSettingDto almSettingDto, boolean isInstanceManaged, AccessToken accessToken) {
  205. DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, almSettingDto.getUrl(), GITHUB_REPO_FULL_NAME, null);
  206. AppInstallationToken authAppInstallToken = isInstanceManaged ? authAppInstallationToken : null;
  207. GithubProjectCreationParameters githubProjectCreationParameters = new GithubProjectCreationParameters(devOpsProjectDescriptor, almSettingDto, userSession, accessToken,
  208. authAppInstallToken);
  209. return new GithubProjectCreator(dbClient, githubApplicationClient, githubPermissionConverter, projectKeyGenerator, permissionUpdater, permissionService,
  210. managedProjectService, projectCreator, githubProjectCreationParameters, gitHubSettings);
  211. }
  212. private AlmSettingDto mockAlmSettingDto(boolean repoAccess) {
  213. AlmSettingDto almSettingDto = mock();
  214. when(almSettingDto.getUrl()).thenReturn(repoAccess ? GITHUB_PROJECT_DESCRIPTOR.url() : "anotherUrl");
  215. when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB);
  216. when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(almSettingDto));
  217. return almSettingDto;
  218. }
  219. private void mockAlmPatDto(AlmSettingDto almSettingDto) {
  220. when(userSession.getUuid()).thenReturn("userUuid");
  221. when(dbClient.almPatDao().selectByUserAndAlmSetting(any(), eq("userUuid"), eq(almSettingDto)))
  222. .thenReturn(Optional.of(new AlmPatDto().setPersonalAccessToken(USER_ACCESS_TOKEN)));
  223. }
  224. }