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