diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt index f652a381c1..0ac95440fb 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt @@ -46,6 +46,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository +import com.x8bit.bitwarden.data.platform.util.userFriendlyMessage import com.x8bit.bitwarden.data.vault.manager.model.GetCipherResult import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.model.ArchiveCipherResult @@ -1190,7 +1191,8 @@ class VaultViewModel @Inject constructor( isPremium = state.isPremium, hasMasterPassword = state.hasMasterPassword, errorTitle = BitwardenString.an_error_has_occurred.asText(), - errorMessage = BitwardenString.generic_error_message.asText(), + errorMessage = vaultData.error.userFriendlyMessage?.asText() + ?: BitwardenString.generic_error_message.asText(), isRefreshing = false, restrictItemTypesPolicyOrgIds = state.restrictItemTypesPolicyOrgIds, isArchiveEnabled = state.isArchiveEnabled, diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt index bf67396d34..edd2076376 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt @@ -7,6 +7,7 @@ import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.data.datasource.disk.model.FlightRecorderDataSet import com.bitwarden.data.repository.model.Environment import com.bitwarden.data.repository.util.baseIconUrl +import com.bitwarden.network.exception.CookieRedirectException import com.bitwarden.network.model.PolicyTypeJson import com.bitwarden.network.model.SyncResponseJson import com.bitwarden.network.model.createMockPolicy @@ -1387,6 +1388,31 @@ class VaultViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `vaultDataStateFlow Error with CookieRedirectException should show user-friendly message`() = + runTest { + mutableVaultDataStateFlow.tryEmit( + value = DataState.Error( + error = CookieRedirectException(hostname = "example.com"), + ), + ) + + val viewModel = createViewModel() + + assertEquals( + createMockVaultState( + viewState = VaultState.ViewState.Error( + message = ( + "Your request was interrupted because the app needed to " + + "re-authenticate. Please try again." + ).asText(), + ), + ), + viewModel.stateFlow.value, + ) + } + @Suppress("MaxLineLength") @Test fun `vaultDataStateFlow Error with items should update state to Content and show an error dialog`() = @@ -1455,6 +1481,77 @@ class VaultViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `vaultDataStateFlow Error with CookieRedirectException with items should show user-friendly error dialog`() = + runTest { + mutableVaultDataStateFlow.tryEmit( + value = DataState.Error( + error = CookieRedirectException(hostname = "example.com"), + data = VaultData( + decryptCipherListResult = createMockDecryptCipherListResult( + number = 1, + successes = listOf(createMockCipherListView(number = 1)), + ), + collectionViewList = listOf(createMockCollectionView(number = 1)), + folderViewList = listOf(createMockFolderView(number = 1)), + sendViewList = listOf(createMockSendView(number = 1)), + ), + ), + ) + + val viewModel = createViewModel() + + assertEquals( + createMockVaultState( + viewState = VaultState.ViewState.Content( + loginItemsCount = 1, + cardItemsCount = 0, + identityItemsCount = 0, + secureNoteItemsCount = 0, + favoriteItems = listOf(), + folderItems = listOf( + VaultState.ViewState.FolderItem( + id = "mockId-1", + name = "mockName-1".asText(), + itemCount = 1, + ), + VaultState.ViewState.FolderItem( + id = null, + name = BitwardenString.folder_none.asText(), + itemCount = 0, + ), + ), + collectionItems = listOf( + VaultState.ViewState.CollectionItem( + id = "mockId-1", + name = "mockName-1", + itemCount = 1, + ), + ), + noFolderItems = listOf(), + trashItemsCount = 0, + totpItemsCount = 1, + itemTypesCount = 5, + sshKeyItemsCount = 0, + archivedItemsCount = 0, + archiveEnabled = true, + archiveSubText = null, + archiveEndIcon = null, + showCardGroup = true, + ), + dialog = VaultState.DialogState.Error( + title = BitwardenString.an_error_has_occurred.asText(), + message = ( + "Your request was interrupted because the app needed to " + + "re-authenticate. Please try again." + ).asText(), + ), + ), + viewModel.stateFlow.value, + ) + } + @Suppress("MaxLineLength") @Test fun `vaultDataStateFlow Error with empty items should update state to NoItems and show an error dialog`() =