]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20699 Infer GitHub DevOps Platform from scanner information
authorAntoine Vigneau <antoine.vigneau@sonarsource.com>
Tue, 10 Oct 2023 09:10:59 +0000 (11:10 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 20 Oct 2023 20:02:39 +0000 (20:02 +0000)
server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java
server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java
server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformService.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsPlatformService.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectDescriptor.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformService.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformServiceTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformServiceTest.java [new file with mode: 0644]
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

index 11848ba0f4a81ca5086df20e36309696e0b3526e..7ecfb54acecaa25fe9f331b2e9e54470712ba3a1 100644 (file)
@@ -46,6 +46,12 @@ public interface GithubApplicationClient {
 
   GithubBinding.GsonApp getApp(GithubAppConfiguration githubAppConfiguration);
 
+  /**
+   * Retrieve the installation id for the given accountName.
+   * @throws IllegalArgumentException if one of the arguments is invalid (for example, wrong private key)
+   */
+  Optional<Long> getInstallationId(GithubAppConfiguration githubAppConfiguration, String repositorySlug);
+
   /**
    * Lists all the organizations accessible to the access token provided.
    */
index ceb5a2f93abc55589d41477d03b1f05a2e01d907..6b242deba7b217f579da0985308f05fdd09752c8 100644 (file)
@@ -145,6 +145,14 @@ public class GithubApplicationClientImpl implements GithubApplicationClient {
     }
   }
 
+  @Override
+  public Optional<Long> getInstallationId(GithubAppConfiguration githubAppConfiguration, String repositorySlug) {
+    AppToken appToken = appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey());
+    String endpoint = String.format("/repos/%s/installation", repositorySlug);
+    return get(githubAppConfiguration.getApiEndpoint(), appToken, endpoint, GithubBinding.GsonInstallation.class)
+      .map(GithubBinding.GsonInstallation::getId);
+  }
+
   @Override
   public Organizations listOrganizations(String appUrl, AccessToken accessToken, int page, int pageSize) {
     checkPageArgs(page, pageSize);
index c516cfafb35fd8ee396421e35667d232d2fba1ff..855f8f66e7c5bc19342615553fd8291c4c5f36aa 100644 (file)
@@ -235,6 +235,39 @@ public class GithubApplicationClientImplTest {
     assertThatCode(() -> underTest.checkAppPermissions(githubAppConfiguration)).isNull();
   }
 
+  @Test
+  public void getInstallationId_returns_installation_id_of_given_account() throws IOException {
+    AppToken appToken = new AppToken(APP_JWT_TOKEN);
+    when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(appToken);
+    when(httpClient.get(appUrl, appToken, "/repos/torvalds/linux/installation"))
+      .thenReturn(new OkGetResponse("{" +
+        "  \"id\": 2," +
+        "  \"account\": {" +
+        "    \"login\": \"torvalds\"" +
+        "  }" +
+        "}"));
+
+    assertThat(underTest.getInstallationId(githubAppConfiguration, "torvalds/linux")).hasValue(2L);
+  }
+
+  @Test
+  public void getInstallationId_throws_IAE_if_fail_to_create_app_token() {
+    when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenThrow(IllegalArgumentException.class);
+
+    assertThatThrownBy(() -> underTest.getInstallationId(githubAppConfiguration, "torvalds"))
+      .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void getInstallationId_return_empty_if_no_installation_found_for_githubAccount() throws IOException {
+    AppToken appToken = new AppToken(APP_JWT_TOKEN);
+    when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(appToken);
+    when(httpClient.get(appUrl, appToken, "/repos/torvalds/linux/installation"))
+      .thenReturn(new ErrorGetResponse(404, null));
+
+    assertThat(underTest.getInstallationId(githubAppConfiguration, "torvalds")).isEmpty();
+  }
+
   @Test
   @UseDataProvider("githubServers")
   public void createUserAccessToken_returns_empty_if_access_token_cant_be_created(String apiUrl, String appUrl) throws IOException {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformService.java
new file mode 100644 (file)
index 0000000..514b583
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Priority;
+import org.apache.commons.lang.NotImplementedException;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+@ServerSide
+@Priority(1)
+public class DelegatingDevOpsPlatformService implements DevOpsPlatformService {
+
+  private final Set<DevOpsPlatformService> delegates;
+
+  public DelegatingDevOpsPlatformService(Set<DevOpsPlatformService> delegates) {
+    this.delegates = delegates;
+  }
+
+  @Override
+  public ALM getDevOpsPlatform() {
+    throw new NotImplementedException();
+  }
+
+  @Override
+  public Optional<DevOpsProjectDescriptor> getDevOpsProjectDescriptor(Map<String, String> characteristics) {
+    return delegates.stream()
+      .flatMap(delegate -> delegate.getDevOpsProjectDescriptor(characteristics).stream())
+      .findFirst();
+  }
+
+  @Override
+  public Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor) {
+    return findDelegate(devOpsProjectDescriptor.alm())
+      .flatMap(delegate -> delegate.getValidAlmSettingDto(dbSession, devOpsProjectDescriptor));
+  }
+
+  private Optional<DevOpsPlatformService> findDelegate(ALM alm) {
+    return delegates.stream()
+      .filter(delegate -> delegate.getDevOpsPlatform().equals(alm))
+      .findFirst();
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsPlatformService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsPlatformService.java
new file mode 100644 (file)
index 0000000..e685e1b
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+public interface DevOpsPlatformService {
+
+  ALM getDevOpsPlatform();
+
+  Optional<DevOpsProjectDescriptor> getDevOpsProjectDescriptor(Map<String, String> characteristics);
+
+  Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor);
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectDescriptor.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/DevOpsProjectDescriptor.java
new file mode 100644 (file)
index 0000000..23db562
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.almsettings.ws;
+
+import org.sonar.db.alm.setting.ALM;
+
+public record DevOpsProjectDescriptor(ALM alm, String url, String projectIdentifier) {
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformService.java
new file mode 100644 (file)
index 0000000..2afec24
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import org.sonar.alm.client.github.GithubApplicationClient;
+import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.github.config.GithubAppConfiguration;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDao;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+@ServerSide
+public class GitHubDevOpsPlatformService implements DevOpsPlatformService {
+
+  public static final String DEVOPS_PLATFORM_URL = "devOpsPlatformUrl";
+  public static final String DEVOPS_PLATFORM_PROJECT_IDENTIFIER = "devOpsPlatformProjectIdentifier";
+
+  private final AlmSettingDao almSettingDao;
+  private final GithubGlobalSettingsValidator githubGlobalSettingsValidator;
+  private final GithubApplicationClient githubApplicationClient;
+
+  public GitHubDevOpsPlatformService(AlmSettingDao almSettingDao, GithubGlobalSettingsValidator githubGlobalSettingsValidator,
+    GithubApplicationClient githubApplicationClient) {
+    this.almSettingDao = almSettingDao;
+    this.githubGlobalSettingsValidator = githubGlobalSettingsValidator;
+    this.githubApplicationClient = githubApplicationClient;
+  }
+
+  @Override
+  public ALM getDevOpsPlatform() {
+    return ALM.GITHUB;
+  }
+
+  @Override
+  public Optional<DevOpsProjectDescriptor> getDevOpsProjectDescriptor(Map<String, String> characteristics) {
+    String githubApiUrl = characteristics.get(DEVOPS_PLATFORM_URL);
+    String githubRepository = characteristics.get(DEVOPS_PLATFORM_PROJECT_IDENTIFIER);
+    if (githubApiUrl != null && githubRepository != null) {
+      return Optional.of(new DevOpsProjectDescriptor(ALM.GITHUB, githubApiUrl, githubRepository));
+    }
+    return Optional.empty();
+  }
+
+  @Override
+  public Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor) {
+    return almSettingDao.selectByAlm(dbSession, getDevOpsPlatform()).stream()
+      .filter(almSettingDto -> devOpsProjectDescriptor.url().equals(almSettingDto.getUrl()))
+      .filter(almSettingDto -> hasAccessToRepo(almSettingDto, devOpsProjectDescriptor.projectIdentifier()))
+      .findFirst();
+  }
+
+  private boolean hasAccessToRepo(AlmSettingDto almSettingDto, String repo) {
+    GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto);
+    return githubApplicationClient.getInstallationId(githubAppConfiguration, repo).isPresent();
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformServiceTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/DelegatingDevOpsPlatformServiceTest.java
new file mode 100644 (file)
index 0000000..0a7e746
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.almsettings.ws;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.apache.commons.lang.NotImplementedException;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DelegatingDevOpsPlatformServiceTest {
+
+  private static final DelegatingDevOpsPlatformService NO_DEVOPS_PLATFORMS = new DelegatingDevOpsPlatformService(emptySet());
+  private static final DelegatingDevOpsPlatformService MULTIPLE_DEVOPS_PLATFORMS = new DelegatingDevOpsPlatformService(
+    Set.of(mockGitHubDevOpsPlatformService(), mockAzureDevOpsPlatformService()));
+
+  @Mock
+  private DbSession dbSession;
+
+  @Test
+  public void getDevOpsPlatform_shouldThrow() {
+    assertThatThrownBy(NO_DEVOPS_PLATFORMS::getDevOpsPlatform)
+      .isInstanceOf(NotImplementedException.class);
+  }
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenNoDelegates_shouldReturnOptionalEmpty() {
+    Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = NO_DEVOPS_PLATFORMS.getDevOpsProjectDescriptor(Map.of());
+
+    assertThat(devOpsProjectDescriptor).isEmpty();
+  }
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenDelegates_shouldReturnDelegateResponse() {
+    Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = MULTIPLE_DEVOPS_PLATFORMS.getDevOpsProjectDescriptor(Map.of(
+      "githubUrl", "githubUrl"
+    ));
+
+    assertThat(devOpsProjectDescriptor)
+      .isPresent()
+      .get().usingRecursiveComparison().isEqualTo(new DevOpsProjectDescriptor(ALM.GITHUB, "githubUrl", "githubRepo"));
+  }
+
+  @Test
+  public void getValidAlmSettingDto_whenNoDelegates_shouldReturnOptionalEmpty() {
+    Optional<AlmSettingDto> almSettingDto = NO_DEVOPS_PLATFORMS.getValidAlmSettingDto(dbSession, mock(DevOpsProjectDescriptor.class));
+
+    assertThat(almSettingDto).isEmpty();
+  }
+
+  @Test
+  public void getValidAlmSettingDto_whenDelegates_shouldReturnDelegateResponse() {
+    Optional<AlmSettingDto> almSettingDto = MULTIPLE_DEVOPS_PLATFORMS.getValidAlmSettingDto(dbSession, new DevOpsProjectDescriptor(ALM.GITHUB, "githubUrl", "githubRepo"));
+
+    assertThat(almSettingDto)
+      .isPresent()
+      .get()
+      .usingRecursiveComparison().isEqualTo(new AlmSettingDto().setAlm(ALM.GITHUB));
+  }
+
+  private static DevOpsPlatformService mockGitHubDevOpsPlatformService() {
+    DevOpsPlatformService mockDevOpsPlatformService = mock();
+    when(mockDevOpsPlatformService.getDevOpsPlatform()).thenReturn(ALM.GITHUB);
+    when(mockDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of("githubUrl", "githubUrl")))
+      .thenReturn(Optional.of(new DevOpsProjectDescriptor(ALM.GITHUB, "githubUrl", "githubRepo")));
+    when(mockDevOpsPlatformService.getValidAlmSettingDto(any(), any()))
+      .thenReturn(Optional.of(new AlmSettingDto().setAlm(ALM.GITHUB)));
+    return mockDevOpsPlatformService;
+  }
+
+  private static DevOpsPlatformService mockAzureDevOpsPlatformService() {
+    DevOpsPlatformService mockDevOpsPlatformService = mock();
+    when(mockDevOpsPlatformService.getDevOpsPlatform()).thenReturn(ALM.AZURE_DEVOPS);
+    when(mockDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of("azureUrl", "azureUrl")))
+      .thenReturn(Optional.of(new DevOpsProjectDescriptor(ALM.AZURE_DEVOPS, "azureUrl", "azureProject")));
+    when(mockDevOpsPlatformService.getValidAlmSettingDto(any(), any()))
+      .thenReturn(Optional.of(new AlmSettingDto().setAlm(ALM.AZURE_DEVOPS)));
+    return mockDevOpsPlatformService;
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformServiceTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GitHubDevOpsPlatformServiceTest.java
new file mode 100644 (file)
index 0000000..2cf2a07
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.almsettings.ws;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.alm.client.github.GithubApplicationClient;
+import org.sonar.alm.client.github.GithubGlobalSettingsValidator;
+import org.sonar.alm.client.github.config.GithubAppConfiguration;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.setting.ALM;
+import org.sonar.db.alm.setting.AlmSettingDao;
+import org.sonar.db.alm.setting.AlmSettingDto;
+
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService.DEVOPS_PLATFORM_PROJECT_IDENTIFIER;
+import static org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService.DEVOPS_PLATFORM_URL;
+
+
+@RunWith(MockitoJUnitRunner.class)
+public class GitHubDevOpsPlatformServiceTest {
+
+  private static final DevOpsProjectDescriptor GITHUB_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "url", "repo");
+
+  @Mock
+  private DbSession dbSession;
+  @Mock
+  private AlmSettingDao almSettingDao;
+  @Mock
+  private GithubGlobalSettingsValidator githubGlobalSettingsValidator;
+  @Mock
+  private GithubApplicationClient githubApplicationClient;
+  @InjectMocks
+  private GitHubDevOpsPlatformService gitHubDevOpsPlatformService;
+
+  @Test
+  public void getDevOpsPlatform_shouldReturnGitHub() {
+    assertThat(gitHubDevOpsPlatformService.getDevOpsPlatform())
+      .isEqualTo(ALM.GITHUB);
+  }
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenNoCharacteristics_shouldReturnEmpty() {
+    Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = gitHubDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of());
+
+    assertThat(devOpsProjectDescriptor).isEmpty();
+  }
+
+  @Test
+  public void getDevOpsProjectDescriptor_whenValidCharacteristics_shouldReturn() {
+    Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = gitHubDevOpsPlatformService.getDevOpsProjectDescriptor(Map.of(
+      DEVOPS_PLATFORM_URL, GITHUB_PROJECT_DESCRIPTOR.url(),
+      DEVOPS_PLATFORM_PROJECT_IDENTIFIER, GITHUB_PROJECT_DESCRIPTOR.projectIdentifier()
+    ));
+
+    assertThat(devOpsProjectDescriptor)
+      .isPresent()
+      .get().usingRecursiveComparison().isEqualTo(GITHUB_PROJECT_DESCRIPTOR);
+  }
+
+  @Test
+  public void getValidAlmSettingDto_whenNoAlmSetting_shouldReturnEmpty() {
+    when(almSettingDao.selectByAlm(dbSession, ALM.GITHUB)).thenReturn(emptyList());
+
+    Optional<AlmSettingDto> almSettingDto = gitHubDevOpsPlatformService.getValidAlmSettingDto(dbSession, GITHUB_PROJECT_DESCRIPTOR);
+
+    assertThat(almSettingDto).isEmpty();
+  }
+
+  @Test
+  public void getValidAlmSettingDto_whenMultipleAlmSetting_shouldReturnTheRightOne() {
+    AlmSettingDto mockGitHubAlmSettingDtoNoAccess = mockGitHubAlmSettingDto(false);
+    AlmSettingDto mockGitHubAlmSettingDtoAccess = mockGitHubAlmSettingDto(true);
+    when(almSettingDao.selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(mockGitHubAlmSettingDtoNoAccess, mockGitHubAlmSettingDtoAccess));
+
+    Optional<AlmSettingDto> almSettingDto = gitHubDevOpsPlatformService.getValidAlmSettingDto(dbSession, GITHUB_PROJECT_DESCRIPTOR);
+
+    assertThat(almSettingDto)
+      .isPresent()
+      .get().isEqualTo(mockGitHubAlmSettingDtoAccess);
+  }
+
+  private AlmSettingDto mockGitHubAlmSettingDto(boolean repoAccess) {
+    AlmSettingDto mockAlmSettingDto = mock();
+    when(mockAlmSettingDto.getUrl()).thenReturn(GITHUB_PROJECT_DESCRIPTOR.url());
+    GithubAppConfiguration mockGithubAppConfiguration = mock(GithubAppConfiguration.class);
+    when(githubGlobalSettingsValidator.validate(mockAlmSettingDto)).thenReturn(mockGithubAppConfiguration);
+    when(githubApplicationClient.getInstallationId(eq(mockGithubAppConfiguration), any())).thenReturn(repoAccess ? Optional.of(1L) : Optional.empty());
+    return mockAlmSettingDto;
+  }
+
+}
index 7345e963bf0ddb6f5655120222b866cc22280fd6..097e0d363d583dd1ddb90650e434cfe0380756dd 100644 (file)
@@ -62,6 +62,8 @@ import org.sonar.server.almintegration.ws.ProjectKeyGenerator;
 import org.sonar.server.almintegration.ws.github.GithubProvisioningWs;
 import org.sonar.server.almsettings.MultipleAlmFeature;
 import org.sonar.server.almsettings.ws.AlmSettingsWsModule;
+import org.sonar.server.almsettings.ws.DelegatingDevOpsPlatformService;
+import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService;
 import org.sonar.server.authentication.AuthenticationModule;
 import org.sonar.server.authentication.DefaultAdminCredentialsVerifierImpl;
 import org.sonar.server.authentication.DefaultAdminCredentialsVerifierNotificationHandler;
@@ -307,6 +309,7 @@ public class PlatformLevel4 extends PlatformLevel {
       DefaultBranchNameResolver.class,
       DefaultDocumentationLinkGenerator.class,
       DelegatingManagedServices.class,
+      DelegatingDevOpsPlatformService.class,
 
       // batch
       new BatchWsModule(),
@@ -548,6 +551,7 @@ public class PlatformLevel4 extends PlatformLevel {
       GithubApplicationHttpClientImpl.class,
       GithubProvisioningConfigValidator.class,
       GithubProvisioningWs.class,
+      GitHubDevOpsPlatformService.class,
       BitbucketCloudRestClientConfiguration.class,
       BitbucketServerRestClient.class,
       GitlabHttpClient.class,