]> source.dussan.org Git - sonarqube.git/blob
ab0840a0eda5a83fedce091c8e7d623eba29fa2a
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.almintegration.ws.github;
21
22 import java.util.Optional;
23 import java.util.Set;
24 import org.junit.Before;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.mockito.ArgumentCaptor;
28 import org.sonar.alm.client.github.GithubApplicationClient;
29 import org.sonar.alm.client.github.GithubApplicationClientImpl;
30 import org.sonar.api.resources.Qualifiers;
31 import org.sonar.api.server.ws.WebService;
32 import org.sonar.api.utils.System2;
33 import org.sonar.auth.github.GitHubSettings;
34 import org.sonar.core.i18n.I18n;
35 import org.sonar.core.platform.EditionProvider;
36 import org.sonar.core.platform.PlatformEditionProvider;
37 import org.sonar.core.util.SequenceUuidFactory;
38 import org.sonar.db.DbSession;
39 import org.sonar.db.DbTester;
40 import org.sonar.db.alm.setting.AlmSettingDto;
41 import org.sonar.db.component.BranchDto;
42 import org.sonar.db.component.ResourceTypesRule;
43 import org.sonar.db.entity.EntityDto;
44 import org.sonar.db.newcodeperiod.NewCodePeriodDto;
45 import org.sonar.db.permission.GlobalPermission;
46 import org.sonar.db.project.CreationMethod;
47 import org.sonar.db.project.ProjectDto;
48 import org.sonar.db.user.UserDto;
49 import org.sonar.server.almintegration.ws.ImportHelper;
50 import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
51 import org.sonar.server.component.ComponentUpdater;
52 import org.sonar.server.es.EsTester;
53 import org.sonar.server.es.IndexersImpl;
54 import org.sonar.server.es.TestIndexers;
55 import org.sonar.server.exceptions.NotFoundException;
56 import org.sonar.server.exceptions.UnauthorizedException;
57 import org.sonar.server.favorite.FavoriteUpdater;
58 import org.sonar.server.management.ManagedProjectService;
59 import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver;
60 import org.sonar.server.permission.GroupPermissionChanger;
61 import org.sonar.server.permission.PermissionService;
62 import org.sonar.server.permission.PermissionServiceImpl;
63 import org.sonar.server.permission.PermissionTemplateService;
64 import org.sonar.server.permission.PermissionUpdater;
65 import org.sonar.server.permission.UserPermissionChange;
66 import org.sonar.server.permission.UserPermissionChanger;
67 import org.sonar.server.permission.index.FooIndexDefinition;
68 import org.sonar.server.permission.index.PermissionIndexer;
69 import org.sonar.server.project.DefaultBranchNameResolver;
70 import org.sonar.server.project.ProjectDefaultVisibility;
71 import org.sonar.server.project.Visibility;
72 import org.sonar.server.tester.UserSessionRule;
73 import org.sonar.server.ws.TestRequest;
74 import org.sonar.server.ws.WsActionTester;
75 import org.sonarqube.ws.Projects;
76
77 import static java.util.Objects.requireNonNull;
78 import static org.assertj.core.api.Assertions.assertThat;
79 import static org.assertj.core.api.Assertions.assertThatThrownBy;
80 import static org.assertj.core.api.Assertions.tuple;
81 import static org.mockito.ArgumentMatchers.any;
82 import static org.mockito.ArgumentMatchers.eq;
83 import static org.mockito.Mockito.mock;
84 import static org.mockito.Mockito.never;
85 import static org.mockito.Mockito.verify;
86 import static org.mockito.Mockito.when;
87 import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
88 import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS;
89 import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
90 import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING;
91 import static org.sonar.server.almintegration.ws.github.ImportGithubProjectAction.PARAM_ORGANIZATION;
92 import static org.sonar.server.almintegration.ws.github.ImportGithubProjectAction.PARAM_REPOSITORY_KEY;
93 import static org.sonar.server.tester.UserSessionRule.standalone;
94 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_TYPE;
95 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE;
96
97 public class ImportGithubProjectActionIT {
98
99   private static final String PROJECT_KEY_NAME = "PROJECT_NAME";
100
101   @Rule
102   public UserSessionRule userSession = standalone();
103
104   private final System2 system2 = mock(System2.class);
105   private final GithubApplicationClientImpl appClient = mock(GithubApplicationClientImpl.class);
106   private final DefaultBranchNameResolver defaultBranchNameResolver = mock(DefaultBranchNameResolver.class);
107
108   @Rule
109   public DbTester db = DbTester.create(system2);
110   private final PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
111   public EsTester es = EsTester.createCustom(new FooIndexDefinition());
112   private final PermissionUpdater<UserPermissionChange> userPermissionUpdater = new PermissionUpdater(
113     new IndexersImpl(new PermissionIndexer(db.getDbClient(), es.client())),
114     Set.of(new UserPermissionChanger(db.getDbClient(), new SequenceUuidFactory()),
115       new GroupPermissionChanger(db.getDbClient(), new SequenceUuidFactory())));
116   private final PermissionService permissionService = new PermissionServiceImpl(new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT));
117   private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), System2.INSTANCE,
118     permissionTemplateService, new FavoriteUpdater(db.getDbClient()), new TestIndexers(), new SequenceUuidFactory(),
119     defaultBranchNameResolver, userPermissionUpdater, permissionService);
120
121   private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession);
122   private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class);
123   private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class);
124   private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class);
125
126   private final GitHubSettings gitHubSettings = mock(GitHubSettings.class);
127   private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider);
128
129   private final ManagedProjectService managedProjectService = mock(ManagedProjectService.class);
130   private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(db.getDbClient(), managedProjectService, userSession,
131     projectDefaultVisibility, appClient, componentUpdater, importHelper, projectKeyGenerator, newCodeDefinitionResolver,
132     defaultBranchNameResolver, gitHubSettings));
133
134   @Before
135   public void before() {
136     when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE);
137     when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn(DEFAULT_MAIN_BRANCH_NAME);
138   }
139
140   @Test
141   public void importProject_ifProjectWithSameNameDoesNotExist_importSucceed() {
142     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
143
144     GithubApplicationClient.Repository repository = mockGithubInteractions();
145
146     Projects.CreateWsResponse response = callWebService(githubAlmSetting);
147
148     Projects.CreateWsResponse.Project result = response.getProject();
149     assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME);
150     assertThat(result.getName()).isEqualTo(repository.getName());
151
152     Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
153     assertThat(projectDto).isPresent();
154     assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.get())).isPresent();
155     Optional<BranchDto> mainBranch = db.getDbClient().branchDao().selectByProject(db.getSession(), projectDto.get()).stream().filter(BranchDto::isMain).findAny();
156     assertThat(mainBranch).isPresent();
157     assertThat(mainBranch.get().getKey()).isEqualTo("default-branch");
158
159     verify(managedProjectService).queuePermissionSyncTask(userSession.getUuid(), mainBranch.get().getUuid() , projectDto.get().getUuid());
160   }
161
162   @Test
163   public void importProject_withNCD_developer_edition() {
164     when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
165
166     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
167
168     mockGithubInteractions();
169
170     Projects.CreateWsResponse response = ws.newRequest()
171       .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
172       .setParam(PARAM_ORGANIZATION, "octocat")
173       .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
174       .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS")
175       .setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30")
176       .executeProtobuf(Projects.CreateWsResponse.class);
177
178     Projects.CreateWsResponse.Project result = response.getProject();
179
180     Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
181     assertThat(projectDto).isPresent();
182
183     assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(),  projectDto.get().getUuid()))
184       .isPresent()
185       .get()
186       .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid)
187       .containsExactly(NUMBER_OF_DAYS, "30", null);
188   }
189
190   @Test
191   public void importProject_withNCD_community_edition() {
192     when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY));
193
194     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
195
196     mockGithubInteractions();
197
198     Projects.CreateWsResponse response = ws.newRequest()
199       .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
200       .setParam(PARAM_ORGANIZATION, "octocat")
201       .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
202       .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "NUMBER_OF_DAYS")
203       .setParam(PARAM_NEW_CODE_DEFINITION_VALUE, "30")
204       .executeProtobuf(Projects.CreateWsResponse.class);
205
206     Projects.CreateWsResponse.Project result = response.getProject();
207
208     Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
209     assertThat(projectDto).isPresent();
210     BranchDto branchDto = db.getDbClient().branchDao().selectMainBranchByProjectUuid(db.getSession(), projectDto.get().getUuid()).orElseThrow();
211
212     String projectUuid = projectDto.get().getUuid();
213     assertThat(db.getDbClient().newCodePeriodDao().selectByBranch(db.getSession(), projectUuid, branchDto.getUuid()))
214       .isPresent()
215       .get()
216       .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid)
217       .containsExactly(NUMBER_OF_DAYS, "30", branchDto.getUuid());
218   }
219
220   @Test
221   public void importProject_reference_branch_ncd_no_default_branch() {
222     when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
223     when(defaultBranchNameResolver.getEffectiveMainBranchName()).thenReturn("default-branch");
224
225     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
226
227     GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
228       "octocat/" + PROJECT_KEY_NAME,
229       "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, null);
230     when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
231     when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
232
233     Projects.CreateWsResponse response = ws.newRequest()
234       .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
235       .setParam(PARAM_ORGANIZATION, "octocat")
236       .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
237       .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "reference_branch")
238       .executeProtobuf(Projects.CreateWsResponse.class);
239
240     Projects.CreateWsResponse.Project result = response.getProject();
241
242     Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
243     assertThat(projectDto).isPresent();
244
245     assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid()))
246       .isPresent()
247       .get()
248       .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue)
249       .containsExactly(REFERENCE_BRANCH, "default-branch");
250   }
251
252   @Test
253   public void importProject_reference_branch_ncd() {
254     when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
255
256     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
257
258     GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
259       "octocat/" + PROJECT_KEY_NAME,
260       "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "mainBranch");
261     when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
262     when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
263
264     Projects.CreateWsResponse response = ws.newRequest()
265       .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
266       .setParam(PARAM_ORGANIZATION, "octocat")
267       .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
268       .setParam(PARAM_NEW_CODE_DEFINITION_TYPE, "reference_branch")
269       .executeProtobuf(Projects.CreateWsResponse.class);
270
271     Projects.CreateWsResponse.Project result = response.getProject();
272
273     Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey());
274     assertThat(projectDto).isPresent();
275
276     assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid()))
277       .isPresent()
278       .get()
279       .extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue)
280       .containsExactly(REFERENCE_BRANCH, "mainBranch");
281   }
282
283   @Test
284   public void importProject_ifProjectWithSameNameAlreadyExists_importSucceed() {
285     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
286     db.components().insertPublicProject(p -> p.setKey("Hello-World")).getMainBranchComponent();
287
288     GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, "Hello-World", false, "Hello-World",
289       "https://github.sonarsource.com/api/v3/repos/octocat/Hello-World", "main");
290     when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
291     when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
292
293     Projects.CreateWsResponse response = ws.newRequest()
294       .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
295       .setParam(PARAM_ORGANIZATION, "octocat")
296       .setParam(PARAM_REPOSITORY_KEY, "Hello-World")
297       .executeProtobuf(Projects.CreateWsResponse.class);
298
299     Projects.CreateWsResponse.Project result = response.getProject();
300     assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME);
301     assertThat(result.getName()).isEqualTo(repository.getName());
302   }
303
304   @Test
305   public void importProject_whenGithubProvisioningIsDisabled_shouldApplyPermissionTemplate() {
306     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
307
308     mockGithubInteractions();
309     when(gitHubSettings.isProvisioningEnabled()).thenReturn(false);
310
311     ws.newRequest()
312       .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
313       .setParam(PARAM_ORGANIZATION, "octocat")
314       .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
315       .executeProtobuf(Projects.CreateWsResponse.class);
316
317     ArgumentCaptor<EntityDto> projectDtoArgumentCaptor = ArgumentCaptor.forClass(EntityDto.class);
318     verify(permissionTemplateService).applyDefaultToNewComponent(any(DbSession.class), projectDtoArgumentCaptor.capture(), eq(userSession.getUuid()));
319     String projectKey = projectDtoArgumentCaptor.getValue().getKey();
320     assertThat(projectKey).isEqualTo(PROJECT_KEY_NAME);
321
322   }
323
324   @Test
325   public void importProject_whenGithubProvisioningIsEnabled_shouldNotApplyPermissionTemplate() {
326     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
327
328     mockGithubInteractions();
329     when(gitHubSettings.isProvisioningEnabled()).thenReturn(true);
330
331     ws.newRequest()
332       .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
333       .setParam(PARAM_ORGANIZATION, "octocat")
334       .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
335       .executeProtobuf(Projects.CreateWsResponse.class);
336
337     verify(permissionTemplateService, never()).applyDefaultToNewComponent(any(), any(), any());
338
339   }
340
341   @Test
342   public void importProject_shouldSetCreationMethodToApi_ifNonBrowserRequest() {
343     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
344     mockGithubInteractions();
345
346     Projects.CreateWsResponse response = callWebService(githubAlmSetting);
347
348     Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), response.getProject().getKey());
349     assertThat(projectDto.orElseThrow().getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_API);
350   }
351
352   @Test
353   public void importProject_shouldSetCreationMethodToBrowser_ifBrowserRequest() {
354     AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings();
355     userSession.flagSessionAsGui();
356     mockGithubInteractions();
357
358     Projects.CreateWsResponse response = callWebService(githubAlmSetting);
359
360     Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), response.getProject().getKey());
361     assertThat(projectDto.orElseThrow().getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_BROWSER);
362   }
363
364   private Projects.CreateWsResponse callWebService(AlmSettingDto githubAlmSetting) {
365     return ws.newRequest()
366       .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
367       .setParam(PARAM_ORGANIZATION, "octocat")
368       .setParam(PARAM_REPOSITORY_KEY, "octocat/" + PROJECT_KEY_NAME)
369       .executeProtobuf(Projects.CreateWsResponse.class);
370   }
371
372   private GithubApplicationClient.Repository mockGithubInteractions() {
373     GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false,
374       "octocat/" + PROJECT_KEY_NAME,
375       "https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch");
376     when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository));
377     when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME);
378     return repository;
379   }
380
381   @Test
382   public void fail_when_not_logged_in() {
383     TestRequest request = ws.newRequest()
384       .setParam(PARAM_ALM_SETTING, "asdfghjkl")
385       .setParam(PARAM_ORGANIZATION, "test")
386       .setParam(PARAM_REPOSITORY_KEY, "test/repo");
387     assertThatThrownBy(request::execute)
388       .isInstanceOf(UnauthorizedException.class);
389   }
390
391   @Test
392   public void fail_when_missing_create_project_permission() {
393     TestRequest request = ws.newRequest();
394     assertThatThrownBy(request::execute)
395       .isInstanceOf(UnauthorizedException.class);
396   }
397
398   @Test
399   public void fail_when_almSetting_does_not_exist() {
400     UserDto user = db.users().insertUser();
401     userSession.logIn(user).addPermission(GlobalPermission.PROVISION_PROJECTS);
402
403     TestRequest request = ws.newRequest()
404       .setParam(PARAM_ALM_SETTING, "unknown")
405       .setParam(PARAM_ORGANIZATION, "test")
406       .setParam(PARAM_REPOSITORY_KEY, "test/repo");
407     assertThatThrownBy(request::execute)
408       .isInstanceOf(NotFoundException.class)
409       .hasMessage("DevOps Platform Setting 'unknown' not found");
410   }
411
412   @Test
413   public void fail_when_personal_access_token_doesnt_exist() {
414     AlmSettingDto githubAlmSetting = setupUserAndAlmSettings();
415
416     TestRequest request = ws.newRequest()
417       .setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey())
418       .setParam(PARAM_ORGANIZATION, "test")
419       .setParam(PARAM_REPOSITORY_KEY, "test/repo");
420     assertThatThrownBy(request::execute)
421       .isInstanceOf(IllegalArgumentException.class)
422       .hasMessage("No personal access token found");
423   }
424
425   @Test
426   public void definition() {
427     WebService.Action def = ws.getDef();
428
429     assertThat(def.since()).isEqualTo("8.4");
430     assertThat(def.isPost()).isTrue();
431     assertThat(def.params())
432       .extracting(WebService.Param::key, WebService.Param::isRequired)
433       .containsExactlyInAnyOrder(
434         tuple(PARAM_ALM_SETTING, true),
435         tuple(PARAM_ORGANIZATION, true),
436         tuple(PARAM_REPOSITORY_KEY, true),
437         tuple(PARAM_NEW_CODE_DEFINITION_TYPE, false),
438         tuple(PARAM_NEW_CODE_DEFINITION_VALUE, false));
439   }
440
441   private AlmSettingDto setupUserWithPatAndAlmSettings() {
442     AlmSettingDto almSettings = setupUserAndAlmSettings();
443     db.almPats().insert(p -> p.setAlmSettingUuid(almSettings.getUuid()).setUserUuid(requireNonNull(userSession.getUuid())));
444     return almSettings;
445   }
446
447   private AlmSettingDto setupUserAndAlmSettings() {
448     UserDto user = db.users().insertUser();
449     userSession.logIn(user).addPermission(GlobalPermission.PROVISION_PROJECTS);
450     return db.almSettings().insertGitHubAlmSetting(alm -> alm.setClientId("client_123").setClientSecret("client_secret_123"));
451   }
452 }