package com.englishtown.bitbucket.hook; import com.atlassian.bitbucket.hook.repository.RepositoryHookContext; import com.atlassian.bitbucket.i18n.I18nService; import com.atlassian.bitbucket.repository.Repository; import com.atlassian.bitbucket.repository.RepositoryService; import com.atlassian.bitbucket.scm.CommandErrorHandler; import com.atlassian.bitbucket.scm.CommandExitHandler; import com.atlassian.bitbucket.scm.CommandOutputHandler; import com.atlassian.bitbucket.scm.ScmService; import com.atlassian.bitbucket.scm.git.command.GitCommand; import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder; import com.atlassian.bitbucket.setting.Settings; import com.atlassian.bitbucket.setting.SettingsValidationErrors; import com.atlassian.sal.api.pluginsettings.PluginSettings; import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; /** * Unit tests for {@link MirrorRepositoryHook} */ @RunWith(MockitoJUnitRunner.class) public class MirrorRepositoryHookTest { private MirrorRepositoryHook hook; private GitScmCommandBuilder builder; @Mock private ScmService scmService; @Mock private GitCommand<String> cmd; @Mock private ScheduledExecutorService executor; @Mock private PasswordEncryptor passwordEncryptor; @Mock private SettingsReflectionHelper settingsReflectionHelper; @Mock private PluginSettingsFactory pluginSettingsFactory; @Mock private PluginSettings pluginSettings; @Mock private RepositoryService repositoryService; private final String mirrorRepoUrlHttp = "https://bitbucket-mirror.englishtown.com/scm/test/test.git"; private final String mirrorRepoUrlSsh = "ssh://git@bitbucket-mirror.englishtown.com/scm/test/test.git"; private final String username = "test-user"; private final String password = "test-password"; private final String repository = "https://test-user:test-password@bitbucket-mirror.englishtown.com/scm/test/test.git"; @Captor ArgumentCaptor<Runnable> argumentCaptor; @Before public void setup() { builder = mock(GitScmCommandBuilder.class); when(builder.command(anyString())).thenReturn(builder); when(builder.argument(anyString())).thenReturn(builder); when(builder.errorHandler(any(CommandErrorHandler.class))).thenReturn(builder); when(builder.exitHandler(any(CommandExitHandler.class))).thenReturn(builder); when(builder.<String>build(any(CommandOutputHandler.class))).thenReturn(cmd); doReturn(builder).when(scmService).createBuilder(any()); when(pluginSettingsFactory.createSettingsForKey(anyString())).thenReturn(pluginSettings); hook = new MirrorRepositoryHook(scmService, mock(I18nService.class), executor, passwordEncryptor , settingsReflectionHelper, pluginSettingsFactory, repositoryService); } @Test public void testPostReceive() throws Exception { when(passwordEncryptor.decrypt(anyString())).thenReturn(password); Repository repo = mock(Repository.class); when(repo.getName()).thenReturn("test"); hook.postReceive(buildContext(repo), new ArrayList<>()); verifyExecutor(); } @Test public void testEmptyRepositoriesNotMirrored() { Repository repo = mock(Repository.class); when(repositoryService.isEmpty(repo)).thenReturn(true); hook.postReceive(buildContext(repo), new ArrayList<>()); verify(executor, never()).submit(Matchers.<Runnable>any()); } @Test public void testRunMirrorCommand_Retries() throws Exception { when(scmService.createBuilder(any())).thenThrow(new RuntimeException("Intentional unit test exception")); MirrorRepositoryHook hook = new MirrorRepositoryHook(scmService, mock(I18nService.class), executor, passwordEncryptor, settingsReflectionHelper, pluginSettingsFactory, repositoryService); MirrorRepositoryHook.MirrorSettings ms = new MirrorRepositoryHook.MirrorSettings(); ms.mirrorRepoUrl = mirrorRepoUrlHttp; ms.username = username; ms.password = password; hook.runMirrorCommand(ms, mock(Repository.class)); verify(executor).submit(argumentCaptor.capture()); Runnable runnable = argumentCaptor.getValue(); runnable.run(); verify(executor, times(1)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); runnable = argumentCaptor.getValue(); runnable.run(); verify(executor, times(2)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); runnable = argumentCaptor.getValue(); runnable.run(); verify(executor, times(3)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); runnable = argumentCaptor.getValue(); runnable.run(); verify(executor, times(4)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); runnable = argumentCaptor.getValue(); runnable.run(); // Make sure it is only called 5 times runnable.run(); runnable.run(); runnable.run(); verify(executor, times(4)).schedule(argumentCaptor.capture(), anyInt(), any(TimeUnit.class)); } private void verifyExecutor() throws Exception { verify(executor).submit(argumentCaptor.capture()); Runnable runnable = argumentCaptor.getValue(); runnable.run(); verify(builder, times(1)).command(eq("push")); verify(builder, times(1)).argument(eq("--prune")); verify(builder, times(1)).argument(eq(repository)); verify(builder, times(1)).argument(eq("+refs/heads/*:refs/heads/*")); verify(builder, times(1)).argument(eq("+refs/tags/*:refs/tags/*")); verify(cmd, times(1)).call(); } @Test public void testGetAuthenticatedUrl() throws Exception { String result = hook.getAuthenticatedUrl(mirrorRepoUrlHttp, username, password); assertEquals(repository, result); } @Test public void testValidate() throws Exception { Settings settings = mock(Settings.class); Map<String, Object> map = new HashMap<String, Object>(); map.put(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL + "0", ""); when(settings.asMap()).thenReturn(map); when(settings.getString(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL + "0"), eq(""))) .thenThrow(new RuntimeException("Intentional unit test exception")) .thenReturn("") .thenReturn(mirrorRepoUrlHttp) .thenReturn("invalid uri") .thenReturn("http://should-not:have-user@bitbucket-mirror.englishtown.com/scm/test/test.git") .thenReturn("ssh://user@bitbucket-mirror.englishtown.com/scm/test/test.git") .thenReturn(mirrorRepoUrlSsh) .thenReturn(mirrorRepoUrlHttp); when(settings.getString(eq(MirrorRepositoryHook.SETTING_USERNAME + "0"), eq(""))) .thenReturn("") .thenReturn("") .thenReturn("") .thenReturn(username); when(settings.getString(eq(MirrorRepositoryHook.SETTING_PASSWORD + "0"), eq(""))) .thenReturn("") .thenReturn("") .thenReturn("") .thenReturn(password); Repository repo = mock(Repository.class); SettingsValidationErrors errors; errors = mock(SettingsValidationErrors.class); hook.validate(settings, errors, repo); verify(errors, times(1)).addFormError(anyString()); verify(errors, never()).addFieldError(anyString(), anyString()); errors = mock(SettingsValidationErrors.class); hook.validate(settings, errors, repo); verify(errors, never()).addFormError(anyString()); verify(errors).addFieldError(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL + "0"), anyString()); verify(errors, never()).addFieldError(eq(MirrorRepositoryHook.SETTING_USERNAME + "0"), anyString()); verify(errors, never()).addFieldError(eq(MirrorRepositoryHook.SETTING_PASSWORD + "0"), anyString()); errors = mock(SettingsValidationErrors.class); hook.validate(settings, errors, repo); verify(errors, never()).addFormError(anyString()); verify(errors, never()).addFieldError(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL + "0"), anyString()); verify(errors).addFieldError(eq(MirrorRepositoryHook.SETTING_USERNAME + "0"), anyString()); verify(errors).addFieldError(eq(MirrorRepositoryHook.SETTING_PASSWORD + "0"), anyString()); errors = mock(SettingsValidationErrors.class); hook.validate(settings, errors, repo); verify(errors, never()).addFormError(anyString()); verify(errors, never()).addFieldError(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL + "0"), anyString()); verify(errors, never()).addFieldError(anyString(), anyString()); errors = mock(SettingsValidationErrors.class); hook.validate(settings, errors, repo); verify(errors, never()).addFormError(anyString()); verify(errors).addFieldError(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL + "0"), anyString()); verify(errors, never()).addFieldError(eq(MirrorRepositoryHook.SETTING_USERNAME + "0"), anyString()); verify(errors, never()).addFieldError(eq(MirrorRepositoryHook.SETTING_PASSWORD + "0"), anyString()); errors = mock(SettingsValidationErrors.class); hook.validate(settings, errors, repo); verify(errors, never()).addFormError(anyString()); verify(errors, never()).addFieldError(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL + "0"), anyString()); verify(errors, never()).addFieldError(eq(MirrorRepositoryHook.SETTING_USERNAME + "0"), anyString()); verify(errors, never()).addFieldError(eq(MirrorRepositoryHook.SETTING_PASSWORD + "0"), anyString()); errors = mock(SettingsValidationErrors.class); hook.validate(settings, errors, repo); verify(errors, never()).addFormError(anyString()); verify(errors, never()).addFieldError(anyString(), anyString()); when(passwordEncryptor.isEncrypted(anyString())).thenReturn(true); errors = mock(SettingsValidationErrors.class); hook.validate(settings, errors, repo); verify(errors, never()).addFormError(anyString()); verify(errors, never()).addFieldError(anyString(), anyString()); } private RepositoryHookContext buildContext(Repository repo) { RepositoryHookContext context = mock(RepositoryHookContext.class); Settings settings = defaultSettings(); when(context.getSettings()).thenReturn(settings); when(context.getRepository()).thenReturn(repo); return context; } private Settings defaultSettings() { Map<String, Object> map = new HashMap<String, Object>(); map.put(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL, ""); Settings settings = mock(Settings.class); when(settings.asMap()).thenReturn(map); when(settings.getString(eq(MirrorRepositoryHook.SETTING_MIRROR_REPO_URL), eq(""))).thenReturn(mirrorRepoUrlHttp); when(settings.getString(eq(MirrorRepositoryHook.SETTING_USERNAME), eq(""))).thenReturn(username); when(settings.getString(eq(MirrorRepositoryHook.SETTING_PASSWORD), eq(""))).thenReturn(password); return settings; } }