org.springframework.cloud.config.server.environment.JGitEnvironmentRepositoryTests.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.cloud.config.server.environment.JGitEnvironmentRepositoryTests.java

Source

/*
 * Copyright 2013-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.config.server.environment;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.DeleteBranchCommand;
import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.api.MergeCommand;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.StatusCommand;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.NotMergedException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.junit.MockSystemReader;
import org.eclipse.jgit.lib.BaseRepositoryBuilder;
import org.eclipse.jgit.lib.ObjectDatabase;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.transport.CredentialItem;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.TrackingRefUpdate;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;

import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.support.AwsCodeCommitCredentialProvider;
import org.springframework.cloud.config.server.support.GitSkipSslValidationCredentialsProvider;
import org.springframework.cloud.config.server.support.PassphraseCredentialsProvider;
import org.springframework.cloud.config.server.test.ConfigServerTestUtils;
import org.springframework.core.env.StandardEnvironment;

import static junit.framework.TestCase.assertTrue;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * @author Dave Syer
 * @author Gareth Clay
 */
public class JGitEnvironmentRepositoryTests {

    @Rule
    public final ExpectedException exception = ExpectedException.none();

    RefDatabase database = Mockito.mock(RefDatabase.class);

    private StandardEnvironment environment = new StandardEnvironment();

    private JGitEnvironmentRepository repository;

    private File basedir = new File("target/config");

    @BeforeClass
    public static void initClass() {
        // mock Git configuration to make tests independent of local Git configuration
        SystemReader.setInstance(new MockSystemReader());
    }

    @Before
    public void init() throws Exception {
        String uri = ConfigServerTestUtils.prepareLocalRepo();
        this.repository = new JGitEnvironmentRepository(this.environment, new JGitEnvironmentProperties());
        this.repository.setUri(uri);
        if (this.basedir.exists()) {
            FileUtils.delete(this.basedir, FileUtils.RECURSIVE | FileUtils.RETRY);
        }
    }

    @Test
    public void vanilla() {
        this.repository.findOne("bar", "staging", "master");
        Environment environment = this.repository.findOne("bar", "staging", "master");
        assertThat(environment.getPropertySources().size()).isEqualTo(2);
        assertThat(environment.getPropertySources().get(0).getName())
                .isEqualTo(this.repository.getUri() + "/bar.properties");
        assertVersion(environment);
    }

    @Test
    public void nested() throws IOException {
        String uri = ConfigServerTestUtils.prepareLocalRepo("another-config-repo");
        this.repository.setUri(uri);
        this.repository.setSearchPaths(new String[] { "sub" });
        this.repository.findOne("bar", "staging", "master");
        Environment environment = this.repository.findOne("bar", "staging", "master");
        assertThat(environment.getPropertySources().size()).isEqualTo(2);
        assertThat(environment.getPropertySources().get(0).getName())
                .isEqualTo(this.repository.getUri() + "/sub/application.yml");
        assertVersion(environment);
    }

    @Test
    public void placeholderInSearchPath() throws IOException {
        String uri = ConfigServerTestUtils.prepareLocalRepo("another-config-repo");
        this.repository.setUri(uri);
        this.repository.setSearchPaths(new String[] { "{application}" });
        this.repository.findOne("sub", "staging", "master");
        Environment environment = this.repository.findOne("sub", "staging", "master");
        assertThat(environment.getPropertySources().size()).isEqualTo(1);
        assertThat(environment.getPropertySources().get(0).getName())
                .isEqualTo(this.repository.getUri() + "/sub/application.yml");
        assertVersion(environment);
    }

    private void assertVersion(Environment environment) {
        String version = environment.getVersion();
        assertThat(version).as("version was null").isNotNull();
        assertTrue("version length was wrong", version.length() >= 40 && version.length() <= 64);
    }

    @Test
    public void nestedPattern() throws IOException {
        String uri = ConfigServerTestUtils.prepareLocalRepo("another-config-repo");
        this.repository.setUri(uri);
        this.repository.setSearchPaths(new String[] { "sub*" });
        this.repository.findOne("bar", "staging", "master");
        Environment environment = this.repository.findOne("bar", "staging", "master");
        assertThat(environment.getPropertySources().size()).isEqualTo(2);
        assertThat(environment.getPropertySources().get(0).getName())
                .isEqualTo(this.repository.getUri() + "/sub/application.yml");
        assertVersion(environment);
    }

    @Test
    public void branch() {
        this.repository.setBasedir(this.basedir);
        Environment environment = this.repository.findOne("bar", "staging", "raw");
        assertThat(environment.getPropertySources().size()).isEqualTo(2);
        assertThat(environment.getPropertySources().get(0).getName())
                .isEqualTo(this.repository.getUri() + "/bar.properties");
        assertVersion(environment);
    }

    @Test
    public void tag() {
        this.repository.setBasedir(this.basedir);
        Environment environment = this.repository.findOne("bar", "staging", "foo");
        assertThat(environment.getPropertySources().size()).isEqualTo(2);
        assertThat(environment.getPropertySources().get(0).getName())
                .isEqualTo(this.repository.getUri() + "/bar.properties");
        assertVersion(environment);
    }

