package com.avg.zaap.crashlibrary;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.FileObserver;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;

import com.avg.zaap.crashlibrary.config.CrashLibConfig;
import com.avg.zaap.crashlibrary.config.EShowErrorType;
import com.avg.zaap.crashlibrary.file.observer.IFilePathObserverCallback;
import com.avg.zaap.crashlibrary.provider.ICrashAsyncProvider;
import com.avg.zaap.crashlibrary.provider.ICrashProviderCallback;
import com.avg.zaap.crashlibrary.publish.cap.ICapHelper;
import com.avg.zaap.crashlibrary.show.error.activity.ErrorActivity;
import com.avg.zaap.crashlibrary.show.error.dialog.DialogActivity;

import org.hamcrest.CoreMatchers;
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.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import static junit.framework.Assert.fail;
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.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
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.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

/**
 * A test class for {@link CrashReporter}.
 * <p/>
 * Created by marek.osvald on 3.5.2016.
 */
@SuppressWarnings("SameParameterValue")
@RunWith(PowerMockRunner.class)
@PrepareForTest({CrashReporter.class, Looper.class, Toast.class, UUID.class})
public class CrashReporterTests {

    @SuppressWarnings("SpellCheckingInspection")
    private static final String DIRECTORY_NATIVE_CRASH = "nativecrash";

    private static final int NOTIFICATION_ID = 132;
    private static final String MOCK_CRASH_CODE = "crashCode";
    private static final String MOCK_EXCEPTION_CODE = "exceptionCode";
    private static final String MOCK_FILE_PATH_NATIVE = "mockFilePathNative";
    private static final int ZERO_OFFSET = 0;
    private static final String ZERO_HEX_OFFSET = "0x0";
    private static final String MODULE_NAME_NATIVE = "native";
    private static final String PROCESS_NAME_NATIVE = "native";
    private static final int NO_FLAGS = 0;
    private static final String MOCK_APPLICATION_VERSION = "mockApplicationVersion";

    private static final int MOCK_INTENT_FLAGS = 42;
    private static final int MOCK_TOAST_TEXT_ID = 111;
    private static final String MOCK_TOAST_TEXT = "mockToastText";
    private static final int MOCK_NOTIFICATION_SMALL_ICON = 201;
    private static final int MOCK_NOTIFICATION_TITLE_ID = 202;
    private static final String MOCK_NOTIFICATION_TITLE = "mockNotificationTitle";
    private static final int MOCK_NOTIFICATION_TEXT_ID = 203;
    private static final String MOCK_NOTIFICATION_TEXT = "mockNotificationText";

    private static final String MOCK_GUID = "00000000-0000-0001-0000-000000000001";

    @SuppressLint("SdCardPath")
    private static final String MOCK_FILES_DIR = File.separatorChar + "data" + File.separatorChar + "data" + File.separatorChar + "app";
    @SuppressLint("SdCardPath")
    private static final String MOCK_EXTERNAL_FILES_DIR = File.separatorChar + "sdcard";

    private static final int NO_ATTEMPTS = 0;
    private static final long NEVER = 0;

    private CrashReporter mCrashReporter;
    private Context mContext;
    private ReportData mReportData;
    private NotificationManager mNotificationManager;
    private Notification mNotification;
    private FileObserver mFilePathObserver;
    private Intent mDialogIntent;
    private Intent mErrorIntent;
    private Intent mCrashReportingServiceIntent;
    private NotificationCompat.Builder mNotificationCompatBuilder;
    private Looper mLooper;
    private Handler mHandler;
    private ICrashProviderCallback mCrashPublisherCallbackHandler;
    private boolean mExternalFilesDirNull;
    private boolean mExternalFilesDirExists;

    private Runnable mLastRunnable;

    private List<ICrashAsyncProvider> mAsyncProviders;
    private ICrashAsyncProvider mAsyncProvider1;
    private ICrashAsyncProvider mAsyncProvider2;
    private IFilePathObserverCallback mCallback;
    private Toast mToast;

    private CrashReporterServiceConnection mServiceConnection;
    private Thread mThread;
    private ICapHelper mCapHelper;
    private ArgumentCaptor<List> mFilesToPackCaptor;
    @SuppressWarnings("FieldCanBeLocal")
    private UUID mGUID;

