diff --git a/feature/orchestrator/src/main/AndroidManifest.xml b/feature/orchestrator/src/main/AndroidManifest.xml index caac1a04d6..1c4aa436b3 100644 --- a/feature/orchestrator/src/main/AndroidManifest.xml +++ b/feature/orchestrator/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + + + + + + + diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/receivers/CacheResetReceiver.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/receivers/CacheResetReceiver.kt new file mode 100644 index 0000000000..b714c146b2 --- /dev/null +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/receivers/CacheResetReceiver.kt @@ -0,0 +1,41 @@ +package com.simprints.feature.orchestrator.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports +import com.simprints.core.SessionCoroutineScope +import com.simprints.feature.orchestrator.cache.OrchestratorCache +import com.simprints.infra.events.session.SessionEventRepository +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import javax.inject.Inject + +/** + * Cached step structure might change between SID versions therefore caches should be cleared to avoid unmarshalling exceptions. + * Since we do not support cross-version sessions, any ongoing scopes should be closed as well. + */ +@ExcludedFromGeneratedTestCoverageReports("Platform glue code") +@AndroidEntryPoint +internal class CacheResetReceiver : BroadcastReceiver() { + @Inject + @SessionCoroutineScope + lateinit var externalScope: CoroutineScope + + @Inject + lateinit var sessionEventRepository: SessionEventRepository + + @Inject + lateinit var orchestratorCache: OrchestratorCache + + override fun onReceive( + context: Context, + intent: Intent, + ) { + if (Intent.ACTION_MY_PACKAGE_REPLACED == intent.action) { + orchestratorCache.clearCache() + externalScope.launch { sessionEventRepository.closeCurrentSession() } + } + } +} diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/ResetLocalRecordsIfConfigChangedUseCase.kt b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/ResetLocalRecordsIfConfigChangedUseCase.kt index 9a2b042fbd..cabc592266 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/ResetLocalRecordsIfConfigChangedUseCase.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/config/usecase/ResetLocalRecordsIfConfigChangedUseCase.kt @@ -23,10 +23,15 @@ internal class ResetLocalRecordsIfConfigChangedUseCase @Inject constructor( } } + // private fun hasPartitionTypeChanged( oldConfig: ProjectConfiguration, newConfig: ProjectConfiguration, ) = (oldConfig.synchronization.down.commCare != newConfig.synchronization.down.commCare) || - (oldConfig.synchronization.down.simprints != newConfig.synchronization.down.simprints) || - (oldConfig.synchronization.down.simprints?.partitionType != newConfig.synchronization.down.simprints?.partitionType) + // This also covers simprints changing from/to null since the partition will always be present if simprints is + ( + oldConfig.synchronization.down.simprints + ?.partitionType != newConfig.synchronization.down.simprints + ?.partitionType + ) } diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/ResetLocalRecordsIfConfigChangedUseCaseTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/ResetLocalRecordsIfConfigChangedUseCaseTest.kt index 98acdd953e..3390fa1b06 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/ResetLocalRecordsIfConfigChangedUseCaseTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/usecase/ResetLocalRecordsIfConfigChangedUseCaseTest.kt @@ -1,6 +1,8 @@ package com.simprints.infra.sync.config.usecase +import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.infra.config.store.models.DownSynchronizationConfiguration +import com.simprints.infra.config.store.models.Frequency import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.sync.SyncOrchestrator @@ -106,6 +108,7 @@ class ResetLocalRecordsIfConfigChangedUseCaseTest { simprints = synchronizationConfiguration.down.simprints?.copy( partitionType = DownSynchronizationConfiguration.PartitionType.PROJECT, ), + commCare = null, ), ), ), @@ -113,7 +116,7 @@ class ResetLocalRecordsIfConfigChangedUseCaseTest { synchronization = synchronizationConfiguration.copy( down = synchronizationConfiguration.down.copy( simprints = null, - commCare = DownSynchronizationConfiguration.CommCareDownSynchronizationConfiguration + commCare = DownSynchronizationConfiguration.CommCareDownSynchronizationConfiguration, ), ), ), @@ -134,7 +137,7 @@ class ResetLocalRecordsIfConfigChangedUseCaseTest { synchronization = synchronizationConfiguration.copy( down = synchronizationConfiguration.down.copy( simprints = null, - commCare = DownSynchronizationConfiguration.CommCareDownSynchronizationConfiguration + commCare = DownSynchronizationConfiguration.CommCareDownSynchronizationConfiguration, ), ), ), @@ -144,6 +147,7 @@ class ResetLocalRecordsIfConfigChangedUseCaseTest { simprints = synchronizationConfiguration.down.simprints?.copy( partitionType = DownSynchronizationConfiguration.PartitionType.PROJECT, ), + commCare = null, ), ), ), @@ -156,4 +160,159 @@ class ResetLocalRecordsIfConfigChangedUseCaseTest { enrolmentRecordRepository.deleteAll() } } + + @Test + fun `should reset local records when sync partition changes`() = runTest { + useCase( + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = synchronizationConfiguration.down.simprints?.copy( + partitionType = DownSynchronizationConfiguration.PartitionType.PROJECT, + ), + commCare = null, + ), + ), + ), + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = null, + commCare = null, + ), + ), + ), + ) + + coVerify { + syncOrchestrator.cancelEventSync() + syncOrchestrator.rescheduleEventSync() + eventSyncManager.resetDownSyncInfo() + enrolmentRecordRepository.deleteAll() + } + } + + @Test + fun `should not reset local records when sync frequency changes`() = runTest { + useCase( + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = synchronizationConfiguration.down.simprints?.copy( + frequency = Frequency.ONLY_PERIODICALLY_UP_SYNC, + ), + ), + ), + ), + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = synchronizationConfiguration.down.simprints?.copy( + frequency = Frequency.PERIODICALLY, + ), + ), + ), + ), + ) + + coVerify(exactly = 0) { + syncOrchestrator.cancelEventSync() + syncOrchestrator.rescheduleEventSync() + eventSyncManager.resetDownSyncInfo() + enrolmentRecordRepository.deleteAll() + } + } + + @Test + fun `should not reset local records when sync modules changes`() = runTest { + useCase( + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = synchronizationConfiguration.down.simprints?.copy( + moduleOptions = listOf("One".asTokenizableEncrypted(), "Two".asTokenizableEncrypted()), + ), + ), + ), + ), + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = synchronizationConfiguration.down.simprints?.copy( + moduleOptions = listOf("Three".asTokenizableEncrypted()), + ), + ), + ), + ), + ) + + coVerify(exactly = 0) { + syncOrchestrator.cancelEventSync() + syncOrchestrator.rescheduleEventSync() + eventSyncManager.resetDownSyncInfo() + enrolmentRecordRepository.deleteAll() + } + } + + @Test + fun `should not reset local records when sync max age changes`() = runTest { + useCase( + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = synchronizationConfiguration.down.simprints?.copy( + maxAge = "PT24H", + ), + ), + ), + ), + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = synchronizationConfiguration.down.simprints?.copy( + maxAge = "PT12H", + ), + ), + ), + ), + ) + + coVerify(exactly = 0) { + syncOrchestrator.cancelEventSync() + syncOrchestrator.rescheduleEventSync() + eventSyncManager.resetDownSyncInfo() + enrolmentRecordRepository.deleteAll() + } + } + + @Test + fun `should not reset local records when sync module count changes`() = runTest { + useCase( + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = synchronizationConfiguration.down.simprints?.copy( + maxNbOfModules = 2, + ), + ), + ), + ), + projectConfiguration.copy( + synchronization = synchronizationConfiguration.copy( + down = synchronizationConfiguration.down.copy( + simprints = synchronizationConfiguration.down.simprints?.copy( + maxNbOfModules = 5, + ), + ), + ), + ), + ) + + coVerify(exactly = 0) { + syncOrchestrator.cancelEventSync() + syncOrchestrator.rescheduleEventSync() + eventSyncManager.resetDownSyncInfo() + enrolmentRecordRepository.deleteAll() + } + } }