package com.avg.zaap.crashlibrary;

import android.content.Context;
import android.content.Intent;

import com.avg.zaap.crashlibrary.config.CrashLibConfig;
import com.avg.zaap.crashlibrary.config.EShowErrorType;
import com.avg.zaap.crashlibrary.core.helper.process.IProcessHelper;
import com.avg.zaap.crashlibrary.core.logger.CrashLogger;
import com.avg.zaap.crashlibrary.core.logger.ILogger;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithDialog;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithErrorScreen;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithIllegalAccessExceptionAsyncProvider;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithIllegalAccessExceptionCrashPublisher;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithIllegalAccessExceptionLogger;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithInstantiationExceptionAsyncProvider;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithInstantiationExceptionCrashPublisher;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithInstantiationExceptionLogger;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithInvocationTargetExceptionSyncProvider;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithNoSuchMethodExceptionSyncProvider;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithNone;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithNotification;
import com.avg.zaap.crashlibrary.crashlibmocks.AnnotatedClassWithToast;
import com.avg.zaap.crashlibrary.crashlibmocks.AsyncProvider1;
import com.avg.zaap.crashlibrary.crashlibmocks.AsyncProvider2;
import com.avg.zaap.crashlibrary.crashlibmocks.AsyncProviderWithIllegalAccessException;
import com.avg.zaap.crashlibrary.crashlibmocks.AsyncProviderWithInstantiationException;
import com.avg.zaap.crashlibrary.crashlibmocks.ClassWithoutAnnotations;
import com.avg.zaap.crashlibrary.crashlibmocks.CrashLibTestsParentFake;
import com.avg.zaap.crashlibrary.crashlibmocks.CrashPublisher1;
import com.avg.zaap.crashlibrary.crashlibmocks.CrashPublisher2;
import com.avg.zaap.crashlibrary.crashlibmocks.CrashPublisherWithIllegalAccessException;
import com.avg.zaap.crashlibrary.crashlibmocks.CrashPublisherWithInstantiationException;
import com.avg.zaap.crashlibrary.crashlibmocks.MockedLogger;
import com.avg.zaap.crashlibrary.crashlibmocks.SyncProvider1;
import com.avg.zaap.crashlibrary.crashlibmocks.SyncProvider2;
import com.avg.zaap.crashlibrary.crashlibmocks.SyncProviderWithInvocationTargetException;
import com.avg.zaap.crashlibrary.crashlibmocks.SyncProviderWithNoSuchMethodException;
import com.avg.zaap.crashlibrary.offline.Connection;
import com.avg.zaap.crashlibrary.offline.Strategy;
import com.avg.zaap.crashlibrary.provider.ICrashAsyncProvider;
import com.avg.zaap.crashlibrary.provider.ICrashSyncProvider;
import com.avg.zaap.crashlibrary.provider.memory.MemoryHeapProvider;
import com.avg.zaap.crashlibrary.publish.ICrashPublisher;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
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 org.powermock.reflect.Whitebox;

import java.io.File;
import java.util.List;

import static com.avg.zaap.crashlibrary.CrashLib.CRASH_DIR;
import static com.avg.zaap.crashlibrary.CrashLib.DEFAULT_DELAY;
import static com.avg.zaap.crashlibrary.CrashLib.DEFAULT_MAX_ATTEMPTS;
import static com.avg.zaap.crashlibrary.CrashLib.NATIVE_CRASH_DIR;
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.assertNotSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * Unit tests for {@link CrashLib}.
 * <p/>
 * Created by marek.osvald on 12. 5. 2016.
 */
@RunWith(PowerMockRunner.class)
@PrepareForTest({Thread.class, CrashLogger.class})
public class CrashLibTests {

    public static final int NOTIFICATION_SMALL_ICON = 3;
    public static final int ERROR_TITLE = 4;
    public static final int ERROR_MESSAGE = 5;

    public static final String MOCK_CRASH_REPORT_URL = "mockCrashReportURL";
    public static final String MOCK_CRASH_UPLOAD_URL = "mockCrashUploadURL";
    public static final String MOCK_CAP_PRODUCT_ID = "mockCapProductID";

