package com.avg.zaap.crashlibrary.publish.cap;

import android.content.Context;

import com.avg.zaap.crashlibrary.BuildConfig;
import com.avg.zaap.crashlibrary.CrashLib;
import com.avg.zaap.crashlibrary.ReportData;
import com.avg.zaap.crashlibrary.config.CrashLibConfig;
import com.avg.zaap.crashlibrary.publish.ICrashPublisher;
import com.avg.zaap.crashlibrary.publish.ICrashPublisherCallback;
import com.avg.zaap.crashlibrary.publish.cap.http.CapRequest;
import com.avg.zaap.crashlibrary.publish.cap.http.CapRequestException;
import com.avg.zaap.crashlibrary.publish.cap.http.CapResponse;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.util.Map;

import static junit.framework.Assert.fail;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyMap;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * A test class for {@link CapCrashPublisher}.
 * <p/>
 * Created by marek.osvald on 9.6.2016.
 */
@RunWith(PowerMockRunner.class)
@PrepareForTest({CrashLib.class})
public class CapCrashPublisherTests {

    private static final String CRASH_CODE = "mockCrashCode";
    private static final String FILE_PATH = "mockFilePath";
    private static final String CRASH_REPORT_URL = "mockCrashReportURL";
    private static final String CRASH_UPLOAD_URL = "mockCrashUploadURL";
    private static final String CAP_PRODUCT_ID = "mockCapProductID";
    private static final String APPLICATION_VERSION = "mockApplicationVersion";

    private static final int HTTP_CODE_OK = 200;
    private static final String HTTP_MESSAGE_OK = "OK";
    private static final int CAP_CODE_FATAL = -666;
    private static final int CAP_CODE_ERROR = -1;
    private static final int CAP_CODE_NO_DUMP = 0;
    private static final int CAP_CODE_MINI_DUMP = 1;
    private static final int CAP_CODE_FULL_DUMP = 2;
    private static final int CAP_CODE_KEEP_DUMPS = 6;
    private static final int CAP_CODE_DIRECT_CFG = 8;
    private static final int CAP_CODE_UNKNOWN = 42;
    private static final String CAP_MESSAGE = "";

    private static final String FORM_KEY_GUID = "guid";
    private static final String FORM_KEY_PRODUCT = "prd";
    private static final String FORM_KEY_PRODUCT_VERSION = "prv";
    @SuppressWarnings("SpellCheckingInspection")
    private static final String FORM_KEY_PROTOCOL_VERSION = "protver";

    private static final String FORM_VALUE_PROTOCOL_VERSION = "2";

    private static final String HEADER_VALUE_CAP_EXCEPTION_CODE = "mockCrashID";
    private static final int HEADER_VALUE_FAULTING_OFFSET = 123;
    private static final String HEADER_VALUE_HEX_FAULTING_OFFSET = "0x7b";
    private static final String HEADER_VALUE_CRASHING_MODULE_NAME = "main";
    private static final String HEADER_VALUE_CRASHING_PROCESS_NAME = "main";
    private static final String HEADER_VALUE_GUID = "mockGuid";

    private ICrashPublisher mCapCrashPublisher;
    private Context mContext;
    private ReportData mReportData;
    private CapRequest mReportRequest;
    private CapRequest mUploadRequest;
    private ICapHelper mCapHelper;
    private ICrashPublisherCallback mCallback;
    private IExceptionHeaderGenerator mExceptionHeaderGenerator;
    private Map<String, String> mExceptionHeader;
    @SuppressWarnings("FieldCanBeLocal")
    private CapResponse mUploadResponse;

    private ArgumentCaptor<String> mReportHeaderKeyCaptor;
    private ArgumentCaptor<String> mReportHeaderValueCaptor;
    private ArgumentCaptor<String> mUploadHeaderKeyCaptor;
    private ArgumentCaptor<String> mUploadHeaderValueCaptor;
    private ArgumentCaptor<String> mPublisherNameCaptor;
    private ArgumentCaptor<ReportData> mReportDataCaptor;
    private ArgumentCaptor<Throwable> mThrowableCaptor;

