com.twitter.hbc.httpclient.BasicClientTest.java Source code

Java tutorial

Introduction

Here is the source code for com.twitter.hbc.httpclient.BasicClientTest.java

Source

/**
 * Copyright 2013 Twitter, Inc.
 * 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
 * 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.twitter.hbc.httpclient;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.twitter.hbc.BasicReconnectionManager;
import com.twitter.hbc.RateTracker;
import com.twitter.hbc.ReconnectionManager;
import com.twitter.hbc.core.HttpConstants;
import com.twitter.hbc.core.HttpHosts;
import com.twitter.hbc.core.endpoint.RawEndpoint;
import com.twitter.hbc.core.event.EventType;
import com.twitter.hbc.core.processor.HosebirdMessageProcessor;
import com.twitter.hbc.httpclient.auth.Authentication;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

public class BasicClientTest {

    private HttpClient mockClient;
    private HttpResponse mockResponse;
    private HttpEntity mockHttpEntity;
    private StatusLine mockStatusLine;
    private ReconnectionManager mockReconnectionManager;
    private HosebirdMessageProcessor mockProcessor;
    private Authentication mockAuth;
    private RateTracker mockRateTracker;

    private InputStream mockInputStream;

    private ClientConnectionManager mockConnectionManager;

    private final ExecutorService executorService;

    public BasicClientTest() {
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setDaemon(true)
                .setNameFormat("hosebird-client-unit-test-%d").build();
        executorService = Executors.newSingleThreadExecutor(threadFactory);
    }

    @Before
    public void setup() throws Exception {
        mockClient = mock(HttpClient.class);
        mockResponse = mock(HttpResponse.class);
        mockStatusLine = mock(StatusLine.class);

        mockReconnectionManager = mock(BasicReconnectionManager.class);
        mockConnectionManager = mock(ClientConnectionManager.class);
        mockRateTracker = mock(RateTracker.class);

        mockInputStream = mock(InputStream.class);
        mockAuth = mock(Authentication.class);

        mockProcessor = mock(HosebirdMessageProcessor.class);

        mockHttpEntity = mock(HttpEntity.class);

        // set up required mocks to mock out all of the clientbase stuff
        when(mockClient.execute(any(HttpUriRequest.class))).thenReturn(mockResponse);
        when(mockClient.getConnectionManager()).thenReturn(mockConnectionManager);

        when(mockResponse.getStatusLine()).thenReturn(mockStatusLine);
        when(mockResponse.getEntity()).thenReturn(mockHttpEntity);
        when(mockHttpEntity.getContent()).thenReturn(mockInputStream);
        when(mockStatusLine.getReasonPhrase()).thenReturn("reason");
    }

    // These tests are going to get a little hairy in terms of mocking, but doable.
    // Some of the functionality is already tested in ClientBaseTest, but this tests
    // the overall flow. Worth it?

    @Test
    public void testIOExceptionDuringProcessing() throws Exception {
        ClientBase clientBase = new ClientBase("name", mockClient, new HttpHosts("http://hi"),
                new RawEndpoint("/endpoint", HttpConstants.HTTP_GET), mockAuth, mockProcessor,
                mockReconnectionManager, mockRateTracker);
        BasicClient client = new BasicClient(clientBase, executorService);
        final CountDownLatch latch = new CountDownLatch(1);
        when(mockStatusLine.getStatusCode()).thenReturn(200);

        doNothing().when(mockProcessor).setup(any(InputStream.class));
        doThrow(new IOException()).doThrow(new IOException()).doThrow(new IOException()).doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                latch.countDown();
                return null;
            }
        }).when(mockProcessor).process();

        client.connect();
        latch.await();
        assertFalse(clientBase.isDone());
        verify(mockProcessor, times(4)).setup(any(InputStream.class));
        // throw 3 exceptions, 4th one keeps going
        verify(mockProcessor, atLeast(4)).process();

        client.stop();
        verify(mockConnectionManager, atLeastOnce()).shutdown();
        assertTrue(client.isDone());
        assertEquals(EventType.STOPPED_BY_USER, clientBase.getExitEvent().getEventType());
    }

    @Test
    public void testInterruptedExceptionDuringProcessing() throws Exception {
        ClientBase clientBase = new ClientBase("name", mockClient, new HttpHosts("http://hi"),
                new RawEndpoint("/endpoint", HttpConstants.HTTP_GET), mockAuth, mockProcessor,
                mockReconnectionManager, mockRateTracker);

        when(mockStatusLine.getStatusCode()).thenReturn(200);

        doThrow(new InterruptedException()).when(mockProcessor).process();

        when(mockClient.getConnectionManager()).thenReturn(mockConnectionManager);

        BasicClient client = new BasicClient(clientBase, executorService);

        assertFalse(clientBase.isDone());
        client.connect();
        assertTrue(client.waitForFinish(100));
        assertTrue(client.isDone());
        verify(mockProcessor).setup(any(InputStream.class));
        verify(mockConnectionManager, atLeastOnce()).shutdown();
        assertEquals(EventType.STOPPED_BY_ERROR, client.getExitEvent().getEventType());
        assertTrue(client.getExitEvent().getUnderlyingException() instanceof InterruptedException);
    }

    @Test
    public void testConnectionRetries() throws Exception {
        HttpHosts mockHttpHosts = mock(HttpHosts.class);
        ClientBase clientBase = new ClientBase("name", mockClient, mockHttpHosts,
                new RawEndpoint("/endpoint", HttpConstants.HTTP_GET), mockAuth, mockProcessor,
                mockReconnectionManager, mockRateTracker);

        BasicClient client = new BasicClient(clientBase, executorService);
        final CountDownLatch latch = new CountDownLatch(1);
        when(mockHttpHosts.nextHost()).thenReturn("http://somehost.com");
        when(mockClient.execute(any(HttpUriRequest.class))).thenReturn(mockResponse).thenReturn(mockResponse)
                .thenThrow(new IOException()).thenReturn(mockResponse);
        when(mockStatusLine.getStatusCode()).thenReturn(HttpConstants.Codes.UNAUTHORIZED)
                .thenReturn(HttpConstants.Codes.SERVICE_UNAVAILABLE).thenReturn(HttpConstants.Codes.SUCCESS);

        // turn off the client when we start processing
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                latch.countDown();
                return null;
            }
        }).when(mockProcessor).process();

        // for 401 Unauthorized
        when(mockReconnectionManager.shouldReconnectOn400s()).thenReturn(true);

        /** for shutdown **/
        when(mockClient.getConnectionManager()).thenReturn(mockConnectionManager);

        assertFalse(clientBase.isDone());
        client.connect();
        latch.await();
        client.stop();
        assertTrue(client.isDone());

        // exponential backoff twice: once for 401 once for 503
        verify(mockReconnectionManager, times(2)).handleExponentialBackoff();
        // for thrown IOException
        verify(mockReconnectionManager).handleLinearBackoff();
        // for successful connection
        verify(mockReconnectionManager).resetCounts();

        // finally start setting up processor/processing for the last attempt that goes through
        verify(mockProcessor, atLeastOnce()).setup(any(InputStream.class));
        verify(mockProcessor, atLeastOnce()).process();

        assertEquals(EventType.STOPPED_BY_USER, clientBase.getExitEvent().getEventType());
        verify(mockConnectionManager, atLeastOnce()).shutdown();
    }
}