package co.smartreceipts.android.persistence.database.controllers.impl;
import android.support.annotation.NonNull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import java.util.Arrays;
import java.util.List;
import co.smartreceipts.android.analytics.Analytics;
import co.smartreceipts.android.analytics.events.ErrorEvent;
import co.smartreceipts.android.persistence.database.controllers.TableEventsListener;
import co.smartreceipts.android.persistence.database.controllers.alterations.TableActionAlterations;
import co.smartreceipts.android.persistence.database.operations.DatabaseOperationMetadata;
import co.smartreceipts.android.persistence.database.tables.Table;
import io.reactivex.Completable;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
public class AbstractTableControllerTest {
/**
* Test impl for our abstract class
*/
private class AbstractTableControllerTestImpl extends AbstractTableController<Object> {
AbstractTableControllerTestImpl(@NonNull Table<Object, ?> table, @NonNull TableActionAlterations<Object> tableActionAlterations, @NonNull Analytics analytics,
@NonNull Scheduler subscribeOnScheduler, @NonNull Scheduler observeOnScheduler) {
super(table, tableActionAlterations, analytics, subscribeOnScheduler, observeOnScheduler);
}
}
// Class under test
AbstractTableController<Object> mAbstractTableController;
@Mock
Table<Object, ?> mTable;
@Mock
TableActionAlterations<Object> mTableActionAlterations;
@Mock
Analytics mAnalytics;
@Mock
TableEventsListener<Object> mListener1;
@Mock
TableEventsListener<Object> mListener2;
@Mock
TableEventsListener<Object> mListener3;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mAbstractTableController = new AbstractTableControllerTestImpl(mTable, mTableActionAlterations, mAnalytics, Schedulers.trampoline(), Schedulers.trampoline());
mAbstractTableController.subscribe(mListener1);
mAbstractTableController.subscribe(mListener2);
mAbstractTableController.subscribe(mListener3);
}
@Test
public void onGetSuccess() throws Exception {
final List<Object> objects = Arrays.asList(new Object(), new Object(), new Object());
when(mTableActionAlterations.preGet()).thenReturn(Completable.complete());
when(mTable.get()).thenReturn(Single.just(objects));
when(mTableActionAlterations.postGet(objects)).thenReturn(Single.just(objects));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.get();
verify(mListener1).onGetSuccess(objects);
verify(mListener3).onGetSuccess(objects);
verifyZeroInteractions(mListener2);
}
@Test
public void onPreGetException() throws Exception {
final List<Object> objects = Arrays.asList(new Object(), new Object(), new Object());
final Exception e = new Exception();
when(mTableActionAlterations.preGet()).thenReturn(Completable.error(e));
when(mTable.get()).thenReturn(Single.just(objects));
when(mTableActionAlterations.postGet(objects)).thenReturn(Single.just(objects));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.get();
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onGetFailure(e);
verify(mListener3).onGetFailure(e);
verifyZeroInteractions(mListener2);
}
@Test
public void onGetException() throws Exception {
final List<Object> objects = Arrays.asList(new Object(), new Object(), new Object());
final Exception e = new Exception();
when(mTableActionAlterations.preGet()).thenReturn(Completable.complete());
when(mTable.get()).thenReturn(Single.error(e));
when(mTableActionAlterations.postGet(objects)).thenReturn(Single.just(objects));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.get();
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onGetFailure(e);
verify(mListener3).onGetFailure(e);
verifyZeroInteractions(mListener2);
}
@Test
public void onPostGetException() throws Exception {
final List<Object> objects = Arrays.asList(new Object(), new Object(), new Object());
when(mTableActionAlterations.preGet()).thenReturn(Completable.complete());
when(mTable.get()).thenReturn(Single.just(objects));
when(mTableActionAlterations.postGet(objects)).thenReturn(Single.error(new Exception("")));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.get();
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onGetFailure(any(Exception.class));
verify(mListener3).onGetFailure(any(Exception.class));
verifyZeroInteractions(mListener2);
}
@Test
public void onInsertSuccess() throws Exception {
final Object insertItem = new Object();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preInsert(insertItem)).thenReturn(Single.just(insertItem));
when(mTable.insert(insertItem, databaseOperationMetadata)).thenReturn(Single.just(insertItem));
when(mTableActionAlterations.postInsert(insertItem)).thenReturn(Single.just(insertItem));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.insert(insertItem, databaseOperationMetadata);
verify(mListener1).onInsertSuccess(insertItem, databaseOperationMetadata);
verify(mListener3).onInsertSuccess(insertItem, databaseOperationMetadata);
verifyZeroInteractions(mListener2);
}
@Test
public void onPreInsertException() throws Exception {
final Object insertItem = new Object();
final Exception e = new Exception();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preInsert(insertItem)).thenReturn(Single.error(e));
when(mTable.insert(insertItem, databaseOperationMetadata)).thenReturn(Single.just(insertItem));
when(mTableActionAlterations.postInsert(insertItem)).thenReturn(Single.just(insertItem));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.insert(insertItem, databaseOperationMetadata);
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onInsertFailure(insertItem, e, databaseOperationMetadata);
verify(mListener3).onInsertFailure(insertItem, e, databaseOperationMetadata);
verifyZeroInteractions(mListener2);
}
@Test
public void onInsertException() throws Exception {
final Object insertItem = new Object();
final Exception e = new Exception();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preInsert(insertItem)).thenReturn(Single.just(insertItem));
when(mTable.insert(insertItem, databaseOperationMetadata)).thenReturn(Single.error(e));
when(mTableActionAlterations.postInsert(insertItem)).thenReturn(Single.just(insertItem));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.insert(insertItem, databaseOperationMetadata);
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onInsertFailure(insertItem, e, databaseOperationMetadata);
verify(mListener3).onInsertFailure(insertItem, e, databaseOperationMetadata);
verifyZeroInteractions(mListener2);
}
@Test
public void onPostInsertException() throws Exception {
final Object insertItem = new Object();
final Exception e = new Exception();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preInsert(insertItem)).thenReturn(Single.just(insertItem));
when(mTable.insert(insertItem, databaseOperationMetadata)).thenReturn(Single.just(insertItem));
when(mTableActionAlterations.postInsert(insertItem)).thenReturn(Single.error(e));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.insert(insertItem, databaseOperationMetadata);
// The Exceptions.propagate call wraps our exception inside a RuntimeException
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onInsertFailure(eq(insertItem), any(Exception.class), eq(databaseOperationMetadata));
verify(mListener3).onInsertFailure(eq(insertItem), any(Exception.class), eq(databaseOperationMetadata));
verifyZeroInteractions(mListener2);
}
@Test
public void onUpdateSuccess() throws Exception {
final Object oldItem = new Object();
final Object newItem = new Object();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preUpdate(oldItem, newItem)).thenReturn(Single.just(newItem));
when(mTable.update(oldItem, newItem, databaseOperationMetadata)).thenReturn(Single.just(newItem));
when(mTableActionAlterations.postUpdate(oldItem, newItem)).thenReturn(Single.just(newItem));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.update(oldItem, newItem, databaseOperationMetadata);
verify(mListener1).onUpdateSuccess(oldItem, newItem, databaseOperationMetadata);
verify(mListener3).onUpdateSuccess(oldItem, newItem, databaseOperationMetadata);
verifyZeroInteractions(mListener2);
}
@Test
public void onPreUpdateException() throws Exception {
final Object oldItem = new Object();
final Object newItem = new Object();
final Exception e = new Exception();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preUpdate(oldItem, newItem)).thenReturn(Single.error(e));
when(mTable.update(oldItem, newItem, databaseOperationMetadata)).thenReturn(Single.just(newItem));
when(mTableActionAlterations.postUpdate(oldItem, newItem)).thenReturn(Single.just(newItem));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.update(oldItem, newItem, databaseOperationMetadata);
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onUpdateFailure(oldItem, e, databaseOperationMetadata);
verify(mListener3).onUpdateFailure(oldItem, e, databaseOperationMetadata);
verifyZeroInteractions(mListener2);
}
@Test
public void onUpdateException() throws Exception {
final Object oldItem = new Object();
final Object newItem = new Object();
final Exception e = new Exception();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preUpdate(oldItem, newItem)).thenReturn(Single.just(newItem));
when(mTable.update(oldItem, newItem, databaseOperationMetadata)).thenReturn(Single.error(e));
when(mTableActionAlterations.postUpdate(oldItem, newItem)).thenReturn(Single.just(newItem));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.update(oldItem, newItem, databaseOperationMetadata);
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onUpdateFailure(oldItem, e, databaseOperationMetadata);
verify(mListener3).onUpdateFailure(oldItem, e, databaseOperationMetadata);
verifyZeroInteractions(mListener2);
}
@Test
public void onPostUpdateException() throws Exception {
final Object oldItem = new Object();
final Object newItem = new Object();
final Exception e = new Exception();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preUpdate(oldItem, newItem)).thenReturn(Single.just(newItem));
when(mTable.update(oldItem, newItem, databaseOperationMetadata)).thenReturn(Single.just(newItem));
when(mTableActionAlterations.postUpdate(oldItem, newItem)).thenReturn(Single.error(e));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.update(oldItem, newItem, databaseOperationMetadata);
// The Exceptions.propagate call wraps our exception inside a RuntimeException
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onUpdateFailure(eq(oldItem), any(Exception.class), eq(databaseOperationMetadata));
verify(mListener3).onUpdateFailure(eq(oldItem), any(Exception.class), eq(databaseOperationMetadata));
verifyZeroInteractions(mListener2);
}
@Test
public void onDeleteSuccess() throws Exception {
final Object deleteCandidateItem = new Object();
final Object deletedItem = new Object();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preDelete(deleteCandidateItem)).thenReturn(Single.just(deleteCandidateItem));
when(mTable.delete(deleteCandidateItem, databaseOperationMetadata)).thenReturn(Single.just(deletedItem));
when(mTableActionAlterations.postDelete(deletedItem)).thenReturn(Single.just(deletedItem));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.delete(deleteCandidateItem, databaseOperationMetadata);
verify(mListener1).onDeleteSuccess(deletedItem, databaseOperationMetadata);
verify(mListener3).onDeleteSuccess(deletedItem, databaseOperationMetadata);
verifyZeroInteractions(mListener2);
}
@Test
public void onPreDeleteException() throws Exception {
final Object deleteCandidateItem = new Object();
final Object deletedItem = new Object();
final Exception e = new Exception();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preDelete(deleteCandidateItem)).thenReturn(Single.error(e));
when(mTable.delete(deleteCandidateItem, databaseOperationMetadata)).thenReturn(Single.just(deletedItem));
when(mTableActionAlterations.postDelete(deletedItem)).thenReturn(Single.just(deletedItem));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.delete(deleteCandidateItem, databaseOperationMetadata);
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onDeleteFailure(deleteCandidateItem, e, databaseOperationMetadata);
verify(mListener3).onDeleteFailure(deleteCandidateItem, e, databaseOperationMetadata);
verifyZeroInteractions(mListener2);
}
@Test
public void onDeleteException() throws Exception {
final Object deleteCandidateItem = new Object();
final Object deletedItem = new Object();
final Exception e = new Exception();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preDelete(deleteCandidateItem)).thenReturn(Single.just(deleteCandidateItem));
when(mTable.delete(deleteCandidateItem, databaseOperationMetadata)).thenReturn(Single.error(e));
when(mTableActionAlterations.postDelete(deletedItem)).thenReturn(Single.just(deletedItem));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.delete(deleteCandidateItem, databaseOperationMetadata);
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onDeleteFailure(deleteCandidateItem, e, databaseOperationMetadata);
verify(mListener3).onDeleteFailure(deleteCandidateItem, e, databaseOperationMetadata);
verifyZeroInteractions(mListener2);
}
@Test
public void onPostDeleteException() throws Exception {
final Object deleteCandidateItem = new Object();
final Object deletedItem = new Object();
final Exception e = new Exception();
final DatabaseOperationMetadata databaseOperationMetadata = new DatabaseOperationMetadata();
when(mTableActionAlterations.preDelete(deleteCandidateItem)).thenReturn(Single.just(deleteCandidateItem));
when(mTable.delete(deleteCandidateItem, databaseOperationMetadata)).thenReturn(Single.just(deletedItem));
when(mTableActionAlterations.postDelete(deletedItem)).thenReturn(Single.error(e));
mAbstractTableController.unsubscribe(mListener2);
mAbstractTableController.delete(deleteCandidateItem, databaseOperationMetadata);
// The Exceptions.propagate call wraps our exception inside a RuntimeException
verify(mAnalytics).record(any(ErrorEvent.class));
verify(mListener1).onDeleteFailure(eq(deleteCandidateItem), any(Exception.class), eq(databaseOperationMetadata));
verify(mListener3).onDeleteFailure(eq(deleteCandidateItem), any(Exception.class), eq(databaseOperationMetadata));
verifyZeroInteractions(mListener2);
}
}