    private static final String DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1 = "defaultUncaughtExceptionHandler";
    private static final String DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1;

    private static final String MOCK_APPLICATION_VERSION = "unknown";

    private static final String DEFAULT_CRASH_REPORT_URL = "http://crashportal.avg.com/excpre_ANDR.php";
    private static final String DEFAULT_CRASH_UPLOAD_URL = "http://crashportal.avg.com/uploader_ANDR.php";
    @SuppressWarnings("SpellCheckingInspection")
    private static final String DEFAULT_CAP_PRODUCT_ID = "ANDR";

    private static final int TEN = 10;
    private static final long MINUTE = 60 * 1000; // milliseconds ~ 1 minute

    private IProcessHelper mProcessHelper;
    private File mCrashDir;
    private File mNativeCrashDir;
    private File mDir;
    private ICrashLibFactory mFactory;
    private Context mContext;
    private Thread.UncaughtExceptionHandler mHandler;
    private Intent mIntent;
    private ICrashLibFactory mFactoryWithoutAnnotations;

    @Before
    public void setUp() throws Exception {
        PowerMockito.mockStatic(Thread.class);

        mProcessHelper = mock(IProcessHelper.class);

        mCrashDir = mock(File.class);
        mNativeCrashDir = mock(File.class);

        mDir = mock(File.class);
        mContext = mock(Context.class);
        when(mContext.getFilesDir()).thenReturn(mDir);

        mHandler = mock(Thread.UncaughtExceptionHandler.class);
        mIntent = mock(Intent.class);

        mFactory = new ICrashLibFactory() {

            @Override
            public File getFile(File dir, String name) {
                switch (name) {
                    case CRASH_DIR:
                        assertThat(dir, is(CrashLibTestsParentFake.MOCK_FILE));
                        return mCrashDir;
                    case NATIVE_CRASH_DIR:
                        assertThat(dir, is(CrashLibTestsParentFake.MOCK_FILE));
                        return mNativeCrashDir;
                    default:
                        fail();
                }
                return null;
            }

            @Override
            public Thread.UncaughtExceptionHandler getCrashUncaughtExceptionHandler(Context context) {
                return mHandler;
            }

            @Override
            public Intent getIntent(Context context, Class<CrashReportingService> serviceClass) {
                return CrashLibTestsParentFake.MOCK_INTENT;
            }

            @Override
            public IProcessHelper getProcessHelper() {
                return mProcessHelper;
            }
        };

        mFactoryWithoutAnnotations = new ICrashLibFactory() {

            @Override
            public File getFile(File dir, String name) {
                switch (name) {
                    case CRASH_DIR:
                        assertThat(dir, is(mDir));
                        return mCrashDir;
                    case NATIVE_CRASH_DIR:
                        assertThat(dir, is(mDir));
                        return mNativeCrashDir;
                    default:
                        fail();
                }
                return null;
            }

            @Override
            public Thread.UncaughtExceptionHandler getCrashUncaughtExceptionHandler(Context context) {
                return mHandler;
            }

            @Override
            public Intent getIntent(Context context, Class<CrashReportingService> serviceClass) {
                return mIntent;
            }

            @Override
            public IProcessHelper getProcessHelper() {
                return mProcessHelper;
            }
        };

        Answer crashLoggerAnswer = new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                String tag = (String) invocation.getArguments()[0];
                String text = (String) invocation.getArguments()[1];

                int paramCount = invocation.getArguments().length;
                if (paramCount == 2) {
                    System.out.println(String.format("[%s]: %s", tag, text));
                } else {
                    String[] args = new String[paramCount - 2 + 1];
                    args[0] = tag;
                    for (int i = 2; i < paramCount; i++) {
                        args[i - 2 + 1] = (String) invocation.getArguments()[i];
                    }
                    System.out.println(String.format("[%s]: " + text, (Object[]) args));
                }
                return null;
            }
        };

        PowerMockito.mockStatic(CrashLogger.class);
        PowerMockito.doAnswer(crashLoggerAnswer).when(CrashLogger.class);
        CrashLogger.d(anyString(), anyString());
        PowerMockito.doAnswer(crashLoggerAnswer).when(CrashLogger.class);
        CrashLogger.e(anyString(), anyString(), anyString());
    }

    private boolean containsInstanceOf(List list, Class clazz) {
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).getClass().equals(clazz)) {
                return true;
            }
        }
        return false;
    }

    @Test
    public void init_annotationPresentWithToastEverythingSetAndFilesExistButNotDirsThreadIsNotService_loadConfigFromAnnotationRemoveFilesAndCreateDirs() {
        //prepare
        AnnotatedClassWithToast annotatedClass = new AnnotatedClassWithToast();
        when(mProcessHelper.isReportingServiceProcess(annotatedClass)).thenReturn(false);

        when(mCrashDir.exists()).thenReturn(true);
        when(mCrashDir.isDirectory()).thenReturn(false);
        when(mCrashDir.delete()).thenReturn(true);

        when(mNativeCrashDir.exists()).thenReturn(true);
        when(mNativeCrashDir.isDirectory()).thenReturn(false);
        when(mNativeCrashDir.delete()).thenReturn(true);

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.TOAST));
        assertThat(CrashLib.getConfig().getErrorDetails(), is(CrashLibTests.ERROR_MESSAGE));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(2));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));

        assertThat(CrashLib.getConfig().getLogger().getClass().equals(MockedLogger.class), is(true));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(MOCK_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(MOCK_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(MOCK_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(true));
        assertThat(CrashLib.getConfig().isDisabledCapPublisher(), is(true));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.ALL));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(TEN));
        assertThat(CrashLib.getConfig().getDelay(), is(MINUTE));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(true));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.OTHER));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(true));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
        assertThat(defaultUncaughtHandler, sameInstance(mHandler));

        assertThat(annotatedClass.startServiceCounter, is(1));

        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).mkdir();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).mkdir();
    }

    @Test
    public void init_annotationPresentWithToastEverythingSetAndDirsExistThreadIsNotService_loadConfigFromAnnotation() throws Exception {
        //prepare
        AnnotatedClassWithToast annotatedClass = new AnnotatedClassWithToast();
        when(mProcessHelper.isReportingServiceProcess(annotatedClass)).thenReturn(false);

        when(mCrashDir.exists()).thenReturn(true);
        when(mCrashDir.isDirectory()).thenReturn(true);

        when(mNativeCrashDir.exists()).thenReturn(true);
        when(mNativeCrashDir.isDirectory()).thenReturn(true);

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.TOAST));
        assertThat(CrashLib.getConfig().getErrorDetails(), is(CrashLibTests.ERROR_MESSAGE));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(2));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));

        assertThat(CrashLib.getConfig().getLogger().getClass().equals(MockedLogger.class), is(true));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(MOCK_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(MOCK_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(MOCK_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(true));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.ALL));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(TEN));
        assertThat(CrashLib.getConfig().getDelay(), is(MINUTE));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(true));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.OTHER));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(true));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1);
        assertThat(defaultUncaughtHandler, sameInstance(mHandler));

        assertThat(annotatedClass.startServiceCounter, is(1));

        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(0)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(0)).mkdir();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(0)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(0)).mkdir();
    }

    @Test
    public void init_annotationPresentWithToastEverythingSetAndDirsNotExistThreadIsNotService_loadConfigFromAnnotation() {
        //prepare
        AnnotatedClassWithToast annotatedClass = new AnnotatedClassWithToast();
        when(mProcessHelper.isReportingServiceProcess(annotatedClass)).thenReturn(false);

        when(mCrashDir.exists()).thenReturn(false);
        when(mNativeCrashDir.exists()).thenReturn(false);

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getErrorDetails(), is(CrashLibTests.ERROR_MESSAGE));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.TOAST));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(2));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));

        assertThat(CrashLib.getConfig().getLogger().getClass().equals(MockedLogger.class), is(true));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(MOCK_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(MOCK_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(MOCK_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(true));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.ALL));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(TEN));
        assertThat(CrashLib.getConfig().getDelay(), is(MINUTE));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(true));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.OTHER));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(true));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1);
        assertThat(defaultUncaughtHandler, sameInstance(mHandler));

        assertThat(annotatedClass.startServiceCounter, is(1));

        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(0)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).mkdirs();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(0)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).mkdirs();
    }

    @Test
    public void init_annotationPresentWithToastEverythingSetAndThreadIsService_loadConfigFromAnnotationDoNotStartService() {
        //prepare
        AnnotatedClassWithToast annotatedClass = new AnnotatedClassWithToast();
        when(mProcessHelper.isReportingServiceProcess(annotatedClass)).thenReturn(true);

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getErrorDetails(), is(CrashLibTests.ERROR_MESSAGE));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.TOAST));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(2));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));

        assertThat(CrashLib.getConfig().getLogger().getClass().equals(MockedLogger.class), is(true));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(MOCK_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(MOCK_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(MOCK_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(true));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.ALL));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(TEN));
        assertThat(CrashLib.getConfig().getDelay(), is(MINUTE));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(true));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.OTHER));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(true));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1);
        assertNotSame(defaultUncaughtHandler, mHandler);

        assertThat(annotatedClass.startServiceCounter, is(0));
    }

    @Test
    public void init_annotationPresentWithNotificationEverythingSetAndFilesExistButNotDirsThreadIsNotService_loadConfigFromAnnotationRemoveFilesAndCreateDirs() {
        //prepare
        AnnotatedClassWithNotification annotatedClass = new AnnotatedClassWithNotification();
        when(mProcessHelper.isReportingServiceProcess(annotatedClass)).thenReturn(false);

        when(mCrashDir.exists()).thenReturn(true);
        when(mCrashDir.isDirectory()).thenReturn(false);
        when(mCrashDir.delete()).thenReturn(true);

        when(mNativeCrashDir.exists()).thenReturn(true);
        when(mNativeCrashDir.isDirectory()).thenReturn(false);
        when(mNativeCrashDir.delete()).thenReturn(true);

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.NOTIFICATION));
        assertThat(CrashLib.getConfig().getErrorMessage(), is(CrashLibTests.ERROR_TITLE));
        assertThat(CrashLib.getConfig().getErrorDetails(), is(CrashLibTests.ERROR_MESSAGE));
        assertThat(CrashLib.getConfig().getNotificationSmallIcon(), is(CrashLibTests.NOTIFICATION_SMALL_ICON));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(2));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));

        assertThat(CrashLib.getConfig().getLogger().getClass().equals(MockedLogger.class), is(true));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(true));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1);
        assertThat(defaultUncaughtHandler, sameInstance(mHandler));

        assertThat(annotatedClass.startServiceCounter, is(1));

        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).mkdir();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).mkdir();
    }

    @Test
    public void init_annotationPresentWithDialogEverythingSetAndFilesExistButNotDirsThreadIsNotService_loadConfigFromAnnotationRemoveFilesAndCreateDirs() {
        //prepare
        AnnotatedClassWithDialog annotatedClass = new AnnotatedClassWithDialog();
        when(mProcessHelper.isReportingServiceProcess(annotatedClass)).thenReturn(false);

        when(mCrashDir.exists()).thenReturn(true);
        when(mCrashDir.isDirectory()).thenReturn(false);
        when(mCrashDir.delete()).thenReturn(true);

        when(mNativeCrashDir.exists()).thenReturn(true);
        when(mNativeCrashDir.isDirectory()).thenReturn(false);
        when(mNativeCrashDir.delete()).thenReturn(true);

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.DIALOG));
        assertThat(CrashLib.getConfig().getErrorMessage(), is(CrashLibTests.ERROR_TITLE));
        assertThat(CrashLib.getConfig().getErrorDetails(), is(CrashLibTests.ERROR_MESSAGE));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(2));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));

        assertThat(CrashLib.getConfig().getLogger().getClass().equals(MockedLogger.class), is(true));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(true));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1);
        assertThat(defaultUncaughtHandler, sameInstance(mHandler));

        assertThat(annotatedClass.startServiceCounter, is(1));

        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).mkdir();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).mkdir();
    }

    @Test
    public void init_annotationPresentWithNoneEverythingSetAndFilesExistButNotDirsThreadIsNotService_loadConfigFromAnnotationRemoveFilesAndCreateDirs() {
        //prepare
        AnnotatedClassWithNone annotatedClass = new AnnotatedClassWithNone();
        when(mProcessHelper.isReportingServiceProcess(annotatedClass)).thenReturn(false);

        when(mCrashDir.exists()).thenReturn(true);
        when(mCrashDir.isDirectory()).thenReturn(false);
        when(mCrashDir.delete()).thenReturn(true);

        when(mNativeCrashDir.exists()).thenReturn(true);
        when(mNativeCrashDir.isDirectory()).thenReturn(false);
        when(mNativeCrashDir.delete()).thenReturn(true);

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.NONE));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(2));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));

        assertThat(CrashLib.getConfig().getLogger().getClass().equals(MockedLogger.class), is(true));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(true));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1);
        assertThat(defaultUncaughtHandler, sameInstance(mHandler));

        assertThat(annotatedClass.startServiceCounter, is(1));

        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).mkdir();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).mkdir();
    }

    @Test
    public void init_annotationPresentWithErrorScreenEverythingSetAndFilesExistButNotDirsThreadIsNotService_loadConfigFromAnnotationRemoveFilesAndCreateDirs() {
        //prepare
        AnnotatedClassWithErrorScreen annotatedClass = new AnnotatedClassWithErrorScreen();
        when(mProcessHelper.isReportingServiceProcess(annotatedClass)).thenReturn(false);

        when(mCrashDir.exists()).thenReturn(true);
        when(mCrashDir.isDirectory()).thenReturn(false);
        when(mCrashDir.delete()).thenReturn(true);

        when(mNativeCrashDir.exists()).thenReturn(true);
        when(mNativeCrashDir.isDirectory()).thenReturn(false);
        when(mNativeCrashDir.delete()).thenReturn(true);

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.ERROR_SCREEN));
        assertThat(CrashLib.getConfig().getErrorMessage(), is(CrashLibTests.ERROR_TITLE));
        assertThat(CrashLib.getConfig().getErrorDetails(), is(CrashLibTests.ERROR_MESSAGE));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(2));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));

        assertThat(CrashLib.getConfig().getLogger().getClass().equals(MockedLogger.class), is(true));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(true));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1);
        assertThat(defaultUncaughtHandler, sameInstance(mHandler));

        assertThat(annotatedClass.startServiceCounter, is(1));

        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).mkdir();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).mkdir();
    }

    @Test
    public void init_invocationTargetExceptionWhenSyncProviderNewInstanceCreating_loadConfigFromAnnotationWithoutOneSyncProvider() throws InstantiationException {
        //prepare
        AnnotatedClassWithInvocationTargetExceptionSyncProvider annotatedClass = new AnnotatedClassWithInvocationTargetExceptionSyncProvider();

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));
        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(3));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProviderWithInvocationTargetException.class), is(false));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));
    }

    @Test
    public void init_noSuchMethodExceptionWhenSyncProviderNewInstanceCreating_loadConfigFromAnnotationWithoutOneSyncProvider() throws InstantiationException {
        //prepare
        AnnotatedClassWithNoSuchMethodExceptionSyncProvider annotatedClass = new AnnotatedClassWithNoSuchMethodExceptionSyncProvider();

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));
        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(3));
        assertThat(containsInstanceOf(syncProviders, SyncProvider1.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProvider2.class), is(true));
        assertThat(containsInstanceOf(syncProviders, SyncProviderWithNoSuchMethodException.class), is(false));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));
    }

    @Test
    public void init_instantiationExceptionWhenCrashPublisherAdding_loadConfigFromAnnotationWithoutOneCrashPublisher() {
        //prepare
        AnnotatedClassWithInstantiationExceptionCrashPublisher annotatedClass = new AnnotatedClassWithInstantiationExceptionCrashPublisher();

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));

        List<ICrashPublisher> publishers = CrashLib.getConfig().getPublishers();
        assertThat(publishers.size(), is(3));
        assertThat(containsInstanceOf(publishers, CrashPublisher1.class), is(true));
        assertThat(containsInstanceOf(publishers, CrashPublisher2.class), is(true));
        assertThat(containsInstanceOf(publishers, CrashPublisherWithInstantiationException.class), is(false));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));
    }

    @Test
    public void init_instantiationExceptionWhenAsyncProviderAdding_loadConfigFromAnnotationWithoutOneAsyncProvider() {
        //prepare
        AnnotatedClassWithInstantiationExceptionAsyncProvider annotatedClass = new AnnotatedClassWithInstantiationExceptionAsyncProvider();

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));
        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProviderWithInstantiationException.class), is(false));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));
    }

    @Test
    public void init_illegalAccessExceptionWhenAsyncProviderAdding_loadConfigFromAnnotationWithoutOneAsyncProvider() {
        //prepare
        AnnotatedClassWithIllegalAccessExceptionAsyncProvider annotatedClass = new AnnotatedClassWithIllegalAccessExceptionAsyncProvider();

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(2));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider1.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProvider2.class), is(true));
        assertThat(containsInstanceOf(asyncProviders, AsyncProviderWithIllegalAccessException.class), is(false));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));
    }

    @Test
    public void init_illegalAccessExceptionWhenCrashPublisherAdding_loadConfigFromAnnotationWithoutOneCrashPublisher() {
        //prepare
        AnnotatedClassWithIllegalAccessExceptionCrashPublisher annotatedClass = new AnnotatedClassWithIllegalAccessExceptionCrashPublisher();

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));

        List<ICrashPublisher> publishers = CrashLib.getConfig().getPublishers();
        assertThat(publishers.size(), is(3));
        assertThat(containsInstanceOf(publishers, CrashPublisher1.class), is(true));
        assertThat(containsInstanceOf(publishers, CrashPublisher2.class), is(true));
        assertThat(containsInstanceOf(publishers, CrashPublisherWithIllegalAccessException.class), is(false));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));
    }

    @Test
    public void init_instantiationExceptionWhenLoggerSetting_loadConfigFromAnnotationWithDefaultLogger() {
        //prepare
        AnnotatedClassWithInstantiationExceptionLogger annotatedClass = new AnnotatedClassWithInstantiationExceptionLogger();

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().getLogger(), is(CrashLib.DEFAULT_LOGGER));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));
    }

    @Test
    public void init_illegalAccessExceptionWhenLoggerSetting_loadConfigFromAnnotationWithDefaultLogger() {
        //prepare
        AnnotatedClassWithIllegalAccessExceptionLogger annotatedClass = new AnnotatedClassWithIllegalAccessExceptionLogger();

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));
        assertThat(CrashLib.getConfig().getLogger(), is(CrashLib.DEFAULT_LOGGER));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));
    }

    @Test
    public void init_annotationNotPresentFilesExistButNotDirsThreadIsNotService_loadDefaultConfigRemoveFilesAndCreateDirs() {
        //prepare
        ClassWithoutAnnotations annotatedClass = new ClassWithoutAnnotations();
        when(mProcessHelper.isReportingServiceProcess(mContext)).thenReturn(false);

        when(mCrashDir.exists()).thenReturn(true);
        when(mCrashDir.isDirectory()).thenReturn(false);
        when(mCrashDir.delete()).thenReturn(true);

        when(mNativeCrashDir.exists()).thenReturn(true);
        when(mNativeCrashDir.isDirectory()).thenReturn(false);
        when(mNativeCrashDir.delete()).thenReturn(true);

        //test
        CrashLib.init(annotatedClass, mFactory);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.TOAST));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(0));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders.size(), is(0));

        assertThat(CrashLib.getConfig().getLogger(), is(CrashLib.DEFAULT_LOGGER));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));

        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(false));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(false));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(false));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(DEFAULT_MAX_ATTEMPTS));
        assertThat(CrashLib.getConfig().getDelay(), is(DEFAULT_DELAY));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));

        assertThat(CrashLib.getConfig().getNotificationSmallIcon(), is(CrashLib.DEFAULT_NOTIFICATION_ICON));

        assertThat(CrashLib.getConfig().getErrorMessage(), is(CrashLib.DEFAULT_ERROR_MESSAGE));
        assertThat(CrashLib.getConfig().getErrorDetails(), is(CrashLib.DEFAULT_ERROR_DETAILS));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1);
        assertThat(defaultUncaughtHandler, sameInstance(mHandler));

        assertThat(annotatedClass.startServiceCounter, is(1));

        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).mkdir();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).mkdir();
    }

    @Test
    public void init_withConfigFilesExistButNotDirsThreadIsNotService_loadDefaultConfigRemoveFilesAndCreateDirs() {
        //prepare
        when(mProcessHelper.isReportingServiceProcess(mContext)).thenReturn(false);

        when(mCrashDir.exists()).thenReturn(true);
        when(mCrashDir.isDirectory()).thenReturn(false);
        when(mCrashDir.delete()).thenReturn(true);

        when(mNativeCrashDir.exists()).thenReturn(true);
        when(mNativeCrashDir.isDirectory()).thenReturn(false);
        when(mNativeCrashDir.delete()).thenReturn(true);

        ILogger logger = mock(ILogger.class);
        CrashLibConfig config = new CrashLibConfig.Builder()
                .showErrorType(EShowErrorType.TOAST)
                .errorMessage(ERROR_TITLE)
                .errorDetails(ERROR_MESSAGE)
                .notificationSmallIcon(NOTIFICATION_SMALL_ICON)
                .logger(logger)
                .disableMemoryHeapProvider()
                .disableStackTraceCrashingThread()
                .disableStackTraceAllThreads()
                .maxAttempts(TEN)
                .delay(MINUTE)
                .build();

        //test
        CrashLib.init(mContext, config, mFactoryWithoutAnnotations);

        //verify
        assertThat(CrashLib.getConfig().getApplicationVersion(), is(MOCK_APPLICATION_VERSION));
        assertThat(CrashLib.getConfig().getErrorDetails(), is(ERROR_MESSAGE));
        assertThat(CrashLib.getConfig().getShowErrorType(), is(EShowErrorType.TOAST));

        List<ICrashSyncProvider> syncProviders = CrashLib.getConfig().getSyncProviders();
        assertThat(syncProviders.size(), is(1));
        assertThat(syncProviders.get(0), instanceOf(MemoryHeapProvider.class));

        List<ICrashAsyncProvider> asyncProviders = CrashLib.getConfig().getAsyncProviders();
        assertThat(asyncProviders, nullValue());

        assertThat(CrashLib.getConfig().getLogger(), is(logger));
        assertThat(CrashLib.getConfig().getCrashReportURL(), is(DEFAULT_CRASH_REPORT_URL));
        assertThat(CrashLib.getConfig().getCrashUploadURL(), is(DEFAULT_CRASH_UPLOAD_URL));
        assertThat(CrashLib.getConfig().getCapProductID(), is(DEFAULT_CAP_PRODUCT_ID));

        assertThat(CrashLib.getConfig().isDisabledMemoryHeapProvider(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceAllThreads(), is(true));
        assertThat(CrashLib.getConfig().isDisabledStackTraceCrashingThread(), is(true));
        assertThat(CrashLib.getConfig().getStrategy(), is(Strategy.BEST_EFFORT));
        assertThat(CrashLib.getConfig().getMaxAttempts(), is(TEN));
        assertThat(CrashLib.getConfig().getDelay(), is(MINUTE));
        assertThat(CrashLib.getConfig().isOfflineSuitable(), is(false));
        assertThat(CrashLib.getConfig().getConnectionLevelSuitable(), is(Connection.WIFI));
        assertThat(CrashLib.getConfig().isRoamingSuitable(), is(false));

        assertThat(CrashLib.getConfig().getNotificationSmallIcon(), is(NOTIFICATION_SMALL_ICON));

        assertThat(CrashLib.getConfig().getErrorDetails(), is(ERROR_MESSAGE));
        assertThat(CrashLib.getConfig().getErrorMessage(), is(ERROR_TITLE));

        Thread.UncaughtExceptionHandler defaultUncaughtHandler = Whitebox.getInternalState(Thread.class, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER1);
        assertThat(defaultUncaughtHandler, sameInstance(mHandler));

        verify(mContext, times(1)).startService(mIntent);

        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mCrashDir, times(1)).mkdir();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).delete();
        //noinspection ResultOfMethodCallIgnored
        verify(mNativeCrashDir, times(1)).mkdir();
    }

}
