diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/AbstractWorkspaceServiceAccount.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/AbstractWorkspaceServiceAccount.java index 39c097ef6bd..b7624fff94e 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/AbstractWorkspaceServiceAccount.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/AbstractWorkspaceServiceAccount.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2022 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -53,6 +53,7 @@ public abstract class AbstractWorkspaceServiceAccount< public static final String CONFIGMAPS_ROLE_NAME = "workspace-configmaps"; public static final String CREDENTIALS_SECRET_NAME = "workspace-credentials-secret"; public static final String PREFERENCES_CONFIGMAP_NAME = "workspace-preferences-configmap"; + public static final String GIT_USERDATA_CONFIGMAP_NAME = "workspace-userdata-gitconfig-configmap"; protected final String namespace; protected final String serviceAccountName; diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserDataConfigurator.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserDataConfigurator.java index 28ce5d506ef..40850f66ef4 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserDataConfigurator.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserDataConfigurator.java @@ -11,6 +11,9 @@ */ package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; +import static java.util.Collections.singletonMap; +import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.GIT_USERDATA_CONFIGMAP_NAME; + import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; @@ -31,7 +34,6 @@ public class GitconfigUserDataConfigurator implements NamespaceConfigurator { private static final Logger LOG = LoggerFactory.getLogger(GitconfigUserDataConfigurator.class); private final KubernetesClientFactory clientFactory; private final Set gitUserDataFetchers; - private static final String CONFIGMAP_NAME = "workspace-userdata-gitconfig"; private static final String CONFIGMAP_DATA_KEY = "gitconfig"; @Inject @@ -68,7 +70,12 @@ public void configure(NamespaceResolutionContext namespaceResolutionContext, Str "controller.devfile.io/watch-configmap", "true"); if (gitUserData != null - && client.configMaps().inNamespace(namespaceName).withName(CONFIGMAP_NAME).get() == null + && client + .configMaps() + .inNamespace(namespaceName) + .withName(GIT_USERDATA_CONFIGMAP_NAME) + .get() + == null && client .configMaps() .inNamespace(namespaceName) @@ -87,13 +94,13 @@ public void configure(NamespaceResolutionContext namespaceResolutionContext, Str ConfigMap configMap = new ConfigMapBuilder() .withNewMetadata() - .withName(CONFIGMAP_NAME) + .withName(GIT_USERDATA_CONFIGMAP_NAME) .withLabels(labels) .withAnnotations(annotations) .endMetadata() .build(); configMap.setData( - ImmutableMap.of( + singletonMap( CONFIGMAP_DATA_KEY, String.format( "[user]\n\tname = %1$s\n\temail = %2$s", diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserdataConfiguratorTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserdataConfiguratorTest.java new file mode 100644 index 00000000000..af4064bdfd0 --- /dev/null +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserdataConfiguratorTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; + +import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.GIT_USERDATA_CONFIGMAP_NAME; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesServer; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import org.eclipse.che.api.factory.server.scm.GitUserData; +import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; +import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(MockitoTestNGListener.class) +public class GitconfigUserdataConfiguratorTest { + + private NamespaceConfigurator configurator; + + @Mock private KubernetesClientFactory clientFactory; + @Mock private GitUserDataFetcher gitUserDataFetcher; + private KubernetesServer serverMock; + + private NamespaceResolutionContext namespaceResolutionContext; + private final String TEST_NAMESPACE_NAME = "namespace123"; + private final String TEST_WORKSPACE_ID = "workspace123"; + private final String TEST_USER_ID = "user123"; + private final String TEST_USERNAME = "jondoe"; + + @BeforeMethod + public void setUp() + throws InfrastructureException, ScmCommunicationException, ScmUnauthorizedException { + configurator = new GitconfigUserDataConfigurator(clientFactory, Set.of(gitUserDataFetcher)); + + serverMock = new KubernetesServer(true, true); + serverMock.before(); + KubernetesClient client = spy(serverMock.getClient()); + when(clientFactory.create()).thenReturn(client); + + namespaceResolutionContext = + new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME); + } + + @Test + public void createUserdataConfigmapWhenDoesNotExist() + throws ScmCommunicationException, ScmUnauthorizedException, InfrastructureException, + InterruptedException { + // given + when(gitUserDataFetcher.fetchGitUserData()).thenReturn(new GitUserData("gitUser", "gitEmail")); + + // when + configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); + + // then create a secret + Assert.assertEquals(serverMock.getLastRequest().getMethod(), "POST"); + Assert.assertNotNull( + serverMock + .getClient() + .configMaps() + .inNamespace(TEST_NAMESPACE_NAME) + .withName(GIT_USERDATA_CONFIGMAP_NAME) + .get()); + } + + @Test + public void doNothingWhenGitUserDataIsNull() + throws InfrastructureException, InterruptedException { + // when + configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); + + // then - don't create the configmap + var configMaps = + serverMock.getClient().configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); + Assert.assertEquals(configMaps.size(), 0); + } + + @Test + public void doNothingWhenSecretAlreadyExists() + throws InfrastructureException, InterruptedException, ScmCommunicationException, + ScmUnauthorizedException { + // given + when(gitUserDataFetcher.fetchGitUserData()).thenReturn(new GitUserData("gitUser", "gitEmail")); + Map annotations = + ImmutableMap.of( + "controller.devfile.io/mount-as", + "subpath", + "controller.devfile.io/mount-path", + "/etc/", + "already", + "created"); + Map labels = + ImmutableMap.of( + "controller.devfile.io/mount-to-devworkspace", + "true", + "controller.devfile.io/watch-configmap", + "true", + "already", + "created"); + ConfigMap configMap = + new ConfigMapBuilder() + .withNewMetadata() + .withName(GIT_USERDATA_CONFIGMAP_NAME) + .withLabels(labels) + .withAnnotations(annotations) + .endMetadata() + .build(); + configMap.setData(Collections.singletonMap("gitconfig", "empty")); + serverMock.getClient().configMaps().inNamespace(TEST_NAMESPACE_NAME).create(configMap); + + // when + configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME); + + // then - don't create the configmap + Assert.assertEquals(serverMock.getLastRequest().getMethod(), "GET"); + var configMaps = + serverMock.getClient().configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems(); + Assert.assertEquals(configMaps.size(), 1); + Assert.assertEquals(configMaps.get(0).getMetadata().getAnnotations().get("already"), "created"); + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcherTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcherTest.java new file mode 100644 index 00000000000..91dedfa9e5d --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcherTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +import java.net.MalformedURLException; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; +import org.eclipse.che.api.factory.server.scm.GitUserData; +import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.commons.subject.SubjectImpl; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(MockitoTestNGListener.class) +public class BitbucketServerUserDataFetcherTest { + String someBitbucketURL = "https://some.bitbucketserver.com"; + Subject subject; + @Mock BitbucketServerApiClient bitbucketServerApiClient; + BitbucketUser bitbucketUser; + BitbucketServerUserDataFetcher fetcher; + + @BeforeMethod + public void setup() throws MalformedURLException { + subject = new SubjectImpl("another_user", "user987", "token111", false); + bitbucketUser = + new BitbucketUser("User", "user", 32423523, "NORMAL", true, "user", "user@users.com"); + fetcher = new BitbucketServerUserDataFetcher(bitbucketServerApiClient, someBitbucketURL); + EnvironmentContext context = new EnvironmentContext(); + context.setSubject(subject); + EnvironmentContext.setCurrent(context); + } + + @Test + public void shouldBeAbleToFetchPersonalAccessToken() + throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException, + ScmBadRequestException { + // given + when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true); + when(bitbucketServerApiClient.getUser(eq(subject))).thenReturn(bitbucketUser); + // when + GitUserData gitUserData = fetcher.fetchGitUserData(); + // then + assertEquals(gitUserData.getScmUsername(), "user"); + assertEquals(gitUserData.getScmUserEmail(), "user@users.com"); + } +} diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java index ebfda1b18e6..9d97bf5e796 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java @@ -49,9 +49,15 @@ public class GithubUserDataFetcher implements GitUserDataFetcher { @Inject public GithubUserDataFetcher(@Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI) { + this(apiEndpoint, oAuthAPI, new GithubApiClient()); + } + + /** Constructor used for testing only. */ + public GithubUserDataFetcher( + String apiEndpoint, OAuthAPI oAuthAPI, GithubApiClient githubApiClient) { this.apiEndpoint = apiEndpoint; this.oAuthAPI = oAuthAPI; - this.githubApiClient = new GithubApiClient(); + this.githubApiClient = githubApiClient; } @Override diff --git a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubGitUserDataFetcherTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubGitUserDataFetcherTest.java new file mode 100644 index 00000000000..d4d3fe77f8b --- /dev/null +++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubGitUserDataFetcherTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.eclipse.che.dto.server.DtoFactory.newDto; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.Slf4jNotifier; +import com.google.common.net.HttpHeaders; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.factory.server.scm.GitUserData; +import org.eclipse.che.security.oauth.OAuthAPI; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(MockitoTestNGListener.class) +public class GithubGitUserDataFetcherTest { + + @Mock OAuthAPI oAuthAPI; + GithubUserDataFetcher githubGUDFetcher; + + final int httpPort = 3301; + WireMockServer wireMockServer; + WireMock wireMock; + + final String githubOauthToken = "gho_token1"; + + @BeforeMethod + void start() { + wireMockServer = + new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).port(httpPort)); + wireMockServer.start(); + WireMock.configureFor("localhost", httpPort); + wireMock = new WireMock("localhost", httpPort); + githubGUDFetcher = + new GithubUserDataFetcher( + "http://che.api", oAuthAPI, new GithubApiClient(wireMockServer.url("/"))); + stubFor( + get(urlEqualTo("/user")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withHeader(GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, "repo") + .withBodyFile("github/rest/user/response.json"))); + } + + @AfterMethod + void stop() { + wireMockServer.stop(); + } + + @Test + public void shouldFetchGitUserData() throws Exception { + OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(githubOauthToken).withScope("repo"); + when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + + GitUserData gitUserData = githubGUDFetcher.fetchGitUserData(); + + assertEquals(gitUserData.getScmUsername(), "Github User"); + assertEquals(gitUserData.getScmUserEmail(), "github-user@acme.com"); + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java index a666eb479c2..a0e36f3cfb0 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java @@ -57,9 +57,9 @@ public class GitlabUserDataFetcher implements GitUserDataFetcher { @Inject public GitlabUserDataFetcher( - @Named("che.api") String apiEndpoint, @Nullable @Named("che.integration.gitlab.server_endpoints") String gitlabEndpoints, @Nullable @Named("che.integration.gitlab.oauth_endpoint") String oauthEndpoint, + @Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI) { this.apiEndpoint = apiEndpoint; if (gitlabEndpoints != null) { diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java new file mode 100644 index 00000000000..4a04050b524 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.eclipse.che.dto.server.DtoFactory.newDto; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.Slf4jNotifier; +import com.google.common.net.HttpHeaders; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.factory.server.scm.GitUserData; +import org.eclipse.che.security.oauth.OAuthAPI; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(MockitoTestNGListener.class) +public class GitlabUserDataFetcherTest { + + @Mock OAuthAPI oAuthAPI; + + GitlabUserDataFetcher gitlabUserDataFetcher; + + WireMockServer wireMockServer; + WireMock wireMock; + + @BeforeMethod + void start() { + wireMockServer = + new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + wireMock = new WireMock("localhost", wireMockServer.port()); + gitlabUserDataFetcher = + new GitlabUserDataFetcher( + wireMockServer.url("/"), wireMockServer.url("/"), "http://che.api", oAuthAPI); + + stubFor( + get(urlEqualTo("/api/v4/user")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer oauthtoken")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("gitlab/rest/api/v4/user/response.json"))); + } + + @AfterMethod + void stop() { + wireMockServer.stop(); + } + + @Test + public void shouldFetchGitUserData() throws Exception { + OAuthToken oAuthToken = + newDto(OAuthToken.class).withToken("oauthtoken").withScope("api write_repository openid"); + when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + + GitUserData gitUserData = gitlabUserDataFetcher.fetchGitUserData(); + assertEquals(gitUserData.getScmUsername(), "John Smith"); + assertEquals(gitUserData.getScmUserEmail(), "john@example.com"); + } +}