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