diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e6d2d251..99151bb0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,6 +49,11 @@ android { testOptions { animationsDisabled = true } + + sourceSets { + // Adds exported schema location as test app assets. + getByName("androidTest").assets.srcDir("$projectDir/../data_local/schemas") + } } dependencies { @@ -88,4 +93,5 @@ dependencies { androidTestImplementation(Deps.UiTest.espresso) androidTestImplementation(Deps.UiTest.espressoContrib) androidTestImplementation(Deps.UiTest.dagger) + androidTestImplementation(Deps.UiTest.room) } diff --git a/app/src/androidTest/java/com/example/util/simpletimetracker/MigrationTest.kt b/app/src/androidTest/java/com/example/util/simpletimetracker/MigrationTest.kt new file mode 100644 index 00000000..1831ca2c --- /dev/null +++ b/app/src/androidTest/java/com/example/util/simpletimetracker/MigrationTest.kt @@ -0,0 +1,47 @@ +package com.example.util.simpletimetracker + +import androidx.room.Room +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.example.util.simpletimetracker.data_local.database.AppDatabase +import com.example.util.simpletimetracker.data_local.database.AppDatabaseMigrations +import java.io.IOException +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MigrationTest { + private val testDatabaseName = "migration-test" + + @get:Rule + val helper: MigrationTestHelper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + AppDatabase::class.java.canonicalName, + FrameworkSQLiteOpenHelperFactory(), + ) + + @Test + @Throws(IOException::class) + fun migrateAll() { + // Create earliest version of the database. + helper.createDatabase(testDatabaseName, 6) + .apply { close() } + + // Open latest version of the database. Room validates the schema + // once all migrations execute. + Room + .databaseBuilder( + InstrumentationRegistry.getInstrumentation().targetContext, + AppDatabase::class.java, + testDatabaseName, + ) + .addMigrations( + *AppDatabaseMigrations.migrations.toTypedArray() + ) + .build() + .apply { openHelper.writableDatabase.close() } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt index 50e9ccdf..7f286cf6 100644 --- a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt +++ b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt @@ -87,5 +87,7 @@ object Deps { "androidx.test.espresso:espresso-idling-resource:${Versions.espresso}" const val dagger = "com.google.dagger:hilt-android-testing:${Versions.dagger}" + const val room = + "androidx.room:room-testing:${Versions.room}" } } \ No newline at end of file diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabaseMigrations.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabaseMigrations.kt index a44be7c2..49ad0f7c 100644 --- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabaseMigrations.kt +++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/database/AppDatabaseMigrations.kt @@ -6,16 +6,31 @@ import androidx.sqlite.db.SupportSQLiteDatabase class AppDatabaseMigrations { companion object { - val migration_1_2 = object : Migration(1, 2) { + val migrations: List + get() = listOf( + migration_1_2, + migration_2_3, + migration_3_4, + migration_4_5, + migration_5_6, + migration_6_7, + migration_7_8, + migration_8_9, + migration_9_10, + migration_10_11, + migration_11_12, + ) + + private val migration_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { // Create the new table database.execSQL( - "CREATE TABLE recordTypes_new (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, icon TEXT NOT NULL, color INTEGER NOT NULL, hidden INTEGER NOT NULL)" + "CREATE TABLE recordTypes_new (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, icon TEXT NOT NULL, color INTEGER NOT NULL, hidden INTEGER NOT NULL)", ) // Copy the data database.execSQL( - "INSERT INTO recordTypes_new (id, name, icon, color, hidden) SELECT id, name, '', color, hidden FROM recordTypes" + "INSERT INTO recordTypes_new (id, name, icon, color, hidden) SELECT id, name, '', color, hidden FROM recordTypes", ) // Remove the old table @@ -26,112 +41,112 @@ class AppDatabaseMigrations { } } - val migration_2_3 = object : Migration(2, 3) { + private val migration_2_3 = object : Migration(2, 3) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "CREATE TABLE IF NOT EXISTS `categories` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `color` INTEGER NOT NULL)" + "CREATE TABLE IF NOT EXISTS `categories` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `color` INTEGER NOT NULL)", ) database.execSQL( - "CREATE TABLE IF NOT EXISTS `recordTypeCategory` (`record_type_id` INTEGER NOT NULL, `category_id` INTEGER NOT NULL, PRIMARY KEY(`record_type_id`, `category_id`))" + "CREATE TABLE IF NOT EXISTS `recordTypeCategory` (`record_type_id` INTEGER NOT NULL, `category_id` INTEGER NOT NULL, PRIMARY KEY(`record_type_id`, `category_id`))", ) } } - val migration_3_4 = object : Migration(3, 4) { + private val migration_3_4 = object : Migration(3, 4) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "ALTER TABLE recordTypes ADD COLUMN goal_time INTEGER NOT NULL DEFAULT 0" + "ALTER TABLE recordTypes ADD COLUMN goal_time INTEGER NOT NULL DEFAULT 0", ) database.execSQL( - "ALTER TABLE records ADD COLUMN comment TEXT NOT NULL DEFAULT ''" + "ALTER TABLE records ADD COLUMN comment TEXT NOT NULL DEFAULT ''", ) } } - val migration_4_5 = object : Migration(4, 5) { + private val migration_4_5 = object : Migration(4, 5) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "ALTER TABLE runningRecords ADD COLUMN comment TEXT NOT NULL DEFAULT ''" + "ALTER TABLE runningRecords ADD COLUMN comment TEXT NOT NULL DEFAULT ''", ) } } - val migration_5_6 = object : Migration(5, 6) { + private val migration_5_6 = object : Migration(5, 6) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "CREATE TABLE IF NOT EXISTS `recordTags` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `archived` INTEGER NOT NULL)" + "CREATE TABLE IF NOT EXISTS `recordTags` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `archived` INTEGER NOT NULL)", ) database.execSQL( - "ALTER TABLE records ADD COLUMN tag_id INTEGER NOT NULL DEFAULT 0" + "ALTER TABLE records ADD COLUMN tag_id INTEGER NOT NULL DEFAULT 0", ) database.execSQL( - "ALTER TABLE runningRecords ADD COLUMN tag_id INTEGER NOT NULL DEFAULT 0" + "ALTER TABLE runningRecords ADD COLUMN tag_id INTEGER NOT NULL DEFAULT 0", ) } } - val migration_6_7 = object : Migration(6, 7) { + private val migration_6_7 = object : Migration(6, 7) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "ALTER TABLE recordTags ADD COLUMN color INTEGER NOT NULL DEFAULT 0" + "ALTER TABLE recordTags ADD COLUMN color INTEGER NOT NULL DEFAULT 0", ) database.execSQL( - "CREATE TABLE IF NOT EXISTS `recordToRecordTag` (`record_id` INTEGER NOT NULL, `record_tag_id` INTEGER NOT NULL, PRIMARY KEY(`record_id`, `record_tag_id`))" + "CREATE TABLE IF NOT EXISTS `recordToRecordTag` (`record_id` INTEGER NOT NULL, `record_tag_id` INTEGER NOT NULL, PRIMARY KEY(`record_id`, `record_tag_id`))", ) database.execSQL( - "INSERT INTO recordToRecordTag (record_id, record_tag_id) SELECT id, tag_id from records WHERE tag_id != 0" + "INSERT INTO recordToRecordTag (record_id, record_tag_id) SELECT id, tag_id from records WHERE tag_id != 0", ) database.execSQL( - "CREATE TABLE IF NOT EXISTS `runningRecordToRecordTag` (`running_record_id` INTEGER NOT NULL, `record_tag_id` INTEGER NOT NULL, PRIMARY KEY(`running_record_id`, `record_tag_id`))" + "CREATE TABLE IF NOT EXISTS `runningRecordToRecordTag` (`running_record_id` INTEGER NOT NULL, `record_tag_id` INTEGER NOT NULL, PRIMARY KEY(`running_record_id`, `record_tag_id`))", ) } } - val migration_7_8 = object : Migration(7, 8) { + private val migration_7_8 = object : Migration(7, 8) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "ALTER TABLE recordTypes ADD COLUMN color_int TEXT NOT NULL DEFAULT ''" + "ALTER TABLE recordTypes ADD COLUMN color_int TEXT NOT NULL DEFAULT ''", ) database.execSQL( - "ALTER TABLE categories ADD COLUMN color_int TEXT NOT NULL DEFAULT ''" + "ALTER TABLE categories ADD COLUMN color_int TEXT NOT NULL DEFAULT ''", ) database.execSQL( - "ALTER TABLE recordTags ADD COLUMN color_int TEXT NOT NULL DEFAULT ''" + "ALTER TABLE recordTags ADD COLUMN color_int TEXT NOT NULL DEFAULT ''", ) } } - val migration_8_9 = object : Migration(8, 9) { + private val migration_8_9 = object : Migration(8, 9) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "CREATE TABLE IF NOT EXISTS `activityFilters` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `selectedIds` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `color` INTEGER NOT NULL, `color_int` TEXT NOT NULL, `selected` INTEGER NOT NULL)" + "CREATE TABLE IF NOT EXISTS `activityFilters` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `selectedIds` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `color` INTEGER NOT NULL, `color_int` TEXT NOT NULL, `selected` INTEGER NOT NULL)", ) } } - val migration_9_10 = object : Migration(9, 10) { + private val migration_9_10 = object : Migration(9, 10) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "ALTER TABLE recordTypes ADD COLUMN daily_goal_time INTEGER NOT NULL DEFAULT 0" + "ALTER TABLE recordTypes ADD COLUMN daily_goal_time INTEGER NOT NULL DEFAULT 0", ) database.execSQL( - "ALTER TABLE recordTypes ADD COLUMN weekly_goal_time INTEGER NOT NULL DEFAULT 0" + "ALTER TABLE recordTypes ADD COLUMN weekly_goal_time INTEGER NOT NULL DEFAULT 0", ) } } - val migration_10_11 = object : Migration(10, 11) { + private val migration_10_11 = object : Migration(10, 11) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "ALTER TABLE recordTypes ADD COLUMN monthly_goal_time INTEGER NOT NULL DEFAULT 0" + "ALTER TABLE recordTypes ADD COLUMN monthly_goal_time INTEGER NOT NULL DEFAULT 0", ) } } - val migration_11_12 = object : Migration(11, 12) { + private val migration_11_12 = object : Migration(11, 12) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( - "CREATE TABLE IF NOT EXISTS `favouriteComments` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `comment` TEXT NOT NULL)" + "CREATE TABLE IF NOT EXISTS `favouriteComments` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `comment` TEXT NOT NULL)", ) } } diff --git a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/di/DataLocalModule.kt b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/di/DataLocalModule.kt index 41f33527..cf4287c7 100644 --- a/data_local/src/main/java/com/example/util/simpletimetracker/data_local/di/DataLocalModule.kt +++ b/data_local/src/main/java/com/example/util/simpletimetracker/data_local/di/DataLocalModule.kt @@ -40,17 +40,7 @@ class DataLocalModule { AppDatabase::class.java, AppDatabase.DATABASE_NAME ) .addMigrations( - AppDatabaseMigrations.migration_1_2, - AppDatabaseMigrations.migration_2_3, - AppDatabaseMigrations.migration_3_4, - AppDatabaseMigrations.migration_4_5, - AppDatabaseMigrations.migration_5_6, - AppDatabaseMigrations.migration_6_7, - AppDatabaseMigrations.migration_7_8, - AppDatabaseMigrations.migration_8_9, - AppDatabaseMigrations.migration_9_10, - AppDatabaseMigrations.migration_10_11, - AppDatabaseMigrations.migration_11_12, + *AppDatabaseMigrations.migrations.toTypedArray() ) .build() }