    @SuppressLint("ShowToast")
    @Before
    public void setUp() throws Exception {
        mContext = mock(Context.class);
        mReportData = mock(ReportData.class);
        mNotificationManager = mock(NotificationManager.class);
        mNotification = mock(Notification.class);
        mFilePathObserver = mock(FileObserver.class);
        mLooper = mock(Looper.class);
        mHandler = mock(Handler.class);
        mCrashPublisherCallbackHandler = mock(ICrashProviderCallback.class);
        mDialogIntent = mock(Intent.class);
        mErrorIntent = mock(Intent.class);
        mCrashReportingServiceIntent = mock(Intent.class);
        mNotificationCompatBuilder = mock(NotificationCompat.Builder.class);

        File mockFilesDir = mock(File.class);
        when(mockFilesDir.getAbsolutePath()).thenReturn(MOCK_FILES_DIR);
        //noinspection ResourceType
        when(mContext.getString(MOCK_TOAST_TEXT_ID)).thenReturn(MOCK_TOAST_TEXT);
        //noinspection ResourceType
        when(mContext.getString(MOCK_NOTIFICATION_TITLE_ID)).thenReturn(MOCK_NOTIFICATION_TITLE);
        //noinspection ResourceType
        when(mContext.getString(MOCK_NOTIFICATION_TEXT_ID)).thenReturn(MOCK_NOTIFICATION_TEXT);
        when(mContext.getFilesDir()).thenReturn(mockFilesDir);
        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(mNotificationManager);

        when(mDialogIntent.getFlags()).thenReturn(MOCK_INTENT_FLAGS);
        when(mDialogIntent.setFlags(anyInt())).thenAnswer(new Answer<Intent>() {

            @Override
            public Intent answer(InvocationOnMock invocation) throws Throwable {
                int flags = (int) invocation.getArguments()[0];
                assertThat(flags, is(MOCK_INTENT_FLAGS | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION));
                return mDialogIntent;
            }

        });

        when(mErrorIntent.getFlags()).thenReturn(MOCK_INTENT_FLAGS);
        when(mErrorIntent.setFlags(anyInt())).thenAnswer(new Answer<Intent>() {

            @Override
            public Intent answer(InvocationOnMock invocation) throws Throwable {
                int flags = (int) invocation.getArguments()[0];
                assertThat(flags, is(MOCK_INTENT_FLAGS | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
                return mErrorIntent;
            }

        });

        when(mNotificationCompatBuilder.setSmallIcon(MOCK_NOTIFICATION_SMALL_ICON)).thenReturn(mNotificationCompatBuilder);
        when(mNotificationCompatBuilder.setContentTitle(MOCK_NOTIFICATION_TITLE)).thenReturn(mNotificationCompatBuilder);
        when(mNotificationCompatBuilder.setContentText(MOCK_NOTIFICATION_TEXT)).thenReturn(mNotificationCompatBuilder);
        when(mNotificationCompatBuilder.build()).thenReturn(mNotification);

        doAnswer(new Answer<Boolean>() {
            @Override
            public Boolean answer(InvocationOnMock invocation) throws Throwable {
                mLastRunnable = (Runnable) invocation.getArguments()[0];
                mLastRunnable.run();
                return Boolean.TRUE;
            }
        }).when(mHandler).post(any(Runnable.class));
        mLastRunnable = null;

        final File mockExternalFilesDir = mock(File.class);
        when(mockExternalFilesDir.getAbsolutePath()).thenReturn(MOCK_EXTERNAL_FILES_DIR);
        when(mockExternalFilesDir.exists()).thenAnswer(new Answer<Boolean>() {
            @Override
            public Boolean answer(InvocationOnMock invocation) throws Throwable {
                return mExternalFilesDirExists;
            }
        });
        when(mContext.getExternalFilesDir(anyString())).thenAnswer(new Answer<File>() {
            @Override
            public File answer(InvocationOnMock invocation) throws Throwable {
                assertThat(invocation.getArguments()[0], nullValue());

                if (mExternalFilesDirNull) {
                    return null;
                } else {
                    return mockExternalFilesDir;
                }
            }
        });

        PowerMockito.mockStatic(Looper.class);
        PowerMockito.when(Looper.getMainLooper()).thenReturn(mLooper);

        mToast = mock(Toast.class);
        PowerMockito.mockStatic(Toast.class);
        //noinspection ResourceType
        PowerMockito.when(Toast.makeText(mContext, MOCK_TOAST_TEXT_ID, Toast.LENGTH_SHORT)).thenAnswer(new Answer<Toast>() {

            @Override
            public Toast answer(InvocationOnMock invocation) throws Throwable {
                return mToast;
            }

        });

        initializeExternalFilesDir(false, true);

        mServiceConnection = mock(CrashReporterServiceConnection.class);
        mThread = mock(Thread.class);
        doAnswer(new Answer<Void>() {

            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                mLastRunnable.run();
                return null;
            }

        }).when(mThread).start();
        mCapHelper = mock(ICapHelper.class);
        when(mCapHelper.getCapFaultingHexOffset(ZERO_OFFSET)).thenReturn(ZERO_HEX_OFFSET);
        when(mCapHelper.getExceptionCodeFromCrashCode(MOCK_CRASH_CODE)).thenReturn(MOCK_EXCEPTION_CODE);

        mFilesToPackCaptor = ArgumentCaptor.forClass(List.class);

        mGUID = new UUID(1, 1);
        PowerMockito.mockStatic(UUID.class);
        PowerMockito.when(UUID.randomUUID()).thenReturn(mGUID);

        mCrashReporter = new CrashReporter(mContext, new ICrashReporterFactory() {

            @Override
            public FileObserver getFilePathObserver(String directory, IFilePathObserverCallback callback) {
                assertThat(directory, is(MOCK_FILES_DIR + File.separatorChar + DIRECTORY_NATIVE_CRASH + File.separatorChar));
                mCallback = callback;
                return mFilePathObserver;
            }

            @Override
            public Handler getHandler(Looper looper) {
                assertThat(looper, sameInstance(mLooper));
                return mHandler;
            }

            @Override
            public ICrashProviderCallback getCrashPublisherCallbackHandler(ReportData reportData, ICrashReporterCallback callback) {
                assertThat(reportData, sameInstance(mReportData));
                assertThat(callback, CoreMatchers.<ICrashReporterCallback>sameInstance(mCrashReporter));
                return mCrashPublisherCallbackHandler;
            }

            @Override
            public Intent getIntent(Context context, Class clazz) {
                assertThat(mContext, sameInstance(context));

                boolean validClass = clazz.equals(DialogActivity.class) || clazz.equals(ErrorActivity.class) || clazz.equals(CrashReportingService.class);
                assertThat(validClass, is(true));

                if (clazz.equals(DialogActivity.class)) {
                    return mDialogIntent;
                }

                if (clazz.equals(ErrorActivity.class)) {
                    return mErrorIntent;
                }

                if (clazz.equals(CrashReportingService.class)) {
                    return mCrashReportingServiceIntent;
                }

                fail();
                return null;
            }

            @Override
            public NotificationCompat.Builder getNotificationCompatBuilder(Context context) {
                assertThat(context, sameInstance(mContext));
                return mNotificationCompatBuilder;
            }

            @Override
            public Thread getThread(Runnable runnable) {
                mLastRunnable = runnable;
                return mThread;
            }

            @Override
            public ServiceConnection getServiceConnection(ReportData reportData, IServiceConnectionCallback callback) {
                assertThat(reportData, sameInstance(mReportData));
                return mServiceConnection;
            }

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

            @Override
            public ReportData getReportData() {
                return mReportData;
            }

        });

        mAsyncProviders = new ArrayList<>();
        mAsyncProvider1 = mock(ICrashAsyncProvider.class);
        mAsyncProvider2 = mock(ICrashAsyncProvider.class);
        mAsyncProviders.add(mAsyncProvider1);
        mAsyncProviders.add(mAsyncProvider2);

        assertThat(mCallback, CoreMatchers.<IFilePathObserverCallback>sameInstance(mCrashReporter));
    }

    @Test
    public void reportAndroidCrash_toast_showsToast() throws Exception {
        // arrange
        initializeCrashLibConfigToast(mAsyncProviders, MOCK_TOAST_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, times(1)).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_toastErrorsNull_showsToast() throws Exception {
        // arrange
        initializeCrashLibConfigToast(mAsyncProviders, MOCK_TOAST_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, times(1)).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_dialog_showsDialog() throws Exception {
        // arrange
        initializeCrashLibConfigDialog(mAsyncProviders);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, never()).show();
        verify(mContext, times(1)).startActivity(mDialogIntent);
        verify(mContext, never()).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_dialogErrorsNull_showsDialog() throws Exception {
        // arrange
        initializeCrashLibConfigDialog(mAsyncProviders);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, never()).show();
        verify(mContext, times(1)).startActivity(mDialogIntent);
        verify(mContext, never()).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_errorScreen_errorScreenDisplayed() throws Exception {
        // arrange
        initializeCrashLibConfigErrorScreen(mAsyncProviders);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(mDialogIntent);
        verify(mContext, times(1)).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_errorScreenErrorsNull_errorScreenDisplayed() throws Exception {
        // arrange
        initializeCrashLibConfigErrorScreen(mAsyncProviders);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(mDialogIntent);
        verify(mContext, times(1)).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_notification_showsNotification() throws Exception {
        // arrange
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mNotificationManager, times(1)).notify(NOTIFICATION_ID, mNotification);
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationCompatBuilder, times(1)).setSmallIcon(MOCK_NOTIFICATION_SMALL_ICON);
        verify(mNotificationCompatBuilder, times(1)).setContentTitle(MOCK_NOTIFICATION_TITLE);
        verify(mNotificationCompatBuilder, times(1)).setContentText(MOCK_NOTIFICATION_TEXT);
        verify(mNotificationCompatBuilder, times(1)).build();
    }

    @Test
    public void reportAndroidCrash_notificationErrorsNull_showsNotification() throws Exception {
        // arrange
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mNotificationManager, times(1)).notify(NOTIFICATION_ID, mNotification);
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationCompatBuilder, times(1)).setSmallIcon(MOCK_NOTIFICATION_SMALL_ICON);
        verify(mNotificationCompatBuilder, times(1)).setContentTitle(MOCK_NOTIFICATION_TITLE);
        verify(mNotificationCompatBuilder, times(1)).setContentText(MOCK_NOTIFICATION_TEXT);
        verify(mNotificationCompatBuilder, times(1)).build();
    }

    @Test
    public void reportAndroidCrash_asyncProvidersEmptyToast_showsToast() throws Exception {
        // arrange
        mAsyncProviders = new ArrayList<>();
        initializeCrashLibConfigToast(mAsyncProviders, MOCK_TOAST_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mToast, times(1)).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_asyncProvidersEmptyToastErrorsNull_showsToast() throws Exception {
        // arrange
        mAsyncProviders = new ArrayList<>();
        initializeCrashLibConfigToast(mAsyncProviders, MOCK_TOAST_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mToast, times(1)).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_asyncProvidersEmptyDialog_showsDialog() throws Exception {
        // arrange
        mAsyncProviders = new ArrayList<>();
        initializeCrashLibConfigDialog(mAsyncProviders);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mToast, never()).show();
        verify(mContext, times(1)).startActivity(mDialogIntent);
        verify(mContext, never()).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_asyncProvidersEmptyDialogErrorsNull_showsDialog() throws Exception {
        // arrange
        mAsyncProviders = new ArrayList<>();
        initializeCrashLibConfigDialog(mAsyncProviders);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mToast, never()).show();
        verify(mContext, times(1)).startActivity(mDialogIntent);
        verify(mContext, never()).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_asyncProvidersEmptyErrorScreen_errorScreenDisplayed() throws Exception {
        // arrange
        mAsyncProviders = new ArrayList<>();
        initializeCrashLibConfigErrorScreen(mAsyncProviders);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(mDialogIntent);
        verify(mContext, times(1)).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_asyncProvidersEmptyErrorScreenErrorsNull_errorScreenDisplayed() throws Exception {
        // arrange
        mAsyncProviders = new ArrayList<>();
        initializeCrashLibConfigErrorScreen(mAsyncProviders);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(mDialogIntent);
        verify(mContext, times(1)).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void reportAndroidCrash_asyncProvidersEmptyNotification_showsNotification() throws Exception {
        // arrange
        mAsyncProviders = new ArrayList<>();
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);
        mLastRunnable.run();
        mLastRunnable = null;

        // assert
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mNotificationManager, times(1)).notify(NOTIFICATION_ID, mNotification);
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationCompatBuilder, times(1)).setSmallIcon(MOCK_NOTIFICATION_SMALL_ICON);
        verify(mNotificationCompatBuilder, times(1)).setContentTitle(MOCK_NOTIFICATION_TITLE);
        verify(mNotificationCompatBuilder, times(1)).setContentText(MOCK_NOTIFICATION_TEXT);
        verify(mNotificationCompatBuilder, times(1)).build();
    }

    @Test
    public void reportAndroidCrash_asyncProvidersEmptyNotificationErrorNull_showsNotification() throws Exception {
        // arrange
        mAsyncProviders = new ArrayList<>();
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mNotificationManager, times(1)).notify(NOTIFICATION_ID, mNotification);
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationCompatBuilder, times(1)).setSmallIcon(MOCK_NOTIFICATION_SMALL_ICON);
        verify(mNotificationCompatBuilder, times(1)).setContentTitle(MOCK_NOTIFICATION_TITLE);
        verify(mNotificationCompatBuilder, times(1)).setContentText(MOCK_NOTIFICATION_TEXT);
        verify(mNotificationCompatBuilder, times(1)).build();
    }

