com.smartling.cms.gateway.client.CmsGatewayClientTest.java Source code

Java tutorial

Introduction

Here is the source code for com.smartling.cms.gateway.client.CmsGatewayClientTest.java

Source

/*
 * Copyright 2014 Smartling, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this work except in compliance with the License.
 * You may obtain a copy of the License in the LICENSE file, or at:
 *
 * http://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 com.smartling.cms.gateway.client;

import static org.hamcrest.core.StringContains.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import javax.websocket.CloseReason;
import javax.websocket.Session;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.entity.EntityBuilder;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.message.BasicStatusLine;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import com.smartling.cms.gateway.client.api.model.ResponseStatus;
import com.smartling.cms.gateway.client.command.CommandChannelHandler;
import com.smartling.cms.gateway.client.command.ErrorResponse;
import com.smartling.cms.gateway.client.command.GetHtmlCommand;
import com.smartling.cms.gateway.client.command.GetResourceCommand;
import com.smartling.cms.gateway.client.command.ReconnectStrategy;
import com.smartling.cms.gateway.client.internal.CommandChannelSession;
import com.smartling.cms.gateway.client.internal.CommandChannelTransport;
import com.smartling.cms.gateway.client.internal.CommandParser;
import com.smartling.cms.gateway.client.upload.FileUpload;

public class CmsGatewayClientTest {
    private static final URI STUB_COMMAND_CHANNEL_URI = URI.create("some:command_channel_uri");
    private static final URI STUB_UPLOAD_CHANNEL_URI = URI
            .create("some_upload_channel_uri?projectId=some+project+id");

    @Mock
    private CommandChannelTransport commandChannelTransport;

    @Mock
    private CommandChannelHandler handler;

    @Mock
    private CommandChannelSession commandChannel;

    @Mock
    private CloseableHttpAsyncClient uploadChannel;

    @Spy
    private CommandParser commandParser;

    @Spy
    private ReconnectStrategy reconnectStrategy;

    @Mock
    private Future<HttpResponse> futureHttpResponse;

    @Mock
    private Session session;

    private CmsGatewayClient client;

    @Before
    public void setup() throws Exception {
        MockitoAnnotations.initMocks(this);

        when(commandChannelTransport.connectToServer(anyObject(), any(URI.class))).thenReturn(commandChannel);
        doNothing().when(reconnectStrategy).delay();

        client = new CmsGatewayClient(STUB_COMMAND_CHANNEL_URI, STUB_UPLOAD_CHANNEL_URI, commandChannelTransport,
                uploadChannel, commandParser, reconnectStrategy);
    }

    @Test
    public void opensWebsocketConnectionOnConnect() throws Exception {
        client.connect(handler);

        verify(commandChannelTransport).connectToServer(anyObject(), eq(STUB_COMMAND_CHANNEL_URI));
    }

    @Test
    public void startsHttpClientOnConnect() throws Exception {
        client.connect(handler);

        verify(uploadChannel).start();
    }

    @Test(expected = CmsGatewayClientException.class)
    public void throwsExceptionOnConnectWhenUnhandledExceptionInCommandChannel() throws Exception {
        doThrow(RuntimeException.class).when(commandChannelTransport).connectToServer(any(), any(URI.class));

        client.connect(handler);
    }

    private CmsGatewayClient.CommandChannelTransportEndpoint getCommandChannelTransportEndpoint() throws Exception {
        ArgumentCaptor<CmsGatewayClient.CommandChannelTransportEndpoint> captor = ArgumentCaptor
                .forClass(CmsGatewayClient.CommandChannelTransportEndpoint.class);

        client.connect(handler);

        verify(commandChannelTransport).connectToServer(captor.capture(), any(URI.class));
        return captor.getValue();
    }

    @Test
    public void callsCommandHandlerOnDisconnectWhenTransportClosedNormally() throws Exception {
        CloseReason reason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, null);

        getCommandChannelTransportEndpoint().onClose(null, reason);

        verify(handler, only()).onDisconnect();
    }

    @Test
    public void autoReconnectsCommandChannelWhenTransportClosedAbnormally() throws Exception {
        CloseReason reason = new CloseReason(CloseReason.CloseCodes.CLOSED_ABNORMALLY, null);

        getCommandChannelTransportEndpoint().onClose(null, reason);

        verify(commandChannelTransport, times(2)).connectToServer(anyObject(), eq(STUB_COMMAND_CHANNEL_URI));
        verifyZeroInteractions(handler);
    }

    @Test
    public void disconnectsAfterFailedAttemptToReconnectCommandChannel() throws Exception {
        CloseReason reason = new CloseReason(CloseReason.CloseCodes.CLOSED_ABNORMALLY, null);

        CmsGatewayClient.CommandChannelTransportEndpoint transportEndpoint = getCommandChannelTransportEndpoint();

        reset(commandChannelTransport);
        when(commandChannelTransport.connectToServer(anyObject(), any(URI.class)))
                .thenThrow(mock(IOException.class)).thenThrow(mock(CmsGatewayClientException.class))
                .thenThrow(mock(RuntimeException.class));

        transportEndpoint.onClose(null, reason);

        verify(commandChannelTransport, times(3)).connectToServer(anyObject(), eq(STUB_COMMAND_CHANNEL_URI));
        verify(handler, times(3)).onError(any(Exception.class));
        verify(handler).onDisconnect();
        verifyNoMoreInteractions(handler);
    }

    @Test
    public void reconnectsAfterFailedAttemptOnIOException() throws Exception {
        CloseReason reason = new CloseReason(CloseReason.CloseCodes.CLOSED_ABNORMALLY, null);

        CmsGatewayClient.CommandChannelTransportEndpoint transportEndpoint = getCommandChannelTransportEndpoint();

        reset(commandChannelTransport);
        when(commandChannelTransport.connectToServer(anyObject(), any(URI.class)))
                .thenThrow(mock(IOException.class)).thenThrow(mock(IOException.class)).thenReturn(commandChannel);

        transportEndpoint.onClose(null, reason);

        verify(commandChannelTransport, times(3)).connectToServer(anyObject(), eq(STUB_COMMAND_CHANNEL_URI));
        verify(handler, never()).onDisconnect();
    }

    @Test
    public void forwardsTransportErrorToCommandHandlerThenCloseSessionWithAbnormallyCode() throws Exception {
        Throwable e = mock(Throwable.class);

        getCommandChannelTransportEndpoint().onError(session, e);

        ArgumentCaptor<CloseReason> reasonCaptor = ArgumentCaptor.forClass(CloseReason.class);
        verify(handler, only()).onError(e);
        verify(session).close(reasonCaptor.capture());
        assertThat(reasonCaptor.getValue().getCloseCode(),
                is((CloseReason.CloseCode) CloseReason.CloseCodes.CLOSED_ABNORMALLY));
    }

    @Test
    public void forwardsParserErrorToCommandHandler() throws Exception {
        doThrow(Throwable.class).when(commandParser).parse(anyString());

        getCommandChannelTransportEndpoint().onMessage(null, null);

        verify(handler, only()).onError(any(Throwable.class));
    }

    @Test
    public void callsCommandHandlerOnConnectWhenAuthenticatedSuccessfully() throws Exception {
        getCommandChannelTransportEndpoint().onMessage("{\"cmd\": \"authenticationSuccess\"}", null);

        verify(handler, only()).onConnect();
    }

    @Test
    public void closesWebsocketAndCallsOnErrorWhenAuthenticationFailed() throws Exception {
        getCommandChannelTransportEndpoint().onMessage("{\"cmd\": \"authenticationError\"}", session);

        ArgumentCaptor<CloseReason> reasonCaptor = ArgumentCaptor.forClass(null);
        verify(session).close(reasonCaptor.capture());
        assertThat(reasonCaptor.getValue().getCloseCode(),
                is((CloseReason.CloseCode) CloseReason.CloseCodes.NORMAL_CLOSURE));

        verify(handler, only()).onError(any(CmsGatewayClientAuthenticationException.class));
    }

    @Test
    public void closesWebsocketAndCallsOnErrorOnDisconnectCommand() throws Exception {
        getCommandChannelTransportEndpoint().onMessage("{\"cmd\": \"disconnect\"}", session);

        ArgumentCaptor<CloseReason> reasonCaptor = ArgumentCaptor.forClass(null);
        verify(session).close(reasonCaptor.capture());
        assertThat(reasonCaptor.getValue().getCloseCode(),
                is((CloseReason.CloseCode) CloseReason.CloseCodes.NORMAL_CLOSURE));

        verify(handler, only()).onError(any(CmsGatewayClientException.class));
    }

    @Test
    public void forwardsGetHtmlCommandToCommandHandler() throws Exception {
        getCommandChannelTransportEndpoint()
                .onMessage("{\"cmd\":\"getHtml\", \"rid\":\"some request id\", \"uri\":\"some file uri\"}", null);

        GetHtmlCommand command = getGetHtmlCommand();
        assertThat(command.getId(), is("some request id"));
        assertThat(command.getUri(), is("some file uri"));
    }

    private GetHtmlCommand getGetHtmlCommand() {
        ArgumentCaptor<GetHtmlCommand> captor = ArgumentCaptor.forClass(null);

        verify(handler, only()).onGetHtmlCommand(captor.capture());

        return captor.getValue();
    }

    @Test
    public void forwardsGetResourceCommandtoCommandHandler() throws Exception {
        getCommandChannelTransportEndpoint().onMessage(
                "{\"cmd\":\"getResource\", \"rid\":\"some request id\", \"uri\":\"some file uri\"}", null);

        GetResourceCommand command = getGetResourceCommand();
        assertThat(command.getId(), is("some request id"));
        assertThat(command.getUri(), is("some file uri"));
    }

    private GetResourceCommand getGetResourceCommand() {
        ArgumentCaptor<GetResourceCommand> captor = ArgumentCaptor.forClass(null);

        verify(handler, only()).onGetResourceCommand(captor.capture());

        return captor.getValue();
    }

    @Test
    public void sendsErrorResponseJsonToCommandChannel() throws Exception {
        ErrorResponse error = new ErrorResponse(new GetResourceCommand("some request id", "some file uri"));
        String expectedJson = error.toJSONString();

        getConnectedClient().send(error);

        verify(commandChannel).send(expectedJson);
    }

    private CmsGatewayClient getConnectedClient() throws Exception {
        client.connect(handler);
        return client;
    }

    @Test
    public void postsFileContentsToUploadChannel() throws Exception {
        FileUpload response = makeFileUploadResponse("some request id", "some file uri", "some file body");

        client.send(response);

        HttpPost httpPost = getHttpPostFromUploadChannel();
        assertThat(httpPost.getURI(), is(URI.create(STUB_UPLOAD_CHANNEL_URI + "&rid=some+request+id")));
        assertThat(IOUtils.toString(httpPost.getEntity().getContent()), is("some file body"));
    }

    private FileUpload makeFileUploadResponse(String requestId, String fileUri, String bodyText) {
        GetResourceCommand request = new GetResourceCommand(requestId, fileUri);
        FileUpload fileUpload = new FileUpload(request);
        fileUpload.setContentStream(IOUtils.toInputStream(bodyText));
        return fileUpload;
    }

    private HttpPost getHttpPostFromUploadChannel() {
        ArgumentCaptor<HttpPost> argPost = ArgumentCaptor.forClass(HttpPost.class);
        verify(uploadChannel).execute(argPost.capture(), Mockito.<FutureCallback<HttpResponse>>any());
        return argPost.getValue();
    }

    private HttpResponse mockHttpResponse(int statusCode, String bodyJson) {
        HttpResponse response = mock(HttpResponse.class);
        when(response.getStatusLine())
                .thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, statusCode, "Status " + statusCode));
        HttpEntity entity = EntityBuilder.create().setContentType(ContentType.APPLICATION_JSON).setText(bodyJson)
                .build();
        when(response.getEntity()).thenReturn(entity);
        return response;
    }

    private Future<ResponseStatus<Void>> onUploadResponse(int statusCode, String responseJson) throws Exception {
        HttpResponse response = mockHttpResponse(statusCode, responseJson);

        when(futureHttpResponse.get()).thenReturn(response);
        when(uploadChannel.execute(any(HttpUriRequest.class), Mockito.<FutureCallback<HttpResponse>>any()))
                .thenReturn(futureHttpResponse);

        return client.send(makeFileUploadResponse("some request id", "some file uri", "some body content"));
    }

    @Test
    public void returnsSuccessStatusOnSuccessfulUpload() throws Exception {
        Future<ResponseStatus<Void>> future = onUploadResponse(200, "{\"response\":{\"code\":\"SUCCESS\"}}");

        assertThat(future.get().getCode(), is("SUCCESS"));
    }

    @Test
    public void returnsErrorStatusOnFailedUpload() throws Exception {
        Future<ResponseStatus<Void>> future = onUploadResponse(200,
                "{\"response\":{\"code\":\"GENERAL_ERROR\",\"messages\":[\"some error message\"]}}");

        assertThat(future.get().getCode(), is("GENERAL_ERROR"));
        assertThat(future.get().getMessages().get(0), is("some error message"));
    }

    @Test
    public void failsOnExceptionInUpload() throws Exception {
        Future<ResponseStatus<Void>> future = onUploadResponse(500,
                "{\"response\":{\"code\":\"GENERAL_ERROR\",\"messages\":[\"some error message\"]}}");

        try {
            future.get();
        } catch (ExecutionException e) {
            CmsGatewayClientException clientException = (CmsGatewayClientException) e.getCause();
            assertThat(clientException.getMessage(), containsString("some error message"));
        }
    }

    @Test
    public void closesCommandChannelOnClose() throws Exception {
        getConnectedClient().close();

        verify(uploadChannel).close();
        verify(commandChannel).close();
    }

    @Test(expected = IllegalStateException.class)
    public void throwsWhenTryingToConnectClosedClient() throws Exception {
        CmsGatewayClient connectedClient = getConnectedClient();
        connectedClient.close();

        connectedClient.connect(handler);
    }

    @Test(expected = IllegalStateException.class)
    public void throwsWhenTryingToSendOnClosedClient() throws Exception {
        CmsGatewayClient connectedClient = getConnectedClient();
        connectedClient.close();

        connectedClient.send(mock(ErrorResponse.class));
    }
}