3 * Copyright (C) 2009-2021 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.azure;
22 import com.google.common.collect.ImmutableList;
23 import org.jetbrains.annotations.NotNull;
24 import org.junit.Before;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.sonar.alm.client.azure.AzureDevOpsHttpClient;
28 import org.sonar.alm.client.azure.GsonAzureProject;
29 import org.sonar.alm.client.azure.GsonAzureRepo;
30 import org.sonar.alm.client.azure.GsonAzureRepoList;
31 import org.sonar.api.server.ws.WebService;
32 import org.sonar.db.DbTester;
33 import org.sonar.db.alm.pat.AlmPatDto;
34 import org.sonar.db.alm.setting.AlmSettingDto;
35 import org.sonar.db.project.ProjectDto;
36 import org.sonar.db.user.UserDto;
37 import org.sonar.server.exceptions.ForbiddenException;
38 import org.sonar.server.exceptions.NotFoundException;
39 import org.sonar.server.exceptions.UnauthorizedException;
40 import org.sonar.server.tester.UserSessionRule;
41 import org.sonar.server.ws.TestRequest;
42 import org.sonar.server.ws.WsActionTester;
44 import static java.util.Collections.emptyList;
45 import static org.assertj.core.api.Assertions.assertThat;
46 import static org.assertj.core.api.Assertions.assertThatThrownBy;
47 import static org.assertj.core.api.Assertions.tuple;
48 import static org.mockito.ArgumentMatchers.any;
49 import static org.mockito.Mockito.mock;
50 import static org.mockito.Mockito.when;
51 import static org.sonar.db.alm.integration.pat.AlmPatsTesting.newAlmPatDto;
52 import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
53 import static org.sonarqube.ws.AlmIntegrations.AzureRepo;
54 import static org.sonarqube.ws.AlmIntegrations.SearchAzureReposWsResponse;
56 public class SearchAzureReposActionTest {
59 public UserSessionRule userSession = UserSessionRule.standalone();
61 public DbTester db = DbTester.create();
63 private AzureDevOpsHttpClient azureDevOpsHttpClient = mock(AzureDevOpsHttpClient.class);
64 private WsActionTester ws = new WsActionTester(new SearchAzureReposAction(db.getDbClient(), userSession, azureDevOpsHttpClient));
67 public void before() {
68 mockClient(new GsonAzureRepoList(ImmutableList.of(getGsonAzureRepo("project-1", "repoName-1"),
69 getGsonAzureRepo("project-2", "repoName-2"))));
73 public void define() {
74 WebService.Action def = ws.getDef();
76 assertThat(def.since()).isEqualTo("8.6");
77 assertThat(def.isPost()).isFalse();
78 assertThat(def.params())
79 .extracting(WebService.Param::key, WebService.Param::isRequired)
80 .containsExactlyInAnyOrder(
81 tuple("almSetting", true),
82 tuple("projectName", false),
83 tuple("searchQuery", false));
87 public void search_repos() {
88 AlmSettingDto almSetting = insertAlmSetting();
90 SearchAzureReposWsResponse response = ws.newRequest()
91 .setParam("almSetting", almSetting.getKey())
92 .executeProtobuf(SearchAzureReposWsResponse.class);
94 assertThat(response.getRepositoriesList())
95 .extracting(AzureRepo::getName, AzureRepo::getProjectName)
96 .containsExactlyInAnyOrder(
97 tuple("repoName-1", "project-1"), tuple("repoName-2", "project-2"));
101 public void search_repos_alphabetically_sorted() {
102 mockClient(new GsonAzureRepoList(ImmutableList.of(getGsonAzureRepo("project-1", "Z-repo"),
103 getGsonAzureRepo("project-1", "A-repo-1"), getGsonAzureRepo("project-1", "a-repo"),
104 getGsonAzureRepo("project-1", "b-repo"))));
106 AlmSettingDto almSetting = insertAlmSetting();
108 SearchAzureReposWsResponse response = ws.newRequest()
109 .setParam("almSetting", almSetting.getKey())
110 .executeProtobuf(SearchAzureReposWsResponse.class);
112 assertThat(response.getRepositoriesList())
113 .extracting(AzureRepo::getName, AzureRepo::getProjectName)
115 tuple("a-repo", "project-1"), tuple("A-repo-1", "project-1"),
116 tuple("b-repo", "project-1"), tuple("Z-repo", "project-1"));
120 public void search_repos_with_project_already_set_up() {
121 AlmSettingDto almSetting = insertAlmSetting();
123 ProjectDto projectDto2 = insertProject(almSetting, "repoName-2", "project-2");
125 SearchAzureReposWsResponse response = ws.newRequest()
126 .setParam("almSetting", almSetting.getKey())
127 .executeProtobuf(SearchAzureReposWsResponse.class);
129 assertThat(response.getRepositoriesCount()).isEqualTo(2);
131 assertThat(response.getRepositoriesList())
132 .extracting(AzureRepo::getName, AzureRepo::getProjectName,
133 AzureRepo::getSqProjectKey, AzureRepo::getSqProjectName)
134 .containsExactlyInAnyOrder(
135 tuple("repoName-1", "project-1", "", ""),
136 tuple("repoName-2", "project-2", projectDto2.getKey(), projectDto2.getName()));
140 public void search_repos_with_project_already_set_u_and_collision_is_handled() {
141 AlmSettingDto almSetting = insertAlmSetting();
143 ProjectDto projectDto2 = insertProject(almSetting, "repoName-2", "project-2");
144 insertProject(almSetting, "repoName-2", "project-2");
146 SearchAzureReposWsResponse response = ws.newRequest()
147 .setParam("almSetting", almSetting.getKey())
148 .executeProtobuf(SearchAzureReposWsResponse.class);
150 assertThat(response.getRepositoriesCount()).isEqualTo(2);
152 assertThat(response.getRepositoriesList())
153 .extracting(AzureRepo::getName, AzureRepo::getProjectName,
154 AzureRepo::getSqProjectKey, AzureRepo::getSqProjectName)
155 .containsExactlyInAnyOrder(
156 tuple("repoName-1", "project-1", "", ""),
157 tuple("repoName-2", "project-2", projectDto2.getKey(), projectDto2.getName()));
161 public void search_repos_with_projects_already_set_up_and_no_collision() {
162 mockClient(new GsonAzureRepoList(ImmutableList.of(getGsonAzureRepo("project-1", "repoName-1"),
163 getGsonAzureRepo("project", "1-repoName-1"))));
164 AlmSettingDto almSetting = insertAlmSetting();
166 ProjectDto projectDto1 = insertProject(almSetting, "repoName-1", "project-1");
167 ProjectDto projectDto2 = insertProject(almSetting, "1-repoName-1", "project");
169 SearchAzureReposWsResponse response = ws.newRequest()
170 .setParam("almSetting", almSetting.getKey())
171 .executeProtobuf(SearchAzureReposWsResponse.class);
173 assertThat(response.getRepositoriesCount()).isEqualTo(2);
175 assertThat(response.getRepositoriesList())
176 .extracting(AzureRepo::getName, AzureRepo::getProjectName,
177 AzureRepo::getSqProjectKey, AzureRepo::getSqProjectName)
178 .containsExactlyInAnyOrder(
179 tuple("repoName-1", "project-1", projectDto1.getKey(), projectDto1.getName()),
180 tuple("1-repoName-1", "project", projectDto2.getKey(), projectDto2.getName()));
184 public void search_repos_with_same_name_and_different_project() {
185 mockClient(new GsonAzureRepoList(ImmutableList.of(getGsonAzureRepo("project-1", "repoName-1"),
186 getGsonAzureRepo("project-2", "repoName-1"))));
187 AlmSettingDto almSetting = insertAlmSetting();
189 ProjectDto projectDto1 = insertProject(almSetting, "repoName-1", "project-1");
190 ProjectDto projectDto2 = insertProject(almSetting, "repoName-1", "project-2");
192 SearchAzureReposWsResponse response = ws.newRequest()
193 .setParam("almSetting", almSetting.getKey())
194 .executeProtobuf(SearchAzureReposWsResponse.class);
196 assertThat(response.getRepositoriesCount()).isEqualTo(2);
198 assertThat(response.getRepositoriesList())
199 .extracting(AzureRepo::getName, AzureRepo::getProjectName,
200 AzureRepo::getSqProjectKey, AzureRepo::getSqProjectName)
201 .containsExactlyInAnyOrder(
202 tuple("repoName-1", "project-1", projectDto1.getKey(), projectDto1.getName()),
203 tuple("repoName-1", "project-2", projectDto2.getKey(), projectDto2.getName()));
207 public void search_repos_with_project_name() {
208 AlmSettingDto almSetting = insertAlmSetting();
210 SearchAzureReposWsResponse response = ws.newRequest()
211 .setParam("almSetting", almSetting.getKey())
212 .setParam("projectName", "project-1")
213 .executeProtobuf(SearchAzureReposWsResponse.class);
215 assertThat(response.getRepositoriesList())
216 .extracting(AzureRepo::getName, AzureRepo::getProjectName)
217 .containsExactlyInAnyOrder(
218 tuple("repoName-1", "project-1"), tuple("repoName-2", "project-2"));
222 public void search_repos_with_project_name_and_empty_criteria() {
223 AlmSettingDto almSetting = insertAlmSetting();
225 SearchAzureReposWsResponse response = ws.newRequest()
226 .setParam("almSetting", almSetting.getKey())
227 .setParam("projectName", "project-1")
228 .setParam("searchQuery", "")
229 .executeProtobuf(SearchAzureReposWsResponse.class);
231 assertThat(response.getRepositoriesList())
232 .extracting(AzureRepo::getName, AzureRepo::getProjectName)
233 .containsExactlyInAnyOrder(
234 tuple("repoName-1", "project-1"), tuple("repoName-2", "project-2"));
238 public void search_and_filter_repos_with_repo_name() {
239 AlmSettingDto almSetting = insertAlmSetting();
241 SearchAzureReposWsResponse response = ws.newRequest()
242 .setParam("almSetting", almSetting.getKey())
243 .setParam("searchQuery", "repoName-2")
244 .executeProtobuf(SearchAzureReposWsResponse.class);
246 assertThat(response.getRepositoriesList())
247 .extracting(AzureRepo::getName, AzureRepo::getProjectName)
248 .containsExactlyInAnyOrder(tuple("repoName-2", "project-2"));
252 public void search_and_filter_repos_with_matching_repo_and_project_name() {
253 mockClient(new GsonAzureRepoList(ImmutableList.of(getGsonAzureRepo("big-project", "repo-1"),
254 getGsonAzureRepo("big-project", "repo-2"),
255 getGsonAzureRepo("big-project", "big-repo"),
256 getGsonAzureRepo("project", "big-repo"),
257 getGsonAzureRepo("project", "small-repo"))));
258 AlmSettingDto almSetting = insertAlmSetting();
260 SearchAzureReposWsResponse response = ws.newRequest()
261 .setParam("almSetting", almSetting.getKey())
262 .setParam("searchQuery", "big")
263 .executeProtobuf(SearchAzureReposWsResponse.class);
265 assertThat(response.getRepositoriesList())
266 .extracting(AzureRepo::getName, AzureRepo::getProjectName)
267 .containsExactlyInAnyOrder(tuple("repo-1", "big-project"), tuple("repo-2", "big-project"),
268 tuple("big-repo", "big-project"), tuple("big-repo", "project"));
272 public void return_empty_list_when_there_are_no_azure_repos() {
273 when(azureDevOpsHttpClient.getRepos(any(), any(), any())).thenReturn(new GsonAzureRepoList(emptyList()));
275 AlmSettingDto almSetting = insertAlmSetting();
277 SearchAzureReposWsResponse response = ws.newRequest()
278 .setParam("almSetting", almSetting.getKey())
279 .executeProtobuf(SearchAzureReposWsResponse.class);
281 assertThat(response.getRepositoriesList()).isEmpty();
285 public void check_pat_is_missing() {
287 AlmSettingDto almSetting = db.almSettings().insertAzureAlmSetting();
289 TestRequest request = ws.newRequest()
290 .setParam("almSetting", almSetting.getKey());
292 assertThatThrownBy(request::execute)
293 .isInstanceOf(IllegalArgumentException.class)
294 .hasMessage("No personal access token found");
298 public void fail_check_pat_alm_setting_not_found() {
299 UserDto user = insertUser();
300 AlmPatDto almPatDto = newAlmPatDto();
301 db.getDbClient().almPatDao().insert(db.getSession(), almPatDto, user.getLogin(), null);
303 TestRequest request = ws.newRequest()
304 .setParam("almSetting", "testKey");
306 assertThatThrownBy(request::execute)
307 .isInstanceOf(NotFoundException.class)
308 .hasMessage("ALM Setting 'testKey' not found");
312 public void fail_when_not_logged_in() {
313 TestRequest request = ws.newRequest()
314 .setParam("almSetting", "anyvalue");
316 assertThatThrownBy(request::execute)
317 .isInstanceOf(UnauthorizedException.class);
321 public void fail_when_no_creation_project_permission() {
322 UserDto user = db.users().insertUser();
323 userSession.logIn(user);
325 TestRequest request = ws.newRequest()
326 .setParam("almSetting", "anyvalue");
328 assertThatThrownBy(request::execute)
329 .isInstanceOf(ForbiddenException.class)
330 .hasMessage("Insufficient privileges");
333 private ProjectDto insertProject(AlmSettingDto almSetting, String repoName, String projectName) {
334 ProjectDto projectDto1 = db.components().insertPrivateProjectDto();
335 db.almSettings().insertAzureProjectAlmSetting(almSetting, projectDto1, projectAlmSettingDto -> projectAlmSettingDto.setAlmRepo(repoName),
336 projectAlmSettingDto -> projectAlmSettingDto.setAlmSlug(projectName));
340 private void mockClient(GsonAzureRepoList repoList) {
341 when(azureDevOpsHttpClient.getRepos(any(), any(), any())).thenReturn(repoList);
344 private AlmSettingDto insertAlmSetting() {
345 UserDto user = insertUser();
346 AlmSettingDto almSetting = db.almSettings().insertAzureAlmSetting();
347 db.almPats().insert(dto -> {
348 dto.setAlmSettingUuid(almSetting.getUuid());
349 dto.setUserUuid(user.getUuid());
350 dto.setPersonalAccessToken(almSetting.getPersonalAccessToken());
356 private UserDto insertUser() {
357 UserDto user = db.users().insertUser();
358 userSession.logIn(user).addPermission(PROVISION_PROJECTS);
362 private GsonAzureRepo getGsonAzureRepo(String projectName, String repoName) {
363 GsonAzureProject project = new GsonAzureProject(projectName, "the best project ever");
364 GsonAzureRepo gsonAzureRepo = new GsonAzureRepo("repo-id", repoName, "url", project, "repo-default-branch");
365 return gsonAzureRepo;