    @Test
    public void reportAndroidCrash_externalDirectoryNull_doesNothing() throws Exception {
        // arrange
        initializeExternalFilesDir(true, true);
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        assertThat(mLastRunnable, nullValue());
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
    }

    @Test
    public void reportAndroidCrash_externalDirectoryNullErrorsNull_doesNothing() throws Exception {
        // arrange
        initializeExternalFilesDir(true, true);
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        assertThat(mLastRunnable, nullValue());
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
    }

    @Test
    public void reportAndroidCrash_externalDirectoryDoesNotExist_doesNothing() throws Exception {
        // arrange
        initializeExternalFilesDir(false, false);
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        assertThat(mLastRunnable, nullValue());
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
    }

    @Test
    public void reportAndroidCrash_externalDirectoryDoesNotExistErrorsNull_doesNothing() throws Exception {
        // arrange
        initializeExternalFilesDir(false, false);
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);

        // act
        mCrashReporter.reportAndroidCrash(mReportData);

        // assert
        assertThat(mLastRunnable, nullValue());
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
    }

    @Test
    public void startWatchingNativeCrashReports_nothing_startsWatchingNativeCrashReports() throws Exception {
        // act
        mCrashReporter.startWatchingNativeCrashReports();

        // assert
        verify(mFilePathObserver, times(1)).startWatching();
    }

    @Test
    public void stopWatchingNativeCrashReports_nothing_stopsWatchingNativeCrashReports() throws Exception {
        // act
        mCrashReporter.stopWatchingNativeCrashReports();

        // assert
        verify(mFilePathObserver, times(1)).stopWatching();
    }

    @Test
    public void onNewFileCreated_toast_showsToast() throws Exception {
        // act
        initializeCrashLibConfigToast(mAsyncProviders, MOCK_TOAST_TEXT_ID);
        mCrashReporter.onNewFileCreated(MOCK_CRASH_CODE, MOCK_FILE_PATH_NATIVE);

        // assert
        // common
        verify(mReportData, times(1)).setCrashCode(MOCK_CRASH_CODE);
        verify(mReportData, times(1)).setErrorName(null);
        verify(mReportData, times(1)).setErrorMessage(null);
        verify(mReportData, times(1)).setErrorDetails(null);
        //noinspection unchecked
        verify(mReportData, times(1)).setFilesToPack(mFilesToPackCaptor.capture());
        assertThat(mFilesToPackCaptor.getValue().size(), is(1));
        assertThat(mFilesToPackCaptor.getValue().contains(MOCK_FILE_PATH_NATIVE), is(true));

        // CAP
        verify(mReportData, times(1)).setCapExceptionCode(MOCK_EXCEPTION_CODE);
        verify(mReportData, times(1)).setCapFaultingOffset(ZERO_OFFSET);
        verify(mReportData, times(1)).setCapModuleName(MODULE_NAME_NATIVE);
        verify(mReportData, times(1)).setCapModuleVersion(MOCK_APPLICATION_VERSION);
        verify(mReportData, times(1)).setCapProcessName(PROCESS_NAME_NATIVE);
        verify(mReportData, times(1)).setCapProcessVersion(BuildConfig.VERSION_NAME);
        verify(mReportData, times(1)).setCapGUID(MOCK_GUID);

        // offline
        verify(mReportData, times(1)).setPublishAttempts(NO_ATTEMPTS);
        verify(mReportData, times(1)).setLastAttemptTimestamp(NEVER);

        verifyNoMoreInteractions(mReportData);

        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, times(1)).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void onNewFileCreated_dialog_showsDialog() throws Exception {
        // act
        initializeCrashLibConfigDialog(mAsyncProviders);
        mCrashReporter.onNewFileCreated(MOCK_CRASH_CODE, MOCK_FILE_PATH_NATIVE);

        // assert
        // common
        verify(mReportData, times(1)).setCrashCode(MOCK_CRASH_CODE);
        verify(mReportData, times(1)).setErrorName(null);
        verify(mReportData, times(1)).setErrorMessage(null);
        verify(mReportData, times(1)).setErrorDetails(null);
        //noinspection unchecked
        verify(mReportData, times(1)).setFilesToPack(mFilesToPackCaptor.capture());
        assertThat(mFilesToPackCaptor.getValue().size(), is(1));
        assertThat(mFilesToPackCaptor.getValue().contains(MOCK_FILE_PATH_NATIVE), is(true));

        // CAP
        verify(mReportData, times(1)).setCapExceptionCode(MOCK_EXCEPTION_CODE);
        verify(mReportData, times(1)).setCapFaultingOffset(ZERO_OFFSET);
        verify(mReportData, times(1)).setCapModuleName(MODULE_NAME_NATIVE);
        verify(mReportData, times(1)).setCapModuleVersion(MOCK_APPLICATION_VERSION);
        verify(mReportData, times(1)).setCapProcessName(PROCESS_NAME_NATIVE);
        verify(mReportData, times(1)).setCapProcessVersion(BuildConfig.VERSION_NAME);
        verify(mReportData, times(1)).setCapGUID(MOCK_GUID);

        // offline
        verify(mReportData, times(1)).setPublishAttempts(NO_ATTEMPTS);
        verify(mReportData, times(1)).setLastAttemptTimestamp(NEVER);

        verifyNoMoreInteractions(mReportData);

        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, never()).show();
        verify(mContext, times(1)).startActivity(mDialogIntent);
        verify(mContext, never()).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void onNewFileCreated_errorScreen_showsErrorScreen() throws Exception {
        // act
        initializeCrashLibConfigErrorScreen(mAsyncProviders);
        mCrashReporter.onNewFileCreated(MOCK_CRASH_CODE, MOCK_FILE_PATH_NATIVE);

        // assert
        // common
        verify(mReportData, times(1)).setCrashCode(MOCK_CRASH_CODE);
        verify(mReportData, times(1)).setErrorName(null);
        verify(mReportData, times(1)).setErrorMessage(null);
        verify(mReportData, times(1)).setErrorDetails(null);
        //noinspection unchecked
        verify(mReportData, times(1)).setFilesToPack(mFilesToPackCaptor.capture());
        assertThat(mFilesToPackCaptor.getValue().size(), is(1));
        assertThat(mFilesToPackCaptor.getValue().contains(MOCK_FILE_PATH_NATIVE), is(true));

        // CAP
        verify(mReportData, times(1)).setCapExceptionCode(MOCK_EXCEPTION_CODE);
        verify(mReportData, times(1)).setCapFaultingOffset(ZERO_OFFSET);
        verify(mReportData, times(1)).setCapModuleName(MODULE_NAME_NATIVE);
        verify(mReportData, times(1)).setCapModuleVersion(MOCK_APPLICATION_VERSION);
        verify(mReportData, times(1)).setCapProcessName(PROCESS_NAME_NATIVE);
        verify(mReportData, times(1)).setCapProcessVersion(BuildConfig.VERSION_NAME);
        verify(mReportData, times(1)).setCapGUID(MOCK_GUID);

        // offline
        verify(mReportData, times(1)).setPublishAttempts(NO_ATTEMPTS);
        verify(mReportData, times(1)).setLastAttemptTimestamp(NEVER);

        verifyNoMoreInteractions(mReportData);

        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(mDialogIntent);
        verify(mContext, times(1)).startActivity(mErrorIntent);
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
    }

    @Test
    public void onNewFileCreated_notification_showsNotification() throws Exception {
        // act
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);
        mCrashReporter.onNewFileCreated(MOCK_CRASH_CODE, MOCK_FILE_PATH_NATIVE);

        // assert
        // common
        verify(mReportData, times(1)).setCrashCode(MOCK_CRASH_CODE);
        verify(mReportData, times(1)).setErrorName(null);
        verify(mReportData, times(1)).setErrorMessage(null);
        verify(mReportData, times(1)).setErrorDetails(null);
        //noinspection unchecked
        verify(mReportData, times(1)).setFilesToPack(mFilesToPackCaptor.capture());
        assertThat(mFilesToPackCaptor.getValue().size(), is(1));
        assertThat(mFilesToPackCaptor.getValue().contains(MOCK_FILE_PATH_NATIVE), is(true));

        // CAP
        verify(mReportData, times(1)).setCapExceptionCode(MOCK_EXCEPTION_CODE);
        verify(mReportData, times(1)).setCapFaultingOffset(ZERO_OFFSET);
        verify(mReportData, times(1)).setCapModuleName(MODULE_NAME_NATIVE);
        verify(mReportData, times(1)).setCapModuleVersion(MOCK_APPLICATION_VERSION);
        verify(mReportData, times(1)).setCapProcessName(PROCESS_NAME_NATIVE);
        verify(mReportData, times(1)).setCapProcessVersion(BuildConfig.VERSION_NAME);
        verify(mReportData, times(1)).setCapGUID(MOCK_GUID);

        // offline
        verify(mReportData, times(1)).setPublishAttempts(NO_ATTEMPTS);
        verify(mReportData, times(1)).setLastAttemptTimestamp(NEVER);

        verifyNoMoreInteractions(mReportData);

        verify(mAsyncProvider1, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mAsyncProvider2, times(1)).provideData(MOCK_FILES_DIR, mCrashPublisherCallbackHandler);
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationManager, times(1)).notify(NOTIFICATION_ID, mNotification);
        verify(mNotificationCompatBuilder, times(1)).setSmallIcon(MOCK_NOTIFICATION_SMALL_ICON);
        verify(mNotificationCompatBuilder, times(1)).setContentTitle(MOCK_NOTIFICATION_TITLE);
        verify(mNotificationCompatBuilder, times(1)).setContentText(MOCK_NOTIFICATION_TEXT);
        verify(mNotificationCompatBuilder, times(1)).build();
    }

    @Test
    public void onNewFileCreated_externalDirectoryNull_doesNothing() throws Exception {
        // arrange
        initializeExternalFilesDir(true, true);

        // act
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);
        mCrashReporter.onNewFileCreated(MOCK_CRASH_CODE, MOCK_FILE_PATH_NATIVE);

        // assert
        // common
        verify(mReportData, times(1)).setCrashCode(MOCK_CRASH_CODE);
        verify(mReportData, times(1)).setErrorName(null);
        verify(mReportData, times(1)).setErrorMessage(null);
        verify(mReportData, times(1)).setErrorDetails(null);
        //noinspection unchecked
        verify(mReportData, times(1)).setFilesToPack(mFilesToPackCaptor.capture());
        assertThat(mFilesToPackCaptor.getValue().size(), is(1));
        assertThat(mFilesToPackCaptor.getValue().contains(MOCK_FILE_PATH_NATIVE), is(true));

        // CAP
        verify(mReportData, times(1)).setCapExceptionCode(MOCK_EXCEPTION_CODE);
        verify(mReportData, times(1)).setCapFaultingOffset(ZERO_OFFSET);
        verify(mReportData, times(1)).setCapModuleName(MODULE_NAME_NATIVE);
        verify(mReportData, times(1)).setCapModuleVersion(MOCK_APPLICATION_VERSION);
        verify(mReportData, times(1)).setCapProcessName(PROCESS_NAME_NATIVE);
        verify(mReportData, times(1)).setCapProcessVersion(BuildConfig.VERSION_NAME);
        verify(mReportData, times(1)).setCapGUID(MOCK_GUID);

        // offline
        verify(mReportData, times(1)).setPublishAttempts(NO_ATTEMPTS);
        verify(mReportData, times(1)).setLastAttemptTimestamp(NEVER);

        verifyNoMoreInteractions(mReportData);

        assertThat(mLastRunnable, nullValue());
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
    }

    @Test
    public void onNewFileCreated_externalDirectoryDoesNotExist_doesNothing() throws Exception {
        // arrange
        initializeExternalFilesDir(false, false);

        // act
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);
        mCrashReporter.onNewFileCreated(MOCK_CRASH_CODE, MOCK_FILE_PATH_NATIVE);

        // assert
        // common
        verify(mReportData, times(1)).setCrashCode(MOCK_CRASH_CODE);
        verify(mReportData, times(1)).setErrorName(null);
        verify(mReportData, times(1)).setErrorMessage(null);
        verify(mReportData, times(1)).setErrorDetails(null);
        //noinspection unchecked
        verify(mReportData, times(1)).setFilesToPack(mFilesToPackCaptor.capture());
        assertThat(mFilesToPackCaptor.getValue().size(), is(1));
        assertThat(mFilesToPackCaptor.getValue().contains(MOCK_FILE_PATH_NATIVE), is(true));

        // CAP
        verify(mReportData, times(1)).setCapExceptionCode(MOCK_EXCEPTION_CODE);
        verify(mReportData, times(1)).setCapFaultingOffset(ZERO_OFFSET);
        verify(mReportData, times(1)).setCapModuleName(MODULE_NAME_NATIVE);
        verify(mReportData, times(1)).setCapModuleVersion(MOCK_APPLICATION_VERSION);
        verify(mReportData, times(1)).setCapProcessName(PROCESS_NAME_NATIVE);
        verify(mReportData, times(1)).setCapProcessVersion(BuildConfig.VERSION_NAME);
        verify(mReportData, times(1)).setCapGUID(MOCK_GUID);

        // offline
        verify(mReportData, times(1)).setPublishAttempts(NO_ATTEMPTS);
        verify(mReportData, times(1)).setLastAttemptTimestamp(NEVER);

        verifyNoMoreInteractions(mReportData);

        assertThat(mLastRunnable, nullValue());
        verify(mToast, never()).show();
        verify(mContext, never()).startActivity(any(Intent.class));
        verify(mNotificationManager, never()).notify(anyInt(), any(Notification.class));
        verify(mAsyncProvider1, never()).provideData(anyString(), any(ICrashProviderCallback.class));
        verify(mAsyncProvider2, never()).provideData(anyString(), any(ICrashProviderCallback.class));
    }

    @Test
    public void publishResults_toast_publishesResults() {
        // arrange
        initializeCrashLibConfigToast(mAsyncProviders, MOCK_TOAST_TEXT_ID);
        mCrashReporter.reportAndroidCrash(mReportData);

        // act
        mCrashReporter.publishResults(mReportData);

        // assert
        verify(mContext, times(1)).bindService(mCrashReportingServiceIntent, mServiceConnection, NO_FLAGS);
    }

    @Test
    public void publishResultsErrorsNull_toast_publishesResults() {
        // arrange
        initializeCrashLibConfigToast(mAsyncProviders, MOCK_TOAST_TEXT_ID);
        mCrashReporter.reportAndroidCrash(mReportData);

        // act
        mCrashReporter.publishResults(mReportData);

        // assert
        verify(mContext, times(1)).bindService(mCrashReportingServiceIntent, mServiceConnection, NO_FLAGS);
    }

    @Test
    public void publishResults_dialog_publishesResults() {
        // arrange
        initializeCrashLibConfigDialog(mAsyncProviders);
        mCrashReporter.reportAndroidCrash(mReportData);

        // act
        mCrashReporter.publishResults(mReportData);

        // assert
        verify(mContext, times(1)).bindService(mCrashReportingServiceIntent, mServiceConnection, NO_FLAGS);
    }

    @Test
    public void publishResults_dialogErrorsNull_publishesResults() {
        // arrange
        initializeCrashLibConfigDialog(mAsyncProviders);
        mCrashReporter.reportAndroidCrash(mReportData);

        // act
        mCrashReporter.publishResults(mReportData);

        // assert
        verify(mContext, times(1)).bindService(mCrashReportingServiceIntent, mServiceConnection, NO_FLAGS);
    }

    @Test
    public void publishResults_errorScreen_publishesResults() {
        // arrange
        initializeCrashLibConfigErrorScreen(mAsyncProviders);
        mCrashReporter.reportAndroidCrash(mReportData);

        // act
        mCrashReporter.publishResults(mReportData);

        // assert
        verify(mContext, times(1)).bindService(mCrashReportingServiceIntent, mServiceConnection, NO_FLAGS);
    }

    @Test
    public void publishResults_errorScreenErrorsNull_publishesResults() {
        // arrange
        initializeCrashLibConfigErrorScreen(mAsyncProviders);
        mCrashReporter.reportAndroidCrash(mReportData);

        // act
        mCrashReporter.publishResults(mReportData);

        // assert
        verify(mContext, times(1)).bindService(mCrashReportingServiceIntent, mServiceConnection, NO_FLAGS);
    }

    @Test
    public void publishResults_notification_publishesResults() {
        // arrange
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);
        mCrashReporter.reportAndroidCrash(mReportData);

        // act
        mCrashReporter.publishResults(mReportData);

        // assert
        verify(mContext, times(1)).bindService(mCrashReportingServiceIntent, mServiceConnection, NO_FLAGS);
    }

    @Test
    public void publishResults_notificationErrorsNull_publishesResults() {
        // arrange
        initializeCrashLibConfigNotification(mAsyncProviders, MOCK_NOTIFICATION_TITLE_ID, MOCK_NOTIFICATION_TEXT_ID);
        mCrashReporter.reportAndroidCrash(mReportData);

        // act
        mCrashReporter.publishResults(mReportData);

        // assert
        verify(mContext, times(1)).bindService(mCrashReportingServiceIntent, mServiceConnection, NO_FLAGS);
    }

    @Test
    public void onCrashPublished_nothing_unbindsService() {
        // arrange
        mCrashReporter.publishResults(mReportData);

        // act
        mCrashReporter.onCrashPublished();

        // assert
        verify(mContext, times(1)).unbindService(mServiceConnection);
    }

    @Test
    public void onCrashPublishingFailed_nothing_unbindsService() {
        // arrange
        mCrashReporter.publishResults(mReportData);

        // act
        mCrashReporter.onCrashPublishingFailed();

        // assert
        verify(mContext, times(1)).unbindService(mServiceConnection);
    }

    private void initializeCrashLibConfigToast(List<ICrashAsyncProvider> asyncProviders, int mainText) {
        CrashLib.setConfig(new CrashLibConfig.Builder()
                .applicationVersion(MOCK_APPLICATION_VERSION)
                .showErrorType(EShowErrorType.TOAST)
                .errorDetails(mainText)
                .asyncProviders(asyncProviders)
                .build());
    }

    private void initializeCrashLibConfigDialog(List<ICrashAsyncProvider> asyncProviders) {
        CrashLib.setConfig(new CrashLibConfig.Builder()
                .applicationVersion(MOCK_APPLICATION_VERSION)
                .showErrorType(EShowErrorType.DIALOG)
                .asyncProviders(asyncProviders)
                .build());
    }

    private void initializeCrashLibConfigErrorScreen(List<ICrashAsyncProvider> asyncProviders) {
        CrashLib.setConfig(new CrashLibConfig.Builder()
                .applicationVersion(MOCK_APPLICATION_VERSION)
                .showErrorType(EShowErrorType.ERROR_SCREEN)
                .asyncProviders(asyncProviders)
                .build());
    }

    private void initializeCrashLibConfigNotification(List<ICrashAsyncProvider> asyncProviders, int errorTitle, int errorMessage) {
        CrashLib.setConfig(new CrashLibConfig.Builder()
                .applicationVersion(MOCK_APPLICATION_VERSION)
                .showErrorType(EShowErrorType.NOTIFICATION)
                .errorMessage(errorTitle)
                .errorDetails(errorMessage)
                .notificationSmallIcon(MOCK_NOTIFICATION_SMALL_ICON)
                .asyncProviders(asyncProviders)
                .build());
    }

    private void initializeExternalFilesDir(boolean isNull, boolean exists) {
        mExternalFilesDirNull = isNull;
        mExternalFilesDirExists = exists;
    }

}
