|
@@ -20,9 +20,9 @@
|
|
|
package com.nextcloud.client.migrations
|
|
|
|
|
|
import androidx.test.annotation.UiThreadTest
|
|
|
-import com.nextcloud.client.account.UserAccountManager
|
|
|
import com.nextcloud.client.appinfo.AppInfo
|
|
|
import com.nextcloud.client.core.ManualAsyncRunner
|
|
|
+import com.nhaarman.mockitokotlin2.inOrder
|
|
|
import com.nhaarman.mockitokotlin2.mock
|
|
|
import com.nhaarman.mockitokotlin2.never
|
|
|
import com.nhaarman.mockitokotlin2.verify
|
|
@@ -34,8 +34,6 @@ import org.junit.Before
|
|
|
import org.junit.Test
|
|
|
import org.mockito.Mock
|
|
|
import org.mockito.MockitoAnnotations
|
|
|
-import java.lang.RuntimeException
|
|
|
-import java.util.LinkedHashSet
|
|
|
|
|
|
class MigrationsManagerTest {
|
|
|
|
|
@@ -44,15 +42,16 @@ class MigrationsManagerTest {
|
|
|
const val NEW_APP_VERSION = 42
|
|
|
}
|
|
|
|
|
|
+ lateinit var migrationStep1: Runnable
|
|
|
+ lateinit var migrationStep2: Runnable
|
|
|
+ lateinit var migrationStep3: Runnable
|
|
|
lateinit var migrations: List<Migrations.Step>
|
|
|
|
|
|
@Mock
|
|
|
lateinit var appInfo: AppInfo
|
|
|
|
|
|
- lateinit var migrationsDb: MockSharedPreferences
|
|
|
-
|
|
|
- @Mock
|
|
|
- lateinit var userAccountManager: UserAccountManager
|
|
|
+ lateinit var migrationsDbStore: MockSharedPreferences
|
|
|
+ lateinit var migrationsDb: MigrationsDb
|
|
|
|
|
|
lateinit var asyncRunner: ManualAsyncRunner
|
|
|
|
|
@@ -61,16 +60,30 @@ class MigrationsManagerTest {
|
|
|
@Before
|
|
|
fun setUp() {
|
|
|
MockitoAnnotations.initMocks(this)
|
|
|
- val migrationStep1: Runnable = mock()
|
|
|
- val migrationStep2: Runnable = mock()
|
|
|
- val migrationStep3: Runnable = mock()
|
|
|
+ migrationStep1 = mock()
|
|
|
+ migrationStep2 = mock()
|
|
|
+ migrationStep3 = mock()
|
|
|
migrations = listOf(
|
|
|
- Migrations.Step(0, "first migration", migrationStep1, true),
|
|
|
- Migrations.Step(1, "second migration", migrationStep2, true),
|
|
|
- Migrations.Step(2, "third optional migration", migrationStep3, false)
|
|
|
+ object : Migrations.Step(0, "first migration", true) {
|
|
|
+ override fun run() {
|
|
|
+ migrationStep1.run()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ object : Migrations.Step(1, "second optional migration", false) {
|
|
|
+ override fun run() {
|
|
|
+ migrationStep2.run()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ object : Migrations.Step(2, "third migration", true) {
|
|
|
+ override fun run() {
|
|
|
+ migrationStep3.run()
|
|
|
+ }
|
|
|
+ }
|
|
|
)
|
|
|
asyncRunner = ManualAsyncRunner()
|
|
|
- migrationsDb = MockSharedPreferences()
|
|
|
+ migrationsDbStore = MockSharedPreferences()
|
|
|
+ migrationsDb = MigrationsDb(migrationsDbStore)
|
|
|
+
|
|
|
whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
|
|
|
migrationsManager = MigrationsManagerImpl(
|
|
|
appInfo = appInfo,
|
|
@@ -80,6 +93,14 @@ class MigrationsManagerTest {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+ private fun assertMigrationsRun(vararg migrationSteps: Runnable) {
|
|
|
+ inOrder(migrationSteps).apply {
|
|
|
+ migrationSteps.forEach {
|
|
|
+ verify(it.run())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Test
|
|
|
fun inital_status_is_unknown() {
|
|
|
// GIVEN
|
|
@@ -90,54 +111,12 @@ class MigrationsManagerTest {
|
|
|
assertEquals(MigrationsManager.Status.UNKNOWN, migrationsManager.status.value)
|
|
|
}
|
|
|
|
|
|
- @Test
|
|
|
- fun applied_migrations_are_returned_in_order() {
|
|
|
- // GIVEN
|
|
|
- // some migrations are marked as applied
|
|
|
- // migration ids are stored in random order
|
|
|
- val storedMigrationIds = LinkedHashSet<String>()
|
|
|
- storedMigrationIds.apply {
|
|
|
- add("3")
|
|
|
- add("0")
|
|
|
- add("2")
|
|
|
- add("1")
|
|
|
- }
|
|
|
- migrationsDb.store.put(MigrationsManagerImpl.DB_KEY_APPLIED_MIGRATIONS, storedMigrationIds)
|
|
|
-
|
|
|
- // WHEN
|
|
|
- // applied migration ids are retrieved
|
|
|
- val ids = migrationsManager.getAppliedMigrations()
|
|
|
-
|
|
|
- // THEN
|
|
|
- // returned list is sorted
|
|
|
- assertEquals(ids, ids.sorted())
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- @Suppress("MagicNumber")
|
|
|
- fun registering_new_applied_migration_preserves_old_ids() {
|
|
|
- // WHEN
|
|
|
- // some applied migrations are registered
|
|
|
- val appliedMigrationIds = setOf("0", "1", "2")
|
|
|
- migrationsDb.store.put(MigrationsManagerImpl.DB_KEY_APPLIED_MIGRATIONS, appliedMigrationIds)
|
|
|
-
|
|
|
- // WHEN
|
|
|
- // new set of migration ids are registered
|
|
|
- // some ids are added again
|
|
|
- migrationsManager.addAppliedMigration(2, 3, 4)
|
|
|
-
|
|
|
- // THEN
|
|
|
- // new ids are appended to set of existing ids
|
|
|
- val expectedIds = setOf("0", "1", "2", "3", "4")
|
|
|
- assertEquals(expectedIds, migrationsDb.store.get(MigrationsManagerImpl.DB_KEY_APPLIED_MIGRATIONS))
|
|
|
- }
|
|
|
-
|
|
|
@Test
|
|
|
@UiThreadTest
|
|
|
fun migrations_are_scheduled_on_background_thread() {
|
|
|
// GIVEN
|
|
|
// migrations can be applied
|
|
|
- assertEquals(0, migrationsManager.getAppliedMigrations().size)
|
|
|
+ assertEquals(0, migrationsDb.getAppliedMigrations().size)
|
|
|
|
|
|
// WHEN
|
|
|
// migration is started
|
|
@@ -158,11 +137,10 @@ class MigrationsManagerTest {
|
|
|
// no migrations are applied yet
|
|
|
// current app version is newer then last recorded migrated version
|
|
|
whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
|
|
|
- migrationsDb.store.put(MigrationsManagerImpl.DB_KEY_LAST_MIGRATED_VERSION, OLD_APP_VERSION)
|
|
|
+ migrationsDb.lastMigratedVersion = OLD_APP_VERSION
|
|
|
|
|
|
// WHEN
|
|
|
// migration is run
|
|
|
- whenever(userAccountManager.migrateUserId()).thenReturn(true)
|
|
|
val count = migrationsManager.startMigration()
|
|
|
assertTrue(asyncRunner.runOne())
|
|
|
|
|
@@ -172,9 +150,14 @@ class MigrationsManagerTest {
|
|
|
// applied migrations are recorded
|
|
|
// new app version code is recorded
|
|
|
assertEquals(migrations.size, count)
|
|
|
- val allAppliedIds = migrations.map { it.id.toString() }.toSet()
|
|
|
- assertEquals(allAppliedIds, migrationsDb.store.get(MigrationsManagerImpl.DB_KEY_APPLIED_MIGRATIONS))
|
|
|
- assertEquals(NEW_APP_VERSION, migrationsDb.store.get(MigrationsManagerImpl.DB_KEY_LAST_MIGRATED_VERSION))
|
|
|
+ inOrder(migrationStep1, migrationStep2, migrationStep3).apply {
|
|
|
+ verify(migrationStep1).run()
|
|
|
+ verify(migrationStep2).run()
|
|
|
+ verify(migrationStep3).run()
|
|
|
+ }
|
|
|
+ val allAppliedIds = migrations.map { it.id }
|
|
|
+ assertEquals(allAppliedIds, migrationsDb.getAppliedMigrations())
|
|
|
+ assertEquals(NEW_APP_VERSION, migrationsDb.lastMigratedVersion)
|
|
|
}
|
|
|
|
|
|
@Test
|
|
@@ -182,13 +165,16 @@ class MigrationsManagerTest {
|
|
|
fun migration_error_is_recorded() {
|
|
|
// GIVEN
|
|
|
// no migrations applied yet
|
|
|
+ // no prior failed migrations
|
|
|
+ assertFalse(migrationsDb.isFailed)
|
|
|
+ assertEquals(MigrationsDb.NO_FAILED_MIGRATION_ID, migrationsDb.failedMigrationId)
|
|
|
|
|
|
// WHEN
|
|
|
// migrations are applied
|
|
|
// one migration throws
|
|
|
val lastMigration = migrations.findLast { it.mandatory } ?: throw IllegalStateException("Test fixture error")
|
|
|
val errorMessage = "error message"
|
|
|
- whenever(lastMigration.function.run()).thenThrow(RuntimeException(errorMessage))
|
|
|
+ whenever(lastMigration.run()).thenThrow(RuntimeException(errorMessage))
|
|
|
migrationsManager.startMigration()
|
|
|
assertTrue(asyncRunner.runOne())
|
|
|
|
|
@@ -197,15 +183,9 @@ class MigrationsManagerTest {
|
|
|
// failure message is recorded
|
|
|
// failed migration id is recorded
|
|
|
assertEquals(MigrationsManager.Status.FAILED, migrationsManager.status.value)
|
|
|
- assertTrue(migrationsDb.getBoolean(MigrationsManagerImpl.DB_KEY_FAILED, false))
|
|
|
- assertEquals(
|
|
|
- errorMessage,
|
|
|
- migrationsDb.getString(MigrationsManagerImpl.DB_KEY_FAILED_MIGRATION_ERROR_MESSAGE, "")
|
|
|
- )
|
|
|
- assertEquals(
|
|
|
- lastMigration.id,
|
|
|
- migrationsDb.getInt(MigrationsManagerImpl.DB_KEY_FAILED_MIGRATION_ID, -1)
|
|
|
- )
|
|
|
+ assertTrue(migrationsDb.isFailed)
|
|
|
+ assertEquals(errorMessage, migrationsDb.failureReason)
|
|
|
+ assertEquals(lastMigration.id, migrationsDb.failedMigrationId)
|
|
|
}
|
|
|
|
|
|
@Test
|
|
@@ -214,7 +194,7 @@ class MigrationsManagerTest {
|
|
|
// GIVEN
|
|
|
// migrations were already run for the current app version
|
|
|
whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
|
|
|
- migrationsDb.store.put(MigrationsManagerImpl.DB_KEY_LAST_MIGRATED_VERSION, NEW_APP_VERSION)
|
|
|
+ migrationsDb.lastMigratedVersion = NEW_APP_VERSION
|
|
|
|
|
|
// WHEN
|
|
|
// app is migrated again
|
|
@@ -224,8 +204,8 @@ class MigrationsManagerTest {
|
|
|
// migration processing is skipped entirely
|
|
|
// status is set to applied
|
|
|
assertEquals(0, migrationCount)
|
|
|
- migrations.forEach {
|
|
|
- verify(it.function, never()).run()
|
|
|
+ listOf(migrationStep1, migrationStep2, migrationStep3).forEach {
|
|
|
+ verify(it, never()).run()
|
|
|
}
|
|
|
assertEquals(MigrationsManager.Status.APPLIED, migrationsManager.status.value)
|
|
|
}
|
|
@@ -237,9 +217,10 @@ class MigrationsManagerTest {
|
|
|
// migrations were applied in previous version
|
|
|
// new version has no new migrations
|
|
|
whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
|
|
|
- migrationsDb.store.put(MigrationsManagerImpl.DB_KEY_LAST_MIGRATED_VERSION, OLD_APP_VERSION)
|
|
|
- val applied = migrations.map { it.id.toString() }.toSet()
|
|
|
- migrationsDb.store.put(MigrationsManagerImpl.DB_KEY_APPLIED_MIGRATIONS, applied)
|
|
|
+ migrationsDb.lastMigratedVersion = OLD_APP_VERSION
|
|
|
+ migrations.forEach {
|
|
|
+ migrationsDb.addAppliedMigration(it.id)
|
|
|
+ }
|
|
|
|
|
|
// WHEN
|
|
|
// migration is started
|
|
@@ -251,7 +232,7 @@ class MigrationsManagerTest {
|
|
|
assertEquals(0, startedCount)
|
|
|
assertEquals(
|
|
|
NEW_APP_VERSION,
|
|
|
- migrationsDb.getInt(MigrationsManagerImpl.DB_KEY_LAST_MIGRATED_VERSION, -1)
|
|
|
+ migrationsDb.lastMigratedVersion
|
|
|
)
|
|
|
}
|
|
|
|
|
@@ -262,8 +243,12 @@ class MigrationsManagerTest {
|
|
|
// pending migrations
|
|
|
// mandatory migrations are passing
|
|
|
// one migration is optional and fails
|
|
|
+ assertEquals("Fixture should provide 1 optional, failing migration",
|
|
|
+ 1,
|
|
|
+ migrations.count { !it.mandatory }
|
|
|
+ )
|
|
|
val optionalFailingMigration = migrations.first { !it.mandatory }
|
|
|
- whenever(optionalFailingMigration.function.run()).thenThrow(RuntimeException())
|
|
|
+ whenever(optionalFailingMigration.run()).thenThrow(RuntimeException())
|
|
|
|
|
|
// WHEN
|
|
|
// migration is started
|
|
@@ -277,12 +262,10 @@ class MigrationsManagerTest {
|
|
|
// no error
|
|
|
// status is applied
|
|
|
// failed migration is available during next migration
|
|
|
- val appliedMigrations = migrations.filter { it.mandatory }
|
|
|
- .map { it.id.toString() }
|
|
|
- .toSet()
|
|
|
+ val appliedMigrations = migrations.filter { it.mandatory }.map { it.id }
|
|
|
assertTrue("Fixture error", appliedMigrations.isNotEmpty())
|
|
|
- assertEquals(appliedMigrations, migrationsDb.store.get(MigrationsManagerImpl.DB_KEY_APPLIED_MIGRATIONS))
|
|
|
- assertFalse(migrationsDb.getBoolean(MigrationsManagerImpl.DB_KEY_FAILED, false))
|
|
|
+ assertEquals(appliedMigrations, migrationsDb.getAppliedMigrations())
|
|
|
+ assertFalse(migrationsDb.isFailed)
|
|
|
assertEquals(MigrationsManager.Status.APPLIED, migrationsManager.status.value)
|
|
|
}
|
|
|
}
|