    @Test
    public void basedir() {
        this.repository.setBasedir(this.basedir);
        this.repository.findOne("bar", "staging", "master");
        Environment environment = this.repository.findOne("bar", "staging", "master");
        assertThat(environment.getPropertySources().size()).isEqualTo(2);
        assertThat(environment.getPropertySources().get(0).getName())
                .isEqualTo(this.repository.getUri() + "/bar.properties");
        assertVersion(environment);
    }

    @Test
    public void basedirExists() throws Exception {
        assertThat(this.basedir.mkdirs()).isTrue();
        assertThat(new File(this.basedir, ".nothing").createNewFile()).isTrue();
        this.repository.setBasedir(this.basedir);
        this.repository.findOne("bar", "staging", "master");
        Environment environment = this.repository.findOne("bar", "staging", "master");
        assertThat(environment.getPropertySources().size()).isEqualTo(2);
        assertThat(environment.getPropertySources().get(0).getName())
                .isEqualTo(this.repository.getUri() + "/bar.properties");
        assertVersion(environment);
    }

    @Test
    public void uriWithHostOnly() throws Exception {
        this.repository.setUri("git://localhost");
        assertThat(this.repository.getUri()).isEqualTo("git://localhost/");
    }

    @Test
    public void uriWithHostAndPath() throws Exception {
        this.repository.setUri("git://localhost/foo/");
        assertThat(this.repository.getUri()).isEqualTo("git://localhost/foo");
    }

    @Test
    public void afterPropertiesSet_CloneOnStartTrue_CloneAndFetchCalled() throws Exception {
        Git mockGit = mock(Git.class);
        CloneCommand mockCloneCommand = mock(CloneCommand.class);

        when(mockCloneCommand.setURI(anyString())).thenReturn(mockCloneCommand);
        when(mockCloneCommand.setDirectory(any(File.class))).thenReturn(mockCloneCommand);

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri("http://somegitserver/somegitrepo");
        envRepository.setCloneOnStart(true);
        envRepository.afterPropertiesSet();
        verify(mockCloneCommand, times(1)).call();
    }

    @Test
    public void afterPropertiesSet_CloneOnStartFalse_CloneAndFetchNotCalled() throws Exception {
        Git mockGit = mock(Git.class);
        CloneCommand mockCloneCommand = mock(CloneCommand.class);

        when(mockCloneCommand.setURI(anyString())).thenReturn(mockCloneCommand);
        when(mockCloneCommand.setDirectory(any(File.class))).thenReturn(mockCloneCommand);

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri("http://somegitserver/somegitrepo");
        envRepository.afterPropertiesSet();
        verify(mockCloneCommand, times(0)).call();
        verify(mockGit, times(0)).fetch();
    }

    @Test
    public void afterPropertiesSet_CloneOnStartTrueWithFileURL_CloneAndFetchNotCalled() throws Exception {
        Git mockGit = mock(Git.class);
        CloneCommand mockCloneCommand = mock(CloneCommand.class);

        when(mockCloneCommand.setURI(anyString())).thenReturn(mockCloneCommand);
        when(mockCloneCommand.setDirectory(any(File.class))).thenReturn(mockCloneCommand);

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri("file://somefilesystem/somegitrepo");
        envRepository.setCloneOnStart(true);
        envRepository.afterPropertiesSet();
        verify(mockCloneCommand, times(0)).call();
        verify(mockGit, times(0)).fetch();
    }

    @Test
    public void shouldPullForcepullNotClean() throws Exception {
        Git git = mock(Git.class);
        StatusCommand statusCommand = mock(StatusCommand.class);
        Status status = mock(Status.class);
        Repository repository = mock(Repository.class);
        StoredConfig storedConfig = mock(StoredConfig.class);

        when(git.status()).thenReturn(statusCommand);
        when(git.getRepository()).thenReturn(repository);
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(status.isClean()).thenReturn(false);

        JGitEnvironmentRepository repo = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        repo.setForcePull(true);

        boolean shouldPull = repo.shouldPull(git);

        assertThat(shouldPull).as("shouldPull was false").isTrue();
    }

    @Test
    public void shouldPullNotClean() throws Exception {
        Git git = mock(Git.class);
        StatusCommand statusCommand = mock(StatusCommand.class);
        Status status = mock(Status.class);
        Repository repository = mock(Repository.class);
        StoredConfig storedConfig = mock(StoredConfig.class);

        when(git.status()).thenReturn(statusCommand);
        when(git.getRepository()).thenReturn(repository);
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(status.isClean()).thenReturn(false);

        JGitEnvironmentRepository repo = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());

        boolean shouldPull = repo.shouldPull(git);

