From 60afbd13a3114cf470380e70a8b0bbe3a1e28bd0 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 27 Nov 2025 12:52:43 +0200 Subject: [PATCH 1/2] MS-1006 Use project ID from auth store in config manager instead of passing it as argument everywhere --- feature/client-api/build.gradle.kts | 1 - .../feature/clientapi/ClientApiViewModel.kt | 4 +-- .../DeleteSessionEventsIfNeededUseCase.kt | 4 +-- .../clientapi/ClientApiViewModelTest.kt | 9 +----- .../DeleteSessionEventsIfNeededUseCaseTest.kt | 22 ++++---------- .../projectdetails/ProjectDetailsViewModel.kt | 4 +-- .../settings/syncinfo/SyncInfoViewModel.kt | 2 +- .../ModuleSelectionViewModel.kt | 6 ++-- .../usecase/ObserveSyncInfoUseCase.kt | 2 +- .../ProjectDetailsViewModelTest.kt | 17 +++-------- .../syncinfo/SyncInfoViewModelTest.kt | 8 ++--- .../ModuleSelectionViewModelTest.kt | 22 +++----------- .../usecase/ObserveSyncInfoUseCaseTest.kt | 6 ++-- .../screen/EnrolLastBiometricViewModel.kt | 20 +++++-------- .../screen/EnrolLastBiometricViewModelTest.kt | 8 ++--- feature/external-credential/build.gradle.kts | 1 - .../ExternalCredentialScanOcrViewModel.kt | 4 +-- .../ExternalCredentialScanQrViewModel.kt | 4 +-- .../ExternalCredentialSearchViewModel.kt | 8 ++--- .../ExternalCredentialEventTrackerUseCase.kt | 4 +-- ...esetExternalCredentialsInSessionUseCase.kt | 3 +- .../ExternalCredentialScanOcrViewModelTest.kt | 13 ++------- .../ExternalCredentialScanQrViewModelTest.kt | 9 +----- .../ExternalCredentialSearchViewModelTest.kt | 10 +------ ...ternalCredentialEventTrackerUseCaseTest.kt | 8 +---- ...ExternalCredentialsInSessionUseCaseTest.kt | 14 ++++----- .../feature/logincheck/LoginCheckViewModel.kt | 2 +- .../logincheck/LoginCheckViewModelTest.kt | 12 ++++---- feature/matcher/build.gradle.kts | 2 -- .../matcher/screen/MatchViewModel.kt | 4 +-- .../matcher/screen/MatchViewModelTest.kt | 5 ---- .../orchestrator/OrchestratorViewModel.kt | 2 +- .../orchestrator/OrchestratorViewModelTest.kt | 4 +-- .../screen/SelectSubjectViewModel.kt | 4 +-- .../screen/SelectSubjectViewModelTest.kt | 9 ++---- .../remote/ImageDistortionConfigRemoteRepo.kt | 2 +- .../ImageDistortionConfigRemoteRepoTest.kt | 2 +- infra/config-sync/build.gradle.kts | 1 + .../infra/config/sync/ConfigManager.kt | 14 ++++++--- .../infra/config/sync/ConfigManagerTest.kt | 29 +++++++++++++++++-- .../down/EventDownSyncScopeRepository.kt | 6 ++-- .../sync/master/EventSyncMasterWorker.kt | 5 ++-- .../sync/up/tasks/EventUpSyncTask.kt | 2 +- .../sync/master/EventSyncMasterWorkerTest.kt | 2 +- .../remote/firebase/FirebaseSampleUploader.kt | 2 +- .../firebase/FirebaseSampleUploaderTest.kt | 2 +- 46 files changed, 121 insertions(+), 203 deletions(-) diff --git a/feature/client-api/build.gradle.kts b/feature/client-api/build.gradle.kts index e7e46967e7..55268cde4e 100644 --- a/feature/client-api/build.gradle.kts +++ b/feature/client-api/build.gradle.kts @@ -8,7 +8,6 @@ android { dependencies { - implementation(project(":infra:auth-store")) implementation(project(":infra:config-store")) implementation(project(":infra:config-sync")) implementation(project(":infra:events")) diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt index 63f2395485..ab17f3af27 100644 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt @@ -22,7 +22,6 @@ import com.simprints.feature.clientapi.usecases.GetCurrentSessionIdUseCase import com.simprints.feature.clientapi.usecases.GetEnrolmentCreationEventForSubjectUseCase import com.simprints.feature.clientapi.usecases.IsFlowCompletedWithErrorUseCase import com.simprints.feature.clientapi.usecases.SimpleEventReporter -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor @@ -55,7 +54,6 @@ class ClientApiViewModel @Inject internal constructor( private val getEnrolmentCreationEventForSubject: GetEnrolmentCreationEventForSubjectUseCase, private val deleteSessionEventsIfNeeded: DeleteSessionEventsIfNeededUseCase, private val isFlowCompletedWithError: IsFlowCompletedWithErrorUseCase, - private val authStore: AuthStore, private val configManager: ConfigManager, private val timeHelper: TimeHelper, private val persistentLogger: PersistentLogger, @@ -73,7 +71,7 @@ class ClientApiViewModel @Inject internal constructor( get() = _showAlert private val _showAlert = MutableLiveData>() - private suspend fun getProject() = runCatching { configManager.getProject(authStore.signedInProjectId) }.getOrNull() + private suspend fun getProject() = runCatching { configManager.getProject() }.getOrNull() suspend fun handleIntent( action: String, diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCase.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCase.kt index ee652f0917..e602409fec 100644 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCase.kt +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCase.kt @@ -1,7 +1,6 @@ package com.simprints.feature.clientapi.usecases import com.simprints.core.SessionCoroutineScope -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.ProjectState import com.simprints.infra.config.store.models.canSyncDataToSimprints import com.simprints.infra.config.sync.ConfigManager @@ -11,13 +10,12 @@ import kotlinx.coroutines.launch import javax.inject.Inject internal class DeleteSessionEventsIfNeededUseCase @Inject constructor( - private val authStore: AuthStore, private val configManager: ConfigManager, private val eventRepository: EventRepository, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, ) { operator fun invoke(sessionId: String) = sessionCoroutineScope.launch { - val projectNotRunning = configManager.getProject(authStore.signedInProjectId).state != ProjectState.RUNNING + val projectNotRunning = configManager.getProject().state != ProjectState.RUNNING val simprintsDataSyncDisabled = !configManager.getProjectConfiguration().canSyncDataToSimprints() if (simprintsDataSyncDisabled || projectNotRunning) { diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt index 4fe7c5a89b..fb26468565 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt @@ -19,7 +19,6 @@ import com.simprints.feature.clientapi.usecases.GetCurrentSessionIdUseCase import com.simprints.feature.clientapi.usecases.GetEnrolmentCreationEventForSubjectUseCase import com.simprints.feature.clientapi.usecases.IsFlowCompletedWithErrorUseCase import com.simprints.feature.clientapi.usecases.SimpleEventReporter -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor @@ -70,9 +69,6 @@ internal class ClientApiViewModelTest { @MockK lateinit var isFlowCompletedWithError: IsFlowCompletedWithErrorUseCase - @MockK - lateinit var authStore: AuthStore - @MockK lateinit var configManager: ConfigManager @@ -108,7 +104,6 @@ internal class ClientApiViewModelTest { getEnrolmentCreationEventForSubject = getEnrolmentCreationEventForSubject, deleteSessionEventsIfNeeded = deleteSessionEventsIfNeeded, isFlowCompletedWithError = isFlowCompletedWithError, - authStore = authStore, configManager = configManager, timeHelper = timeHelper, persistentLogger = persistentLogger, @@ -323,9 +318,7 @@ internal class ClientApiViewModelTest { project: Project, returnValue: TokenizableString, ) { - val projectId = "projectId" - every { authStore.signedInProjectId } returns projectId - coEvery { configManager.getProject(projectId) } returns project + coEvery { configManager.getProject() } returns project every { tokenizationProcessor.decrypt( encrypted = any(), diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCaseTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCaseTest.kt index 7084a1a931..17e0ed05b3 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCaseTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCaseTest.kt @@ -1,15 +1,11 @@ package com.simprints.feature.clientapi.usecases -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.ProjectState import com.simprints.infra.config.store.models.UpSynchronizationConfiguration import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.events.EventRepository import com.simprints.testtools.common.coroutines.TestCoroutineRule -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.runTest @@ -21,9 +17,6 @@ class DeleteSessionEventsIfNeededUseCaseTest { @get:Rule val testCoroutineRule = TestCoroutineRule() - @MockK - private lateinit var authStore: AuthStore - @MockK private lateinit var configManager: ConfigManager @@ -36,10 +29,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { fun setUp() { MockKAnnotations.init(this, relaxed = true) - every { authStore.signedInProjectId } returns "projectId" - deleteUseCase = DeleteSessionEventsIfNeededUseCase( - authStore, configManager, eventRepository, CoroutineScope(testCoroutineRule.testCoroutineDispatcher), @@ -48,7 +38,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { @Test fun `deletes session events if project paused`() = runTest { - coEvery { configManager.getProject(any()).state } returns ProjectState.PROJECT_PAUSED + coEvery { configManager.getProject().state } returns ProjectState.PROJECT_PAUSED coEvery { configManager .getProjectConfiguration() @@ -62,7 +52,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { @Test fun `deletes session events if project ending`() = runTest { - coEvery { configManager.getProject(any()).state } returns ProjectState.PROJECT_ENDING + coEvery { configManager.getProject().state } returns ProjectState.PROJECT_ENDING coEvery { configManager .getProjectConfiguration() @@ -76,7 +66,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { @Test fun `deletes session events if project ended`() = runTest { - coEvery { configManager.getProject(any()).state } returns ProjectState.PROJECT_ENDED + coEvery { configManager.getProject().state } returns ProjectState.PROJECT_ENDED coEvery { configManager .getProjectConfiguration() @@ -90,7 +80,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { @Test fun `deletes session events if data sync disabled in running project`() = runTest { - coEvery { configManager.getProject(any()).state } returns ProjectState.RUNNING + coEvery { configManager.getProject().state } returns ProjectState.RUNNING coEvery { configManager .getProjectConfiguration() @@ -104,7 +94,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { @Test fun `does not delete session events if data sync enabled in running project`() = runTest { - coEvery { configManager.getProject(any()).state } returns ProjectState.RUNNING + coEvery { configManager.getProject().state } returns ProjectState.RUNNING coEvery { configManager .getProjectConfiguration() diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModel.kt index 0c20721b6c..06c11c933c 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModel.kt @@ -17,7 +17,6 @@ import javax.inject.Inject @HiltViewModel internal class ProjectDetailsViewModel @Inject constructor( private val configManager: ConfigManager, - private val authStore: AuthStore, private val recentUserActivityManager: RecentUserActivityManager, private val tokenizationProcessor: TokenizationProcessor, ) : ViewModel() { @@ -31,8 +30,7 @@ internal class ProjectDetailsViewModel @Inject constructor( fun load() = viewModelScope.launch { val state = try { - val projectId = authStore.signedInProjectId - val cachedProject = configManager.getProject(projectId) + val cachedProject = configManager.getProject() val recentUserActivity = recentUserActivityManager.getRecentUserActivity() val decryptedUserId = when (val userId = recentUserActivity.lastUserUsed) { is TokenizableString.Raw -> userId diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index 62cf5c7cf6..13137b9c60 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -145,7 +145,7 @@ internal class SyncInfoViewModel @Inject constructor( syncOrchestrator.stopEventSync() val projectState = try { - configManager.getProject(authStore.signedInProjectId).state + configManager.getProject().state } catch (_: Exception) { // If the device is compromised, project data is deleted. Access attempts will throw an exception, // effectively appearing to the user as if the project has ended. diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt index 913ddb766e..476852ef6a 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt @@ -10,7 +10,6 @@ import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.excepti import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.exceptions.TooManyModulesSelectedException import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.repository.Module import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.repository.ModuleRepository -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.SettingsPasswordConfig import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor @@ -23,7 +22,6 @@ import javax.inject.Inject @HiltViewModel internal class ModuleSelectionViewModel @Inject constructor( - private val authStore: AuthStore, private val moduleRepository: ModuleRepository, private val syncOrchestrator: SyncOrchestrator, private val configManager: ConfigManager, @@ -54,7 +52,7 @@ internal class ModuleSelectionViewModel @Inject constructor( is TokenizableString.Tokenized -> tokenizationProcessor.decrypt( encrypted = name, tokenKeyType = TokenKeyType.ModuleId, - project = configManager.getProject(authStore.signedInProjectId), + project = configManager.getProject(), ) } module.copy(name = decryptedName) @@ -102,7 +100,7 @@ internal class ModuleSelectionViewModel @Inject constructor( is TokenizableString.Raw -> tokenizationProcessor.encrypt( decrypted = name, tokenKeyType = TokenKeyType.ModuleId, - project = configManager.getProject(authStore.signedInProjectId), + project = configManager.getProject(), ) is TokenizableString.Tokenized -> name diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCase.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCase.kt index e1db51b63a..8414575068 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCase.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCase.kt @@ -236,7 +236,7 @@ internal class ObserveSyncInfoUseCase @Inject constructor( } val project = try { - projectId.takeUnless { it.isBlank() }?.let { configManager.getProject(it) } + configManager.getProject() } catch (_: Exception) { // If the device is compromised, project data is deleted. Access attempts will throw an exception, // effectively appearing to the user as if the project has ended. diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModelTest.kt index 23501e61fc..a7f87cf6dd 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModelTest.kt @@ -1,10 +1,9 @@ package com.simprints.feature.dashboard.main.projectdetails import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.* import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.core.domain.tokenization.asTokenizableEncrypted -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.ProjectState import com.simprints.infra.config.store.models.TokenKeyType @@ -13,9 +12,7 @@ import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.recent.user.activity.RecentUserActivityManager import com.simprints.infra.recent.user.activity.domain.RecentUserActivity import com.simprints.testtools.common.coroutines.TestCoroutineRule -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK import kotlinx.coroutines.test.runTest import org.junit.Before @@ -29,9 +26,6 @@ class ProjectDetailsViewModelTest { @get:Rule val testCoroutineRule = TestCoroutineRule() - @MockK - private lateinit var authStore: AuthStore - @MockK private lateinit var configManager: ConfigManager @@ -47,13 +41,12 @@ class ProjectDetailsViewModelTest { fun setUp() { MockKAnnotations.init(this) - every { authStore.signedInProjectId } returns PROJECT_ID coEvery { recentUserActivityManager.getRecentUserActivity() } returns RECENT_USER_ACTIVITY } @Test fun `should initialize the live data correctly`() = runTest { - coEvery { configManager.getProject(PROJECT_ID) } returns PROJECT + coEvery { configManager.getProject() } returns PROJECT every { tokenizationProcessor.decrypt( RECENT_USER_ACTIVITY.lastUserUsed as TokenizableString.Tokenized, @@ -64,7 +57,6 @@ class ProjectDetailsViewModelTest { viewModel = ProjectDetailsViewModel( configManager = configManager, - authStore = authStore, recentUserActivityManager = recentUserActivityManager, tokenizationProcessor = tokenizationProcessor, ) @@ -75,11 +67,10 @@ class ProjectDetailsViewModelTest { @Test fun `Should handle exception by producing correct state`() = runTest { - coEvery { configManager.getProject(PROJECT_ID) } throws Exception() + coEvery { configManager.getProject() } throws Exception() viewModel = ProjectDetailsViewModel( configManager = configManager, - authStore = authStore, recentUserActivityManager = recentUserActivityManager, tokenizationProcessor = tokenizationProcessor, ) diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt index d50572d491..d4bea2d2a1 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt @@ -123,7 +123,7 @@ class SyncInfoViewModelTest { every { configManager.observeDeviceConfiguration() } returns MutableStateFlow(mockDeviceConfiguration) coEvery { configManager.getProjectConfiguration() } returns mockProjectConfiguration coEvery { configManager.getDeviceConfiguration() } returns mockDeviceConfiguration - coEvery { configManager.getProject(any()) } returns mockProject + coEvery { configManager.getProject() } returns mockProject val eventSyncLiveData = MutableLiveData(mockEventSyncState) every { eventSyncManager.getLastSyncState() } returns eventSyncLiveData @@ -401,7 +401,7 @@ class SyncInfoViewModelTest { val mockPausedProject = mockk { every { state } returns ProjectState.PROJECT_PAUSED } - coEvery { configManager.getProject(any()) } returns mockPausedProject + coEvery { configManager.getProject() } returns mockPausedProject createViewModel() viewModel.isPreLogoutUpSync = false @@ -416,7 +416,7 @@ class SyncInfoViewModelTest { val mockEndingProject = mockk { every { state } returns ProjectState.PROJECT_ENDING } - coEvery { configManager.getProject(any()) } returns mockEndingProject + coEvery { configManager.getProject() } returns mockEndingProject createViewModel() viewModel.isPreLogoutUpSync = false @@ -431,7 +431,7 @@ class SyncInfoViewModelTest { val mockEndingProject = mockk { every { state } throws RemoteDbNotSignedInException("stub!") } - coEvery { configManager.getProject(any()) } returns mockEndingProject + coEvery { configManager.getProject() } returns mockEndingProject createViewModel() viewModel.isPreLogoutUpSync = false diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModelTest.kt index 21576db381..748857ffb5 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModelTest.kt @@ -1,8 +1,8 @@ package com.simprints.feature.dashboard.settings.syncinfo.moduleselection import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat +import androidx.test.ext.junit.runners.* +import com.google.common.truth.Truth.* import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.core.domain.tokenization.asTokenizableRaw @@ -10,23 +10,17 @@ import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.excepti import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.exceptions.TooManyModulesSelectedException import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.repository.Module import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.repository.ModuleRepository -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.SettingsPasswordConfig import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.config.sync.ConfigManager -import com.simprints.infra.eventsync.EventSyncManager import com.simprints.infra.sync.SyncOrchestrator import com.simprints.testtools.common.coroutines.TestCoroutineRule import com.simprints.testtools.common.livedata.getOrAwaitValue import com.simprints.testtools.common.syntax.assertThrows -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.mockk import kotlinx.coroutines.CoroutineScope import org.junit.Before import org.junit.Rule @@ -44,9 +38,6 @@ class ModuleSelectionViewModelTest { @MockK private lateinit var repository: ModuleRepository - @MockK - private lateinit var eventSyncManager: EventSyncManager - @MockK private lateinit var syncOrchestrator: SyncOrchestrator @@ -56,9 +47,6 @@ class ModuleSelectionViewModelTest { @MockK private lateinit var tokenizationProcessor: TokenizationProcessor - @MockK - private lateinit var authStore: AuthStore - @MockK private lateinit var project: Project @@ -79,8 +67,7 @@ class ModuleSelectionViewModelTest { coEvery { configManager.getProjectConfiguration() } returns mockk { every { general.settingsPassword } returns SettingsPasswordConfig.Locked("1234") } - every { authStore.signedInProjectId } returns PROJECT_ID - coEvery { configManager.getProject(PROJECT_ID) } returns project + coEvery { configManager.getProject() } returns project modulesDefault.forEach { coEvery { tokenizationProcessor.decrypt( @@ -92,7 +79,6 @@ class ModuleSelectionViewModelTest { } viewModel = ModuleSelectionViewModel( - authStore = authStore, moduleRepository = repository, syncOrchestrator = syncOrchestrator, configManager = configManager, diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCaseTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCaseTest.kt index 2d8de4f833..497af92f1f 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCaseTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCaseTest.kt @@ -145,7 +145,7 @@ class ObserveSyncInfoUseCaseTest { every { configManager.observeDeviceConfiguration() } returns MutableStateFlow(mockDeviceConfiguration) coEvery { configManager.getProjectConfiguration() } returns mockProjectConfiguration coEvery { configManager.getDeviceConfiguration() } returns mockDeviceConfiguration - coEvery { configManager.getProject(any()) } returns mockProject + coEvery { configManager.getProject() } returns mockProject val eventSyncLiveData = MutableLiveData(mockEventSyncState) every { eventSyncManager.getLastSyncState() } returns eventSyncLiveData @@ -255,7 +255,7 @@ class ObserveSyncInfoUseCaseTest { val mockPausedProject = mockk { every { state } returns ProjectState.PROJECT_PAUSED } - coEvery { configManager.getProject(any()) } returns mockPausedProject + coEvery { configManager.getProject() } returns mockPausedProject createUseCase() val result = useCase().first() @@ -268,7 +268,7 @@ class ObserveSyncInfoUseCaseTest { val mockEndingProject = mockk { every { state } returns ProjectState.PROJECT_ENDING } - coEvery { configManager.getProject(any()) } returns mockEndingProject + coEvery { configManager.getProject() } returns mockEndingProject createUseCase() val result = useCase().first() diff --git a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt index 42d25137d0..10e0e0ff36 100644 --- a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt +++ b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt @@ -60,7 +60,7 @@ internal class EnrolLastBiometricViewModel @Inject constructor( params.scannedCredential?.let { scannedCredential -> val guidToEnrol = getPreviousEnrolmentResult(params.steps)?.subjectId if (isCredentialLinkedToAnotherSubject(scannedCredential, guidToEnrol = guidToEnrol, projectId = params.projectId)) { - displayAddCredentialDialog(scannedCredential, params.projectId) + displayAddCredentialDialog(scannedCredential) return@launch } } @@ -77,7 +77,7 @@ internal class EnrolLastBiometricViewModel @Inject constructor( enrolWasAttempted = true val projectConfig = configManager.getProjectConfiguration() - val project = configManager.getProject(projectConfig.projectId) + val project = configManager.getProject() val modalities = projectConfig.general.modalities val previousLastEnrolmentResult = getPreviousEnrolmentResult(params.steps) @@ -98,7 +98,7 @@ internal class EnrolLastBiometricViewModel @Inject constructor( try { val subject = buildSubject(params, isAddingCredential = isAddingCredential) - registerEvent(params, subject) + registerEvent(subject) enrolmentRecordRepository.performActions(listOf(SubjectAction.Creation(subject)), project) _finish.send(EnrolLastState.Success(subject.subjectId, scannedCredential?.toExternalCredential(subject.subjectId))) } catch (t: Throwable) { @@ -107,11 +107,8 @@ internal class EnrolLastBiometricViewModel @Inject constructor( } } - private suspend fun displayAddCredentialDialog( - scannedCredential: ScannedCredential, - projectId: String, - ) { - val project = configManager.getProject(projectId) + private suspend fun displayAddCredentialDialog(scannedCredential: ScannedCredential) { + val project = configManager.getProject() val decrypted = tokenizationProcessor.decrypt( encrypted = scannedCredential.credential, tokenKeyType = TokenKeyType.ExternalCredential, @@ -139,15 +136,12 @@ internal class EnrolLastBiometricViewModel @Inject constructor( private fun getPreviousEnrolmentResult(steps: List) = steps.filterIsInstance().firstOrNull() - private suspend fun registerEvent( - params: EnrolLastBiometricParams, - subject: Subject, - ) { + private suspend fun registerEvent(subject: Subject) { Simber.d("Register events for enrolments", tag = ENROLMENT) val events = eventRepository.getEventsInCurrentSession() // Ensures that any previous confirmations are removed from session - resetEnrolmentUpdateEventsFromSession(params.projectId) + resetEnrolmentUpdateEventsFromSession() val biometricReferenceIds = events .filterIsInstance() diff --git a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt index 94070cf4e8..e856e65f5f 100644 --- a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt +++ b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt @@ -276,7 +276,7 @@ internal class EnrolLastBiometricViewModelTest { fun `shows add credential dialog when scanned credential is linked to another subject`() = runTest { val decryptedCredential = "decryptedCredential".asTokenizableRaw() coEvery { enrolmentRecordRepository.load(any()) } returns listOf(subject) - coEvery { configManager.getProject(PROJECT_ID) } returns project + coEvery { configManager.getProject() } returns project coEvery { tokenizationProcessor.decrypt( encrypted = scannedCredential.credential, @@ -309,7 +309,7 @@ internal class EnrolLastBiometricViewModelTest { fun `add credential dialog is not shown when there is no result`() = runTest { val decryptedCredential = "decryptedCredential".asTokenizableRaw() coEvery { enrolmentRecordRepository.load(any()) } returns listOf(subject) - coEvery { configManager.getProject(PROJECT_ID) } returns project + coEvery { configManager.getProject() } returns project coEvery { tokenizationProcessor.decrypt( encrypted = scannedCredential.credential, @@ -327,7 +327,7 @@ internal class EnrolLastBiometricViewModelTest { fun `add credential dialog is not shown when there are no credentials`() = runTest { val decryptedCredential = "decryptedCredential".asTokenizableRaw() coEvery { enrolmentRecordRepository.load(any()) } returns listOf(subject) - coEvery { configManager.getProject(PROJECT_ID) } returns project + coEvery { configManager.getProject() } returns project coEvery { tokenizationProcessor.decrypt( encrypted = scannedCredential.credential, @@ -350,7 +350,7 @@ internal class EnrolLastBiometricViewModelTest { fun `add credential dialog is not shown when credential is already linked to same subject`() = runTest { val decryptedCredential = "decryptedCredential".asTokenizableRaw() coEvery { enrolmentRecordRepository.load(any()) } returns listOf(subject) - coEvery { configManager.getProject(PROJECT_ID) } returns project + coEvery { configManager.getProject() } returns project coEvery { tokenizationProcessor.decrypt( encrypted = scannedCredential.credential, diff --git a/feature/external-credential/build.gradle.kts b/feature/external-credential/build.gradle.kts index 411b4dc0e1..9ea9fbf615 100644 --- a/feature/external-credential/build.gradle.kts +++ b/feature/external-credential/build.gradle.kts @@ -13,7 +13,6 @@ dependencies { implementation(project(":infra:ui-base")) implementation(project(":feature:exit-form")) implementation(project(":infra:enrolment-records:repository")) - implementation(project(":infra:auth-store")) implementation(project(":infra:matching")) implementation(project(":infra:events")) implementation(project(":infra:credential-store")) diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt index ff0873f996..dcc05962a5 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt @@ -23,7 +23,6 @@ import com.simprints.feature.externalcredential.screens.scanocr.usecase.KeepOnly import com.simprints.feature.externalcredential.screens.scanocr.usecase.NormalizeBitmapToPreviewUseCase import com.simprints.feature.externalcredential.screens.scanocr.usecase.ZoomOntoCredentialUseCase import com.simprints.feature.externalcredential.screens.search.model.ScannedCredential -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.models.experimental import com.simprints.infra.config.store.tokenization.TokenizationProcessor @@ -49,7 +48,6 @@ internal class ExternalCredentialScanOcrViewModel @AssistedInject constructor( private val credentialImageRepository: CredentialImageRepository, private val zoomOntoCredentialUseCase: ZoomOntoCredentialUseCase, private val tokenizationProcessor: TokenizationProcessor, - private val authStore: AuthStore, private val configManager: ConfigManager, @DispatcherBG private val bgDispatcher: CoroutineDispatcher, ) : ViewModel() { @@ -137,7 +135,7 @@ internal class ExternalCredentialScanOcrViewModel @AssistedInject constructor( fun processOcrResultsAndFinish() { updateState { ScanOcrState.Complete } viewModelScope.launch { - val project = configManager.getProject(authStore.signedInProjectId) + val project = configManager.getProject() val detectedBlock = keepOnlyBestDetectedBlockUseCase(detectedBlocks, ocrDocumentType) val credentialType = detectedBlock.documentType.asExternalCredentialType() val blockBoundingBox = detectedBlock.blockBoundingBox diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModel.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModel.kt index 17be0e35f2..5fda2a2d09 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModel.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModel.kt @@ -10,7 +10,6 @@ import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.core.tools.time.TimeHelper import com.simprints.core.tools.time.Timestamp import com.simprints.feature.externalcredential.screens.scanqr.usecase.ExternalCredentialQrCodeValidatorUseCase -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.config.sync.ConfigManager @@ -22,7 +21,6 @@ import javax.inject.Inject internal class ExternalCredentialScanQrViewModel @Inject constructor( private val timeHelper: TimeHelper, private val externalCredentialQrCodeValidator: ExternalCredentialQrCodeValidatorUseCase, - private val authStore: AuthStore, private val configManager: ConfigManager, private val tokenizationProcessor: TokenizationProcessor, ) : ViewModel() { @@ -44,7 +42,7 @@ internal class ExternalCredentialScanQrViewModel @Inject constructor( val newState = when (value) { null -> ScanQrState.ReadyToScan else -> { - val project = configManager.getProject(authStore.signedInProjectId) + val project = configManager.getProject() val qrCodeEncrypted = tokenizationProcessor.encrypt( decrypted = value.asTokenizableRaw(), tokenKeyType = TokenKeyType.ExternalCredential, diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModel.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModel.kt index d13785ac91..a096cc7e6f 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModel.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModel.kt @@ -18,7 +18,6 @@ import com.simprints.feature.externalcredential.screens.search.model.SearchCrede import com.simprints.feature.externalcredential.screens.search.model.SearchState import com.simprints.feature.externalcredential.screens.search.usecase.MatchCandidatesUseCase import com.simprints.feature.externalcredential.usecase.ExternalCredentialEventTrackerUseCase -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.config.sync.ConfigManager @@ -35,7 +34,6 @@ internal class ExternalCredentialSearchViewModel @AssistedInject constructor( @Assisted val scannedCredential: ScannedCredential, @Assisted val externalCredentialParams: ExternalCredentialParams, private val timeHelper: TimeHelper, - private val authStore: AuthStore, private val configManager: ConfigManager, private val matchCandidatesUseCase: MatchCandidatesUseCase, private val tokenizationProcessor: TokenizationProcessor, @@ -81,7 +79,7 @@ internal class ExternalCredentialSearchViewModel @AssistedInject constructor( fun confirmCredentialUpdate(updatedCredential: TokenizableString.Raw) { viewModelScope.launch { - val project = configManager.getProject(authStore.signedInProjectId) + val project = configManager.getProject() val encryptedCredential = tokenizationProcessor.encrypt( decrypted = updatedCredential, tokenKeyType = TokenKeyType.ExternalCredential, @@ -123,7 +121,7 @@ internal class ExternalCredentialSearchViewModel @AssistedInject constructor( } private suspend fun decryptCredentialToDisplay(credential: TokenizableString.Tokenized) { - val project = configManager.getProject(authStore.signedInProjectId) + val project = configManager.getProject() val decrypted = tokenizationProcessor.decrypt( encrypted = credential, tokenKeyType = TokenKeyType.ExternalCredential, @@ -134,7 +132,7 @@ internal class ExternalCredentialSearchViewModel @AssistedInject constructor( private suspend fun searchSubjectsLinkedToCredential(credential: TokenizableString.Tokenized) { updateState { it.copy(searchState = SearchState.Searching) } - val project = configManager.getProject(authStore.signedInProjectId) + val project = configManager.getProject() val candidates = enrolmentRecordRepository.load(SubjectQuery(projectId = project.id, externalCredential = credential)) val startTime = timeHelper.now() diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCase.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCase.kt index cb936b120c..7be461473d 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCase.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCase.kt @@ -7,7 +7,6 @@ import com.simprints.core.tools.time.Timestamp import com.simprints.feature.externalcredential.screens.scanocr.usecase.CalculateLevenshteinDistanceUseCase import com.simprints.feature.externalcredential.screens.search.model.ScannedCredential import com.simprints.feature.externalcredential.screens.search.model.toExternalCredential -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.config.sync.ConfigManager @@ -24,7 +23,6 @@ import javax.inject.Inject internal class ExternalCredentialEventTrackerUseCase @Inject constructor( private val timeHelper: TimeHelper, - private val authStore: AuthStore, private val configManager: ConfigManager, private val tokenizationProcessor: TokenizationProcessor, private val eventRepository: SessionEventRepository, @@ -85,7 +83,7 @@ internal class ExternalCredentialEventTrackerUseCase @Inject constructor( } private suspend fun calculateOcrErrorCount(scannedCredential: ScannedCredential): Int { - val project = configManager.getProject(authStore.signedInProjectId) + val project = configManager.getProject() val actualCredentialRaw = tokenizationProcessor.decrypt( scannedCredential.credential, TokenKeyType.ExternalCredential, diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCase.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCase.kt index 23c96223b2..ae6e58ab15 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCase.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCase.kt @@ -19,7 +19,6 @@ class ResetExternalCredentialsInSessionUseCase @Inject() constructor( @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, ) { suspend operator fun invoke( - projectId: String, scannedCredential: ScannedCredential? = null, subjectId: String = "", ) { @@ -54,7 +53,7 @@ class ResetExternalCredentialsInSessionUseCase @Inject() constructor( emptyList() } - val project = configManager.getProject(projectId) + val project = configManager.getProject() val updateActions = credentialsToRemove + credentialsToAdd enrolmentRecordRepository.performActions(updateActions, project) diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModelTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModelTest.kt index cec68f27a9..0857a60734 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModelTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModelTest.kt @@ -16,7 +16,6 @@ import com.simprints.feature.externalcredential.screens.scanocr.usecase.GetCrede import com.simprints.feature.externalcredential.screens.scanocr.usecase.KeepOnlyBestDetectedBlockUseCase import com.simprints.feature.externalcredential.screens.scanocr.usecase.NormalizeBitmapToPreviewUseCase import com.simprints.feature.externalcredential.screens.scanocr.usecase.ZoomOntoCredentialUseCase -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor @@ -62,9 +61,6 @@ internal class ExternalCredentialScanOcrViewModelTest { @MockK private lateinit var tokenizationProcessor: TokenizationProcessor - @MockK - private lateinit var authStore: AuthStore - @MockK private lateinit var configManager: ConfigManager @@ -98,7 +94,6 @@ internal class ExternalCredentialScanOcrViewModelTest { bgDispatcher = testCoroutineRule.testCoroutineDispatcher, tokenizationProcessor = tokenizationProcessor, configManager = configManager, - authStore = authStore, ) @Test @@ -136,7 +131,6 @@ internal class ExternalCredentialScanOcrViewModelTest { val detectedBlockImagePath = "detectedBlockImagePath" val readoutValue = "readoutValue" val zoomedImagePath = "zoomedImagePath" - val projectId = "projectId" val mockBoundingBox = mockk() val mockBitmap = mockk() val mockProject = mockk() @@ -149,8 +143,7 @@ internal class ExternalCredentialScanOcrViewModelTest { every { this@mockk.readoutValue } returns readoutValue } - coEvery { authStore.signedInProjectId } returns projectId - coEvery { configManager.getProject(projectId) } returns mockProject + coEvery { configManager.getProject() } returns mockProject coEvery { keepOnlyBestDetectedBlockUseCase(any(), documentType) } returns mockBestBlock coEvery { tokenizationProcessor.encrypt(any(), TokenKeyType.ExternalCredential, mockProject) } returns mockTokenizedCredential coEvery { zoomOntoCredentialUseCase(detectedBlockImagePath, mockBoundingBox) } returns mockBitmap @@ -175,7 +168,6 @@ internal class ExternalCredentialScanOcrViewModelTest { fun `processOcrResultsAndFinish sets null zoomed image path when zoom fails`() = runTest { val detectedBlockImagePath = "detectedBlockImagePath" val readoutValue = "readoutValue" - val projectId = "projectId" val mockBoundingBox = mockk() val mockProject = mockk() val mockTokenizedCredential = mockk() @@ -187,8 +179,7 @@ internal class ExternalCredentialScanOcrViewModelTest { every { this@mockk.readoutValue } returns readoutValue } - coEvery { authStore.signedInProjectId } returns projectId - coEvery { configManager.getProject(projectId) } returns mockProject + coEvery { configManager.getProject() } returns mockProject coEvery { keepOnlyBestDetectedBlockUseCase(any(), documentType) } returns mockBestBlock coEvery { tokenizationProcessor.encrypt(any(), TokenKeyType.ExternalCredential, mockProject) } returns mockTokenizedCredential coEvery { zoomOntoCredentialUseCase(detectedBlockImagePath, mockBoundingBox) } throws Exception("Zoom failed") diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModelTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModelTest.kt index eaa430a722..79135aecf2 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModelTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModelTest.kt @@ -9,7 +9,6 @@ import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.core.tools.time.TimeHelper import com.simprints.core.tools.time.Timestamp import com.simprints.feature.externalcredential.screens.scanqr.usecase.ExternalCredentialQrCodeValidatorUseCase -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor @@ -38,9 +37,6 @@ internal class ExternalCredentialScanQrViewModelTest { @MockK private lateinit var tokenizationProcessor: TokenizationProcessor - @MockK - private lateinit var authStore: AuthStore - @MockK private lateinit var configManager: ConfigManager @@ -54,7 +50,6 @@ internal class ExternalCredentialScanQrViewModelTest { externalCredentialQrCodeValidator = validator, tokenizationProcessor = tokenizationProcessor, configManager = configManager, - authStore = authStore, ) every { timeHelper.now() } returns Timestamp(1L) @@ -77,12 +72,10 @@ internal class ExternalCredentialScanQrViewModelTest { fun `updateCapturedValue with non-null sets QrCodeCaptured`() = runTest { val observer = viewModel.stateLiveData.test() val value = "value" - val projectId = "projectId" val mockProject = mockk() val mockTokenizedCredential = mockk() - every { authStore.signedInProjectId } returns projectId - coEvery { configManager.getProject(projectId) } returns mockProject + coEvery { configManager.getProject() } returns mockProject coEvery { tokenizationProcessor.encrypt(any(), TokenKeyType.ExternalCredential, mockProject) } returns mockTokenizedCredential viewModel.updateCameraPermissionStatus(permissionStatus = PermissionStatus.Granted) // inits the capture timing diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModelTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModelTest.kt index e72cf4d9a9..ed6eca7885 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModelTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModelTest.kt @@ -17,7 +17,6 @@ import com.simprints.feature.externalcredential.screens.search.model.SearchCrede import com.simprints.feature.externalcredential.screens.search.model.SearchState import com.simprints.feature.externalcredential.screens.search.usecase.MatchCandidatesUseCase import com.simprints.feature.externalcredential.usecase.ExternalCredentialEventTrackerUseCase -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor @@ -43,9 +42,6 @@ internal class ExternalCredentialSearchViewModelTest { @MockK lateinit var timeHelper: TimeHelper - @MockK - lateinit var authStore: AuthStore - @MockK lateinit var configManager: ConfigManager @@ -81,14 +77,11 @@ internal class ExternalCredentialSearchViewModelTest { private lateinit var viewModel: ExternalCredentialSearchViewModel - private val projectId = "projectId" - @Before fun setUp() { MockKAnnotations.init(this, relaxed = true) every { timeHelper.now() } returns Timestamp(1L) - every { authStore.signedInProjectId } returns projectId - coEvery { configManager.getProject(projectId) } returns project + coEvery { configManager.getProject() } returns project coEvery { configManager.getProjectConfiguration() } returns projectConfig coJustRun { eventsTracker.saveSearchEvent(any(), any(), any()) } coJustRun { eventsTracker.saveConfirmation(any(), any()) } @@ -99,7 +92,6 @@ internal class ExternalCredentialSearchViewModelTest { scannedCredential = mockScannedCredential, externalCredentialParams = externalCredentialParams, timeHelper = timeHelper, - authStore = authStore, configManager = configManager, matchCandidatesUseCase = matchCandidatesUseCase, tokenizationProcessor = tokenizationProcessor, diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCaseTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCaseTest.kt index e006fdcfcb..53d2a6998d 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCaseTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCaseTest.kt @@ -8,7 +8,6 @@ import com.simprints.core.tools.time.TimeHelper import com.simprints.core.tools.time.Timestamp import com.simprints.feature.externalcredential.screens.scanocr.usecase.CalculateLevenshteinDistanceUseCase import com.simprints.feature.externalcredential.screens.search.model.ScannedCredential -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.config.sync.ConfigManager @@ -29,9 +28,6 @@ class ExternalCredentialEventTrackerUseCaseTest { @MockK private lateinit var timeHelper: TimeHelper - @MockK - private lateinit var authStore: AuthStore - @MockK private lateinit var configManager: ConfigManager @@ -51,7 +47,6 @@ class ExternalCredentialEventTrackerUseCaseTest { MockKAnnotations.init(this, relaxed = true) useCase = ExternalCredentialEventTrackerUseCase( timeHelper = timeHelper, - authStore = authStore, configManager = configManager, tokenizationProcessor = tokenizationProcessor, eventRepository = eventRepository, @@ -60,8 +55,7 @@ class ExternalCredentialEventTrackerUseCaseTest { every { timeHelper.now() } returns END_TIME - coEvery { authStore.signedInProjectId } returns "" - coEvery { configManager.getProject(any()) } returns mockk() + coEvery { configManager.getProject() } returns mockk() coEvery { tokenizationProcessor.decrypt(any(), TokenKeyType.ExternalCredential, any()) } returns RAW_SCANNED_VALUE.asTokenizableRaw() diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCaseTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCaseTest.kt index a29a37d6f3..bcfda0ce1e 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCaseTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCaseTest.kt @@ -50,7 +50,7 @@ internal class ResetExternalCredentialsInSessionUseCaseTest { fun setUp() { MockKAnnotations.init(this, relaxed = true) - coEvery { configManager.getProject(PROJECT_ID) } returns project + coEvery { configManager.getProject() } returns project useCase = ResetExternalCredentialsInSessionUseCase( enrolmentRecordRepository = enrolmentRecordRepository, @@ -66,7 +66,7 @@ internal class ResetExternalCredentialsInSessionUseCaseTest { fun `invokes enrolment repository with correct update action`() = runTest { coEvery { eventRepository.getEventsInCurrentSession() } returns emptyList() - useCase(PROJECT_ID, scannedCredential, SUBJECT_ID) + useCase(scannedCredential, SUBJECT_ID) val actionsSlot = slot>() coVerify { enrolmentRecordRepository.performActions(capture(actionsSlot), project) } @@ -84,7 +84,7 @@ internal class ResetExternalCredentialsInSessionUseCaseTest { fun `adds correct external credential to subject`() = runTest { coEvery { eventRepository.getEventsInCurrentSession() } returns listOf() - useCase(PROJECT_ID, scannedCredential, SUBJECT_ID) + useCase(scannedCredential, SUBJECT_ID) val actionsSlot = slot>() coVerify { enrolmentRecordRepository.performActions(capture(actionsSlot), project) } @@ -104,7 +104,6 @@ internal class ResetExternalCredentialsInSessionUseCaseTest { useCase( scannedCredential = scannedCredential, subjectId = SUBJECT_ID, - projectId = PROJECT_ID, ) val actionsSlot = slot>() @@ -132,7 +131,6 @@ internal class ResetExternalCredentialsInSessionUseCaseTest { useCase( scannedCredential = scannedCredential, subjectId = SUBJECT_ID, - projectId = PROJECT_ID, ) coEvery { eventRepository.deleteEvents(match { it.size == 1 }) } @@ -143,7 +141,6 @@ internal class ResetExternalCredentialsInSessionUseCaseTest { useCase( scannedCredential = scannedCredential, subjectId = "none_selected", - projectId = PROJECT_ID, ) val actionsSlot = slot>() @@ -154,8 +151,8 @@ internal class ResetExternalCredentialsInSessionUseCaseTest { @Test fun `retrieves project using correct project id`() = runTest { - useCase(PROJECT_ID, scannedCredential, SUBJECT_ID) - coVerify { configManager.getProject(PROJECT_ID) } + useCase(scannedCredential, SUBJECT_ID) + coVerify { configManager.getProject() } } private fun enrolmentUpdateEvent( @@ -174,7 +171,6 @@ internal class ResetExternalCredentialsInSessionUseCaseTest { ) companion object { - private const val PROJECT_ID = "projectId" private const val SUBJECT_ID = "bbaa8ff3-34f7-41d3-a6c9-ff3b952d832e" private val CREDENTIAL = "credential".asTokenizableEncrypted() private val CREDENTIAL_TYE = ExternalCredentialType.NHISCard diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt index 7e5fbbe7fc..2ae983f020 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt @@ -134,7 +134,7 @@ class LoginCheckViewModel @Inject internal constructor( } private suspend fun validateProjectAndProceed(actionRequest: ActionRequest) { - when (configManager.getProject(actionRequest.projectId).state) { + when (configManager.getProject().state) { ProjectState.PROJECT_PAUSED -> _showAlert.send(LoginCheckError.PROJECT_PAUSED) ProjectState.PROJECT_ENDING -> _showAlert.send(LoginCheckError.PROJECT_ENDING) ProjectState.PROJECT_ENDED -> startSignInAttempt(actionRequest) diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt index 24f91f5d58..658d15a0fa 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt @@ -262,7 +262,7 @@ internal class LoginCheckViewModelTest { viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) viewModel.handleLoginResult(LoginResult(true, null)) - coVerify { configManager.getProject(any()) } + coVerify { configManager.getProject() } } @Test @@ -271,13 +271,13 @@ internal class LoginCheckViewModelTest { viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) - coVerify { configManager.getProject(any()) } + coVerify { configManager.getProject() } } @Test fun `Triggers alert if project is paused`() = runTest { coEvery { isUserSignedInUseCase.invoke(any()) } returns IsUserSignedInUseCase.SignedInState.SIGNED_IN - coEvery { configManager.getProject(any()).state } returns ProjectState.PROJECT_PAUSED + coEvery { configManager.getProject().state } returns ProjectState.PROJECT_PAUSED viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) @@ -289,7 +289,7 @@ internal class LoginCheckViewModelTest { @Test fun `Triggers alert if project is ending`() = runTest { coEvery { isUserSignedInUseCase.invoke(any()) } returns IsUserSignedInUseCase.SignedInState.SIGNED_IN - coEvery { configManager.getProject(any()).state } returns ProjectState.PROJECT_ENDING + coEvery { configManager.getProject().state } returns ProjectState.PROJECT_ENDING viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) @@ -301,7 +301,7 @@ internal class LoginCheckViewModelTest { @Test fun `Triggers login attempt if project has ended`() = runTest { coEvery { isUserSignedInUseCase.invoke(any()) } returns IsUserSignedInUseCase.SignedInState.SIGNED_IN - coEvery { configManager.getProject(any()).state } returns ProjectState.PROJECT_ENDED + coEvery { configManager.getProject().state } returns ProjectState.PROJECT_ENDED viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) @@ -313,7 +313,7 @@ internal class LoginCheckViewModelTest { @Test fun `Correctly handles if user signed in active project`() = runTest { coEvery { isUserSignedInUseCase.invoke(any()) } returns IsUserSignedInUseCase.SignedInState.SIGNED_IN - coEvery { configManager.getProject(any()).state } returns ProjectState.RUNNING + coEvery { configManager.getProject().state } returns ProjectState.RUNNING viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) diff --git a/feature/matcher/build.gradle.kts b/feature/matcher/build.gradle.kts index 630deb5152..76afd68239 100644 --- a/feature/matcher/build.gradle.kts +++ b/feature/matcher/build.gradle.kts @@ -16,6 +16,4 @@ dependencies { implementation(project(":infra:matching")) implementation(project(":infra:config-store")) implementation(project(":infra:config-sync")) - - implementation(project(":infra:auth-store")) } diff --git a/feature/matcher/src/main/java/com/simprints/matcher/screen/MatchViewModel.kt b/feature/matcher/src/main/java/com/simprints/matcher/screen/MatchViewModel.kt index 1c2e989d84..1b305a9498 100644 --- a/feature/matcher/src/main/java/com/simprints/matcher/screen/MatchViewModel.kt +++ b/feature/matcher/src/main/java/com/simprints/matcher/screen/MatchViewModel.kt @@ -8,7 +8,6 @@ import com.simprints.core.domain.sample.MatchComparisonResult import com.simprints.core.livedata.LiveDataEventWithContent import com.simprints.core.livedata.send import com.simprints.core.tools.time.TimeHelper -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.DecisionPolicy import com.simprints.infra.config.store.models.FaceConfiguration import com.simprints.infra.config.store.models.getModalitySdkConfig @@ -29,7 +28,6 @@ internal class MatchViewModel @Inject constructor( private val faceMatcher: FaceMatcherUseCase, private val fingerprintMatcher: FingerprintMatcherUseCase, private val saveMatchEvent: SaveMatchEventUseCase, - private val authStore: AuthStore, private val configManager: ConfigManager, private val timeHelper: TimeHelper, ) : ViewModel() { @@ -59,7 +57,7 @@ internal class MatchViewModel @Inject constructor( is FaceConfiguration.BioSdk -> faceMatcher else -> fingerprintMatcher } - val project = configManager.getProject(authStore.signedInProjectId) + val project = configManager.getProject() val decisionPolicy = getDecisionPolicy(params) candidatesLoaded = 0 diff --git a/feature/matcher/src/test/java/com/simprints/matcher/screen/MatchViewModelTest.kt b/feature/matcher/src/test/java/com/simprints/matcher/screen/MatchViewModelTest.kt index fffb306ae2..9fa8cde69e 100644 --- a/feature/matcher/src/test/java/com/simprints/matcher/screen/MatchViewModelTest.kt +++ b/feature/matcher/src/test/java/com/simprints/matcher/screen/MatchViewModelTest.kt @@ -10,7 +10,6 @@ import com.simprints.core.domain.sample.MatchComparisonResult import com.simprints.core.domain.sample.SampleIdentifier import com.simprints.core.tools.time.TimeHelper import com.simprints.core.tools.time.Timestamp -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.DecisionPolicy import com.simprints.infra.config.store.models.FaceConfiguration import com.simprints.infra.config.store.models.FingerprintConfiguration.BioSdk.SECUGEN_SIM_MATCHER @@ -57,9 +56,6 @@ internal class MatchViewModelTest { @MockK lateinit var timeHelper: TimeHelper - @MockK - lateinit var authStore: AuthStore - @MockK lateinit var configManager: ConfigManager @@ -78,7 +74,6 @@ internal class MatchViewModelTest { faceMatcherUseCase, fingerprintMatcherUseCase, saveMatchEvent, - authStore, configManager, timeHelper, ) diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt index ccc0fc1f77..791ffa6fce 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt @@ -238,7 +238,7 @@ internal class OrchestratorViewModel @Inject constructor( private fun buildAppResponse() = viewModelScope.launch { val projectConfiguration = configManager.getProjectConfiguration() - val project = configManager.getProject(projectConfiguration.projectId) + val project = configManager.getProject() val appResponse = appResponseBuilder( projectConfiguration = projectConfiguration, request = actionRequest, diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt index 87d7382965..fcf17fc263 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt @@ -373,7 +373,7 @@ internal class OrchestratorViewModelTest { every { projectId } returns id every { general.modalities } returns emptyList() andThen projectModalities } - coEvery { configManager.getProject(id) } returns mockk() + coEvery { configManager.getProject() } returns mockk() viewModel.handleAction(mockk()) viewModel.restoreModalitiesIfNeeded() @@ -392,7 +392,7 @@ internal class OrchestratorViewModelTest { every { projectId } returns id every { general.modalities } returns projectModalities } - coEvery { configManager.getProject(id) } returns mockk() + coEvery { configManager.getProject() } returns mockk() viewModel.handleAction(mockk()) viewModel.restoreModalitiesIfNeeded() diff --git a/feature/select-subject/src/main/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModel.kt b/feature/select-subject/src/main/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModel.kt index 0a17de0cd8..5bffdb41f0 100644 --- a/feature/select-subject/src/main/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModel.kt +++ b/feature/select-subject/src/main/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModel.kt @@ -98,7 +98,7 @@ internal class SelectSubjectViewModel @AssistedInject constructor( ): SelectSubjectState.CredentialDialogDisplayed? { if (scannedCredential == null) return null val credential = scannedCredential.credential - val project = configManager.getProject(authStore.signedInProjectId) + val project = configManager.getProject() val alreadyLinkedSubject = enrolmentRecordRepository .load( SubjectQuery( @@ -112,7 +112,6 @@ internal class SelectSubjectViewModel @AssistedInject constructor( // Confirmation of "none_selected" (or any non UUID value) should not display the dialog, // but still remove update event from session and reset previously linked external credentials resetExternalCredentialsUseCase( - projectId = params.projectId, scannedCredential = scannedCredential, subjectId = params.subjectId, ) @@ -137,7 +136,6 @@ internal class SelectSubjectViewModel @AssistedInject constructor( resetExternalCredentialsUseCase( scannedCredential = scannedCredential, subjectId = params.subjectId, - projectId = params.projectId, ) // Confirmation of "none_selected" (or any non UUID value) should not produce an EnrolmentUpdateEvent diff --git a/feature/select-subject/src/test/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModelTest.kt b/feature/select-subject/src/test/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModelTest.kt index 73abef8e6b..fe842bdc6b 100644 --- a/feature/select-subject/src/test/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModelTest.kt +++ b/feature/select-subject/src/test/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModelTest.kt @@ -245,7 +245,7 @@ internal class SelectSubjectViewModelTest { coEvery { authStore.isProjectIdSignedIn(PROJECT_ID) } returns true coEvery { authStore.signedInProjectId } returns PROJECT_ID - coEvery { configManager.getProject(PROJECT_ID) } returns project + coEvery { configManager.getProject() } returns project every { project.id } returns PROJECT_ID coEvery { enrolmentRecordRepository.load(any()) } returns repositoryResponse coEvery { @@ -275,7 +275,7 @@ internal class SelectSubjectViewModelTest { ) coJustRun { - resetScannedCredentialsInSession(any(), any(), any()) + resetScannedCredentialsInSession(any(), any()) } val viewModel = createViewModel(params = selectSubjectParams.copy(scannedCredential = scannedCredential)) @@ -294,7 +294,6 @@ internal class SelectSubjectViewModelTest { coVerify { resetScannedCredentialsInSession( - projectId = PROJECT_ID, scannedCredential = scannedCredential, subjectId = SUBJECT_ID, ) @@ -313,7 +312,7 @@ internal class SelectSubjectViewModelTest { } coJustRun { - resetScannedCredentialsInSession(any(), any(), any()) + resetScannedCredentialsInSession(any(), any()) } val viewModel = createViewModel( @@ -335,7 +334,6 @@ internal class SelectSubjectViewModelTest { coVerify { // Still needs to remove previous links resetScannedCredentialsInSession( - projectId = PROJECT_ID, scannedCredential = scannedCredential, subjectId = "none_selected", ) @@ -350,7 +348,6 @@ internal class SelectSubjectViewModelTest { resetScannedCredentialsInSession( scannedCredential = scannedCredential, subjectId = SUBJECT_ID, - projectId = PROJECT_ID, ) } throws RuntimeException("RuntimeException") diff --git a/fingerprint/infra/image-distortion-config/src/main/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepo.kt b/fingerprint/infra/image-distortion-config/src/main/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepo.kt index 9d59ef52fa..43bf17e85e 100644 --- a/fingerprint/infra/image-distortion-config/src/main/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepo.kt +++ b/fingerprint/infra/image-distortion-config/src/main/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepo.kt @@ -34,7 +34,7 @@ internal class ImageDistortionConfigRemoteRepo @Inject constructor( return false } - val bucketUrl = configManager.getProject(projectId).imageBucket + val bucketUrl = configManager.getProject().imageBucket val rootRef = FirebaseStorage .getInstance( diff --git a/fingerprint/infra/image-distortion-config/src/test/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepoTest.kt b/fingerprint/infra/image-distortion-config/src/test/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepoTest.kt index 629c1db07b..6c326d89c2 100644 --- a/fingerprint/infra/image-distortion-config/src/test/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepoTest.kt +++ b/fingerprint/infra/image-distortion-config/src/test/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepoTest.kt @@ -100,7 +100,7 @@ class ImageDistortionConfigRemoteRepoTest { val bucketUrl = "bucket123" every { authStore.getCoreApp().options.projectId } returns "firebaseProject" every { authStore.signedInProjectId } returns PROJECT_ID - coEvery { configManager.getProject(PROJECT_ID).imageBucket } returns bucketUrl + coEvery { configManager.getProject().imageBucket } returns bucketUrl val mockRootRef: StorageReference = mockk() mockkStatic(FirebaseStorage::class) diff --git a/infra/config-sync/build.gradle.kts b/infra/config-sync/build.gradle.kts index 3b129eafa7..bda60c4a8c 100644 --- a/infra/config-sync/build.gradle.kts +++ b/infra/config-sync/build.gradle.kts @@ -11,4 +11,5 @@ dependencies { implementation(project(":infra:config-store")) implementation(project(":infra:enrolment-records:repository")) + implementation(project(":infra:auth-store")) } 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 4588f3511d..15c792453d 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 @@ -1,5 +1,6 @@ package com.simprints.infra.config.sync +import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.DeviceConfiguration import com.simprints.infra.config.store.models.DeviceState @@ -20,6 +21,7 @@ class ConfigManager @Inject constructor( private val configRepository: ConfigRepository, private val enrolmentRecordRepository: EnrolmentRecordRepository, private val configSyncCache: ConfigSyncCache, + private val authStore: AuthStore, ) { private val isProjectRefreshingFlow: MutableStateFlow = MutableStateFlow(false) @@ -35,19 +37,23 @@ class ConfigManager @Inject constructor( } } - suspend fun getProject(projectId: String): Project = try { + suspend fun getProject(): Project = try { configRepository.getProject() - } catch (_: NoSuchElementException) { - refreshProject(projectId).project + } catch (ex: NoSuchElementException) { + val signedProjectId = authStore.signedInProjectId.takeUnless { it.isEmpty() } + ?: throw ex // re-throw the initial exception since there is no way to refresh the data + refreshProject(signedProjectId).project } suspend fun getProjectConfiguration(): ProjectConfiguration { val localConfig = configRepository.getProjectConfiguration() // If projectId is empty, configuration hasn't been downloaded yet return if (localConfig.projectId.isEmpty()) { + val signedProjectId = authStore.signedInProjectId.takeUnless { it.isEmpty() } ?: return localConfig + try { // Try to refresh it with logged in projectId (if any) - refreshProject(configRepository.getProject().id).configuration + refreshProject(signedProjectId).configuration } 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 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 22bcc1fb14..4ff97cbbab 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,6 +1,7 @@ package com.simprints.infra.config.sync import com.google.common.truth.Truth.* +import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.ConfigRepository import com.simprints.infra.config.store.models.DeviceConfiguration import com.simprints.infra.config.store.models.Project @@ -37,6 +38,9 @@ class ConfigManagerTest { @MockK private lateinit var configSyncCache: ConfigSyncCache + @MockK + private lateinit var authStore: AuthStore + @MockK private lateinit var projectWithConfig: ProjectWithConfig @@ -56,6 +60,7 @@ class ConfigManagerTest { configRepository = configRepository, enrolmentRecordRepository = enrolmentRecordRepository, configSyncCache = configSyncCache, + authStore = authStore, ) } @@ -73,18 +78,27 @@ class ConfigManagerTest { fun `getProject should call the correct method`() = runTest { coEvery { configRepository.getProject() } returns project - val gottenProject = configManager.getProject(PROJECT_ID) + val gottenProject = configManager.getProject() assertThat(gottenProject).isEqualTo(project) } @Test fun `getProject should call the refresh method when cannot get from local`() = runTest { + every { authStore.signedInProjectId } returns PROJECT_ID coEvery { configRepository.getProject() } throws NoSuchElementException() - configManager.getProject(PROJECT_ID) + configManager.getProject() coVerify(exactly = 1) { configRepository.refreshProject(PROJECT_ID) } } + @Test(expected = NoSuchElementException::class) + fun `getProject should throw when cannot get from local and logged out`() = runTest { + every { authStore.signedInProjectId } returns "" + coEvery { configRepository.getProject() } throws NoSuchElementException() + + configManager.getProject() + } + @Test fun `getProjectConfiguration should call the correct method`() = runTest { coEvery { configRepository.getProjectConfiguration() } returns projectConfiguration @@ -97,6 +111,17 @@ class ConfigManagerTest { @Test fun `getProjectConfiguration return default config if not logged in`() = runTest { every { projectConfiguration.projectId } returns "" + every { authStore.signedInProjectId } returns "" + coEvery { configRepository.getProjectConfiguration() } returns projectConfiguration + + val gottenProjectConfiguration = configManager.getProjectConfiguration() + assertThat(gottenProjectConfiguration).isEqualTo(projectConfiguration) + } + + @Test + fun `getProjectConfiguration return default config refresh fails`() = runTest { + every { projectConfiguration.projectId } returns "" + every { authStore.signedInProjectId } returns "projectId" coEvery { configRepository.getProjectConfiguration() } returns projectConfiguration coEvery { configRepository.refreshProject(any()) } throws Exception() diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/EventDownSyncScopeRepository.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/EventDownSyncScopeRepository.kt index d64d549d91..6788d385ef 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/EventDownSyncScopeRepository.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/EventDownSyncScopeRepository.kt @@ -34,7 +34,7 @@ internal class EventDownSyncScopeRepository @Inject constructor( val syncScope = when (syncPartitioning) { Partitioning.GLOBAL -> SubjectProjectScope(projectId, modes) - Partitioning.USER -> SubjectUserScope(projectId, getUserId(projectId), modes) + Partitioning.USER -> SubjectUserScope(projectId, getUserId(), modes) Partitioning.MODULE -> SubjectModuleScope(projectId, selectedModuleIDs, modes) } @@ -50,7 +50,7 @@ internal class EventDownSyncScopeRepository @Inject constructor( return projectId } - private suspend fun getUserId(projectId: String): String { + private suspend fun getUserId(): String { // After we are certain that all users have user IDs cached (no-one uses 2023.3 for a while), the fallback can be removed val possibleUserId: TokenizableString = authStore.signedInUserId ?: recentUserActivityManager.getRecentUserActivity().lastUserUsed @@ -64,7 +64,7 @@ internal class EventDownSyncScopeRepository @Inject constructor( .encrypt( decrypted = possibleUserId, tokenKeyType = TokenKeyType.AttendantId, - project = configManager.getProject(projectId), + project = configManager.getProject(), ).value is TokenizableString.Tokenized -> possibleUserId.value } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt index 9895dc6d6a..93703db1d7 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt @@ -18,11 +18,11 @@ import com.simprints.infra.config.store.models.isSimprintsEventDownSyncAllowed import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.models.scope.EventScopeType -import com.simprints.infra.eventsync.sync.down.CommCareEventSyncWorkersBuilder import com.simprints.infra.eventsync.sync.common.EventSyncCache import com.simprints.infra.eventsync.sync.common.getAllSubjectsSyncWorkersInfo import com.simprints.infra.eventsync.sync.common.getUniqueSyncId import com.simprints.infra.eventsync.sync.common.sortByScheduledTime +import com.simprints.infra.eventsync.sync.down.CommCareEventSyncWorkersBuilder import com.simprints.infra.eventsync.sync.down.SimprintsEventDownSyncWorkersBuilder import com.simprints.infra.eventsync.sync.up.EventUpSyncWorkersBuilder import com.simprints.infra.logging.Simber @@ -161,8 +161,7 @@ class EventSyncMasterWorker @AssistedInject internal constructor( } private suspend fun isEventDownSyncAllowed(configuration: ProjectConfiguration): Boolean { - val isProjectPaused = - configManager.getProject(configuration.projectId).state == ProjectState.PROJECT_PAUSED + val isProjectPaused = configManager.getProject().state == ProjectState.PROJECT_PAUSED val isSimprintsDownSyncEnabled = configuration.isSimprintsEventDownSyncAllowed() val isCommCareDownSyncEnabled = configuration.isCommCareEventDownSyncAllowed() diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt index dd9ea6d14e..1bf724e5be 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt @@ -70,7 +70,7 @@ internal class EventUpSyncTask @Inject constructor( } } - val project = configManager.getProject(operation.projectId) + val project = configManager.getProject() val config = configManager.getProjectConfiguration() var lastOperation = operation.copy() var isUsefulUpload = false diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt index cdf71375df..c5301249f0 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt @@ -353,7 +353,7 @@ internal class EventSyncMasterWorkerTest { projectState: ProjectState, syncConfig: Frequency, ): ListenableWorker.Result { - coEvery { configManager.getProject(any()).state } returns projectState + coEvery { configManager.getProject().state } returns projectState coEvery { configManager.getProjectConfiguration() } returns mockk { every { projectId } returns "projectId" every { synchronization } returns mockk { diff --git a/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt b/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt index dd7c17b7ac..81d7d6d6de 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt @@ -43,7 +43,7 @@ internal class FirebaseSampleUploader @Inject constructor( var allImagesUploaded = true Simber.i("Starting sample upload to Firebase storage", tag = SAMPLE_UPLOAD) - val bucketUrl = configManager.getProject(projectId).imageBucket + val bucketUrl = configManager.getProject().imageBucket val rootRef = FirebaseStorage .getInstance(firebaseApp, bucketUrl) .reference diff --git a/infra/images/src/test/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploaderTest.kt b/infra/images/src/test/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploaderTest.kt index d9e34de344..4122f0b5a6 100644 --- a/infra/images/src/test/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploaderTest.kt +++ b/infra/images/src/test/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploaderTest.kt @@ -207,7 +207,7 @@ class FirebaseSampleUploaderTest { } private fun setupProjectConfig() { - coEvery { configManager.getProject(any()).imageBucket } returns "gs://`simprints-dev.appspot.com" + coEvery { configManager.getProject().imageBucket } returns "gs://`simprints-dev.appspot.com" every { authStore.getLegacyAppFallback().options.projectId } returns "projectId" every { authStore.signedInProjectId } returns "projectId" } From 4d56b2b8f83192273b2460deee4244d0e33ca2b0 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 2 Dec 2025 17:07:16 +0200 Subject: [PATCH 2/2] MS-1006 Make project in config manager nullable --- .../DeleteSessionEventsIfNeededUseCase.kt | 2 +- .../DeleteSessionEventsIfNeededUseCaseTest.kt | 24 +++++++-- .../projectdetails/ProjectDetailsViewModel.kt | 32 ++++++------ .../settings/syncinfo/SyncInfoViewModel.kt | 9 +--- .../ModuleSelectionViewModel.kt | 25 ++++++---- .../usecase/ObserveSyncInfoUseCase.kt | 9 +--- .../ProjectDetailsViewModelTest.kt | 2 +- .../syncinfo/SyncInfoViewModelTest.kt | 5 +- .../usecase/ObserveSyncInfoUseCaseTest.kt | 10 ++++ .../screen/EnrolLastBiometricViewModel.kt | 6 ++- .../screen/EnrolLastBiometricViewModelTest.kt | 19 +++++++ .../ExternalCredentialScanOcrViewModel.kt | 4 +- .../ExternalCredentialScanQrViewModel.kt | 5 +- .../ExternalCredentialSearchViewModel.kt | 50 +++++++++++-------- .../ExternalCredentialEventTrackerUseCase.kt | 2 +- ...esetExternalCredentialsInSessionUseCase.kt | 7 +-- .../ExternalCredentialScanQrViewModelTest.kt | 11 ++++ .../ExternalCredentialSearchViewModelTest.kt | 45 ++++++++++++----- ...ternalCredentialEventTrackerUseCaseTest.kt | 15 ++++++ ...ExternalCredentialsInSessionUseCaseTest.kt | 11 ++++ .../feature/logincheck/LoginCheckViewModel.kt | 4 +- .../logincheck/LoginCheckViewModelTest.kt | 20 ++++++-- .../matcher/screen/MatchViewModel.kt | 2 +- .../orchestrator/OrchestratorViewModel.kt | 2 +- .../screen/SelectSubjectViewModel.kt | 2 +- .../screen/SelectSubjectViewModelTest.kt | 36 +++++++++++-- .../remote/ImageDistortionConfigRemoteRepo.kt | 6 ++- .../ImageDistortionConfigRemoteRepoTest.kt | 19 +++++-- .../infra/config/sync/ConfigManager.kt | 17 +++++-- .../infra/config/sync/ConfigManagerTest.kt | 7 +-- .../down/EventDownSyncScopeRepository.kt | 11 ++-- .../sync/master/EventSyncMasterWorker.kt | 2 +- .../sync/up/tasks/EventUpSyncTask.kt | 2 +- .../sync/master/EventSyncMasterWorkerTest.kt | 2 +- .../sync/up/tasks/EventUpSyncTaskTest.kt | 13 +++-- .../remote/firebase/FirebaseSampleUploader.kt | 6 ++- .../firebase/FirebaseSampleUploaderTest.kt | 16 ++++-- 37 files changed, 320 insertions(+), 140 deletions(-) diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCase.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCase.kt index e602409fec..836b8fa6d2 100644 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCase.kt +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCase.kt @@ -15,7 +15,7 @@ internal class DeleteSessionEventsIfNeededUseCase @Inject constructor( @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, ) { operator fun invoke(sessionId: String) = sessionCoroutineScope.launch { - val projectNotRunning = configManager.getProject().state != ProjectState.RUNNING + val projectNotRunning = configManager.getProject()?.state != ProjectState.RUNNING val simprintsDataSyncDisabled = !configManager.getProjectConfiguration().canSyncDataToSimprints() if (simprintsDataSyncDisabled || projectNotRunning) { diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCaseTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCaseTest.kt index 17e0ed05b3..6e2022453b 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCaseTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/usecases/DeleteSessionEventsIfNeededUseCaseTest.kt @@ -36,9 +36,23 @@ class DeleteSessionEventsIfNeededUseCaseTest { ) } + @Test + fun `deletes session events if project missing`() = runTest { + coEvery { configManager.getProject()?.state } returns null + coEvery { + configManager + .getProjectConfiguration() + .synchronization.up.simprints.kind + } returns UpSynchronizationConfiguration.UpSynchronizationKind.ALL + + deleteUseCase("sessionId") + + coVerify { eventRepository.deleteEventScope("sessionId") } + } + @Test fun `deletes session events if project paused`() = runTest { - coEvery { configManager.getProject().state } returns ProjectState.PROJECT_PAUSED + coEvery { configManager.getProject()?.state } returns ProjectState.PROJECT_PAUSED coEvery { configManager .getProjectConfiguration() @@ -52,7 +66,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { @Test fun `deletes session events if project ending`() = runTest { - coEvery { configManager.getProject().state } returns ProjectState.PROJECT_ENDING + coEvery { configManager.getProject()?.state } returns ProjectState.PROJECT_ENDING coEvery { configManager .getProjectConfiguration() @@ -66,7 +80,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { @Test fun `deletes session events if project ended`() = runTest { - coEvery { configManager.getProject().state } returns ProjectState.PROJECT_ENDED + coEvery { configManager.getProject()?.state } returns ProjectState.PROJECT_ENDED coEvery { configManager .getProjectConfiguration() @@ -80,7 +94,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { @Test fun `deletes session events if data sync disabled in running project`() = runTest { - coEvery { configManager.getProject().state } returns ProjectState.RUNNING + coEvery { configManager.getProject()?.state } returns ProjectState.RUNNING coEvery { configManager .getProjectConfiguration() @@ -94,7 +108,7 @@ class DeleteSessionEventsIfNeededUseCaseTest { @Test fun `does not delete session events if data sync enabled in running project`() = runTest { - coEvery { configManager.getProject().state } returns ProjectState.RUNNING + coEvery { configManager.getProject()?.state } returns ProjectState.RUNNING coEvery { configManager .getProjectConfiguration() diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModel.kt index 06c11c933c..fcdff5717c 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.simprints.core.domain.tokenization.TokenizableString -import com.simprints.infra.authstore.AuthStore import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.config.sync.ConfigManager @@ -30,22 +29,23 @@ internal class ProjectDetailsViewModel @Inject constructor( fun load() = viewModelScope.launch { val state = try { - val cachedProject = configManager.getProject() - val recentUserActivity = recentUserActivityManager.getRecentUserActivity() - val decryptedUserId = when (val userId = recentUserActivity.lastUserUsed) { - is TokenizableString.Raw -> userId - is TokenizableString.Tokenized -> tokenizationProcessor.decrypt( - encrypted = userId, - tokenKeyType = TokenKeyType.AttendantId, - project = cachedProject, + configManager.getProject()?.let { cachedProject -> + val recentUserActivity = recentUserActivityManager.getRecentUserActivity() + val decryptedUserId = when (val userId = recentUserActivity.lastUserUsed) { + is TokenizableString.Raw -> userId + is TokenizableString.Tokenized -> tokenizationProcessor.decrypt( + encrypted = userId, + tokenKeyType = TokenKeyType.AttendantId, + project = cachedProject, + ) + } + DashboardProjectState( + title = cachedProject.name, + lastUser = decryptedUserId.value, + lastScanner = recentUserActivity.lastScannerUsed, + isLoaded = true, ) - } - DashboardProjectState( - title = cachedProject.name, - lastUser = decryptedUserId.value, - lastScanner = recentUserActivity.lastScannerUsed, - isLoaded = true, - ) + } ?: DashboardProjectState(isLoaded = false) } catch (_: Throwable) { DashboardProjectState(isLoaded = false) } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt index 13137b9c60..6b370d3089 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModel.kt @@ -144,15 +144,8 @@ internal class SyncInfoViewModel @Inject constructor( } syncOrchestrator.stopEventSync() - val projectState = try { - configManager.getProject().state - } catch (_: Exception) { - // If the device is compromised, project data is deleted. Access attempts will throw an exception, - // effectively appearing to the user as if the project has ended. - ProjectState.PROJECT_ENDED - } - val isDownSyncAllowed = !isPreLogoutUpSync && projectState == ProjectState.RUNNING + val isDownSyncAllowed = !isPreLogoutUpSync && configManager.getProject()?.state == ProjectState.RUNNING syncOrchestrator.startEventSync(isDownSyncAllowed) } } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt index 476852ef6a..47ae81ffb8 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/moduleselection/ModuleSelectionViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.simprints.core.ExternalScope import com.simprints.core.domain.tokenization.TokenizableString +import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.exceptions.NoModuleSelectedException import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.exceptions.TooManyModulesSelectedException import com.simprints.feature.dashboard.settings.syncinfo.moduleselection.repository.Module @@ -49,11 +50,13 @@ internal class ModuleSelectionViewModel @Inject constructor( moduleRepository.getModules().map { module -> val decryptedName = when (val name = module.name) { is TokenizableString.Raw -> name - is TokenizableString.Tokenized -> tokenizationProcessor.decrypt( - encrypted = name, - tokenKeyType = TokenKeyType.ModuleId, - project = configManager.getProject(), - ) + is TokenizableString.Tokenized -> configManager.getProject()?.let { + tokenizationProcessor.decrypt( + encrypted = name, + tokenKeyType = TokenKeyType.ModuleId, + project = it, + ) + } ?: "".asTokenizableRaw() } module.copy(name = decryptedName) } @@ -97,11 +100,13 @@ internal class ModuleSelectionViewModel @Inject constructor( externalScope.launch { val modules = modules.map { module -> val encryptedName = when (val name = module.name) { - is TokenizableString.Raw -> tokenizationProcessor.encrypt( - decrypted = name, - tokenKeyType = TokenKeyType.ModuleId, - project = configManager.getProject(), - ) + is TokenizableString.Raw -> configManager.getProject()?.let { project -> + tokenizationProcessor.encrypt( + decrypted = name, + tokenKeyType = TokenKeyType.ModuleId, + project = project, + ) + } ?: "".asTokenizableRaw() is TokenizableString.Tokenized -> name } diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCase.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCase.kt index 8414575068..c56fdcf8b4 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCase.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCase.kt @@ -235,14 +235,7 @@ internal class ObserveSyncInfoUseCase @Inject constructor( else -> DownSyncCounts(0, isLowerBound = false) } - val project = try { - configManager.getProject() - } catch (_: Exception) { - // If the device is compromised, project data is deleted. Access attempts will throw an exception, - // effectively appearing to the user as if the project has ended. - null - } - + val project = configManager.getProject() val isProjectRunning = project?.state == ProjectState.RUNNING val moduleCounts = if (project != null) { deviceConfig.selectedModules.map { moduleName -> diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModelTest.kt index a7f87cf6dd..b38bc700b0 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/main/projectdetails/ProjectDetailsViewModelTest.kt @@ -67,7 +67,7 @@ class ProjectDetailsViewModelTest { @Test fun `Should handle exception by producing correct state`() = runTest { - coEvery { configManager.getProject() } throws Exception() + coEvery { configManager.getProject() } returns null viewModel = ProjectDetailsViewModel( configManager = configManager, diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt index d4bea2d2a1..8723514f61 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoViewModelTest.kt @@ -428,10 +428,7 @@ class SyncInfoViewModelTest { @Test fun `should start event sync with down sync disabled event sync when logged out`() = runTest { - val mockEndingProject = mockk { - every { state } throws RemoteDbNotSignedInException("stub!") - } - coEvery { configManager.getProject() } returns mockEndingProject + coEvery { configManager.getProject() } returns null createViewModel() viewModel.isPreLogoutUpSync = false diff --git a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCaseTest.kt b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCaseTest.kt index 497af92f1f..9a55f73a5d 100644 --- a/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCaseTest.kt +++ b/feature/dashboard/src/test/java/com/simprints/feature/dashboard/settings/syncinfo/usecase/ObserveSyncInfoUseCaseTest.kt @@ -276,6 +276,16 @@ class ObserveSyncInfoUseCaseTest { assertThat(result.syncInfoSectionRecords.isCounterRecordsToDownloadVisible).isFalse() } + @Test + fun `should handle missing project state correctly in sync info`() = runTest { + coEvery { configManager.getProject() } returns null + createUseCase() + + val result = useCase().first() + + assertThat(result.syncInfoSectionRecords.isCounterRecordsToDownloadVisible).isFalse() + } + @Test fun `should show correct login prompt visibility when not logged in`() = runTest { every { authStore.observeSignedInProjectId() } returns MutableStateFlow("") diff --git a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt index 10e0e0ff36..04d7017473 100644 --- a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt +++ b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt @@ -78,6 +78,10 @@ internal class EnrolLastBiometricViewModel @Inject constructor( val projectConfig = configManager.getProjectConfiguration() val project = configManager.getProject() + if (project == null) { + _finish.send(EnrolLastState.Failed(GENERAL_ERROR, emptyList())) + return@launch + } val modalities = projectConfig.general.modalities val previousLastEnrolmentResult = getPreviousEnrolmentResult(params.steps) @@ -108,7 +112,7 @@ internal class EnrolLastBiometricViewModel @Inject constructor( } private suspend fun displayAddCredentialDialog(scannedCredential: ScannedCredential) { - val project = configManager.getProject() + val project = configManager.getProject() ?: return val decrypted = tokenizationProcessor.decrypt( encrypted = scannedCredential.credential, tokenKeyType = TokenKeyType.ExternalCredential, diff --git a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt index e856e65f5f..c97c5dfa48 100644 --- a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt +++ b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt @@ -160,6 +160,25 @@ internal class EnrolLastBiometricViewModelTest { coVerify(exactly = 0) { enrolmentRecordRepository.performActions(any(), any()) } } + @Test + fun `returns failure when project is not available`() = runTest { + coEvery { configManager.getProject() } returns null + viewModel.enrolBiometric( + createParams( + listOf( + EnrolLastBiometricStepResult.EnrolLastBiometricsResult(null), + ), + ), + isAddingCredential = false, + ) + + val result = viewModel.finish + .test() + .value() + .getContentIfNotHandled() as EnrolLastState.Failed + assertThat(result.errorType).isEqualTo(EnrolLastState.ErrorType.GENERAL_ERROR) + } + @Test fun `returns failure when has previous enrolment without subject`() = runTest { viewModel.enrolBiometric( diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt index dcc05962a5..a93aff11c1 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt @@ -135,7 +135,9 @@ internal class ExternalCredentialScanOcrViewModel @AssistedInject constructor( fun processOcrResultsAndFinish() { updateState { ScanOcrState.Complete } viewModelScope.launch { - val project = configManager.getProject() + // Missing project at this point is impossible, so no special handling required + val project = configManager.getProject() ?: return@launch + val detectedBlock = keepOnlyBestDetectedBlockUseCase(detectedBlocks, ocrDocumentType) val credentialType = detectedBlock.documentType.asExternalCredentialType() val blockBoundingBox = detectedBlock.blockBoundingBox diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModel.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModel.kt index 5fda2a2d09..c6f50ec8f4 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModel.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModel.kt @@ -41,8 +41,7 @@ internal class ExternalCredentialScanQrViewModel @Inject constructor( viewModelScope.launch { val newState = when (value) { null -> ScanQrState.ReadyToScan - else -> { - val project = configManager.getProject() + else -> configManager.getProject()?.let { project -> val qrCodeEncrypted = tokenizationProcessor.encrypt( decrypted = value.asTokenizableRaw(), tokenKeyType = TokenKeyType.ExternalCredential, @@ -54,7 +53,7 @@ internal class ExternalCredentialScanQrViewModel @Inject constructor( qrCode = value.asTokenizableRaw(), qrCodeEncrypted = qrCodeEncrypted, ) - } + } ?: ScanQrState.ReadyToScan } updateState { newState } } diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModel.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModel.kt index a096cc7e6f..1ddee3bb08 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModel.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModel.kt @@ -18,6 +18,7 @@ import com.simprints.feature.externalcredential.screens.search.model.SearchCrede import com.simprints.feature.externalcredential.screens.search.model.SearchState import com.simprints.feature.externalcredential.screens.search.usecase.MatchCandidatesUseCase import com.simprints.feature.externalcredential.usecase.ExternalCredentialEventTrackerUseCase +import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.config.sync.ConfigManager @@ -68,8 +69,10 @@ internal class ExternalCredentialSearchViewModel @AssistedInject constructor( init { viewModelScope.launch { - decryptCredentialToDisplay(scannedCredential.credential) - searchSubjectsLinkedToCredential(scannedCredential.credential) + configManager.getProject()?.let { + decryptCredentialToDisplay(it, scannedCredential.credential) + searchSubjectsLinkedToCredential(it, scannedCredential.credential) + } } } @@ -79,22 +82,23 @@ internal class ExternalCredentialSearchViewModel @AssistedInject constructor( fun confirmCredentialUpdate(updatedCredential: TokenizableString.Raw) { viewModelScope.launch { - val project = configManager.getProject() - val encryptedCredential = tokenizationProcessor.encrypt( - decrypted = updatedCredential, - tokenKeyType = TokenKeyType.ExternalCredential, - project = project, - ) as TokenizableString.Tokenized - updateState { currentState -> - currentState.copy( - isConfirmed = false, - scannedCredential = currentState.scannedCredential.copy( - credential = encryptedCredential, - ), - displayedCredential = updatedCredential, - ) + configManager.getProject()?.let { project -> + val encryptedCredential = tokenizationProcessor.encrypt( + decrypted = updatedCredential, + tokenKeyType = TokenKeyType.ExternalCredential, + project = project, + ) as TokenizableString.Tokenized + updateState { currentState -> + currentState.copy( + isConfirmed = false, + scannedCredential = currentState.scannedCredential.copy( + credential = encryptedCredential, + ), + displayedCredential = updatedCredential, + ) + } + searchSubjectsLinkedToCredential(project, encryptedCredential) } - searchSubjectsLinkedToCredential(encryptedCredential) } } @@ -120,8 +124,10 @@ internal class ExternalCredentialSearchViewModel @AssistedInject constructor( } } - private suspend fun decryptCredentialToDisplay(credential: TokenizableString.Tokenized) { - val project = configManager.getProject() + private fun decryptCredentialToDisplay( + project: Project, + credential: TokenizableString.Tokenized, + ) { val decrypted = tokenizationProcessor.decrypt( encrypted = credential, tokenKeyType = TokenKeyType.ExternalCredential, @@ -130,9 +136,11 @@ internal class ExternalCredentialSearchViewModel @AssistedInject constructor( updateState { it.copy(displayedCredential = decrypted) } } - private suspend fun searchSubjectsLinkedToCredential(credential: TokenizableString.Tokenized) { + private suspend fun searchSubjectsLinkedToCredential( + project: Project, + credential: TokenizableString.Tokenized, + ) { updateState { it.copy(searchState = SearchState.Searching) } - val project = configManager.getProject() val candidates = enrolmentRecordRepository.load(SubjectQuery(projectId = project.id, externalCredential = credential)) val startTime = timeHelper.now() diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCase.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCase.kt index 7be461473d..c388506cb1 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCase.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCase.kt @@ -83,7 +83,7 @@ internal class ExternalCredentialEventTrackerUseCase @Inject constructor( } private suspend fun calculateOcrErrorCount(scannedCredential: ScannedCredential): Int { - val project = configManager.getProject() + val project = configManager.getProject() ?: return 0 val actualCredentialRaw = tokenizationProcessor.decrypt( scannedCredential.credential, TokenKeyType.ExternalCredential, diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCase.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCase.kt index ae6e58ab15..fd9a461c04 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCase.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCase.kt @@ -53,9 +53,10 @@ class ResetExternalCredentialsInSessionUseCase @Inject() constructor( emptyList() } - val project = configManager.getProject() - val updateActions = credentialsToRemove + credentialsToAdd - enrolmentRecordRepository.performActions(updateActions, project) + configManager.getProject()?.let { project -> + val updateActions = credentialsToRemove + credentialsToAdd + enrolmentRecordRepository.performActions(updateActions, project) + } // Since we are potentially linking the credentials to a new subject, previous updates must be deleted with(sessionCoroutineScope) { eventRepository.deleteEvents(enrolmentUpdateEvents) } diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModelTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModelTest.kt index 79135aecf2..81b532a21b 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModelTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanqr/ExternalCredentialScanQrViewModelTest.kt @@ -68,6 +68,17 @@ internal class ExternalCredentialScanQrViewModelTest { assertThat(observer.value()).isEqualTo(ScanQrState.ReadyToScan) } + @Test + fun `updateCapturedValue with missing project`() = runTest { + val observer = viewModel.stateLiveData.test() + val value = "value" + + coEvery { configManager.getProject() } returns null + viewModel.updateCapturedValue(value) + + assertThat(observer.value()).isEqualTo(ScanQrState.ReadyToScan) + } + @Test fun `updateCapturedValue with non-null sets QrCodeCaptured`() = runTest { val observer = viewModel.stateLiveData.test() diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModelTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModelTest.kt index ed6eca7885..11a3fb647d 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModelTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/search/ExternalCredentialSearchViewModelTest.kt @@ -85,7 +85,6 @@ internal class ExternalCredentialSearchViewModelTest { coEvery { configManager.getProjectConfiguration() } returns projectConfig coJustRun { eventsTracker.saveSearchEvent(any(), any(), any()) } coJustRun { eventsTracker.saveConfirmation(any(), any()) } - viewModel = createViewModel() } fun createViewModel() = ExternalCredentialSearchViewModel( @@ -99,6 +98,17 @@ internal class ExternalCredentialSearchViewModelTest { eventsTracker = eventsTracker, ) + @Test + fun `initial state handles missing project`() = runTest { + clearMocks(configManager) + coEvery { configManager.getProject() } returns null + + viewModel = createViewModel() + + verify(exactly = 0) { tokenizationProcessor.decrypt(any(), any(), any()) } + coVerify(exactly = 0) { enrolmentRecordRepository.load(any()) } + } + @Test fun `initial state starts searching when credential not found`() = runTest { val decryptedCredential = mockk() @@ -147,6 +157,17 @@ internal class ExternalCredentialSearchViewModelTest { assertThat(observer.value()?.isConfirmed).isFalse() } + @Test + fun `confirmCredentialUpdate handles missing project`() = runTest { + clearMocks(configManager) // reset default behaviour + coEvery { configManager.getProject() } returns null + + viewModel = createViewModel() + viewModel.confirmCredentialUpdate("".asTokenizableRaw()) + + coVerify(exactly = 0) { tokenizationProcessor.encrypt(any(), any(), any()) } + } + @Test fun `confirmCredentialUpdate triggers new search and encrypts credential`() = runTest { val decryptedCredential = mockk() @@ -168,14 +189,14 @@ internal class ExternalCredentialSearchViewModelTest { @Test fun `getButtonTextResource returns null when searching`() = runTest { - val result = viewModel.getButtonTextResource(SearchState.Searching, FlowType.IDENTIFY) + val result = createViewModel().getButtonTextResource(SearchState.Searching, FlowType.IDENTIFY) assertThat(result).isNull() } @Test fun `getButtonTextResource returns 'enrol anyway' when credential linked and flow is ENROL`() = runTest { val searchState = SearchState.CredentialLinked(emptyList()) - val result = viewModel.getButtonTextResource(searchState, FlowType.ENROL) + val result = createViewModel().getButtonTextResource(searchState, FlowType.ENROL) assertThat(result).isEqualTo(IDR.string.mfid_action_enrol_anyway) } @@ -184,37 +205,34 @@ internal class ExternalCredentialSearchViewModelTest { coEvery { enrolmentRecordRepository.load(any()) } returns emptyList() val searchState = SearchState.CredentialLinked(listOf(candidateMatch)) every { candidateMatch.isVerificationSuccessful } returns false - val result = viewModel.getButtonTextResource(searchState, FlowType.IDENTIFY) + val result = createViewModel().getButtonTextResource(searchState, FlowType.IDENTIFY) assertThat(result).isEqualTo(IDR.string.mfid_action_continue) } @Test fun `getButtonTextResource returns 'enrol' when credential not found and flow is ENROL`() = runTest { - val result = viewModel.getButtonTextResource(SearchState.CredentialNotFound, FlowType.ENROL) + val result = createViewModel().getButtonTextResource(SearchState.CredentialNotFound, FlowType.ENROL) assertThat(result).isEqualTo(IDR.string.mfid_action_enrol) } @Test fun `getKeyBoardInputType returns number for NHIS card`() = runTest { every { mockScannedCredential.credentialType } returns ExternalCredentialType.NHISCard - viewModel = createViewModel() - val result = viewModel.getKeyBoardInputType() + val result = createViewModel().getKeyBoardInputType() assertThat(result).isEqualTo(InputType.TYPE_CLASS_NUMBER) } @Test fun `getKeyBoardInputType returns text for Ghana ID card`() = runTest { every { mockScannedCredential.credentialType } returns ExternalCredentialType.GhanaIdCard - viewModel = createViewModel() - val result = viewModel.getKeyBoardInputType() + val result = createViewModel().getKeyBoardInputType() assertThat(result).isEqualTo(InputType.TYPE_CLASS_TEXT) } @Test fun `getKeyBoardInputType returns text for QR code`() = runTest { every { mockScannedCredential.credentialType } returns ExternalCredentialType.QRCode - viewModel = createViewModel() - val result = viewModel.getKeyBoardInputType() + val result = createViewModel().getKeyBoardInputType() assertThat(result).isEqualTo(InputType.TYPE_CLASS_TEXT) } @@ -253,6 +271,7 @@ internal class ExternalCredentialSearchViewModelTest { every { matchResults } returns listOf(candidateMatch) } } + viewModel = createViewModel() viewModel.finish(state) val finishEvent = viewModel.finishEvent.value?.peekContent() assertThat(finishEvent).isNotNull() @@ -263,7 +282,7 @@ internal class ExternalCredentialSearchViewModelTest { @Test fun `trackRecapture sends confirmation event`() = runTest { - viewModel.trackRecapture() + createViewModel().trackRecapture() coVerify { eventsTracker.saveConfirmation(any(), any()) } } @@ -276,7 +295,7 @@ internal class ExternalCredentialSearchViewModelTest { every { matchResults } returns listOf(candidateMatch) } } - viewModel.finish(state) + createViewModel().finish(state) coVerify { eventsTracker.saveConfirmation(any(), any()) } } diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCaseTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCaseTest.kt index 53d2a6998d..9a910e0bc1 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCaseTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ExternalCredentialEventTrackerUseCaseTest.kt @@ -88,6 +88,21 @@ class ExternalCredentialEventTrackerUseCaseTest { } } + @Test + fun `saveCaptureEvents should handle missing project in capture events`() = runTest { + clearMocks(configManager) + coEvery { configManager.getProject() } returns null + + val scannedCredential = makeScannedCredential(ExternalCredentialType.QRCode) + useCase.saveCaptureEvents(START_TIME, SUBJECT_ID, scannedCredential, SELECTION_ID) + + val captureEventSlot = slot() + coVerify(exactly = 1) { eventRepository.addOrUpdateEvent(capture(captureEventSlot)) } + with(captureEventSlot.captured) { + assertThat(payload.ocrErrorCount).isEqualTo(0) + } + } + @Test fun `saveCaptureEvents should correctly calculate length for NHISCard`() = runTest { val scannedCredential = makeScannedCredential(ExternalCredentialType.NHISCard) diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCaseTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCaseTest.kt index bcfda0ce1e..4eaf657dd3 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCaseTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/usecase/ResetExternalCredentialsInSessionUseCaseTest.kt @@ -95,6 +95,17 @@ internal class ResetExternalCredentialsInSessionUseCaseTest { assertThat(addedCredential.type).isEqualTo(CREDENTIAL_TYE) } + @Test + fun `handles missing project`() = runTest { + clearMocks(configManager) + coEvery { configManager.getProject() } returns null + coEvery { eventRepository.getEventsInCurrentSession() } returns listOf() + + useCase(scannedCredential, SUBJECT_ID) + + coVerify(exactly = 0) { enrolmentRecordRepository.performActions(any(), any()) } + } + @Test fun `removes correct external credential to subject`() = runTest { coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt index 2ae983f020..090dfc0d1e 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/LoginCheckViewModel.kt @@ -134,9 +134,9 @@ class LoginCheckViewModel @Inject internal constructor( } private suspend fun validateProjectAndProceed(actionRequest: ActionRequest) { - when (configManager.getProject().state) { + when (configManager.getProject()?.state) { + null, ProjectState.PROJECT_ENDING -> _showAlert.send(LoginCheckError.PROJECT_ENDING) ProjectState.PROJECT_PAUSED -> _showAlert.send(LoginCheckError.PROJECT_PAUSED) - ProjectState.PROJECT_ENDING -> _showAlert.send(LoginCheckError.PROJECT_ENDING) ProjectState.PROJECT_ENDED -> startSignInAttempt(actionRequest) ProjectState.RUNNING -> proceedWithAction(actionRequest) } diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt index 658d15a0fa..f7ddc7a49b 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/LoginCheckViewModelTest.kt @@ -277,7 +277,7 @@ internal class LoginCheckViewModelTest { @Test fun `Triggers alert if project is paused`() = runTest { coEvery { isUserSignedInUseCase.invoke(any()) } returns IsUserSignedInUseCase.SignedInState.SIGNED_IN - coEvery { configManager.getProject().state } returns ProjectState.PROJECT_PAUSED + coEvery { configManager.getProject()?.state } returns ProjectState.PROJECT_PAUSED viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) @@ -286,10 +286,22 @@ internal class LoginCheckViewModelTest { .assertValue { it.peekContent() == LoginCheckError.PROJECT_PAUSED } } + @Test + fun `Triggers alert if project is not available`() = runTest { + coEvery { configManager.getProject() } returns null + coEvery { isUserSignedInUseCase.invoke(any()) } returns IsUserSignedInUseCase.SignedInState.SIGNED_IN + + viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) + + viewModel.showAlert + .test() + .assertValue { it.peekContent() == LoginCheckError.PROJECT_ENDING } + } + @Test fun `Triggers alert if project is ending`() = runTest { coEvery { isUserSignedInUseCase.invoke(any()) } returns IsUserSignedInUseCase.SignedInState.SIGNED_IN - coEvery { configManager.getProject().state } returns ProjectState.PROJECT_ENDING + coEvery { configManager.getProject()?.state } returns ProjectState.PROJECT_ENDING viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) @@ -301,7 +313,7 @@ internal class LoginCheckViewModelTest { @Test fun `Triggers login attempt if project has ended`() = runTest { coEvery { isUserSignedInUseCase.invoke(any()) } returns IsUserSignedInUseCase.SignedInState.SIGNED_IN - coEvery { configManager.getProject().state } returns ProjectState.PROJECT_ENDED + coEvery { configManager.getProject()?.state } returns ProjectState.PROJECT_ENDED viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) @@ -313,7 +325,7 @@ internal class LoginCheckViewModelTest { @Test fun `Correctly handles if user signed in active project`() = runTest { coEvery { isUserSignedInUseCase.invoke(any()) } returns IsUserSignedInUseCase.SignedInState.SIGNED_IN - coEvery { configManager.getProject().state } returns ProjectState.RUNNING + coEvery { configManager.getProject()?.state } returns ProjectState.RUNNING viewModel.validateSignInAndProceed(ActionFactory.getIdentifyRequest()) diff --git a/feature/matcher/src/main/java/com/simprints/matcher/screen/MatchViewModel.kt b/feature/matcher/src/main/java/com/simprints/matcher/screen/MatchViewModel.kt index 1b305a9498..71c77c06ab 100644 --- a/feature/matcher/src/main/java/com/simprints/matcher/screen/MatchViewModel.kt +++ b/feature/matcher/src/main/java/com/simprints/matcher/screen/MatchViewModel.kt @@ -57,7 +57,7 @@ internal class MatchViewModel @Inject constructor( is FaceConfiguration.BioSdk -> faceMatcher else -> fingerprintMatcher } - val project = configManager.getProject() + val project = configManager.getProject() ?: return@launch val decisionPolicy = getDecisionPolicy(params) candidatesLoaded = 0 diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt index 791ffa6fce..291f9ae6dd 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt @@ -238,7 +238,7 @@ internal class OrchestratorViewModel @Inject constructor( private fun buildAppResponse() = viewModelScope.launch { val projectConfiguration = configManager.getProjectConfiguration() - val project = configManager.getProject() + val project = configManager.getProject() ?: return@launch val appResponse = appResponseBuilder( projectConfiguration = projectConfiguration, request = actionRequest, diff --git a/feature/select-subject/src/main/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModel.kt b/feature/select-subject/src/main/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModel.kt index 5bffdb41f0..42026cda90 100644 --- a/feature/select-subject/src/main/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModel.kt +++ b/feature/select-subject/src/main/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModel.kt @@ -98,7 +98,7 @@ internal class SelectSubjectViewModel @AssistedInject constructor( ): SelectSubjectState.CredentialDialogDisplayed? { if (scannedCredential == null) return null val credential = scannedCredential.credential - val project = configManager.getProject() + val project = configManager.getProject() ?: return null val alreadyLinkedSubject = enrolmentRecordRepository .load( SubjectQuery( diff --git a/feature/select-subject/src/test/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModelTest.kt b/feature/select-subject/src/test/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModelTest.kt index fe842bdc6b..a8039d6b8f 100644 --- a/feature/select-subject/src/test/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModelTest.kt +++ b/feature/select-subject/src/test/java/com/simprints/feature/selectsubject/screen/SelectSubjectViewModelTest.kt @@ -51,6 +51,9 @@ internal class SelectSubjectViewModelTest { @MockK lateinit var configManager: ConfigManager + @MockK + lateinit var project: Project + @MockK lateinit var resetScannedCredentialsInSession: ResetExternalCredentialsInSessionUseCase @@ -223,6 +226,30 @@ internal class SelectSubjectViewModelTest { assertThat(result?.isSubjectIdSaved).isTrue() } + @Test + fun `does not display credential dialog when project not availalbe`() = runTest { + val scannedCredential = mockk(relaxed = true) + val displayedCredential = mockk(relaxed = true) + val repositoryResponse = listOf(mockk { every { subjectId } returns "not_this_subject_id" }) + setupCredentialState( + displayedCredential, + repositoryResponse = repositoryResponse, + configuredProject = null, + ) + + val viewModel = createViewModel( + params = selectSubjectParams.copy( + scannedCredential = scannedCredential, + ), + ) + + val result = viewModel.finish + .test() + .value() + .getContentIfNotHandled() + assertThat(result?.isSubjectIdSaved).isTrue() + } + @Test fun `finishes without credential when no credential is scanned`() = runTest { coEvery { authStore.isProjectIdSignedIn(PROJECT_ID) } returns true @@ -240,19 +267,18 @@ internal class SelectSubjectViewModelTest { private fun setupCredentialState( displayedCredential: TokenizableString.Raw, repositoryResponse: List, + configuredProject: Project? = project, ) { - val project = mockk(relaxed = true) - coEvery { authStore.isProjectIdSignedIn(PROJECT_ID) } returns true coEvery { authStore.signedInProjectId } returns PROJECT_ID - coEvery { configManager.getProject() } returns project - every { project.id } returns PROJECT_ID + every { project?.id } returns PROJECT_ID + coEvery { configManager.getProject() } returns configuredProject coEvery { enrolmentRecordRepository.load(any()) } returns repositoryResponse coEvery { tokenizationProcessor.decrypt( encrypted = any(), tokenKeyType = TokenKeyType.ExternalCredential, - project = project, + project = any(), ) } returns displayedCredential } diff --git a/fingerprint/infra/image-distortion-config/src/main/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepo.kt b/fingerprint/infra/image-distortion-config/src/main/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepo.kt index 43bf17e85e..1366c77b8d 100644 --- a/fingerprint/infra/image-distortion-config/src/main/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepo.kt +++ b/fingerprint/infra/image-distortion-config/src/main/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepo.kt @@ -34,7 +34,11 @@ internal class ImageDistortionConfigRemoteRepo @Inject constructor( return false } - val bucketUrl = configManager.getProject().imageBucket + val bucketUrl = configManager.getProject()?.imageBucket + if (bucketUrl == null) { + log("bucketUrl projectId is empty") + return false + } val rootRef = FirebaseStorage .getInstance( diff --git a/fingerprint/infra/image-distortion-config/src/test/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepoTest.kt b/fingerprint/infra/image-distortion-config/src/test/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepoTest.kt index 6c326d89c2..e215d6e26d 100644 --- a/fingerprint/infra/image-distortion-config/src/test/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepoTest.kt +++ b/fingerprint/infra/image-distortion-config/src/test/java/com/simprints/fingerprint/infra/imagedistortionconfig/remote/ImageDistortionConfigRemoteRepoTest.kt @@ -51,10 +51,22 @@ class ImageDistortionConfigRemoteRepoTest { Truth.assertThat(result).isFalse() } + @Test + fun `uploadConfig returns false when project is missing`() = runTest { + every { authStore.getCoreApp().options.projectId } returns "firebaseProject" + every { authStore.signedInProjectId } returns PROJECT_ID + coEvery { configManager.getProject() } returns null + + val result = repo.uploadConfig(UN20_SERIAL_NUMBER, byteArrayOf()) + + Truth.assertThat(result).isFalse() + } + @Test fun `uploadConfig returns true when file is already uploaded`() = runTest { val mockFileRef: StorageReference = mockk(relaxed = true) setupMockFirebase(mockFileRef) + coEvery { configManager.getProject()?.imageBucket } returns BUCKET_URL coEvery { mockFileRef.metadata.await() } returns mockk() val result = repo.uploadConfig(UN20_SERIAL_NUMBER, byteArrayOf()) @@ -68,6 +80,7 @@ class ImageDistortionConfigRemoteRepoTest { every { task.isSuccessful } returns true } setupMockFirebase(mockFileRef) + coEvery { configManager.getProject()?.imageBucket } returns BUCKET_URL coEvery { mockFileRef.metadata.await() } throws mockk { every { errorCode } returns StorageException.ERROR_OBJECT_NOT_FOUND } @@ -87,6 +100,7 @@ class ImageDistortionConfigRemoteRepoTest { } setupMockFirebase(mockFileRef) + coEvery { configManager.getProject()?.imageBucket } returns BUCKET_URL coEvery { mockFileRef.putBytes(any()).await() } returns mockUploadTask coEvery { mockFileRef.metadata.await() } throws mockk { every { errorCode } returns StorageException.ERROR_OBJECT_NOT_FOUND @@ -97,16 +111,14 @@ class ImageDistortionConfigRemoteRepoTest { } private fun setupMockFirebase(fileRef: StorageReference) { - val bucketUrl = "bucket123" every { authStore.getCoreApp().options.projectId } returns "firebaseProject" every { authStore.signedInProjectId } returns PROJECT_ID - coEvery { configManager.getProject().imageBucket } returns bucketUrl val mockRootRef: StorageReference = mockk() mockkStatic(FirebaseStorage::class) mockkStatic("kotlinx.coroutines.tasks.TasksKt") every { - FirebaseStorage.getInstance(any(), bucketUrl).reference + FirebaseStorage.getInstance(any(), BUCKET_URL).reference } returns mockRootRef every { mockRootRef.child("$PROJECTS_FOLDER/$PROJECT_ID/$UN20_MODULES_FOLDER/$UN20_SERIAL_NUMBER/$FILE_NAME") } returns fileRef } @@ -116,6 +128,7 @@ class ImageDistortionConfigRemoteRepoTest { private const val UN20_MODULES_FOLDER = "un20modules" private const val UN20_SERIAL_NUMBER = "serial123" private const val PROJECT_ID = "project123" + private const val BUCKET_URL = "bucket123" private const val FILE_NAME = "calibration.dat" } } 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 15c792453d..f3e00c2bcb 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 @@ -37,12 +37,19 @@ class ConfigManager @Inject constructor( } } - suspend fun getProject(): Project = try { + suspend fun getProject(): Project? = try { configRepository.getProject() - } catch (ex: NoSuchElementException) { - val signedProjectId = authStore.signedInProjectId.takeUnless { it.isEmpty() } - ?: throw ex // re-throw the initial exception since there is no way to refresh the data - refreshProject(signedProjectId).project + } catch (_: NoSuchElementException) { + val projectId = authStore.signedInProjectId + if (projectId.isEmpty()) { + null + } else { + try { + refreshProject(projectId).project + } catch (_: Exception) { + null + } + } } suspend fun getProjectConfiguration(): ProjectConfiguration { 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 4ff97cbbab..d041e68989 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 @@ -91,12 +91,13 @@ class ConfigManagerTest { coVerify(exactly = 1) { configRepository.refreshProject(PROJECT_ID) } } - @Test(expected = NoSuchElementException::class) - fun `getProject should throw when cannot get from local and logged out`() = runTest { + @Test + fun `getProject should returns null when refresh fails`() = runTest { every { authStore.signedInProjectId } returns "" coEvery { configRepository.getProject() } throws NoSuchElementException() + coEvery { configRepository.refreshProject(any()) } throws NoSuchElementException() - configManager.getProject() + assertThat(configManager.getProject()).isNull() } @Test diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/EventDownSyncScopeRepository.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/EventDownSyncScopeRepository.kt index 6788d385ef..574f967cee 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/EventDownSyncScopeRepository.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/EventDownSyncScopeRepository.kt @@ -59,13 +59,12 @@ internal class EventDownSyncScopeRepository @Inject constructor( throw MissingArgumentForDownSyncScopeException("UserId required") } return when (possibleUserId) { - is TokenizableString.Raw -> + is TokenizableString.Raw -> configManager.getProject()?.let { project -> tokenizationProcessor - .encrypt( - decrypted = possibleUserId, - tokenKeyType = TokenKeyType.AttendantId, - project = configManager.getProject(), - ).value + .encrypt(decrypted = possibleUserId, tokenKeyType = TokenKeyType.AttendantId, project = project) + .value + } ?: possibleUserId.value + is TokenizableString.Tokenized -> possibleUserId.value } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt index 93703db1d7..0c62aa1d98 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorker.kt @@ -161,7 +161,7 @@ class EventSyncMasterWorker @AssistedInject internal constructor( } private suspend fun isEventDownSyncAllowed(configuration: ProjectConfiguration): Boolean { - val isProjectPaused = configManager.getProject().state == ProjectState.PROJECT_PAUSED + val isProjectPaused = configManager.getProject()?.state == ProjectState.PROJECT_PAUSED val isSimprintsDownSyncEnabled = configuration.isSimprintsEventDownSyncAllowed() val isCommCareDownSyncEnabled = configuration.isCommCareEventDownSyncAllowed() diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt index 1bf724e5be..01184887ff 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt @@ -70,7 +70,7 @@ internal class EventUpSyncTask @Inject constructor( } } - val project = configManager.getProject() + val project = configManager.getProject() ?: throw IllegalStateException("Project is missing") val config = configManager.getProjectConfiguration() var lastOperation = operation.copy() var isUsefulUpload = false diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt index c5301249f0..0aba5b5f20 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/master/EventSyncMasterWorkerTest.kt @@ -353,7 +353,7 @@ internal class EventSyncMasterWorkerTest { projectState: ProjectState, syncConfig: Frequency, ): ListenableWorker.Result { - coEvery { configManager.getProject().state } returns projectState + coEvery { configManager.getProject()?.state } returns projectState coEvery { configManager.getProjectConfiguration() } returns mockk { every { projectId } returns "projectId" every { synchronization } returns mockk { diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt index 3989dd8ffc..2650b0d3e1 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt @@ -42,7 +42,6 @@ import com.simprints.infra.eventsync.status.up.EventUpSyncScopeRepository import com.simprints.infra.eventsync.status.up.domain.EventUpSyncOperation import com.simprints.infra.eventsync.status.up.domain.EventUpSyncOperation.UpSyncState import com.simprints.infra.network.exceptions.NetworkConnectionException -import com.simprints.testtools.common.syntax.assertThrows import io.mockk.* import io.mockk.impl.annotations.MockK import kotlinx.coroutines.flow.toList @@ -324,11 +323,15 @@ internal class EventUpSyncTaskTest { assertThat(capturedRequest.captured).hasSize(5) } - @Test + @Test(expected = TryToUploadEventsForNotSignedProject::class) fun `should not upload sessions for not signed project`() = runTest { - assertThrows { - eventUpSyncTask.upSync(EventUpSyncOperation(randomUUID()), eventScope).toList() - } + eventUpSyncTask.upSync(EventUpSyncOperation(randomUUID()), eventScope).toList() + } + + @Test(expected = IllegalStateException::class) + fun `should not upload sessions if missing project`() = runTest { + coEvery { configManager.getProject() } returns null + eventUpSyncTask.upSync(operation, eventScope).toList() } @Test diff --git a/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt b/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt index 81d7d6d6de..82c074ea8a 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt @@ -43,7 +43,11 @@ internal class FirebaseSampleUploader @Inject constructor( var allImagesUploaded = true Simber.i("Starting sample upload to Firebase storage", tag = SAMPLE_UPLOAD) - val bucketUrl = configManager.getProject().imageBucket + val bucketUrl = configManager.getProject()?.imageBucket + if (bucketUrl == null) { + Simber.i("Bucket url is null", tag = SAMPLE_UPLOAD) + return false + } val rootRef = FirebaseStorage .getInstance(firebaseApp, bucketUrl) .reference diff --git a/infra/images/src/test/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploaderTest.kt b/infra/images/src/test/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploaderTest.kt index 4122f0b5a6..801e9df6dc 100644 --- a/infra/images/src/test/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploaderTest.kt +++ b/infra/images/src/test/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploaderTest.kt @@ -91,7 +91,7 @@ class FirebaseSampleUploaderTest { } @Test - fun `null project returns failed upload`() = runTest { + fun `null project ID returns failed upload`() = runTest { every { authStore.getLegacyAppFallback().options.projectId } returns null val result = remoteDataSource.uploadAllSamples(PROJECT_ID) @@ -100,7 +100,7 @@ class FirebaseSampleUploaderTest { } @Test - fun `empty project returns failed upload`() = runTest { + fun `empty project ID returns failed upload`() = runTest { every { authStore.getLegacyAppFallback().options.projectId } returns "" val result = remoteDataSource.uploadAllSamples(PROJECT_ID) @@ -108,6 +108,16 @@ class FirebaseSampleUploaderTest { assertThat(result).isFalse() } + @Test + fun `null project returns failed upload`() = runTest { + every { authStore.getLegacyAppFallback().options.projectId } returns "projectId" + coEvery { configManager.getProject() } returns null + + val result = remoteDataSource.uploadAllSamples(PROJECT_ID) + + assertThat(result).isFalse() + } + @Test fun `when no samples to upload returns success`() = runTest { setupProjectConfig() @@ -207,7 +217,7 @@ class FirebaseSampleUploaderTest { } private fun setupProjectConfig() { - coEvery { configManager.getProject().imageBucket } returns "gs://`simprints-dev.appspot.com" + coEvery { configManager.getProject()?.imageBucket } returns "gs://`simprints-dev.appspot.com" every { authStore.getLegacyAppFallback().options.projectId } returns "projectId" every { authStore.signedInProjectId } returns "projectId" }