/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation * SAP - implementation *******************************************************************************/ package org.eclipse.che.git.impl.jgit; import org.eclipse.che.api.core.util.LineConsumerFactory; import org.eclipse.che.api.git.CredentialsLoader; import org.eclipse.che.api.git.GitUserResolver; import org.eclipse.che.api.git.exception.GitException; import org.eclipse.che.api.git.params.CloneParams; import org.eclipse.che.api.git.params.CommitParams; import org.eclipse.che.api.git.shared.GitUser; import org.eclipse.che.api.git.shared.Status; import org.eclipse.che.plugin.ssh.key.script.SshKeyProvider; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.TransportCommand; import org.eclipse.jgit.api.TransportConfigCallback; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.transport.SshTransport; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.TransportHttp; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import java.io.File; import java.lang.reflect.Field; import static java.util.Collections.singletonList; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; /** * Test class for {@link JGitConnection} * * @author Igor Vinokur */ @Listeners(value = MockitoTestNGListener.class) public class JGitConnectionTest { @Mock private Repository repository; @Mock private CredentialsLoader credentialsLoader; @Mock private SshKeyProvider sshKeyProvider; @Mock private GitUserResolver gitUserResolver; @Mock private TransportCommand transportCommand; @Mock private GitUserResolver userResolver; private JGitConnection jGitConnection; @BeforeMethod public void setup() { jGitConnection = spy(new JGitConnection(repository, credentialsLoader, sshKeyProvider, userResolver)); RepositoryState repositoryState = mock(RepositoryState.class); GitUser gitUser = mock(GitUser.class); when(repositoryState.canAmend()).thenReturn(true); when(repositoryState.canCommit()).thenReturn(true); when(repository.getRepositoryState()).thenReturn(repositoryState); when(gitUser.getName()).thenReturn("username"); when(gitUser.getEmail()).thenReturn("email"); when(userResolver.getUser()).thenReturn(gitUser); } @DataProvider(name = "gitUrlsWithCredentialsProvider") private static Object[][] gitUrlsWithCredentials() { return new Object[][]{{"http://username:password@host.xz/path/to/repo.git"}, {"https://username:password@host.xz/path/to/repo.git"}}; } @DataProvider(name = "gitUrlsWithoutOrWrongCredentialsProvider") private static Object[][] gitUrlsWithoutOrWrongCredentials() { return new Object[][]{{"http://host.xz/path/to/repo.git"}, {"https://host.xz/path/to/repo.git"}, {"http://username:@host.xz/path/to/repo.git"}, {"https://username:@host.xz/path/to/repo.git"}, {"http://:password@host.xz/path/to/repo.git"}, {"https://:password@host.xz/path/to/repo.git"}}; } @Test(dataProvider = "gitUrlsWithCredentials") public void shouldExecuteRemoteCommandByHttpOrHttpsUrlWithCredentials(String url) throws Exception { //given ArgumentCaptor<UsernamePasswordCredentialsProvider> captor = ArgumentCaptor.forClass(UsernamePasswordCredentialsProvider.class); Field usernameField = UsernamePasswordCredentialsProvider.class.getDeclaredField("username"); Field passwordField = UsernamePasswordCredentialsProvider.class.getDeclaredField("password"); usernameField.setAccessible(true); passwordField.setAccessible(true); //when jGitConnection.executeRemoteCommand(url, transportCommand, null, null); //then verify(transportCommand).setCredentialsProvider(captor.capture()); UsernamePasswordCredentialsProvider credentialsProvider = captor.getValue(); String username = (String)usernameField.get(credentialsProvider); char[] password = (char[])passwordField.get(credentialsProvider); assertEquals(username, "username"); assertEquals(String.valueOf(password), "password"); } @Test(dataProvider = "gitUrlsWithoutOrWrongCredentials") public void shouldNotSetCredentialsProviderIfUrlDoesNotContainCredentials(String url) throws Exception { //when jGitConnection.executeRemoteCommand(url, transportCommand, null, null); //then verify(transportCommand, never()).setCredentialsProvider(any()); } @Test public void shouldSetSshSessionFactoryWhenSshTransportReceived() throws Exception { //given SshTransport sshTransport = mock(SshTransport.class); when(sshKeyProvider.getPrivateKey(anyString())).thenReturn(new byte[0]); doAnswer(invocation -> { TransportConfigCallback callback = (TransportConfigCallback)invocation.getArguments()[0]; callback.configure(sshTransport); return null; }).when(transportCommand).setTransportConfigCallback(any()); //when jGitConnection.executeRemoteCommand("ssh://host.xz/repo.git", transportCommand, null, null); //then verify(sshTransport).setSshSessionFactory(any()); } @Test public void shouldDoNothingWhenTransportHttpReceived() throws Exception { //given /* * We need create {@link TransportHttp} mock, but this class has parent * abstract class {@link Transport}. Class Transport uses fields of children * classes for static initialization collection {@link Transport#protocols}. * When we create mock for {@link TransportHttp} - Mockito mocks fields and * they return null value. For full mock creation TransportHttp Mockito * launches static block in the parent class {@link Transport}, but static * block initializes collection with help mocked children fields which * return null values, so Transport class loses real field value in the * collection. It creates troubles in other tests when we use real object * of TransportHttp(collection 'protocols' contains not all values). * To realize right initialization {@link Transport#protocols} we create * mock of {@link Transport} and this class initializes collection "protocols" * with help real children {@link TransportHttp}, which returns real not null * value. And then we can create mock {@link TransportHttp}. */ mock(Transport.class); TransportHttp transportHttp = mock(TransportHttp.class); when(sshKeyProvider.getPrivateKey(anyString())).thenReturn(new byte[0]); doAnswer(invocation -> { TransportConfigCallback callback = (TransportConfigCallback)invocation.getArguments()[0]; callback.configure(transportHttp); return null; }).when(transportCommand).setTransportConfigCallback(any()); //when jGitConnection.executeRemoteCommand("ssh://host.xz/repo.git", transportCommand, null, null); //then verifyZeroInteractions(transportHttp); } /** * Check branch using current repository reference is returned * * @throws Exception * if it fails */ @Test public void checkCurrentBranch() throws Exception { String branchTest = "helloWorld"; Ref ref = mock(Ref.class); when(repository.exactRef(Constants.HEAD)).thenReturn(ref); when(ref.getLeaf()).thenReturn(ref); when(ref.getName()).thenReturn(branchTest); String branchName = jGitConnection.getCurrentBranch(); assertEquals(branchName, branchTest); } /** Test for workaround related to https://bugs.eclipse.org/bugs/show_bug.cgi?id=510685.*/ @Test(expectedExceptions = GitException.class, expectedExceptionsMessageRegExp = "Changes are present but not changed path was specified for commit.") public void testCommitNotChangedSpecifiedPathsWithAmendWhenOtherStagedChangesArePresent() throws Exception { //given Status status = mock(Status.class); when(status.getChanged()).thenReturn(singletonList("ChangedNotSpecified")); doReturn(status).when(jGitConnection).status(anyObject()); //when jGitConnection.commit(CommitParams.create("message").withFiles(singletonList("NotChangedSpecified")).withAmend(true)); } /** Test for workaround related to https://bugs.eclipse.org/bugs/show_bug.cgi?id=510685.*/ @Test(expectedExceptions = GitException.class, expectedExceptionsMessageRegExp = "Changes are present but not changed path was specified for commit.") public void testCommitNotChangedSpecifiedPathsWithAmendAndWithAllWhenOtherUnstagedChangesArePresent() throws Exception { //given Status status = mock(Status.class); when(status.getModified()).thenReturn(singletonList("ChangedNotSpecified")); doReturn(status).when(jGitConnection).status(anyObject()); //when jGitConnection.commit(CommitParams.create("message").withFiles(singletonList("NotChangedSpecified")).withAmend(true).withAll(true)); } @Test public void shouldCloseCloneCommand() throws Exception { //given File fileMock = mock(File.class); Git cloneCommand = mock(Git.class); jGitConnection.setOutputLineConsumerFactory(mock(LineConsumerFactory.class)); when(repository.getWorkTree()).thenReturn(fileMock); when(repository.getDirectory()).thenReturn(fileMock); when(repository.getConfig()).thenReturn(mock(StoredConfig.class)); doReturn(cloneCommand).when(jGitConnection).executeRemoteCommand(anyString(), anyObject(), anyString(), anyString()); //when jGitConnection.clone(CloneParams.create("url").withWorkingDir("fakePath")); //then verify(cloneCommand).close(); } }