package co.smartreceipts.android.persistence.database.tables; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import junit.framework.Assert; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.TimeZone; import co.smartreceipts.android.model.Distance; import co.smartreceipts.android.model.Trip; import co.smartreceipts.android.model.factory.DistanceBuilderFactory; import co.smartreceipts.android.persistence.DatabaseHelper; import co.smartreceipts.android.persistence.database.defaults.TableDefaultsCustomizer; import co.smartreceipts.android.persistence.database.operations.DatabaseOperationMetadata; import io.reactivex.Single; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) public class DistanceTableTest { private static final double DISTANCE_1 = 12.55d; private static final String LOCATION_1 = "Location"; private static final String TRIP_1 = "Trip"; private static final double DISTANCE_2 = 140d; private static final String LOCATION_2 = "Location2"; private static final String TRIP_2 = "Trip2"; private static final double DISTANCE_3 = 12.123; private static final String LOCATION_3 = "Location3"; private static final String TRIP_3 = "Trip3"; private static final long DATE = 1409703721000L; private static final String TIMEZONE = TimeZone.getDefault().getID(); private static final String COMMENT = "Comment"; private static final double RATE = 0.33d; private static final String CURRENCY_CODE = "USD"; // Class under test DistanceTable mDistanceTable; @Mock SQLiteDatabase mSQLiteDatabase; @Mock TableDefaultsCustomizer mTableDefaultsCustomizer; @Mock Table<Trip, String> mTripsTable; @Mock Trip mTrip1; @Mock Trip mTrip2; @Mock Trip mTrip3; @Captor ArgumentCaptor<String> mSqlCaptor; SQLiteOpenHelper mSQLiteOpenHelper; Distance mDistance1; Distance mDistance2; DistanceBuilderFactory mBuilder; @Before public void setup() { MockitoAnnotations.initMocks(this); when(mTrip1.getName()).thenReturn(TRIP_1); when(mTrip2.getName()).thenReturn(TRIP_2); when(mTrip3.getName()).thenReturn(TRIP_3); when(mTripsTable.findByPrimaryKey(TRIP_1)).thenReturn(Single.just(mTrip1)); when(mTripsTable.findByPrimaryKey(TRIP_2)).thenReturn(Single.just(mTrip2)); when(mTripsTable.findByPrimaryKey(TRIP_3)).thenReturn(Single.just(mTrip3)); mSQLiteOpenHelper = new TestSQLiteOpenHelper(RuntimeEnvironment.application); mDistanceTable = new DistanceTable(mSQLiteOpenHelper, mTripsTable, CURRENCY_CODE); // Now create the table and insert some defaults mDistanceTable.onCreate(mSQLiteOpenHelper.getWritableDatabase(), mTableDefaultsCustomizer); mBuilder = new DistanceBuilderFactory(); mBuilder.setDate(DATE).setTimezone(TIMEZONE).setComment(COMMENT).setRate(RATE).setCurrency(CURRENCY_CODE); mDistance1 = mDistanceTable.insert(mBuilder.setDistance(DISTANCE_1).setLocation(LOCATION_1).setTrip(mTrip1).build(), new DatabaseOperationMetadata()).blockingGet(); mDistance2 = mDistanceTable.insert(mBuilder.setDistance(DISTANCE_2).setLocation(LOCATION_2).setTrip(mTrip2).build(), new DatabaseOperationMetadata()).blockingGet(); } @After public void tearDown() { mSQLiteOpenHelper.getWritableDatabase().execSQL("DROP TABLE IF EXISTS " + mDistanceTable.getTableName()); } @Test public void getTableName() { assertEquals("distance", mDistanceTable.getTableName()); } @Test public void onCreate() { final TableDefaultsCustomizer customizer = mock(TableDefaultsCustomizer.class); mDistanceTable.onCreate(mSQLiteDatabase, customizer); verify(mSQLiteDatabase).execSQL(mSqlCaptor.capture()); verifyZeroInteractions(customizer); assertTrue(mSqlCaptor.getValue().contains("CREATE TABLE distance")); // Table name assertTrue(mSqlCaptor.getValue().contains("id INTEGER PRIMARY KEY AUTOINCREMENT")); assertTrue(mSqlCaptor.getValue().contains("parent TEXT")); assertTrue(mSqlCaptor.getValue().contains("distance DECIMAL(10, 2)")); assertTrue(mSqlCaptor.getValue().contains("location TEXT")); assertTrue(mSqlCaptor.getValue().contains("date DATE")); assertTrue(mSqlCaptor.getValue().contains("timezone TEXT")); assertTrue(mSqlCaptor.getValue().contains("comment TEXT")); assertTrue(mSqlCaptor.getValue().contains("rate DECIMAL(10, 2)")); assertTrue(mSqlCaptor.getValue().contains("rate_currency TEXT")); assertTrue(mSqlCaptor.getValue().contains("drive_sync_id TEXT")); assertTrue(mSqlCaptor.getValue().contains("drive_is_synced BOOLEAN DEFAULT 0")); assertTrue(mSqlCaptor.getValue().contains("drive_marked_for_deletion BOOLEAN DEFAULT 0")); assertTrue(mSqlCaptor.getValue().contains("last_local_modification_time DATE")); } @Test public void onUpgradeFromV12() { final int oldVersion = 12; final int newVersion = DatabaseHelper.DATABASE_VERSION; final TableDefaultsCustomizer customizer = mock(TableDefaultsCustomizer.class); mDistanceTable.onUpgrade(mSQLiteDatabase, oldVersion, newVersion, customizer); verify(mSQLiteDatabase, atLeastOnce()).execSQL(mSqlCaptor.capture()); verifyZeroInteractions(customizer); assertTrue(mSqlCaptor.getAllValues().get(0).contains("distance")); // Table name assertTrue(mSqlCaptor.getAllValues().get(0).contains("id")); assertTrue(mSqlCaptor.getAllValues().get(0).contains("parent")); assertTrue(mSqlCaptor.getAllValues().get(0).contains("distance")); assertTrue(mSqlCaptor.getAllValues().get(0).contains("location")); assertTrue(mSqlCaptor.getAllValues().get(0).contains("date")); assertTrue(mSqlCaptor.getAllValues().get(0).contains("timezone")); assertTrue(mSqlCaptor.getAllValues().get(0).contains("comment")); assertTrue(mSqlCaptor.getAllValues().get(0).contains("rate")); assertTrue(mSqlCaptor.getAllValues().get(0).contains("rate_currency")); // Create assertEquals(mSqlCaptor.getAllValues().get(0), "CREATE TABLE distance (id INTEGER PRIMARY KEY AUTOINCREMENT,parent TEXT REFERENCES name ON DELETE CASCADE,distance DECIMAL(10, 2) DEFAULT 0.00,location TEXT,date DATE,timezone TEXT,comment TEXT,rate_currency TEXT NOT NULL, rate DECIMAL(10, 2) DEFAULT 0.00);"); // Migrate Trip Distances to Distance WHERE the Trip Currency != NULL assertEquals(mSqlCaptor.getAllValues().get(1), "INSERT INTO distance(parent, distance, location, date, timezone, comment, rate_currency) SELECT name, miles_new , \"\" as location, from_date, from_timezone , \"\" as comment, trips_default_currency FROM trips WHERE trips_default_currency IS NOT NULL AND miles_new > 0;"); // Migrate Trip Distances to Distance WHERE the Trip Currency == NULL assertEquals(mSqlCaptor.getAllValues().get(2), "INSERT INTO distance(parent, distance, location, date, timezone, comment, rate_currency) SELECT name, miles_new , \"\" as location, from_date, from_timezone , \"\" as comment, \"USD\" as rate_currency FROM trips WHERE trips_default_currency IS NULL AND miles_new > 0;"); assertEquals(mSqlCaptor.getAllValues().get(3), "ALTER TABLE " + mDistanceTable.getTableName() + " ADD drive_sync_id TEXT"); assertEquals(mSqlCaptor.getAllValues().get(4), "ALTER TABLE " + mDistanceTable.getTableName() + " ADD drive_is_synced BOOLEAN DEFAULT 0"); assertEquals(mSqlCaptor.getAllValues().get(5), "ALTER TABLE " + mDistanceTable.getTableName() + " ADD drive_marked_for_deletion BOOLEAN DEFAULT 0"); assertEquals(mSqlCaptor.getAllValues().get(6), "ALTER TABLE " + mDistanceTable.getTableName() + " ADD last_local_modification_time DATE"); } @Test public void onUpgradeFromV14() { final int oldVersion = 14; final int newVersion = DatabaseHelper.DATABASE_VERSION; final TableDefaultsCustomizer customizer = mock(TableDefaultsCustomizer.class); mDistanceTable.onUpgrade(mSQLiteDatabase, oldVersion, newVersion, customizer); verify(mSQLiteDatabase, atLeastOnce()).execSQL(mSqlCaptor.capture()); verifyZeroInteractions(customizer); assertEquals(mSqlCaptor.getAllValues().get(0), "ALTER TABLE " + mDistanceTable.getTableName() + " ADD drive_sync_id TEXT"); assertEquals(mSqlCaptor.getAllValues().get(1), "ALTER TABLE " + mDistanceTable.getTableName() + " ADD drive_is_synced BOOLEAN DEFAULT 0"); assertEquals(mSqlCaptor.getAllValues().get(2), "ALTER TABLE " + mDistanceTable.getTableName() + " ADD drive_marked_for_deletion BOOLEAN DEFAULT 0"); assertEquals(mSqlCaptor.getAllValues().get(3), "ALTER TABLE " + mDistanceTable.getTableName() + " ADD last_local_modification_time DATE"); } @Test public void onUpgradeAlreadyOccurred() { final int oldVersion = DatabaseHelper.DATABASE_VERSION; final int newVersion = DatabaseHelper.DATABASE_VERSION; final TableDefaultsCustomizer customizer = mock(TableDefaultsCustomizer.class); mDistanceTable.onUpgrade(mSQLiteDatabase, oldVersion, newVersion, customizer); verify(mSQLiteDatabase, never()).execSQL(mSqlCaptor.capture()); verifyZeroInteractions(customizer); } @Test public void get() { final List<Distance> distances = mDistanceTable.get().blockingGet(); assertEquals(distances, Arrays.asList(mDistance1, mDistance2)); } @Test public void getForTrip() { // Note: We're adding this one to trip 1 final Distance distance = mDistanceTable.insert(mBuilder.setDistance(DISTANCE_3).setLocation(LOCATION_3).setTrip(mTrip1).build(), new DatabaseOperationMetadata()).blockingGet(); assertNotNull(distance); final List<Distance> list1 = mDistanceTable.get(mTrip1).blockingGet(); final List<Distance> list2 = mDistanceTable.get(mTrip2).blockingGet(); final List<Distance> list3 = mDistanceTable.get(mTrip3).blockingGet(); assertEquals(list1, Arrays.asList(mDistance1, distance)); assertEquals(list2, Collections.singletonList(mDistance2)); assertEquals(list3, Collections.<Distance>emptyList()); } @Test public void insert() { final Distance distance = mDistanceTable.insert(mBuilder.setDistance(DISTANCE_3).setLocation(LOCATION_3).setTrip(mTrip3).build(), new DatabaseOperationMetadata()).blockingGet(); assertNotNull(distance); final List<Distance> distances = mDistanceTable.get().blockingGet(); assertEquals(distances, Arrays.asList(mDistance1, mDistance2, distance)); } @Test public void findByPrimaryKey() { mDistanceTable.findByPrimaryKey(mDistance1.getId()) .test() .assertNoErrors() .assertResult(mDistance1); } @Test public void findByPrimaryMissingKey() { mDistanceTable.findByPrimaryKey(-1) .test() .assertError(Exception.class); } @Test public void update() { final Distance updatedDistance = mDistanceTable.update(mDistance1, mBuilder.setDistance(DISTANCE_3).setLocation(LOCATION_3).setTrip(mTrip3).build(), new DatabaseOperationMetadata()).blockingGet(); assertNotNull(updatedDistance); assertFalse(mDistance1.equals(updatedDistance)); final List<Distance> distances = mDistanceTable.get().blockingGet(); assertEquals(distances, Arrays.asList(updatedDistance, mDistance2)); } @Test public void delete() { Assert.assertEquals(mDistance1, mDistanceTable.delete(mDistance1, new DatabaseOperationMetadata()).blockingGet()); final List<Distance> distances = mDistanceTable.get().blockingGet(); assertEquals(distances, Collections.singletonList(mDistance2)); } }