|
@@ -1,257 +0,0 @@
|
|
|
-/*
|
|
|
- * Nextcloud Android client application
|
|
|
- *
|
|
|
- * @author Chris Narkiewicz
|
|
|
- * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
|
|
- *
|
|
|
- * This program is free software: you can redistribute it and/or modify
|
|
|
- * it under the terms of the GNU Affero General Public License as published by
|
|
|
- * the Free Software Foundation, either version 3 of the License, or
|
|
|
- * (at your option) any later version.
|
|
|
- *
|
|
|
- * This program is distributed in the hope that it will be useful,
|
|
|
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
- * GNU Affero General Public License for more details.
|
|
|
- *
|
|
|
- * You should have received a copy of the GNU Affero General Public License
|
|
|
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
- */
|
|
|
-package com.nextcloud.client.migrations.android
|
|
|
-
|
|
|
-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.nextcloud.client.migrations.Migrations
|
|
|
-import com.nextcloud.client.migrations.MigrationsManager
|
|
|
-import com.nextcloud.client.migrations.MigrationsManagerImpl
|
|
|
-import com.nhaarman.mockitokotlin2.mock
|
|
|
-import com.nhaarman.mockitokotlin2.never
|
|
|
-import com.nhaarman.mockitokotlin2.verify
|
|
|
-import com.nhaarman.mockitokotlin2.whenever
|
|
|
-import org.junit.Assert.assertEquals
|
|
|
-import org.junit.Assert.assertTrue
|
|
|
-import org.junit.Before
|
|
|
-import org.junit.Test
|
|
|
-import org.mockito.Mock
|
|
|
-import org.mockito.MockitoAnnotations
|
|
|
-import java.lang.RuntimeException
|
|
|
-import java.util.LinkedHashSet
|
|
|
-
|
|
|
-@Suppress("FunctionNaming")
|
|
|
-class TestMigrationsManager {
|
|
|
-
|
|
|
- companion object {
|
|
|
- const val OLD_APP_VERSION = 41
|
|
|
- const val NEW_APP_VERSION = 42
|
|
|
- }
|
|
|
-
|
|
|
- lateinit var migrations: List<Migrations.Step>
|
|
|
-
|
|
|
- @Mock
|
|
|
- lateinit var appInfo: AppInfo
|
|
|
-
|
|
|
- lateinit var migrationsDb: MockSharedPreferences
|
|
|
-
|
|
|
- @Mock
|
|
|
- lateinit var userAccountManager: UserAccountManager
|
|
|
-
|
|
|
- lateinit var asyncRunner: ManualAsyncRunner
|
|
|
-
|
|
|
- internal lateinit var migrationsManager: MigrationsManagerImpl
|
|
|
-
|
|
|
- @Before
|
|
|
- fun setUp() {
|
|
|
- MockitoAnnotations.initMocks(this)
|
|
|
- val migrationStep1: Runnable = mock()
|
|
|
- val migrationStep2: Runnable = mock()
|
|
|
- migrations = listOf(
|
|
|
- Migrations.Step(0, "first migration", migrationStep1),
|
|
|
- Migrations.Step(1, "second migration", migrationStep2)
|
|
|
- )
|
|
|
- asyncRunner = ManualAsyncRunner()
|
|
|
- migrationsDb = MockSharedPreferences()
|
|
|
- whenever(appInfo.versionCode).thenReturn(NEW_APP_VERSION)
|
|
|
- migrationsManager = MigrationsManagerImpl(
|
|
|
- appInfo = appInfo,
|
|
|
- migrationsDb = migrationsDb,
|
|
|
- asyncRunner = asyncRunner,
|
|
|
- migrations = migrations
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- fun inital_status_is_unknown() {
|
|
|
- // GIVEN
|
|
|
- // migration manager has not been used yets
|
|
|
-
|
|
|
- // THEN
|
|
|
- // status is not set
|
|
|
- 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)
|
|
|
-
|
|
|
- // WHEN
|
|
|
- // migration is started
|
|
|
- val count = migrationsManager.startMigration()
|
|
|
-
|
|
|
- // THEN
|
|
|
- // all migrations are scheduled on background thread
|
|
|
- // single task is scheduled
|
|
|
- assertEquals(migrations.size, count)
|
|
|
- assertEquals(1, asyncRunner.size)
|
|
|
- assertEquals(MigrationsManager.Status.RUNNING, migrationsManager.status.value)
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- @UiThreadTest
|
|
|
- fun applied_migrations_are_recorded() {
|
|
|
- // GIVEN
|
|
|
- // 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)
|
|
|
-
|
|
|
- // WHEN
|
|
|
- // migration is run
|
|
|
- whenever(userAccountManager.migrateUserId()).thenReturn(true)
|
|
|
- val count = migrationsManager.startMigration()
|
|
|
- assertTrue(asyncRunner.runOne())
|
|
|
-
|
|
|
- // THEN
|
|
|
- // total migrations count is returned
|
|
|
- // migration functions are called
|
|
|
- // applied migrations are recorded
|
|
|
- // new app version code is recorded
|
|
|
- assertEquals(migrations.size, count)
|
|
|
- assertEquals(setOf("0", "1"), migrationsDb.store.get(MigrationsManagerImpl.DB_KEY_APPLIED_MIGRATIONS))
|
|
|
- assertEquals(NEW_APP_VERSION, migrationsDb.store.get(MigrationsManagerImpl.DB_KEY_LAST_MIGRATED_VERSION))
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- @UiThreadTest
|
|
|
- fun migration_error_is_recorded() {
|
|
|
- // GIVEN
|
|
|
- // no migrations applied yet
|
|
|
-
|
|
|
- // WHEN
|
|
|
- // migrations are applied
|
|
|
- // one migration throws
|
|
|
- val lastMigration = migrations.last()
|
|
|
- val errorMessage = "error message"
|
|
|
- whenever(lastMigration.function.run()).thenThrow(RuntimeException(errorMessage))
|
|
|
- migrationsManager.startMigration()
|
|
|
- assertTrue(asyncRunner.runOne())
|
|
|
-
|
|
|
- // THEN
|
|
|
- // failure is marked in the migration db
|
|
|
- // 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)
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- @UiThreadTest
|
|
|
- fun migrations_are_not_run_if_already_run_for_an_app_version() {
|
|
|
- // 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)
|
|
|
-
|
|
|
- // WHEN
|
|
|
- // app is migrated again
|
|
|
- val migrationCount = migrationsManager.startMigration()
|
|
|
-
|
|
|
- // THEN
|
|
|
- // migration processing is skipped entirely
|
|
|
- // status is set to applied
|
|
|
- assertEquals(0, migrationCount)
|
|
|
- migrations.forEach {
|
|
|
- verify(it.function, never()).run()
|
|
|
- }
|
|
|
- assertEquals(MigrationsManager.Status.APPLIED, migrationsManager.status.value)
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- @UiThreadTest
|
|
|
- fun new_app_version_is_marked_as_migrated_if_no_new_migrations_are_available() {
|
|
|
- // GIVEN
|
|
|
- // 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)
|
|
|
-
|
|
|
- // WHEN
|
|
|
- // migration is started
|
|
|
- val startedCount = migrationsManager.startMigration()
|
|
|
-
|
|
|
- // THEN
|
|
|
- // no new migrations are run
|
|
|
- // new version is marked as migrated
|
|
|
- assertEquals(0, startedCount)
|
|
|
- assertEquals(
|
|
|
- NEW_APP_VERSION,
|
|
|
- migrationsDb.getInt(MigrationsManagerImpl.DB_KEY_LAST_MIGRATED_VERSION, -1)
|
|
|
- )
|
|
|
- }
|
|
|
-}
|