From cf711b656dae207da82ab403ea8459efca4a375e Mon Sep 17 00:00:00 2001 From: Andre Rosado Date: Tue, 6 Jan 2026 15:54:05 +0000 Subject: [PATCH] Checking if migration should be called after unlock, removing the force sync call when user should migrate on SyncManager and added a new Sync call on the MigrateToMyItemsVM --- .../vault/manager/VaultSyncManagerImpl.kt | 10 ++---- .../MigrateToMyItemsViewModel.kt | 9 +++++ .../vault/manager/VaultSyncManagerTest.kt | 35 +++++++++++++++++++ .../MigrateToMyItemsViewModelTest.kt | 6 ++++ 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerImpl.kt index 0a6ba37fd7..367d9fa3aa 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerImpl.kt @@ -406,6 +406,8 @@ class VaultSyncManagerImpl( .onStart { mutableDecryptCipherListResultFlow.updateToPendingOrLoading() } .map { vaultLockManager.waitUntilUnlocked(userId = userId) + // Verify migration state after unlock with the cipher list from disk + vaultMigrationManager.verifyAndUpdateMigrationState(it) vaultSdkSource .decryptCipherListWithFailures( userId = userId, @@ -413,14 +415,6 @@ class VaultSyncManagerImpl( ) .fold( onSuccess = { result -> - // We need to be sure the data on device is updated - // before sending the user to the migration screen - if (vaultMigrationManager.shouldMigrateVault { - result.successes.any { it.organizationId == null } - } - ) { - sync(forced = true) - } DataState.Loaded( result.copy(successes = result.successes.sortAlphabetically()), ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModel.kt index 10812d2a3a..7245a41101 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModel.kt @@ -9,6 +9,8 @@ import com.bitwarden.ui.util.Text import com.bitwarden.ui.util.asText import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent +import com.x8bit.bitwarden.data.vault.manager.VaultMigrationManager +import com.x8bit.bitwarden.data.vault.manager.VaultSyncManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn @@ -26,6 +28,8 @@ private const val KEY_STATE = "state" @HiltViewModel class MigrateToMyItemsViewModel @Inject constructor( private val organizationEventManager: OrganizationEventManager, + val vaultMigrationManager: VaultMigrationManager, + val vaultSyncManager: VaultSyncManager, savedStateHandle: SavedStateHandle, ) : BaseViewModel( initialState = savedStateHandle[KEY_STATE] ?: run { @@ -42,6 +46,11 @@ class MigrateToMyItemsViewModel @Inject constructor( stateFlow .onEach { savedStateHandle[KEY_STATE] = it } .launchIn(viewModelScope) + viewModelScope.launch { + // We need to ensure we have the most recent data. No need to do anything + // RootNavViewModel will take care of navigating if needed + vaultSyncManager.syncForResult(forced = true) + } } override fun handleAction(action: MigrateToMyItemsAction) { diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerTest.kt index fe50488a16..042b07cbbc 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultSyncManagerTest.kt @@ -497,6 +497,41 @@ class VaultSyncManagerTest { } } + @Test + fun `vault unlock should trigger migration state verification with cipher list from disk`() = + runTest { + fakeAuthDiskSource.userState = MOCK_USER_STATE + val userId = "mockId-1" + val mockCipherList = listOf(createMockCipher(number = 1)) + val mockEncryptedCipherList = mockCipherList.toEncryptedSdkCipherList() + val mockDecryptCipherListResult = createMockDecryptCipherListResult(number = 1) + val mutableCiphersStateFlow = + bufferedMutableSharedFlow>(replay = 1) + setupVaultDiskSourceFlows(ciphersFlow = mutableCiphersStateFlow) + coEvery { + vaultSdkSource.decryptCipherListWithFailures( + userId = userId, + cipherList = mockEncryptedCipherList, + ) + } returns mockDecryptCipherListResult.asSuccess() + + vaultSyncManager + .decryptCipherListResultStateFlow + .test { + assertEquals(DataState.Loading, awaitItem()) + mutableCiphersStateFlow.tryEmit(mockCipherList) + + expectNoEvents() + setVaultToUnlocked(userId = userId) + + assertEquals(DataState.Loaded(mockDecryptCipherListResult), awaitItem()) + + verify(exactly = 1) { + vaultMigrationManager.verifyAndUpdateMigrationState(mockCipherList) + } + } + } + @Suppress("MaxLineLength") @Test fun `collectionsStateFlow should emit decrypted list of collections when decryptCollectionList succeeds`() = diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt index d64d1227ae..046701e472 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt @@ -7,6 +7,8 @@ import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.asText import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent +import com.x8bit.bitwarden.data.vault.manager.VaultMigrationManager +import com.x8bit.bitwarden.data.vault.manager.VaultSyncManager import io.mockk.every import io.mockk.just import io.mockk.mockk @@ -26,6 +28,8 @@ class MigrateToMyItemsViewModelTest : BaseViewModelTest() { private val mockOrganizationEventManager: OrganizationEventManager = mockk { every { trackEvent(any()) } just runs } + private val mockVaultMigrationManager: VaultMigrationManager = mockk(relaxed = true) + private val mockVaultSyncManager: VaultSyncManager = mockk(relaxed = true) @BeforeEach fun setup() { @@ -176,6 +180,8 @@ class MigrateToMyItemsViewModelTest : BaseViewModelTest() { ) return MigrateToMyItemsViewModel( organizationEventManager = mockOrganizationEventManager, + vaultMigrationManager = mockVaultMigrationManager, + vaultSyncManager = mockVaultSyncManager, savedStateHandle = savedStateHandle, ) }