From 0c909cad4dde1e043269a97a9de569be147bb745 Mon Sep 17 00:00:00 2001 From: Melad Raouf Date: Thu, 19 Jun 2025 15:41:35 +0300 Subject: [PATCH 1/2] [MS-1032] Schedule Realm to Room migration on project config refresh --- .../dashboard/tools/di/FakeCoreModule.kt | 8 +++++ .../infra/config/sync/ConfigManager.kt | 7 ++-- .../infra/config/sync/ConfigManagerTest.kt | 33 +++++++++++++++---- .../java/com/simprints/core/CoreModule.kt | 6 ++++ .../com/simprints/infra/sync/SyncModule.kt | 15 +-------- 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt index 6e1578218c..10fde14e01 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/tools/di/FakeCoreModule.kt @@ -1,5 +1,7 @@ package com.simprints.feature.dashboard.tools.di +import android.content.Context +import androidx.work.WorkManager import com.simprints.core.AppScope import com.simprints.core.AvailableProcessors import com.simprints.core.CoreModule @@ -17,6 +19,7 @@ import com.simprints.core.tools.utils.StringTokenizer import com.simprints.testtools.unit.EncodingUtilsImplForTests import dagger.Module import dagger.Provides +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.hilt.testing.TestInstallIn import io.mockk.mockk @@ -88,4 +91,9 @@ object FakeCoreModule { @Provides @Singleton fun provideEncodingUtils(): EncodingUtils = EncodingUtilsImplForTests + + @Provides + fun provideWorkManager( + @ApplicationContext context: Context, + ): WorkManager = WorkManager.getInstance(context) } diff --git a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ConfigManager.kt b/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ConfigManager.kt index 7d257aa7e3..52115149c3 100644 --- a/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ConfigManager.kt +++ b/infra/config-sync/src/main/java/com/simprints/infra/config/sync/ConfigManager.kt @@ -8,6 +8,7 @@ import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.ProjectWithConfig import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository +import com.simprints.infra.enrolment.records.repository.local.migration.RealmToRoomMigrationScheduler import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.onStart import javax.inject.Inject @@ -16,15 +17,17 @@ class ConfigManager @Inject constructor( private val configRepository: ConfigRepository, private val enrolmentRecordRepository: EnrolmentRecordRepository, private val configSyncCache: ConfigSyncCache, + private val realmToRoomMigrationScheduler: RealmToRoomMigrationScheduler, ) { suspend fun refreshProject(projectId: String): ProjectWithConfig = configRepository.refreshProject(projectId).also { enrolmentRecordRepository.tokenizeExistingRecords(it.project) configSyncCache.saveUpdateTime() + realmToRoomMigrationScheduler.scheduleMigrationWorkerIfNeeded() } suspend fun getProject(projectId: String): Project = try { configRepository.getProject() - } catch (e: NoSuchElementException) { + } catch (_: NoSuchElementException) { refreshProject(projectId).project } @@ -35,7 +38,7 @@ class ConfigManager @Inject constructor( try { // Try to refresh it with logged in projectId (if any) refreshProject(configRepository.getProject().id).configuration - } catch (e: Exception) { + } catch (_: Exception) { // If not logged in the above will fail. However we still depend on the 'default' // configuration to create the session when login is attempted. Possibly in other // places, too. diff --git a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/ConfigManagerTest.kt b/infra/config-sync/src/test/java/com/simprints/infra/config/sync/ConfigManagerTest.kt index 92ffbe74fa..118bc34f2a 100644 --- a/infra/config-sync/src/test/java/com/simprints/infra/config/sync/ConfigManagerTest.kt +++ b/infra/config-sync/src/test/java/com/simprints/infra/config/sync/ConfigManagerTest.kt @@ -1,16 +1,14 @@ package com.simprints.infra.config.sync -import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.* import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.DeviceConfiguration import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.ProjectWithConfig import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import com.simprints.infra.enrolment.records.repository.local.migration.RealmToRoomMigrationScheduler +import io.mockk.* import io.mockk.impl.annotations.MockK import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest @@ -43,6 +41,9 @@ class ConfigManagerTest { @MockK private lateinit var deviceConfiguration: DeviceConfiguration + @MockK + private lateinit var realmToRoomMigrationScheduler: RealmToRoomMigrationScheduler + @MockK private lateinit var project: Project @@ -53,6 +54,7 @@ class ConfigManagerTest { configRepository = configRepository, enrolmentRecordRepository = enrolmentRecordRepository, configSyncCache = configSyncCache, + realmToRoomMigrationScheduler = realmToRoomMigrationScheduler, ) } @@ -64,6 +66,7 @@ class ConfigManagerTest { assertThat(refreshedProject).isEqualTo(projectWithConfig) coVerify { configSyncCache.saveUpdateTime() } + coVerify { realmToRoomMigrationScheduler.scheduleMigrationWorkerIfNeeded() } } @Test @@ -74,6 +77,14 @@ class ConfigManagerTest { assertThat(gottenProject).isEqualTo(project) } + @Test + fun `getProject should call the refresh method when cannot get from local`() = runTest { + coEvery { configRepository.getProject() } throws NoSuchElementException() + + configManager.getProject(PROJECT_ID) + coVerify(exactly = 1) { configRepository.refreshProject(PROJECT_ID) } + } + @Test fun `getProjectConfiguration should call the correct method`() = runTest { coEvery { configRepository.getProjectConfiguration() } returns projectConfiguration @@ -83,6 +94,16 @@ class ConfigManagerTest { assertThat(gottenProjectConfiguration).isEqualTo(projectConfiguration) } + @Test + fun `getProjectConfiguration return default config if not logged in`() = runTest { + every { projectConfiguration.projectId } returns "" + coEvery { configRepository.getProjectConfiguration() } returns projectConfiguration + coEvery { configRepository.refreshProject(any()) } throws Exception() + + val gottenProjectConfiguration = configManager.getProjectConfiguration() + assertThat(gottenProjectConfiguration).isEqualTo(projectConfiguration) + } + @Test fun `refreshProjectConfiguration should call the correct method`() = runTest { coEvery { configRepository.refreshProject(PROJECT_ID) } returns projectWithConfig @@ -120,7 +141,7 @@ class ConfigManagerTest { configManager.getPrivacyNotice(PROJECT_ID, LANGUAGE) coVerify(exactly = 1) { configRepository.getPrivacyNotice(PROJECT_ID, LANGUAGE) } } - + @Test fun `watchProjectConfiguration should emit values from the local data source`() = runTest { val config1 = projectConfiguration.copy(projectId = "project1") diff --git a/infra/core/src/main/java/com/simprints/core/CoreModule.kt b/infra/core/src/main/java/com/simprints/core/CoreModule.kt index ffbae4b1c7..d2dc20408f 100644 --- a/infra/core/src/main/java/com/simprints/core/CoreModule.kt +++ b/infra/core/src/main/java/com/simprints/core/CoreModule.kt @@ -1,6 +1,7 @@ package com.simprints.core import android.content.Context +import androidx.work.WorkManager import com.lyft.kronos.AndroidClockFactory import com.simprints.core.tools.exceptions.AppCoroutineExceptionHandler import com.simprints.core.tools.extentions.deviceHardwareId @@ -134,6 +135,11 @@ object CoreModule { ): CoroutineScope = CoroutineScope( SupervisorJob() + dispatcherMain + AppCoroutineExceptionHandler(), ) + + @Provides + fun provideWorkManager( + @ApplicationContext context: Context, + ): WorkManager = WorkManager.getInstance(context) } @Qualifier diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt index 6c689c914c..48cf24d717 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncModule.kt @@ -1,26 +1,13 @@ package com.simprints.infra.sync -import android.content.Context -import androidx.work.WorkManager import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -object SyncModule { - @Provides - fun provideWorkManager( - @ApplicationContext context: Context, - ): WorkManager = WorkManager.getInstance(context) -} - -@Module -@InstallIn(SingletonComponent::class) -abstract class SyncOrchestratorModule { +abstract class SyncModule { @Binds internal abstract fun provideSyncOrchestrator(syncOrchestratorImpl: SyncOrchestratorImpl): SyncOrchestrator } From 085af0fc7d808cd5949b8dab66893af9760b130a Mon Sep 17 00:00:00 2001 From: Melad Raouf Date: Sat, 21 Jun 2025 18:13:36 +0300 Subject: [PATCH 2/2] Clear RealmToRoom migration flags on logout --- .../feature/dashboard/logout/usecase/LogoutUseCase.kt | 4 ++++ .../dashboard/logout/usecase/LogoutUseCaseTest.kt | 6 ++++++ .../local/migration/RealmToRoomMigrationFlagsStore.kt | 8 ++++++++ .../migration/RealmToRoomMigrationFlagsStoreTest.kt | 10 ++++++++++ 4 files changed, 28 insertions(+) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt index 7b197f5b35..3cd1fde411 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCase.kt @@ -1,6 +1,7 @@ package com.simprints.feature.dashboard.logout.usecase import com.simprints.infra.authlogic.AuthManager +import com.simprints.infra.enrolment.records.repository.local.migration.RealmToRoomMigrationFlagsStore import com.simprints.infra.sync.SyncOrchestrator import kotlinx.coroutines.runBlocking import javax.inject.Inject @@ -8,6 +9,7 @@ import javax.inject.Inject internal class LogoutUseCase @Inject constructor( private val syncOrchestrator: SyncOrchestrator, private val authManager: AuthManager, + private val flagsStore: RealmToRoomMigrationFlagsStore, ) { operator fun invoke() = runBlocking { // Cancel all background sync @@ -15,5 +17,7 @@ internal class LogoutUseCase @Inject constructor( syncOrchestrator.deleteEventSyncInfo() // sign out the user authManager.signOut() + // Reset migration flags + flagsStore.clearMigrationFlags() } } diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCaseTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCaseTest.kt index 6270445f52..960cd5fc79 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCaseTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/logout/usecase/LogoutUseCaseTest.kt @@ -2,6 +2,7 @@ package com.simprints.feature.dashboard.logout.usecase import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.simprints.infra.authlogic.AuthManager +import com.simprints.infra.enrolment.records.repository.local.migration.RealmToRoomMigrationFlagsStore import com.simprints.infra.sync.SyncOrchestrator import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.MockKAnnotations @@ -25,6 +26,9 @@ class LogoutUseCaseTest { @MockK private lateinit var authManager: AuthManager + @MockK + private lateinit var flagsStore: RealmToRoomMigrationFlagsStore + private lateinit var useCase: LogoutUseCase @Before @@ -34,6 +38,7 @@ class LogoutUseCaseTest { useCase = LogoutUseCase( syncOrchestrator = syncOrchestrator, authManager = authManager, + flagsStore = flagsStore, ) } @@ -45,6 +50,7 @@ class LogoutUseCaseTest { syncOrchestrator.cancelBackgroundWork() syncOrchestrator.deleteEventSyncInfo() authManager.signOut() + flagsStore.clearMigrationFlags() } } } diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationFlagsStore.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationFlagsStore.kt index 80d5b9d65c..0e8ba5b04d 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationFlagsStore.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationFlagsStore.kt @@ -90,4 +90,12 @@ class RealmToRoomMigrationFlagsStore @Inject constructor( $KEY_DOWN_SYNC_STATUS: $downSync """.trimIndent() } + + /** + * Clears all migration-related keys from the store. + */ + fun clearMigrationFlags() { + prefs.edit { clear() } + Simber.i("[RealmToRoomMigrationFlagsStore] Migration flags cleared", tag = REALM_DB_MIGRATION) + } } diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationFlagsStoreTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationFlagsStoreTest.kt index 45642c6747..145cefe169 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationFlagsStoreTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/migration/RealmToRoomMigrationFlagsStoreTest.kt @@ -300,4 +300,14 @@ class RealmToRoomMigrationFlagsStoreTest { verify { editor.putBoolean(RealmToRoomMigrationFlagsStore.KEY_DOWN_SYNC_STATUS, false) } verify { editor.apply() } } + + @Test + fun `clearMigrationFlags should remove all migration-related keys`() { + // Given + every { editor.clear() } returns editor + // When + store.clearMigrationFlags() + // Then + verify { editor.clear() } + } }