    @Before
    public void setUp() throws Exception {
        mReportHeaderKeyCaptor = ArgumentCaptor.forClass(String.class);
        mReportHeaderValueCaptor = ArgumentCaptor.forClass(String.class);
        mUploadHeaderKeyCaptor = ArgumentCaptor.forClass(String.class);
        mUploadHeaderValueCaptor = ArgumentCaptor.forClass(String.class);
        mPublisherNameCaptor = ArgumentCaptor.forClass(String.class);
        mReportDataCaptor = ArgumentCaptor.forClass(ReportData.class);
        mThrowableCaptor = ArgumentCaptor.forClass(Throwable.class);

        mContext = mock(Context.class);
        mReportData = mock(ReportData.class);
        when(mReportData.getCapExceptionCode()).thenReturn(HEADER_VALUE_CAP_EXCEPTION_CODE);
        when(mReportData.getCapFaultingOffset()).thenReturn(HEADER_VALUE_FAULTING_OFFSET);
        when(mReportData.getCapModuleName()).thenReturn(HEADER_VALUE_CRASHING_MODULE_NAME);
        when(mReportData.getCapModuleVersion()).thenReturn(APPLICATION_VERSION);
        when(mReportData.getCapProcessName()).thenReturn(HEADER_VALUE_CRASHING_PROCESS_NAME);
        when(mReportData.getCapProcessVersion()).thenReturn(BuildConfig.VERSION_NAME);
        when(mReportData.getCapGUID()).thenReturn(HEADER_VALUE_GUID);
        when(mReportData.getCrashCode()).thenReturn(CRASH_CODE);

        mReportRequest = mock(CapRequest.class);
        mUploadRequest = mock(CapRequest.class);
        mCapHelper = mock(ICapHelper.class);
        when(mCapHelper.getCapFaultingHexOffset(anyInt())).thenAnswer(new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocation) throws Throwable {
                assertThat((int) invocation.getArguments()[0], is(HEADER_VALUE_FAULTING_OFFSET));
                return HEADER_VALUE_HEX_FAULTING_OFFSET;
            }
        });
        mCallback = mock(ICrashPublisherCallback.class);
        //noinspection unchecked
        mExceptionHeader = mock(Map.class);
        mExceptionHeaderGenerator = mock(IExceptionHeaderGenerator.class);
        mUploadResponse = mock(CapResponse.class);
        when(mUploadRequest.send()).thenReturn(mUploadResponse);

        when(mExceptionHeaderGenerator.generateHeader(
                HEADER_VALUE_CAP_EXCEPTION_CODE,
                HEADER_VALUE_HEX_FAULTING_OFFSET,
                HEADER_VALUE_CRASHING_MODULE_NAME,
                APPLICATION_VERSION,
                HEADER_VALUE_CRASHING_PROCESS_NAME,
                BuildConfig.VERSION_NAME,
                CRASH_CODE
        )).thenReturn(mExceptionHeader);

        PowerMockito.mockStatic(CrashLib.class);
        PowerMockito.when(CrashLib.getConfig()).thenReturn(
                new CrashLibConfig.Builder()
                        .applicationVersion(APPLICATION_VERSION)
                        .capProductID(CAP_PRODUCT_ID)
                        .crashReportURL(CRASH_REPORT_URL)
                        .crashUploadURL(CRASH_UPLOAD_URL)
                        .build()
        );

        initializeCapRequests();

        mCapCrashPublisher = new CapCrashPublisher(new ICapCrashPublisherFactory() {

            private boolean mFirstRequest = true;

            @Override
            public IExceptionHeaderGenerator getExceptionHeaderGenerator() {
                return mExceptionHeaderGenerator;
            }

            @Override
            public CapRequest getCapRequest() {
                if (mFirstRequest) {
                    mFirstRequest = false;
                    return mReportRequest;
                } else {
                    return mUploadRequest;
                }
            }

            @Override
            public ICapHelper getCapHelper() {
                return mCapHelper;
            }

        });
    }

    @Test
    public void publish_capRequestFails_callbackOnFailureCalled() throws Exception {
        // arrange
        initializeReportResponse(Result.EXCEPTION);

        // act
        mCapCrashPublisher.publish(mContext, mReportData, FILE_PATH, mCallback);

        // assert
        verify(mReportRequest, times(1)).openURL(CRASH_REPORT_URL);

        verify(mReportRequest, times(4)).addFormValue(mReportHeaderKeyCaptor.capture(), mReportHeaderValueCaptor.capture());
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(2), is(FORM_KEY_PRODUCT_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(2), is(APPLICATION_VERSION));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(3), is(FORM_KEY_PROTOCOL_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(3), is(FORM_VALUE_PROTOCOL_VERSION));
        verify(mReportRequest, times(1)).addExceptionHeader(mExceptionHeader);

        verify(mReportRequest, times(1)).send();

        verify(mCallback, times(1)).onCrashPublisherFailure(mPublisherNameCaptor.capture(), mReportDataCaptor.capture(), mThrowableCaptor.capture());
        assertThat(mPublisherNameCaptor.getValue(), is(CapCrashPublisher.class.getSimpleName()));
        assertThat(mReportDataCaptor.getValue(), sameInstance(mReportData));
        assertThat(mThrowableCaptor.getValue(), instanceOf(CapRequestException.class));
        verify(mUploadRequest, never()).send();
    }

    @Test
    public void publish_capRequestRespondsFatal_callbackOnFailureCalled() throws Exception {
        // arrange
        initializeReportResponse(Result.FATAL);

        // act
        mCapCrashPublisher.publish(mContext, mReportData, FILE_PATH, mCallback);

        // assert
        verify(mReportRequest, times(1)).openURL(CRASH_REPORT_URL);

        verify(mReportRequest, times(4)).addFormValue(mReportHeaderKeyCaptor.capture(), mReportHeaderValueCaptor.capture());
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(2), is(FORM_KEY_PRODUCT_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(2), is(APPLICATION_VERSION));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(3), is(FORM_KEY_PROTOCOL_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(3), is(FORM_VALUE_PROTOCOL_VERSION));
        verify(mReportRequest, times(1)).addExceptionHeader(mExceptionHeader);

        verify(mReportRequest, times(1)).send();

        verify(mCallback, times(1)).onCrashPublisherFailure(mPublisherNameCaptor.capture(), mReportDataCaptor.capture(), mThrowableCaptor.capture());
        assertThat(mPublisherNameCaptor.getValue(), is(CapCrashPublisher.class.getSimpleName()));
        assertThat(mReportDataCaptor.getValue(), sameInstance(mReportData));
        assertThat(mThrowableCaptor.getValue(), nullValue());
        verify(mUploadRequest, never()).send();
    }

    @Test
    public void publish_capRequestRespondsError_callbackOnFailureCalled() throws Exception {
        // arrange
        initializeReportResponse(Result.ERROR);

        // act
        mCapCrashPublisher.publish(mContext, mReportData, FILE_PATH, mCallback);

        // assert
        verify(mReportRequest, times(1)).openURL(CRASH_REPORT_URL);

        verify(mReportRequest, times(4)).addFormValue(mReportHeaderKeyCaptor.capture(), mReportHeaderValueCaptor.capture());
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(2), is(FORM_KEY_PRODUCT_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(2), is(APPLICATION_VERSION));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(3), is(FORM_KEY_PROTOCOL_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(3), is(FORM_VALUE_PROTOCOL_VERSION));
        verify(mReportRequest, times(1)).addExceptionHeader(mExceptionHeader);

        verify(mReportRequest, times(1)).send();

        verify(mCallback, times(1)).onCrashPublisherFailure(mPublisherNameCaptor.capture(), mReportDataCaptor.capture(), mThrowableCaptor.capture());
        assertThat(mPublisherNameCaptor.getValue(), is(CapCrashPublisher.class.getSimpleName()));
        assertThat(mReportDataCaptor.getValue(), sameInstance(mReportData));
        assertThat(mThrowableCaptor.getValue(), nullValue());
        verify(mUploadRequest, never()).send();
    }

    @Test
    public void publish_capRequestRespondsNoDump_callbackOnFailureCalled() throws Exception {
        // arrange
        initializeReportResponse(Result.NO_DUMP);

        // act
        mCapCrashPublisher.publish(mContext, mReportData, FILE_PATH, mCallback);

        // assert
        verify(mReportRequest, times(1)).openURL(CRASH_REPORT_URL);

        verify(mReportRequest, times(4)).addFormValue(mReportHeaderKeyCaptor.capture(), mReportHeaderValueCaptor.capture());
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(2), is(FORM_KEY_PRODUCT_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(2), is(APPLICATION_VERSION));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(3), is(FORM_KEY_PROTOCOL_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(3), is(FORM_VALUE_PROTOCOL_VERSION));
        verify(mReportRequest, times(1)).addExceptionHeader(mExceptionHeader);

        verify(mReportRequest, times(1)).send();

        verify(mCallback, times(1)).onCrashPublisherFailure(mPublisherNameCaptor.capture(), mReportDataCaptor.capture(), mThrowableCaptor.capture());
        assertThat(mPublisherNameCaptor.getValue(), is(CapCrashPublisher.class.getSimpleName()));
        assertThat(mReportDataCaptor.getValue(), sameInstance(mReportData));
        assertThat(mThrowableCaptor.getValue(), nullValue());
        verify(mUploadRequest, never()).send();
    }

    @Test
    public void publish_capRequestRespondsMiniDump_callbackOnSuccessCalled() throws Exception {
        // arrange
        initializeReportResponse(Result.MINI_DUMP);

        // act
        mCapCrashPublisher.publish(mContext, mReportData, FILE_PATH, mCallback);

        // assert
        verify(mReportRequest, times(1)).openURL(CRASH_REPORT_URL);

        verify(mReportRequest, times(4)).addFormValue(mReportHeaderKeyCaptor.capture(), mReportHeaderValueCaptor.capture());
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(2), is(FORM_KEY_PRODUCT_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(2), is(APPLICATION_VERSION));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(3), is(FORM_KEY_PROTOCOL_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(3), is(FORM_VALUE_PROTOCOL_VERSION));
        verify(mReportRequest, times(1)).addExceptionHeader(mExceptionHeader);

        verify(mReportRequest, times(1)).send();

        verify(mUploadRequest, times(1)).openURL(CRASH_UPLOAD_URL);

        verify(mUploadRequest, times(2)).addFormValue(mUploadHeaderKeyCaptor.capture(), mUploadHeaderValueCaptor.capture());

        assertThat(mUploadHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mUploadHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mUploadHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));

        verify(mUploadRequest, times(1)).addFile(FILE_PATH);
        verify(mUploadRequest, times(1)).send();

        verify(mCallback, times(1)).onCrashPublisherSuccess(CapCrashPublisher.class.getSimpleName(), mReportData);
    }

    @Test
    public void publish_capRequestRespondsFullDump_callbackOnSuccessCalled() throws Exception {
        // arrange
        initializeReportResponse(Result.FULL_DUMP);

        // act
        mCapCrashPublisher.publish(mContext, mReportData, FILE_PATH, mCallback);

        // assert
        verify(mReportRequest, times(1)).openURL(CRASH_REPORT_URL);

        verify(mReportRequest, times(4)).addFormValue(mReportHeaderKeyCaptor.capture(), mReportHeaderValueCaptor.capture());
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(2), is(FORM_KEY_PRODUCT_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(2), is(APPLICATION_VERSION));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(3), is(FORM_KEY_PROTOCOL_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(3), is(FORM_VALUE_PROTOCOL_VERSION));
        verify(mReportRequest, times(1)).addExceptionHeader(mExceptionHeader);

        verify(mReportRequest, times(1)).send();

        verify(mUploadRequest, times(1)).openURL(CRASH_UPLOAD_URL);

        verify(mUploadRequest, times(2)).addFormValue(mUploadHeaderKeyCaptor.capture(), mUploadHeaderValueCaptor.capture());

        assertThat(mUploadHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mUploadHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mUploadHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));

        verify(mUploadRequest, times(1)).addFile(FILE_PATH);
        verify(mUploadRequest, times(1)).send();

        verify(mCallback, times(1)).onCrashPublisherSuccess(CapCrashPublisher.class.getSimpleName(), mReportData);
    }

    @Test
    public void publish_capRequestRespondsKeepDumps_callbackOnSuccessCalled() throws Exception {
        // arrange
        initializeReportResponse(Result.KEEP_DUMPS);

        // act
        mCapCrashPublisher.publish(mContext, mReportData, FILE_PATH, mCallback);

        // assert
        verify(mReportRequest, times(1)).openURL(CRASH_REPORT_URL);

        verify(mReportRequest, times(4)).addFormValue(mReportHeaderKeyCaptor.capture(), mReportHeaderValueCaptor.capture());
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(2), is(FORM_KEY_PRODUCT_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(2), is(APPLICATION_VERSION));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(3), is(FORM_KEY_PROTOCOL_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(3), is(FORM_VALUE_PROTOCOL_VERSION));
        verify(mReportRequest, times(1)).addExceptionHeader(mExceptionHeader);

        verify(mReportRequest, times(1)).send();

        verify(mUploadRequest, times(1)).openURL(CRASH_UPLOAD_URL);

        verify(mUploadRequest, times(2)).addFormValue(mUploadHeaderKeyCaptor.capture(), mUploadHeaderValueCaptor.capture());

        assertThat(mUploadHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mUploadHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mUploadHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));

        verify(mUploadRequest, times(1)).addFile(FILE_PATH);
        verify(mUploadRequest, times(1)).send();

        verify(mCallback, times(1)).onCrashPublisherSuccess(CapCrashPublisher.class.getSimpleName(), mReportData);
    }

    @Test
    public void publish_capRequestRespondsDirectCfg_callbackOnSuccessCalled() throws Exception {
        // arrange
        initializeReportResponse(Result.DIRECT_CFG);

        // act
        mCapCrashPublisher.publish(mContext, mReportData, FILE_PATH, mCallback);

        // assert
        verify(mReportRequest, times(1)).openURL(CRASH_REPORT_URL);

        verify(mReportRequest, times(4)).addFormValue(mReportHeaderKeyCaptor.capture(), mReportHeaderValueCaptor.capture());
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(2), is(FORM_KEY_PRODUCT_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(2), is(APPLICATION_VERSION));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(3), is(FORM_KEY_PROTOCOL_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(3), is(FORM_VALUE_PROTOCOL_VERSION));
        verify(mReportRequest, times(1)).addExceptionHeader(mExceptionHeader);

        verify(mReportRequest, times(1)).send();

        verify(mUploadRequest, times(1)).openURL(CRASH_UPLOAD_URL);

        verify(mUploadRequest, times(2)).addFormValue(mUploadHeaderKeyCaptor.capture(), mUploadHeaderValueCaptor.capture());

        assertThat(mUploadHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mUploadHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mUploadHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));

        verify(mUploadRequest, times(1)).addFile(FILE_PATH);
        verify(mUploadRequest, times(1)).send();

        verify(mCallback, times(1)).onCrashPublisherSuccess(CapCrashPublisher.class.getSimpleName(), mReportData);
    }

    @Test
    public void publish_capRequestRespondsUnknown_callbackOnFailureCalled() throws Exception {
        // arrange
        initializeReportResponse(Result.UNKNOWN);

        // act
        mCapCrashPublisher.publish(mContext, mReportData, FILE_PATH, mCallback);

        // assert
        verify(mReportRequest, times(1)).openURL(CRASH_REPORT_URL);

        verify(mReportRequest, times(4)).addFormValue(mReportHeaderKeyCaptor.capture(), mReportHeaderValueCaptor.capture());
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(0), is(FORM_KEY_GUID));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(0), is(HEADER_VALUE_GUID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(1), is(FORM_KEY_PRODUCT));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(1), is(CAP_PRODUCT_ID));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(2), is(FORM_KEY_PRODUCT_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(2), is(APPLICATION_VERSION));
        assertThat(mReportHeaderKeyCaptor.getAllValues().get(3), is(FORM_KEY_PROTOCOL_VERSION));
        assertThat(mReportHeaderValueCaptor.getAllValues().get(3), is(FORM_VALUE_PROTOCOL_VERSION));
        verify(mReportRequest, times(1)).addExceptionHeader(mExceptionHeader);

        verify(mReportRequest, times(1)).send();

        verify(mCallback, times(1)).onCrashPublisherFailure(mPublisherNameCaptor.capture(), mReportDataCaptor.capture(), mThrowableCaptor.capture());
        assertThat(mPublisherNameCaptor.getValue(), is(CapCrashPublisher.class.getSimpleName()));
        assertThat(mReportDataCaptor.getValue(), sameInstance(mReportData));
        assertThat(mThrowableCaptor.getValue(), nullValue());
        verify(mUploadRequest, never()).send();
    }

    private void initializeCapRequests() throws Exception {
        //noinspection unchecked
        when(mReportRequest.addExceptionHeader(anyMap())).thenReturn(mReportRequest);
        when(mReportRequest.addFile(anyString())).thenReturn(mReportRequest);
        when(mReportRequest.addFormValue(anyString(), anyString())).thenReturn(mReportRequest);
        when(mReportRequest.openURL(anyString())).thenReturn(mReportRequest);

        //noinspection unchecked
        when(mUploadRequest.addExceptionHeader(anyMap())).thenReturn(mUploadRequest);
        when(mUploadRequest.addFile(anyString())).thenReturn(mUploadRequest);
        when(mUploadRequest.addFormValue(anyString(), anyString())).thenReturn(mUploadRequest);
        when(mUploadRequest.openURL(anyString())).thenReturn(mUploadRequest);
    }

    private void initializeReportResponse(Result result) throws CapRequestException {
        switch (result) {
            case FATAL:
                when(mReportRequest.send()).thenReturn(new CapResponse(HTTP_CODE_OK, HTTP_MESSAGE_OK, CAP_CODE_FATAL, CAP_MESSAGE));
                break;

            case ERROR:
                when(mReportRequest.send()).thenReturn(new CapResponse(HTTP_CODE_OK, HTTP_MESSAGE_OK, CAP_CODE_ERROR, CAP_MESSAGE));
                break;

            case NO_DUMP:
                when(mReportRequest.send()).thenReturn(new CapResponse(HTTP_CODE_OK, HTTP_MESSAGE_OK, CAP_CODE_NO_DUMP, CAP_MESSAGE));
                break;

            case MINI_DUMP:
                when(mReportRequest.send()).thenReturn(new CapResponse(HTTP_CODE_OK, HTTP_MESSAGE_OK, CAP_CODE_MINI_DUMP, CAP_MESSAGE));
                break;

            case FULL_DUMP:
                when(mReportRequest.send()).thenReturn(new CapResponse(HTTP_CODE_OK, HTTP_MESSAGE_OK, CAP_CODE_FULL_DUMP, CAP_MESSAGE));
                break;

            case KEEP_DUMPS:
                when(mReportRequest.send()).thenReturn(new CapResponse(HTTP_CODE_OK, HTTP_MESSAGE_OK, CAP_CODE_KEEP_DUMPS, CAP_MESSAGE));
                break;

            case DIRECT_CFG:
                when(mReportRequest.send()).thenReturn(new CapResponse(HTTP_CODE_OK, HTTP_MESSAGE_OK, CAP_CODE_DIRECT_CFG, CAP_MESSAGE));
                break;

            case UNKNOWN:
                when(mReportRequest.send()).thenReturn(new CapResponse(HTTP_CODE_OK, HTTP_MESSAGE_OK, CAP_CODE_UNKNOWN, CAP_MESSAGE));
                break;

            case EXCEPTION:
                when(mReportRequest.send()).thenThrow(new CapRequestException());
                break;

            default:
                fail();
        }
    }

    enum Result {
        FATAL, ERROR, NO_DUMP, MINI_DUMP, FULL_DUMP, KEEP_DUMPS, DIRECT_CFG, UNKNOWN, EXCEPTION
    }

}
