package com.atlassian.bitbucket.internal.mirroring.mirror.client;

import com.atlassian.bitbucket.auth.AuthenticationSystemException;
import com.atlassian.bitbucket.internal.mirroring.MirroringConstants;
import com.atlassian.bitbucket.internal.mirroring.MirroringRequest;
import com.atlassian.bitbucket.internal.mirroring.SimpleMirroringRequest;
import com.atlassian.bitbucket.internal.mirroring.mirror.*;
import com.atlassian.bitbucket.internal.mirroring.mirror.auth.DelegatedAuthenticationFailureException;
import com.atlassian.bitbucket.internal.mirroring.mirror.nav.MirroringUrl;
import com.atlassian.bitbucket.internal.mirroring.mirror.rest.RestSimpleRsaSshKey;
import com.atlassian.bitbucket.internal.mirroring.rest.auth.RestApplicationUserWithPermissions;
import com.atlassian.bitbucket.internal.mirroring.ssh.encoding.DefaultPublicKeyEncodingHelper;
import com.atlassian.bitbucket.internal.mirroring.user.ApplicationUserWithPermissions;
import com.atlassian.bitbucket.internal.mirroring.user.SimpleApplicationUserWithPermissions;
import com.atlassian.bitbucket.mirroring.mirror.AnalyticsSettings;
import com.atlassian.bitbucket.mockito.MockitoUtils;
import com.atlassian.bitbucket.permission.EffectivePermission;
import com.atlassian.bitbucket.permission.SimpleEffectiveGlobalPermission;
import com.atlassian.bitbucket.rest.RestErrorMessage;
import com.atlassian.bitbucket.rest.RestErrors;
import com.atlassian.bitbucket.rest.permission.RestEffectiveGlobalPermission;
import com.atlassian.bitbucket.rest.user.RestApplicationUser;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.httpclient.api.Request;
import com.atlassian.httpclient.api.Response;
import com.atlassian.httpclient.api.ResponsePromise;
import com.atlassian.httpclient.api.ResponsePromises;
import com.atlassian.util.concurrent.Promise;
import com.atlassian.util.concurrent.Promises;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.net.MediaType;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.util.encoders.Base64;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.net.URI;
import java.security.PublicKey;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.atlassian.bitbucket.mirroring.mirror.UpstreamServerType.BITBUCKET_SERVER;
import static com.atlassian.bitbucket.permission.Permission.LICENSED_USER;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class DefaultServerUpstreamClientTest extends UpstreamClientTestBase<DefaultServerUpstreamClient> {

    public static final byte[] SAMPLE_ENCODED_RSA_KEY = new byte[] {
            48, -126, 1, 32, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13, 1, 1, 1, 5, 0, 3, -126, 1, 13, 0, 48, -126, 1,
            8, 2, -126, 1, 1, 0, -63, 14, -124, -54, 12, -127, 117, -100, 11, -128, 124, 109, -40, 32, -41, -54, -103,
            -91, 103, 35, -83, 125, 108, 15, 76, -61, 19, 125, 125, -67, 62, 98, -22, 112, 106, -20, 44, -103, -105,
            99, -16, 57, 69, -84, 4, 52, -92, 114, -10, -58, -102, 60, -8, 44, -54, 105, 124, 118, -7, -29, 72, 58,
            -104, -44, 126, 108, -20, -3, -54, -21, 107, 112, 51, -41, 116, 113, -77, 4, -42, 114, 92, -13, 20, -19,
            -14, 74, -62, 66, -95, -44, 91, -123, 25, -30, -76, 95, -115, -124, -57, -19, 122, 5, -13, -46, -26, 7, 17,
            -49, 13, 98, 51, -6, 79, 73, 2, 3, 46, -76, 115, -21, -23, 46, 36, 112, -34, -101, 10, 123, -105, -2, -48,
            55, -64, 10, -64, 39, -65, -124, 92, -76, 89, 1, 50, -108, 2, 109, -98, 48, -14, 113, 69, 96, 61, 55, -75,
            -112, -28, -91, 7, 125, -113, -118, -113, 53, -16, -65, 76, 93, 84, 19, 103, 59, -86, 75, -91, -65, 85,
            -32, 123, 125, -66, 125, -53, 42, -79, 5, -99, -65, -11, 73, 105, -31, -26, 23, 99, 78, 121, -46, -19, 82,
            -93, -6, -91, 24, 54, 30, 38, 18, -33, -104, -66, 45, -44, 114, 27, 81, -35, -62, -37, 70, 16, -75, 6, -108,
            -19, 110, 100, 110, -8, 7, 28, 81, 57, -42, -127, 18, 23, 122, 84, 87, 95, 9, -37, -20, 99, -20, -16, 59,
            -19, -65, 17, -17, 95, 27, -12, 97, 2, 1, 35};
    public static final String SAMPLE_RSA_KEY_BASE_64 = Base64.toBase64String(SAMPLE_ENCODED_RSA_KEY);

    private static final String BASEURL = "https://www.example.bitbucket.com";
    private static final String SERVER_ID = "server-id";
    private static final String UPSTREAM_ID = "upstream-id";

    @Mock
    private MirroringConfig config;
    @Mock
    private ApplicationPropertiesService propertiesService;
    @Spy
    private DefaultPublicKeyEncodingHelper publicKeyCodec;

    @Before
    public void setUp() {
        when(upstream.getBaseUrl()).thenReturn(BASEURL);
        when(upstream.getType()).thenReturn(BITBUCKET_SERVER);
        when(upstream.getId()).thenReturn(UPSTREAM_ID);

        when(config.getUpstreamRepositoryRequestPageSize()).thenReturn(PAGE_SIZE);

        when(propertiesService.getServerId()).thenReturn(SERVER_ID);

        requestBuilder = mock(Request.Builder.class, MockitoUtils.returnSelf(Request.Builder.class));

        //noinspection unchecked
        when(jsonRenderer.render(anyObject(), anyMap())).thenAnswer(
                invocationOnMock -> toJson(invocationOnMock.getArguments()[0]));
        when(validator.validate(anyObject())).thenReturn(Collections.<ConstraintViolation<Object>>emptySet());

        promise = ResponsePromises.toResponsePromise(Promises.promise(response));

        client = new DefaultServerUpstreamClient(config, httpClient, i18nService, jsonRenderer, mirroringNavBuilder,
                publicKeyCodec, propertiesService, requestFactory, upstream, validator);
    }

    @Test
    public void testAuthenticateWithPublicKeyFailsBecauseOfInvalidCredentials() throws Exception {
        expectException(DelegatedAuthenticationFailureException.class,
                "bitbucket.mirroring.authentication.delegation.failed");

        mockAuthRequestResponse(toJson(
                new RestErrors(new RestErrorMessage("credentials", "Invalid credentials supplied"))), 401);

        authenticateWithPublicKey();
    }

    @Test
    public void testAuthenticateWithPublicKeyFailsBecauseOfInvalidResponse() throws Exception {
        expectException(AuthenticationSystemException.class,
                "bitbucket.mirroring.authentication.delegation.failure.upstream");

        mockAuthRequestResponse("GA^^&*$&*%^&RB^^$*$*AGE", 200);

        authenticateWithPublicKey();
    }

    @Test
    public void testAuthenticateWithPublicKeyFailsBecauseOfPermissionDenied() throws Exception {
        expectException(DelegatedAuthenticationFailureException.class,
                "bitbucket.mirroring.authentication.delegation.failure.misconfiguration");

        mockAuthRequestResponse("Permission denied", 401);

        authenticateWithPublicKey();
    }

    @Test
    public void testAuthenticateWithPublicKeyFailsBecauseOfServerError() throws Exception {
        expectException(AuthenticationSystemException.class,
                "bitbucket.mirroring.authentication.delegation.failure.unexpected");

        mockAuthRequestResponse("Internal server error", 500);

        authenticateWithPublicKey();
    }

    @Test
    public void testAuthenticateWithUsernameAndPassword() throws Exception {
        mockAuthRequestResponse(toJson(new RestApplicationUserWithPermissions(
                new SimpleApplicationUserWithPermissions(RestApplicationUser.RESPONSE_EXAMPLE,
                        Lists.<EffectivePermission>newArrayList(
                                new RestEffectiveGlobalPermission(new SimpleEffectiveGlobalPermission(LICENSED_USER))
                        )))), 200);

        authenticateWithUsernamePassword();

        verify(requestBuilder).setEntity("{\"credentials\":{\"username\":\"username\",\"password\":\"password\"}}");
    }

    @Test
    public void testAuthenticateWithUsernameAndPasswordFailsBecauseOfInvalidCredentials() throws Exception {
        expectException(DelegatedAuthenticationFailureException.class,
                "bitbucket.mirroring.authentication.delegation.failed");

        mockAuthRequestResponse(toJson(
                new RestErrors(new RestErrorMessage("credentials", "Invalid credentials supplied"))), 401);

        authenticateWithUsernamePassword();
    }

    @Test
    public void testAuthenticateWithUsernameAndPasswordFailsBecauseOfInvalidResponse() throws Exception {
        expectException(AuthenticationSystemException.class,
                "bitbucket.mirroring.authentication.delegation.failure.upstream");

        mockAuthRequestResponse("GA^^&*$&*%^&RB^^$*$*AGE", 200);

        authenticateWithUsernamePassword();
    }

    @Test
    public void testAuthenticateWithUsernameAndPasswordFailsBecauseOfPermissionDenied() throws Exception {
        expectException(DelegatedAuthenticationFailureException.class,
                "bitbucket.mirroring.authentication.delegation.failure.misconfiguration");

        mockAuthRequestResponse("Permission denied", 401);

        authenticateWithUsernamePassword();
    }

    @Test
    public void testAuthenticateWithUsernameAndPasswordFailsBecauseOfServerError() throws Exception {
        expectException(AuthenticationSystemException.class,
                "bitbucket.mirroring.authentication.delegation.failure.unexpected");

        mockAuthRequestResponse("Internal server error", 500);

        authenticateWithUsernamePassword();
    }

    @Test
    public void testAuthenticationWithPublicKey() throws Exception {
        mockAuthRequestResponse(toJson(new RestApplicationUserWithPermissions(
                new SimpleApplicationUserWithPermissions(RestApplicationUser.RESPONSE_EXAMPLE,
                        Lists.<EffectivePermission>newArrayList(
                                new RestEffectiveGlobalPermission(new SimpleEffectiveGlobalPermission(LICENSED_USER))
                        )))), 200);

        authenticateWithPublicKey();

        verify(requestBuilder).setEntity(String.format(
                "{\"credentials\":{\"username\":\"username\",\"publicKey\":{\"keyText\":\"%s\",\"algorithm\":\"RSA\"}}}",
                SAMPLE_RSA_KEY_BASE_64));
    }

    @Test
    public void testGetAnalyticsSettings() throws Exception {
        mockJwtGetResponse(mirroringNavBuilder.serverUpstream(upstream).analyticsSettings(),
                "{\"canCollectAnalytics\":true,\"serviceEntitlementNumber\": \"12345\"}", 200);

        AnalyticsSettings analyticsSettings = client.getAnalyticsSettings().claim();

        assertNotNull(analyticsSettings);
        assertEquals("12345", analyticsSettings.getSupportEntitlementNumber());
        assertTrue(analyticsSettings.canCollectAnalytics());
    }

    @Test
    public void testIsMirrorInstalled() throws Exception {
        String absoluteUrl = mirroringNavBuilder.serverUpstream(upstream)
                .mirrors(PageUtils.newRequest(0, 250))
                .getAbsolute();
        mockGetResponse(absoluteUrl,
                "{\"size\": 1, \"limit\": 250, \"isLastPage\": true, \"values\": [" +
                        jsonMirrorServer("server-id", "https://baseurl", "mirror-name", true) +
                        "], \"start\": 0}",
                200);

        assertTrue(client.isMirrorInstalled().claim());
    }

    @Test
    public void testIsMirrorInstalledNotFound() throws Exception {
        String absoluteUrl = mirroringNavBuilder.serverUpstream(upstream)
                .mirrors(PageUtils.newRequest(0, 250))
                .getAbsolute();
        mockGetResponse(absoluteUrl,
                "{\"size\": 1, \"limit\": 250, \"isLastPage\": true, \"values\": [" +
                        jsonMirrorServer("mirror", "https://baseurl", "mirror-name", true) +
                        "], \"start\": 0}",
                200);

        boolean enabled = client.isMirrorInstalled().claim();

        assertFalse(enabled);
    }

    @Test(expected = UpstreamServerRequestFailedException.class)
    public void testIsMirrorInstalled404() throws Exception {
        String absoluteUrl = mirroringNavBuilder.serverUpstream(upstream)
                .mirrors(PageUtils.newRequest(0, 250))
                .getAbsolute();
        mockGetResponse(absoluteUrl, "", 404);

        client.isMirrorInstalled().claim();
    }

    @Test(expected = UpstreamServerRequestFailedException.class)
    public void testIsMirrorInstalledValidationIssue() throws Exception {
        String absoluteUrl = mirroringNavBuilder.serverUpstream(upstream)
                .mirrors(PageUtils.newRequest(0, 250))
                .getAbsolute();
        mockGetResponse(absoluteUrl,
                "{\"size\": 1, \"limit\": 250, \"isLastPage\": true, \"values\": [" +
                        jsonMirrorServer("mirror", "https://baseurl", "mirror-name", true) +
                        "], \"start\": 0}",
                200);
        //noinspection unchecked
        ConstraintViolation<Object> violation = mock(ConstraintViolation.class);
        when(validator.validate(any())).thenReturn(ImmutableSet.of(violation));

        client.isMirrorInstalled().claim();
    }

    @Test
    public void testRegisterWithMaster() throws Exception {
        mockPostResponse(getRegistrationUrl(), "", 204);

        client.registerAsMirror(createMirroringRequest()).claim();

        verify(httpClient).newRequest(getRegistrationUrl());
    }

    @Test
    public void testInvalidRegisterWithMaster() throws Exception {
        SimpleMirroringRequest request = new SimpleMirroringRequest.Builder()
                .addonDescriptor(URI.create("https://mirror.example.com/descriptor"))
                .mirrorBaseUrl("https://mirror.example.com")
                .createdDate(new Date())
                .mirrorServer(StringUtils.repeat('a', MirroringConstants.MAX_MIRROR_ID_LENGTH + 1), "display-name")
                .product("product", "version")
                .build();

        when(validator.validate(same(request), anyVararg())).thenThrow(new ConstraintViolationException(Collections.emptySet()));

        Promise<Void> promise = client.registerAsMirror(request);
        // only set the expected exception to ensure its the Promise and not registerAsMirror with the failure
        expectedException.expect(MirrorRegistrationFailedException.class);
        try {
            promise.claim();
        } finally {
            verify(validator).validate(same(request), anyVararg());
        }
    }

    @Test(expected = MirrorRegistrationFailedException.class)
    public void testRegisterWithMasterReturns404() throws Exception {
        mockPostResponse(getRegistrationUrl(), "Not found", 404);

        client.registerAsMirror(createMirroringRequest()).claim();
    }

    @Test(expected = MirrorRegistrationFailedException.class)
    public void testRegisterWithMasterReturns500() throws Exception {
        mockPostResponse(getRegistrationUrl(), "Internal server error", 500);

        client.registerAsMirror(createMirroringRequest()).claim();
    }

    @Test
    public void testRegisterWithMasterRethrowsImplementationException() throws Exception {
        String message = "some network issue";
        NullPointerException innerException = new NullPointerException(message);

        expectedException.expect(new TypeSafeMatcher<MirrorRegistrationFailedException>() {
            @Override
            protected boolean matchesSafely(MirrorRegistrationFailedException e) {
                return "bitbucket.mirroring.upstream.registration.error".equals(e.getMessage())
                        && e.getCause() == innerException;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText(
                        "Exception thrown within HTTP client is rethrown as a MirrorRegistrationFailedException");
            }
        });

        promise = ResponsePromises.toResponsePromise(Promises.rejected(innerException));

        mockPostResponse(getRegistrationUrl(), null, -1);

        client.registerAsMirror(createMirroringRequest()).claim();
    }

    @Test
    public void testGetRepositories() throws ExecutionException, InterruptedException {
        PageRequest pageRequest = PageUtils.newRequest(0, PAGE_SIZE);

        String jsonProject = jsonProject(9, "name9", "key9", false);
        String responseEntity = jsonPage(0, PAGE_SIZE, true,
                jsonRepo(1, "name1", "slug1", "git", false, jsonProject),
                jsonRepo(2, "name2", "slug2", "git", true, jsonProject));

        mockJwtGetResponse(mirroringNavBuilder.serverUpstream(upstream).repositories(pageRequest), responseEntity, 200);

        Stream<ExternalRepository> repositories = client.getRepositories();

        assertNotNull(repositories);
        List<ExternalRepository> values = ImmutableList.copyOf(repositories.iterator());
        assertEquals(2, values.size());
        assertRepo(1, "name1", "slug1", "git", false, values.get(0));
        assertRepo(2, "name2", "slug2", "git", true, values.get(1));
    }

    @Test
    public void testGetRepositoriesWithMultiplePageRequests() throws ExecutionException, InterruptedException {
        String jsonProject = jsonProject(9, "name9", "key9", false);
        String page1ResponseEntity = jsonPage(0, PAGE_SIZE, false,
                jsonRepo(1, "name1", "slug1", "git", false, jsonProject),
                jsonRepo(2, "name2", "slug2", "git", true, jsonProject));
        String page2ResponseEntity = jsonPage(2, PAGE_SIZE, true,
                jsonRepo(3, "name3", "slug3", "git", false, jsonProject),
                jsonRepo(4, "name4", "slug4", "git", true, jsonProject));

        PageRequest pageRequest1 = PageUtils.newRequest(0, PAGE_SIZE);
        Response response1 = mock(Response.class);
        mockResponse(response1, page1ResponseEntity, 200);
        ResponsePromise responsePromise1 = ResponsePromises.toResponsePromise(Promises.promise(response1));

        PageRequest pageRequest2 = PageUtils.newRequest(2, PAGE_SIZE);
        Response response2 = mock(Response.class);
        mockResponse(response2, page2ResponseEntity, 200);
        ResponsePromise responsePromise2 = ResponsePromises.toResponsePromise(Promises.promise(response2));

        when(requestBuilder.get()).thenReturn(responsePromise1).thenReturn(responsePromise2);
        mockJwtRequest(mirroringNavBuilder.serverUpstream(upstream).repositories(pageRequest1));
        mockJwtRequest(mirroringNavBuilder.serverUpstream(upstream).repositories(pageRequest2));

        Stream<ExternalRepository> repositories = client.getRepositories();

        assertNotNull(repositories);
        List<ExternalRepository> values = ImmutableList.copyOf(repositories.iterator());
        assertEquals(4, values.size());
        assertRepo(1, "name1", "slug1", "git", false, values.get(0));
        assertRepo(2, "name2", "slug2", "git", true, values.get(1));
        assertRepo(3, "name3", "slug3", "git", false, values.get(2));
        assertRepo(4, "name4", "slug4", "git", true, values.get(3));
    }

    @Test(expected = UnauthorizedException.class)
    public void testGetRepositoriesNotAuthorized() throws ExecutionException, InterruptedException {
        mockJwtRequest(mirroringNavBuilder.serverUpstream(upstream).repositories(PageUtils.newRequest(0, PAGE_SIZE)));
        when(requestBuilder.get()).thenReturn(promise);
        mockResponse(response, "", 401);

        ImmutableList.copyOf(client.getRepositories().iterator());
    }

    @Test(expected = UpstreamServerRequestFailedException.class)
    public void testGetRepositoriesNotFound() throws ExecutionException, InterruptedException {
        mockJwtRequest(mirroringNavBuilder.serverUpstream(upstream).repositories(PageUtils.newRequest(0, PAGE_SIZE)));
        when(requestBuilder.get()).thenReturn(promise);
        mockResponse(response, "", 404);
        when(response.getContentType()).thenReturn(MediaType.JSON_UTF_8.toString());

        ImmutableList.copyOf(client.getRepositories().iterator());
    }

    @Test(expected = RepositoryRequestRateExceededException.class)
    public void testGetRepositoriesRateLimitExceeded() throws ExecutionException, InterruptedException {
        mockJwtRequest(mirroringNavBuilder.serverUpstream(upstream).repositories(PageUtils.newRequest(0, PAGE_SIZE)));
        when(requestBuilder.get()).thenReturn(promise);
        mockResponse(response, "", 429);

        client.getRepositories().collect(Collectors.toList());
    }

    @Test(expected = UpstreamServerRequestFailedException.class)
    public void testGetRepositoriesWithNonPageJsonResponse() throws ExecutionException, InterruptedException {
        PageRequest pageRequest = PageUtils.newRequest(0, PAGE_SIZE);

        // a valid JSON object but not a page object
        mockJwtGetResponse(mirroringNavBuilder.serverUpstream(upstream).repositories(pageRequest),
                "{\"progress\":{\"message\":\"Starting plugin framework\",\"percentage\":35},\"state\":\"STARTING\"}", 200);

        client.getRepositories();
    }

    @Test(expected = UpstreamServerRequestFailedException.class)
    public void testGetRepositoriesWithObjectJsonResponse() throws ExecutionException, InterruptedException {
        PageRequest pageRequest = PageUtils.newRequest(0, PAGE_SIZE);

        // a valid JSON object but not a page object
        mockJwtGetResponse(mirroringNavBuilder.serverUpstream(upstream).repositories(pageRequest), "[]", 200);

        client.getRepositories();
    }

    @Test(expected = UpstreamServerRequestFailedException.class)
    public void testGetRepositoriesWithNullJson() throws ExecutionException, InterruptedException {
        PageRequest pageRequest = PageUtils.newRequest(0, PAGE_SIZE);

        mockJwtGetResponse(mirroringNavBuilder.serverUpstream(upstream).repositories(pageRequest), "null", 200);

        client.getRepositories();
    }

    @Test
    public void testUploadSshKey() throws Exception {
        MirroringUrl keysUrl = mirroringNavBuilder.serverUpstream(upstream).sshKeys();
        mockJwtPostResponse(keysUrl, "", 201);

        client.uploadSshKey("TestKey").claim();

        //noinspection unchecked
        verify(jsonRenderer).render(isA(RestSimpleRsaSshKey.class), anyMap());
        verify(requestFactory).newRequest(eq(upstream), eq(keysUrl));
    }

    @Test(expected = NullPointerException.class)
    public void testUploadNullSshKey() {
        //noinspection ConstantConditions
        client.uploadSshKey(null);
    }

    @Test(expected = MirrorKeyUploadFailedException.class)
    public void testUploadSshKeyFailed() throws Exception {
        mockJwtPostResponse(mirroringNavBuilder.serverUpstream(upstream).sshKeys(), "", 500);

        client.uploadSshKey("TestKey").claim();
    }

    @Test
    public void testGetRepositoryNotFound() throws ExecutionException, InterruptedException {
        mockJwtRequest(mirroringNavBuilder.upstream(upstream).repositoryById("1"));
        when(requestBuilder.get()).thenReturn(promise);
        mockResponse(response, "", 404);
        when(response.getContentType()).thenReturn(MediaType.JSON_UTF_8.toString());

        assertNull(client.getRepository("1").claim());
    }

    @Test(expected = UpstreamServerRequestFailedException.class)
    public void testGetRepositoryNotFoundNotJsonContentType() throws ExecutionException, InterruptedException {
        mockJwtRequest(mirroringNavBuilder.upstream(upstream).repositoryById("1"));
        when(requestBuilder.get()).thenReturn(promise);
        mockResponse(response, "", 404);
        when(response.getContentType()).thenReturn(MediaType.HTML_UTF_8.toString());

        client.getRepository("1").claim();
    }

    @Test(expected = UpstreamServerRequestFailedException.class)
    public void testGetRepositoryNotFoundWithInvalidContentType() throws ExecutionException, InterruptedException {
        mockJwtRequest(mirroringNavBuilder.upstream(upstream).repositoryById("1"));
        when(requestBuilder.get()).thenReturn(promise);
        mockResponse(response, "", 404);
        when(response.getContentType()).thenReturn("%^£&$%&^£%$");

        client.getRepository("1").claim();
    }

    @Test
    public void testNewScmHttpRequest() {
        String relativePath = "relativePath";
        assertEquals(client.newRequest(relativePath), client.newScmHttpRequest(relativePath));
    }

    private void authenticateWithPublicKey() throws InterruptedException, ExecutionException {
        Promise<ApplicationUserWithPermissions> promise = client.authenticateForUser("username", mockPublicKey());
        assertNotNull(promise);
        ApplicationUserWithPermissions user = promise.claim();
        assertNotNull(user);
    }

    private void authenticateWithUsernamePassword() throws Exception {
        Promise<ApplicationUserWithPermissions> promise = client.authenticateForUser("username", "password");
        assertNotNull(promise);
        ApplicationUserWithPermissions user = promise.claim();
        assertNotNull(user);
    }

    private String getRegistrationUrl() {
        return mirroringNavBuilder.serverUpstream(upstream).mirrorRequests().getAbsolute();
    }

    private String jsonPage(int start, int limit, boolean lastPage, String... values) {
        return "{\"start\": " + start +
                ", \"limit\": " + limit +
                ", \"size\": " + values.length +
                ", \"isLastPage\": " + lastPage +
                ", \"values\": [" + StringUtils.join(values, ",") + "]" +
                "}";
    }

    @Override
    @SuppressWarnings("Duplicates")
    protected void assertRepo(Integer id, String name, String slug, String scmId, boolean isPublic, ExternalRepository actual) {
        assertEquals(String.valueOf(id), actual.getId());
        assertEquals(name, actual.getName());
        assertEquals(slug, actual.getSlug());
        assertEquals(scmId, actual.getScmId());
        assertEquals(isPublic, actual.isPublic());
    }

    @Override
    protected String jsonProject(Object id, String name, String key, boolean isPublic) {
        return "{\"id\":" + id +
                ", \"name\": \"" + name + "\"" +
                ", \"key\": \"" + key + "\"" +
                ", \"public\": " + isPublic +
                "}";
    }

    @Override
    protected String jsonRepo(Object id, String name, String slug, String scmId, boolean isPublic, String jsonProject) {
        return "{\"id\":" + id +
                ", \"name\": \"" + name + "\"" +
                ", \"slug\": \"" + slug + "\"" +
                ", \"scmId\": \"" + scmId + "\"" +
                ", \"project\": " + jsonProject +
                ", \"public\": " + isPublic +
                "}";
    }

    private String jsonMirrorServer(String id, String baseUrl, String name, boolean enabled) {
        return "{\"id\": \"" + id + "\"" +
                ", \"baseUrl\": \"" + baseUrl + "\"" +
                ", \"name\": \"" + name + "\"" +
                ", \"productType\": \"Bitbucket Mirror\"" +
                ", \"productVersion\": \"version\"" +
                ", \"enabled\": " + enabled +
                "}";
    }

    private MirroringRequest createMirroringRequest() {
        return new SimpleMirroringRequest.Builder()
                .addonDescriptor(URI.create("https://mirror.example.com/descriptor"))
                .mirrorBaseUrl("https://mirror.example.com")
                .createdDate(new Date())
                .mirrorServer("mirror-id", "display-name")
                .product("product", "version")
                .build();
    }

    private ResponsePromise mockAuthRequestResponse(String responseEntity, int statusCode) throws Exception {
        return mockJwtPostResponse(mirroringNavBuilder.serverUpstream(upstream).authenticate(),
                responseEntity, statusCode);
    }

    private PublicKey mockPublicKey() {
        PublicKey publicKey = mock(PublicKey.class);
        when(publicKey.getAlgorithm()).thenReturn("RSA");
        when(publicKey.getEncoded()).thenReturn(SAMPLE_ENCODED_RSA_KEY);
        return publicKey;
    }
}