        assertThat(shouldPull).as("shouldPull was true").isFalse();
    }

    @Test
    public void shouldPullClean() throws Exception {
        Git git = mock(Git.class);
        StatusCommand statusCommand = mock(StatusCommand.class);
        Status status = mock(Status.class);
        Repository repository = mock(Repository.class);
        StoredConfig storedConfig = mock(StoredConfig.class);

        when(git.status()).thenReturn(statusCommand);
        when(git.getRepository()).thenReturn(repository);
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(status.isClean()).thenReturn(true);

        JGitEnvironmentRepository repo = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());

        boolean shouldPull = repo.shouldPull(git);

        assertThat(shouldPull).as("shouldPull was false").isTrue();
    }

    @Test
    public void shouldNotRefresh() throws Exception {
        Git git = mock(Git.class);
        StatusCommand statusCommand = mock(StatusCommand.class);
        Status status = mock(Status.class);
        Repository repository = mock(Repository.class);
        StoredConfig storedConfig = mock(StoredConfig.class);

        when(git.status()).thenReturn(statusCommand);
        when(git.getRepository()).thenReturn(repository);
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(status.isClean()).thenReturn(true);

        JGitEnvironmentProperties properties = new JGitEnvironmentProperties();
        properties.setRefreshRate(2);

        JGitEnvironmentRepository repo = new JGitEnvironmentRepository(this.environment, properties);

        repo.setLastRefresh(System.currentTimeMillis() - 5000);

        boolean shouldPull = repo.shouldPull(git);

        assertThat(shouldPull).as("shouldPull was false").isTrue();

        repo.setRefreshRate(30);

        shouldPull = repo.shouldPull(git);

        assertThat(shouldPull).as("shouldPull was true").isFalse();
    }

    @Test
    public void shouldUpdateLastRefresh() throws Exception {
        Git git = mock(Git.class);
        StatusCommand statusCommand = mock(StatusCommand.class);
        Status status = mock(Status.class);
        Repository repository = mock(Repository.class);
        StoredConfig storedConfig = mock(StoredConfig.class);
        FetchCommand fetchCommand = mock(FetchCommand.class);
        FetchResult fetchResult = mock(FetchResult.class);

        when(git.status()).thenReturn(statusCommand);
        when(git.getRepository()).thenReturn(repository);
        when(fetchCommand.call()).thenReturn(fetchResult);
        when(git.fetch()).thenReturn(fetchCommand);
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(status.isClean()).thenReturn(true);

        JGitEnvironmentProperties properties = new JGitEnvironmentProperties();
        properties.setRefreshRate(1000);
        JGitEnvironmentRepository repo = new JGitEnvironmentRepository(this.environment, properties);

        repo.setLastRefresh(0);
        repo.fetch(git, "master");

        long timeDiff = System.currentTimeMillis() - repo.getLastRefresh();

        assertThat(timeDiff < 1000L).as("time difference (" + timeDiff + ") was longer than 1 second").isTrue();
    }

    @Test
    public void testFetchException() throws Exception {

        Git git = mock(Git.class);
        CloneCommand cloneCommand = mock(CloneCommand.class);
        MockGitFactory factory = new MockGitFactory(git, cloneCommand);
        this.repository.setGitFactory(factory);
        this.repository.setDeleteUntrackedBranches(true);

        // refresh()->shouldPull
        StatusCommand statusCommand = mock(StatusCommand.class);
        Status status = mock(Status.class);
        when(git.status()).thenReturn(statusCommand);

        Repository repository = stubbedRepo();
        when(git.getRepository()).thenReturn(repository);
        StoredConfig storedConfig = mock(StoredConfig.class);
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(status.isClean()).thenReturn(true);

        // refresh()->fetch
        FetchCommand fetchCommand = mock(FetchCommand.class);
        when(git.fetch()).thenReturn(fetchCommand);
        when(fetchCommand.setRemote(anyString())).thenReturn(fetchCommand);
        when(fetchCommand.call()).thenThrow(new InvalidRemoteException("invalid mock remote")); // here
        // is
        // our
        // exception
        // we
        // are
        // testing

        // refresh()->checkout
        CheckoutCommand checkoutCommand = mock(CheckoutCommand.class);
        // refresh()->checkout->containsBranch
        ListBranchCommand listBranchCommand = mock(ListBranchCommand.class);
        when(git.checkout()).thenReturn(checkoutCommand);
        when(git.branchList()).thenReturn(listBranchCommand);
        List<Ref> refs = new ArrayList<>();
        Ref ref = mock(Ref.class);
        refs.add(ref);
        when(ref.getName()).thenReturn("/master");
        when(listBranchCommand.call()).thenReturn(refs);

        // refresh()->merge
        MergeCommand mergeCommand = mock(MergeCommand.class);
        when(git.merge()).thenReturn(mergeCommand);
        when(mergeCommand.call()).thenThrow(new NotMergedException()); // here
        // is
        // our
        // exception
        // we
        // are
        // testing

        // refresh()->return
        // git.getRepository().findRef("HEAD").getObjectId().getName();
        Ref headRef = mock(Ref.class);
        when(this.database.getRef(anyString())).thenReturn(headRef);

        ObjectId newObjectId = ObjectId.fromRaw(new int[] { 1, 2, 3, 4, 5 });
        when(headRef.getObjectId()).thenReturn(newObjectId);

        SearchPathLocator.Locations locations = this.repository.getLocations("bar", "staging", null);
        assertThat(newObjectId.getName()).isEqualTo(locations.getVersion());

        verify(git, times(0)).branchDelete();
    }

    private Repository stubbedRepo() {
        return spy(new Repository(new BaseRepositoryBuilder()) {
            @Override
            public void create(boolean bare) throws IOException {

            }

            @Override
            public ObjectDatabase getObjectDatabase() {
                return null;
            }

            @Override
            public RefDatabase getRefDatabase() {
                return JGitEnvironmentRepositoryTests.this.database;
            }

            @Override
            public StoredConfig getConfig() {
                return null;
            }

            @Override
            public AttributesNodeProvider createAttributesNodeProvider() {
                return null;
            }

            @Override
            public void scanForRepoChanges() throws IOException {

            }

            @Override
            public void notifyIndexChanged(boolean internal) {

            }

            @Override
            public ReflogReader getReflogReader(String refName) throws IOException {
                return null;
            }
        });
    }

    @Test
    public void testMergeException() throws Exception {

        Git git = mock(Git.class);
        CloneCommand cloneCommand = mock(CloneCommand.class);
        MockGitFactory factory = new MockGitFactory(git, cloneCommand);
        this.repository.setGitFactory(factory);

        // refresh()->shouldPull
        StatusCommand statusCommand = mock(StatusCommand.class);
        Status status = mock(Status.class);
        when(git.status()).thenReturn(statusCommand);
        Repository repository = stubbedRepo();
        when(git.getRepository()).thenReturn(repository);
        StoredConfig storedConfig = mock(StoredConfig.class);
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(status.isClean()).thenReturn(true);

        // refresh()->fetch
        FetchCommand fetchCommand = mock(FetchCommand.class);
        FetchResult fetchResult = mock(FetchResult.class);
        when(git.fetch()).thenReturn(fetchCommand);
        when(fetchCommand.setRemote(anyString())).thenReturn(fetchCommand);
        when(fetchCommand.call()).thenReturn(fetchResult);
        when(fetchResult.getTrackingRefUpdates()).thenReturn(Collections.<TrackingRefUpdate>emptyList());

        // refresh()->checkout
        CheckoutCommand checkoutCommand = mock(CheckoutCommand.class);
        // refresh()->checkout->containsBranch
        ListBranchCommand listBranchCommand = mock(ListBranchCommand.class);
        when(git.checkout()).thenReturn(checkoutCommand);
        when(git.branchList()).thenReturn(listBranchCommand);
        List<Ref> refs = new ArrayList<>();
        Ref ref = mock(Ref.class);
        refs.add(ref);
        when(ref.getName()).thenReturn("/master");
        when(listBranchCommand.call()).thenReturn(refs);

        // refresh()->merge
        MergeCommand mergeCommand = mock(MergeCommand.class);
        when(git.merge()).thenReturn(mergeCommand);
        when(mergeCommand.call()).thenThrow(new NotMergedException()); // here is our
        // exception we
        // are testing

        // refresh()->return git.getRepository().findRef("HEAD").getObjectId().getName();
        Ref headRef = mock(Ref.class);
        when(this.database.getRef(anyString())).thenReturn(headRef);

        ObjectId newObjectId = ObjectId.fromRaw(new int[] { 1, 2, 3, 4, 5 });
        when(headRef.getObjectId()).thenReturn(newObjectId);

        SearchPathLocator.Locations locations = this.repository.getLocations("bar", "staging", "master");
        assertThat(newObjectId.getName()).isEqualTo(locations.getVersion());

        verify(git, times(0)).branchDelete();
    }

    @Test
    public void testRefreshWithoutFetch() throws Exception {
        Git git = mock(Git.class);

        CloneCommand cloneCommand = mock(CloneCommand.class);
        when(cloneCommand.setURI(anyString())).thenReturn(cloneCommand);
        when(cloneCommand.setDirectory(any(File.class))).thenReturn(cloneCommand);
        when(cloneCommand.call()).thenReturn(git);

        MockGitFactory factory = new MockGitFactory(git, cloneCommand);

        StatusCommand statusCommand = mock(StatusCommand.class);
        CheckoutCommand checkoutCommand = mock(CheckoutCommand.class);
        Status status = mock(Status.class);
        Repository repository = mock(Repository.class, Mockito.RETURNS_DEEP_STUBS);
        StoredConfig storedConfig = mock(StoredConfig.class);
        Ref ref = mock(Ref.class);
        ListBranchCommand listBranchCommand = mock(ListBranchCommand.class);
        FetchCommand fetchCommand = mock(FetchCommand.class);
        FetchResult fetchResult = mock(FetchResult.class);
        Ref branch1Ref = mock(Ref.class);

        when(git.branchList()).thenReturn(listBranchCommand);
        when(git.status()).thenReturn(statusCommand);
        when(git.getRepository()).thenReturn(repository);
        when(git.checkout()).thenReturn(checkoutCommand);
        when(git.fetch()).thenReturn(fetchCommand);
        when(git.merge()).thenReturn(mock(MergeCommand.class, Mockito.RETURNS_DEEP_STUBS));
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(checkoutCommand.call()).thenReturn(ref);
        when(listBranchCommand.call()).thenReturn(Arrays.asList(branch1Ref));
        when(fetchCommand.call()).thenReturn(fetchResult);
        when(branch1Ref.getName()).thenReturn("origin/master");
        when(status.isClean()).thenReturn(true);

        JGitEnvironmentRepository repo = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        repo.setGitFactory(factory);
        repo.setUri("http://somegitserver/somegitrepo");
        repo.setBasedir(this.basedir);

        // Set the refresh rate to 2 seconds and last update before 100ms. There should be
        // no remote repo fetch.
        repo.setLastRefresh(System.currentTimeMillis() - 100);
        repo.setRefreshRate(2);

        repo.refresh("master");

        // Verify no fetch but merge only.
        verify(git, times(0)).fetch();
        verify(git).merge();
    }

    @Test
    public void testResetHardException() throws Exception {

        Git git = mock(Git.class);
        CloneCommand cloneCommand = mock(CloneCommand.class);
        MockGitFactory factory = new MockGitFactory(git, cloneCommand);
        this.repository.setGitFactory(factory);

        // refresh()->shouldPull
        StatusCommand statusCommand = mock(StatusCommand.class);
        Status status = mock(Status.class);
        when(git.status()).thenReturn(statusCommand);
        Repository repository = stubbedRepo();
        when(git.getRepository()).thenReturn(repository);
        StoredConfig storedConfig = mock(StoredConfig.class);
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(status.isClean()).thenReturn(true).thenReturn(false);

        // refresh()->fetch
        FetchCommand fetchCommand = mock(FetchCommand.class);
        FetchResult fetchResult = mock(FetchResult.class);
        when(git.fetch()).thenReturn(fetchCommand);
        when(fetchCommand.setRemote(anyString())).thenReturn(fetchCommand);
        when(fetchCommand.call()).thenReturn(fetchResult);
        when(fetchResult.getTrackingRefUpdates()).thenReturn(Collections.<TrackingRefUpdate>emptyList());

        // refresh()->checkout
        CheckoutCommand checkoutCommand = mock(CheckoutCommand.class);
        // refresh()->checkout->containsBranch
        ListBranchCommand listBranchCommand = mock(ListBranchCommand.class);
        when(git.checkout()).thenReturn(checkoutCommand);
        when(git.branchList()).thenReturn(listBranchCommand);
        List<Ref> refs = new ArrayList<>();
        Ref ref = mock(Ref.class);
        refs.add(ref);
        when(ref.getName()).thenReturn("/master");
        when(listBranchCommand.call()).thenReturn(refs);

        // refresh()->merge
        MergeCommand mergeCommand = mock(MergeCommand.class);
        when(git.merge()).thenReturn(mergeCommand);
        when(mergeCommand.call()).thenThrow(new NotMergedException()); // here
        // is
        // our
        // exception
        // we
        // are
        // testing

        // refresh()->hardReset
        ResetCommand resetCommand = mock(ResetCommand.class);
        when(git.reset()).thenReturn(resetCommand);
        when(resetCommand.call()).thenReturn(ref);

        // refresh()->return
        // git.getRepository().findRef("HEAD").getObjectId().getName();
        Ref headRef = mock(Ref.class);
        when(this.database.getRef(anyString())).thenReturn(headRef);

        ObjectId newObjectId = ObjectId.fromRaw(new int[] { 1, 2, 3, 4, 5 });
        when(headRef.getObjectId()).thenReturn(newObjectId);

        SearchPathLocator.Locations locations = this.repository.getLocations("bar", "staging", "master");
        assertThat(newObjectId.getName()).isEqualTo(locations.getVersion());

        verify(git, times(0)).branchDelete();
    }

    @Test
    public void shouldDeleteBaseDirWhenCloneFails() throws Exception {
        Git mockGit = mock(Git.class);
        CloneCommand mockCloneCommand = mock(CloneCommand.class);

        when(mockCloneCommand.setURI(anyString())).thenReturn(mockCloneCommand);
        when(mockCloneCommand.setDirectory(any(File.class))).thenReturn(mockCloneCommand);
        when(mockCloneCommand.call()).thenThrow(new TransportException("failed to clone"));

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri("http://somegitserver/somegitrepo");
        envRepository.setBasedir(this.basedir);

        try {
            envRepository.findOne("bar", "staging", "master");
        } catch (Exception ex) {
            // expected - ignore
        }

        assertThat(this.basedir.listFiles().length > 0).as("baseDir should be deleted when clone fails").isFalse();
    }

    @Test
    public void usernamePasswordShouldSetCredentials() throws Exception {
        Git mockGit = mock(Git.class);
        MockCloneCommand mockCloneCommand = new MockCloneCommand(mockGit);

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri("git+ssh://git@somegitserver/somegitrepo");
        envRepository.setBasedir(new File("./mybasedir"));
        final String username = "someuser";
        final String password = "mypassword";
        envRepository.setUsername(username);
        envRepository.setPassword(password);
        envRepository.setCloneOnStart(true);
        envRepository.afterPropertiesSet();

        assertTrue(mockCloneCommand.getCredentialsProvider() instanceof UsernamePasswordCredentialsProvider);

        CredentialsProvider provider = mockCloneCommand.getCredentialsProvider();
        CredentialItem.Username usernameCredential = new CredentialItem.Username();
        CredentialItem.Password passwordCredential = new CredentialItem.Password();
        assertThat(provider.supports(usernameCredential)).isTrue();
        assertThat(provider.supports(passwordCredential)).isTrue();

        provider.get(new URIish(), usernameCredential);
        assertThat(username).isEqualTo(usernameCredential.getValue());
        provider.get(new URIish(), passwordCredential);
        assertThat(password).isEqualTo(String.valueOf(passwordCredential.getValue()));
    }

    @Test
    public void passphraseShouldSetCredentials() throws Exception {
        final String passphrase = "mypassphrase";
        Git mockGit = mock(Git.class);
        MockCloneCommand mockCloneCommand = new MockCloneCommand(mockGit);

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri("git+ssh://git@somegitserver/somegitrepo");
        envRepository.setBasedir(new File("./mybasedir"));
        envRepository.setPassphrase(passphrase);
        envRepository.setCloneOnStart(true);
        envRepository.afterPropertiesSet();

        assertThat(mockCloneCommand.hasPassphraseCredentialsProvider()).isTrue();

        CredentialsProvider provider = mockCloneCommand.getCredentialsProvider();
        assertThat(provider.isInteractive()).isFalse();

        CredentialItem.StringType stringCredential = new CredentialItem.StringType(
                PassphraseCredentialsProvider.PROMPT, true);

        assertThat(provider.supports(stringCredential)).isTrue();
        provider.get(new URIish(), stringCredential);
        assertThat(passphrase).isEqualTo(stringCredential.getValue());
    }

    @Test
    public void gitCredentialsProviderFactoryCreatesPassphraseProvider() throws Exception {
        final String passphrase = "mypassphrase";
        final String gitUri = "git+ssh://git@somegitserver/somegitrepo";
        Git mockGit = mock(Git.class);
        MockCloneCommand mockCloneCommand = new MockCloneCommand(mockGit);

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri(gitUri);
        envRepository.setBasedir(new File("./mybasedir"));
        envRepository.setPassphrase(passphrase);
        envRepository.setCloneOnStart(true);
        envRepository.afterPropertiesSet();

        assertThat(mockCloneCommand.hasPassphraseCredentialsProvider()).isTrue();

        CredentialsProvider provider = mockCloneCommand.getCredentialsProvider();
        assertThat(provider.isInteractive()).isFalse();

        CredentialItem.StringType stringCredential = new CredentialItem.StringType(
                PassphraseCredentialsProvider.PROMPT, true);

        assertThat(provider.supports(stringCredential)).isTrue();
        provider.get(new URIish(), stringCredential);
        assertThat(passphrase).isEqualTo(stringCredential.getValue());

    }

    @Test
    public void gitCredentialsProviderFactoryCreatesUsernamePasswordProvider() throws Exception {
        Git mockGit = mock(Git.class);
        MockCloneCommand mockCloneCommand = new MockCloneCommand(mockGit);
        final String username = "someuser";
        final String password = "mypassword";

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri("git+ssh://git@somegitserver/somegitrepo");
        envRepository.setBasedir(new File("./mybasedir"));
        envRepository.setUsername(username);
        envRepository.setPassword(password);
        envRepository.setCloneOnStart(true);
        envRepository.afterPropertiesSet();

        assertTrue(mockCloneCommand.getCredentialsProvider() instanceof UsernamePasswordCredentialsProvider);

        CredentialsProvider provider = mockCloneCommand.getCredentialsProvider();
        CredentialItem.Username usernameCredential = new CredentialItem.Username();
        CredentialItem.Password passwordCredential = new CredentialItem.Password();
        assertThat(provider.supports(usernameCredential)).isTrue();
        assertThat(provider.supports(passwordCredential)).isTrue();

        provider.get(new URIish(), usernameCredential);
        assertThat(username).isEqualTo(usernameCredential.getValue());
        provider.get(new URIish(), passwordCredential);
        assertThat(password).isEqualTo(String.valueOf(passwordCredential.getValue()));
    }

    @Test
    public void gitCredentialsProviderFactoryCreatesAwsCodeCommitProvider() throws Exception {
        Git mockGit = mock(Git.class);
        MockCloneCommand mockCloneCommand = new MockCloneCommand(mockGit);
        final String awsUri = "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test";

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri(awsUri);
        envRepository.setCloneOnStart(true);
        envRepository.afterPropertiesSet();

        assertTrue(mockCloneCommand.getCredentialsProvider() instanceof AwsCodeCommitCredentialProvider);
    }

    @Test
    public void gitCredentialsProviderFactoryCreatesSkipSslValidationProvider() throws Exception {
        Git mockGit = mock(Git.class);
        MockCloneCommand mockCloneCommand = new MockCloneCommand(mockGit);
        final String username = "someuser";
        final String password = "mypassword";

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri("https://somegitserver/somegitrepo");
        envRepository.setBasedir(new File("./mybasedir"));
        envRepository.setUsername(username);
        envRepository.setPassword(password);
        envRepository.setSkipSslValidation(true);
        envRepository.setCloneOnStart(true);
        envRepository.afterPropertiesSet();

        CredentialsProvider provider = mockCloneCommand.getCredentialsProvider();

        assertThat(provider instanceof GitSkipSslValidationCredentialsProvider).isTrue();

        CredentialItem.Username usernameCredential = new CredentialItem.Username();
        CredentialItem.Password passwordCredential = new CredentialItem.Password();
        assertThat(provider.supports(usernameCredential)).isTrue();
        assertThat(provider.supports(passwordCredential)).isTrue();

        provider.get(new URIish(), usernameCredential);
        assertThat(username).isEqualTo(usernameCredential.getValue());
        provider.get(new URIish(), passwordCredential);
        assertThat(password).isEqualTo(String.valueOf(passwordCredential.getValue()));
    }

    @Test
    public void shouldPrintStacktraceIfDebugEnabled() throws Exception {
        final Log mockLogger = mock(Log.class);
        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties()) {
            @Override
            public void afterPropertiesSet() throws Exception {
                this.logger = mockLogger;
            }
        };
        envRepository.afterPropertiesSet();
        when(mockLogger.isDebugEnabled()).thenReturn(true);

        envRepository.warn("", new RuntimeException());

        verify(mockLogger).warn(eq(""));
        verify(mockLogger).debug(eq("Stacktrace for: "), any(RuntimeException.class));

        int numberOfInvocations = mockingDetails(mockLogger).getInvocations().size();
        assertThat(numberOfInvocations).as("should call isDebugEnabled warn and debug").isEqualTo(3);
    }

    @Test
    public void shouldSetTransportConfigCallbackOnCloneAndFetch() throws Exception {
        Git mockGit = mock(Git.class);
        FetchCommand fetchCommand = mock(FetchCommand.class);
        when(mockGit.fetch()).thenReturn(fetchCommand);
        when(fetchCommand.call()).thenReturn(mock(FetchResult.class));

        CloneCommand mockCloneCommand = mock(CloneCommand.class);
        when(mockCloneCommand.setURI(anyString())).thenReturn(mockCloneCommand);
        when(mockCloneCommand.setDirectory(any(File.class))).thenReturn(mockCloneCommand);

        TransportConfigCallback configCallback = mock(TransportConfigCallback.class);
        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mockCloneCommand));
        envRepository.setUri("http://somegitserver/somegitrepo");
        envRepository.setTransportConfigCallback(configCallback);
        envRepository.setCloneOnStart(true);

        envRepository.afterPropertiesSet();
        verify(mockCloneCommand, times(1)).setTransportConfigCallback(configCallback);

        envRepository.fetch(mockGit, "master");
        verify(fetchCommand, times(1)).setTransportConfigCallback(configCallback);
    }

    @Test
    public void shouldSetRemoveBranchesFlagToFetchCommand() throws Exception {
        Git mockGit = mock(Git.class);
        FetchCommand fetchCommand = mock(FetchCommand.class);

        when(mockGit.fetch()).thenReturn(fetchCommand);
        when(fetchCommand.call()).thenReturn(mock(FetchResult.class));

        JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment,
                new JGitEnvironmentProperties());
        envRepository.setGitFactory(new MockGitFactory(mockGit, mock(CloneCommand.class)));
        envRepository.setUri("http://somegitserver/somegitrepo");
        envRepository.setDeleteUntrackedBranches(true);

        envRepository.fetch(mockGit, "master");

        verify(fetchCommand, times(1)).setRemoveDeletedRefs(true);
        verify(fetchCommand, times(1)).call();
    }

    @Test
    public void shouldHandleExceptionWhileRemovingBranches() throws Exception {
        Git git = mock(Git.class);
        CloneCommand cloneCommand = mock(CloneCommand.class);
        MockGitFactory factory = new MockGitFactory(git, cloneCommand);
        this.repository.setGitFactory(factory);
        this.repository.setDeleteUntrackedBranches(true);

        // refresh()->shouldPull
        StatusCommand statusCommand = mock(StatusCommand.class);
        Status status = mock(Status.class);
        when(git.status()).thenReturn(statusCommand);
        Repository repository = stubbedRepo();
        when(git.getRepository()).thenReturn(repository);
        StoredConfig storedConfig = mock(StoredConfig.class);
        when(repository.getConfig()).thenReturn(storedConfig);
        when(storedConfig.getString("remote", "origin", "url")).thenReturn("http://example/git");
        when(statusCommand.call()).thenReturn(status);
        when(status.isClean()).thenReturn(true);

        // refresh()->fetch
        FetchCommand fetchCommand = mock(FetchCommand.class);
        FetchResult fetchResult = mock(FetchResult.class);

        TrackingRefUpdate trackingRefUpdate = mock(TrackingRefUpdate.class);
        Collection<TrackingRefUpdate> trackingRefUpdates = Collections.singletonList(trackingRefUpdate);

        when(git.fetch()).thenReturn(fetchCommand);
        when(fetchCommand.setRemote(anyString())).thenReturn(fetchCommand);
        when(fetchCommand.call()).thenReturn(fetchResult);
        when(fetchResult.getTrackingRefUpdates()).thenReturn(trackingRefUpdates);

        // refresh()->deleteBranch
        ReceiveCommand receiveCommand = mock(ReceiveCommand.class);
        when(trackingRefUpdate.asReceiveCommand()).thenReturn(receiveCommand);
        when(receiveCommand.getType()).thenReturn(ReceiveCommand.Type.DELETE);
        when(trackingRefUpdate.getLocalName()).thenReturn("refs/remotes/origin/feature/deletedBranchFromOrigin");

        DeleteBranchCommand deleteBranchCommand = mock(DeleteBranchCommand.class);
        when(git.branchDelete()).thenReturn(deleteBranchCommand);
        when(deleteBranchCommand.setBranchNames(eq("feature/deletedBranchFromOrigin")))
                .thenReturn(deleteBranchCommand);
        when(deleteBranchCommand.setForce(true)).thenReturn(deleteBranchCommand);
        when(deleteBranchCommand.call()).thenThrow(new NotMergedException()); // here
        // is
        // our
        // exception
        // we
        // are
        // testing

        // refresh()->checkout
        CheckoutCommand checkoutCommand = mock(CheckoutCommand.class);
        // refresh()->checkout->containsBranch
        ListBranchCommand listBranchCommand = mock(ListBranchCommand.class);
        when(git.checkout()).thenReturn(checkoutCommand);
        when(git.branchList()).thenReturn(listBranchCommand);
        List<Ref> refs = new ArrayList<>();
        Ref ref = mock(Ref.class);
        refs.add(ref);
        when(ref.getName()).thenReturn("/master");
        when(listBranchCommand.call()).thenReturn(refs);

        // refresh()->merge
        MergeResult mergeResult = mock(MergeResult.class);
        MergeResult.MergeStatus mergeStatus = mock(MergeResult.MergeStatus.class);
        MergeCommand mergeCommand = mock(MergeCommand.class);
        when(git.merge()).thenReturn(mergeCommand);
        when(mergeCommand.call()).thenReturn(mergeResult);
        when(mergeResult.getMergeStatus()).thenReturn(mergeStatus);
        when(mergeStatus.isSuccessful()).thenReturn(true);

        // refresh()->return
        // git.getRepository().findRef("HEAD").getObjectId().getName();
        Ref headRef = mock(Ref.class);
        when(this.database.getRef(anyString())).thenReturn(headRef);

        ObjectId newObjectId = ObjectId.fromRaw(new int[] { 1, 2, 3, 4, 5 });
        when(headRef.getObjectId()).thenReturn(newObjectId);

        SearchPathLocator.Locations locations = this.repository.getLocations("bar", "staging", "master");
        assertThat(newObjectId.getName()).isEqualTo(locations.getVersion());

        verify(deleteBranchCommand).setBranchNames(eq("feature/deletedBranchFromOrigin"));
        verify(deleteBranchCommand).setForce(true);
        verify(deleteBranchCommand).call();
    }

    class MockCloneCommand extends CloneCommand {

        private Git mockGit;

        MockCloneCommand(Git mockGit) {
            this.mockGit = mockGit;
        }

        @Override
        public Git call() throws GitAPIException, InvalidRemoteException {
            return this.mockGit;
        }

        public boolean hasPassphraseCredentialsProvider() {
            return this.credentialsProvider instanceof PassphraseCredentialsProvider;
        }

        public CredentialsProvider getCredentialsProvider() {
            return this.credentialsProvider;
        }

    }

    class MockGitFactory extends JGitEnvironmentRepository.JGitFactory {

        private Git mockGit;

        private CloneCommand mockCloneCommand;

        MockGitFactory(Git mockGit, CloneCommand mockCloneCommand) {
            this.mockGit = mockGit;
            this.mockCloneCommand = mockCloneCommand;
        }

        @Override
        public Git getGitByOpen(File file) throws IOException {
            return this.mockGit;
        }

        @Override
        public CloneCommand getCloneCommandByCloneRepository() {
            return this.mockCloneCommand;
        }

    }

}