diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialStoreImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialStoreImpl.kt index fa85cbd60b..04f724849a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialStoreImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialStoreImpl.kt @@ -114,7 +114,10 @@ class Fido2CredentialStoreImpl( private fun UpdateCipherResult.toCreateCipherResult(): CreateCipherResult = when (this) { UpdateCipherResult.Success -> CreateCipherResult.Success - is UpdateCipherResult.Error -> CreateCipherResult.Error(errorMessage, error) + is UpdateCipherResult.Error -> CreateCipherResult.Error( + error = error, + errorMessage = errorMessage, + ) } /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt index a20dcfa1bd..43d068f613 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt @@ -85,10 +85,7 @@ class CipherManagerImpl( override suspend fun createCipher(cipherView: CipherView): CreateCipherResult { val userId = activeUserId - ?: return CreateCipherResult.Error( - error = NoActiveUserException(), - errorMessage = null, - ) + ?: return CreateCipherResult.Error(error = NoActiveUserException()) return vaultSdkSource .encryptCipher( userId = userId, @@ -111,7 +108,7 @@ class CipherManagerImpl( } } .fold( - onFailure = { CreateCipherResult.Error(errorMessage = null, error = it) }, + onFailure = { CreateCipherResult.Error(error = it) }, onSuccess = { reviewPromptManager.registerAddCipherAction() it @@ -124,7 +121,7 @@ class CipherManagerImpl( collectionIds: List, ): CreateCipherResult { val userId = activeUserId - ?: return CreateCipherResult.Error(errorMessage = null, error = NoActiveUserException()) + ?: return CreateCipherResult.Error(error = NoActiveUserException()) return vaultSdkSource .encryptCipher( userId = userId, @@ -157,7 +154,7 @@ class CipherManagerImpl( } } .fold( - onFailure = { CreateCipherResult.Error(errorMessage = null, error = it) }, + onFailure = { CreateCipherResult.Error(error = it) }, onSuccess = { reviewPromptManager.registerAddCipherAction() it @@ -169,7 +166,8 @@ class CipherManagerImpl( cipherId: String, cipherView: CipherView, ): ArchiveCipherResult { - val userId = activeUserId ?: return ArchiveCipherResult.Error(NoActiveUserException()) + val userId = activeUserId + ?: return ArchiveCipherResult.Error(error = NoActiveUserException()) return ciphersService .archiveCipher(cipherId = cipherId) .flatMap { response -> @@ -204,7 +202,8 @@ class CipherManagerImpl( cipherId: String, cipherView: CipherView, ): UnarchiveCipherResult { - val userId = activeUserId ?: return UnarchiveCipherResult.Error(NoActiveUserException()) + val userId = activeUserId + ?: return UnarchiveCipherResult.Error(error = NoActiveUserException()) return ciphersService .unarchiveCipher(cipherId = cipherId) .flatMap { response -> @@ -368,7 +367,7 @@ class CipherManagerImpl( cipherView: CipherView, ): UpdateCipherResult { val userId = activeUserId - ?: return UpdateCipherResult.Error(errorMessage = null, error = NoActiveUserException()) + ?: return UpdateCipherResult.Error(error = NoActiveUserException()) return vaultSdkSource .encryptCipher( userId = userId, @@ -399,7 +398,7 @@ class CipherManagerImpl( } } .fold( - onFailure = { UpdateCipherResult.Error(errorMessage = null, error = it) }, + onFailure = { UpdateCipherResult.Error(error = it) }, onSuccess = { it }, ) } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ArchiveCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ArchiveCipherResult.kt index e6e2652d7c..e054f5aedb 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ArchiveCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ArchiveCipherResult.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository.model +import com.x8bit.bitwarden.data.platform.util.userFriendlyMessage + /** * Models result of archiving a cipher. */ @@ -16,6 +18,6 @@ sealed class ArchiveCipherResult { */ data class Error( val error: Throwable, - val errorMessage: String? = null, + val errorMessage: String? = error.userFriendlyMessage, ) : ArchiveCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/CreateCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/CreateCipherResult.kt index 23d5487040..e21be603c4 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/CreateCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/CreateCipherResult.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository.model +import com.x8bit.bitwarden.data.platform.util.userFriendlyMessage + /** * Models result of creating a cipher. */ @@ -14,5 +16,8 @@ sealed class CreateCipherResult { * Generic error while creating cipher. The optional [errorMessage] may be displayed directly in * the UI when present. */ - data class Error(val errorMessage: String?, val error: Throwable?) : CreateCipherResult() + data class Error( + val error: Throwable?, + val errorMessage: String? = error?.userFriendlyMessage, + ) : CreateCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteAttachmentResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteAttachmentResult.kt index 014a9c23ac..dab5b6abdc 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteAttachmentResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteAttachmentResult.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository.model +import com.x8bit.bitwarden.data.platform.util.userFriendlyMessage + /** * Models result of deleting an attachment from a cipher. */ @@ -16,6 +18,6 @@ sealed class DeleteAttachmentResult { */ data class Error( val error: Throwable, - val errorMessage: String? = null, + val errorMessage: String? = error.userFriendlyMessage, ) : DeleteAttachmentResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteCipherResult.kt index 3997d42dc9..ccdda11499 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteCipherResult.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository.model +import com.x8bit.bitwarden.data.platform.util.userFriendlyMessage + /** * Models result of deleting a cipher. */ @@ -16,6 +18,6 @@ sealed class DeleteCipherResult { */ data class Error( val error: Throwable, - val errorMessage: String? = null, + val errorMessage: String? = error.userFriendlyMessage, ) : DeleteCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/RestoreCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/RestoreCipherResult.kt index 797146290d..64646a0c01 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/RestoreCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/RestoreCipherResult.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository.model +import com.x8bit.bitwarden.data.platform.util.userFriendlyMessage + /** * Models result of restoring a cipher. */ @@ -16,6 +18,6 @@ sealed class RestoreCipherResult { */ data class Error( val error: Throwable, - val errorMessage: String? = null, + val errorMessage: String? = error.userFriendlyMessage, ) : RestoreCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ShareCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ShareCipherResult.kt index 829719050d..e9930dfb28 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ShareCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ShareCipherResult.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository.model +import com.x8bit.bitwarden.data.platform.util.userFriendlyMessage + /** * Models result of sharing a cipher. */ @@ -15,6 +17,6 @@ sealed class ShareCipherResult { */ data class Error( val error: Throwable, - val errorMessage: String? = null, + val errorMessage: String? = error.userFriendlyMessage, ) : ShareCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UnarchiveCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UnarchiveCipherResult.kt index 900d327328..4d19cd9d0a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UnarchiveCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UnarchiveCipherResult.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository.model +import com.x8bit.bitwarden.data.platform.util.userFriendlyMessage + /** * Models result of unarchiving a cipher. */ @@ -16,6 +18,6 @@ sealed class UnarchiveCipherResult { */ data class Error( val error: Throwable, - val errorMessage: String? = null, + val errorMessage: String? = error.userFriendlyMessage, ) : UnarchiveCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UpdateCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UpdateCipherResult.kt index 23fcb43ad3..8d1e5830c1 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UpdateCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UpdateCipherResult.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.data.vault.repository.model +import com.x8bit.bitwarden.data.platform.util.userFriendlyMessage + /** * Models result of updating a cipher. */ @@ -14,5 +16,8 @@ sealed class UpdateCipherResult { * Generic error while updating cipher. The optional [errorMessage] may be displayed directly in * the UI when present. */ - data class Error(val errorMessage: String?, val error: Throwable?) : UpdateCipherResult() + data class Error( + val error: Throwable?, + val errorMessage: String? = error?.userFriendlyMessage, + ) : UpdateCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt index 89d0a7ebc8..a303dc3de2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt @@ -676,7 +676,8 @@ class SearchViewModel @Inject constructor( it.copy( dialogState = SearchState.DialogState.Error( title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.unable_to_archive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_archive_selected_item.asText(), throwable = result.error, ), ) @@ -697,7 +698,8 @@ class SearchViewModel @Inject constructor( it.copy( dialogState = SearchState.DialogState.Error( title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.unable_to_unarchive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_unarchive_selected_item.asText(), throwable = result.error, ), ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index 69ca5beb61..ab2e69029d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -1821,7 +1821,8 @@ class VaultAddEditViewModel @Inject constructor( is ArchiveCipherResult.Error -> { showDialog( dialogState = VaultAddEditState.DialogState.Generic( - message = BitwardenString.unable_to_archive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_archive_selected_item.asText(), error = result.error, ), ) @@ -1845,7 +1846,8 @@ class VaultAddEditViewModel @Inject constructor( is UnarchiveCipherResult.Error -> { showDialog( dialogState = VaultAddEditState.DialogState.Generic( - message = BitwardenString.unable_to_unarchive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_unarchive_selected_item.asText(), error = result.error, ), ) @@ -1873,7 +1875,8 @@ class VaultAddEditViewModel @Inject constructor( is DeleteCipherResult.Error -> { showDialog( dialogState = VaultAddEditState.DialogState.Generic( - message = BitwardenString.generic_error_message.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.generic_error_message.asText(), error = result.error, ), ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsViewModel.kt index ea5f0a061e..f3dbd8081e 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsViewModel.kt @@ -298,7 +298,10 @@ class AttachmentsViewModel @Inject constructor( it.copy( dialogState = AttachmentsState.DialogState.Error( title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.generic_error_message.asText(), + message = result + .errorMessage + ?.asText() + ?: BitwardenString.generic_error_message.asText(), throwable = result.error, ), ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt index 044d93f6b5..321aac9d53 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt @@ -1183,7 +1183,8 @@ class VaultItemViewModel @Inject constructor( is DeleteCipherResult.Error -> { updateDialogState( VaultItemState.DialogState.Generic( - message = BitwardenString.generic_error_message.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.generic_error_message.asText(), error = result.error, ), ) @@ -1211,7 +1212,8 @@ class VaultItemViewModel @Inject constructor( is RestoreCipherResult.Error -> { updateDialogState( VaultItemState.DialogState.Generic( - message = BitwardenString.generic_error_message.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.generic_error_message.asText(), error = result.error, ), ) @@ -1291,7 +1293,8 @@ class VaultItemViewModel @Inject constructor( is ArchiveCipherResult.Error -> { updateDialogState( dialog = VaultItemState.DialogState.Generic( - message = BitwardenString.unable_to_archive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_archive_selected_item.asText(), error = result.error, ), ) @@ -1315,7 +1318,8 @@ class VaultItemViewModel @Inject constructor( is UnarchiveCipherResult.Error -> { updateDialogState( dialog = VaultItemState.DialogState.Generic( - message = BitwardenString.unable_to_unarchive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_unarchive_selected_item.asText(), error = result.error, ), ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index 84acdda7dc..6cecd2ed98 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -1723,7 +1723,8 @@ class VaultItemListingViewModel @Inject constructor( it.copy( dialogState = VaultItemListingState.DialogState.Error( title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.unable_to_archive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_archive_selected_item.asText(), throwable = result.error, ), ) @@ -1750,7 +1751,8 @@ class VaultItemListingViewModel @Inject constructor( it.copy( dialogState = VaultItemListingState.DialogState.Error( title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.unable_to_unarchive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_unarchive_selected_item.asText(), throwable = result.error, ), ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt index ba41fc0f14..6de9d0cb09 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModel.kt @@ -161,7 +161,10 @@ class VaultMoveToOrganizationViewModel @Inject constructor( mutableStateFlow.update { it.copy( dialogState = VaultMoveToOrganizationState.DialogState.Error( - message = BitwardenString.generic_error_message.asText(), + message = result + .errorMessage + ?.asText() + ?: BitwardenString.generic_error_message.asText(), throwable = result.error, ), ) 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 a37b416d20..c93044f300 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 @@ -1018,7 +1018,8 @@ class VaultViewModel @Inject constructor( it.copy( dialog = VaultState.DialogState.Error( title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.unable_to_archive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_archive_selected_item.asText(), error = result.error, ), ) @@ -1039,7 +1040,8 @@ class VaultViewModel @Inject constructor( it.copy( dialog = VaultState.DialogState.Error( title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.unable_to_unarchive_selected_item.asText(), + message = result.errorMessage?.asText() + ?: BitwardenString.unable_to_unarchive_selected_item.asText(), error = result.error, ), ) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt index ab06ede848..4d52097ac6 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt @@ -8,6 +8,7 @@ import com.bitwarden.core.data.util.asFailure import com.bitwarden.core.data.util.asSuccess import com.bitwarden.data.manager.file.FileManager import com.bitwarden.data.manager.model.DownloadResult +import com.bitwarden.network.exception.CookieRedirectException import com.bitwarden.network.model.ArchiveCipherResponseJson import com.bitwarden.network.model.AttachmentJsonRequest import com.bitwarden.network.model.CreateCipherInOrganizationJsonRequest @@ -2933,6 +2934,114 @@ class CipherManagerTest { } } + @Suppress("MaxLineLength") + @Test + fun `hardDeleteCipher with CookieRedirectException should return DeleteCipherResult Error with errorMessage`() = + runTest { + fakeAuthDiskSource.userState = MOCK_USER_STATE + val cipherId = "mockId-1" + val error = CookieRedirectException("test.host") + coEvery { + ciphersService.hardDeleteCipher(cipherId = cipherId) + } returns error.asFailure() + + val result = cipherManager.hardDeleteCipher(cipherId) + + assertEquals( + DeleteCipherResult.Error( + errorMessage = "Your request was interrupted because " + + "the app needed to re-authenticate. Please try again.", + error = error, + ), + result, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `softDeleteCipher with CookieRedirectException should return DeleteCipherResult Error with errorMessage`() = + runTest { + fakeAuthDiskSource.userState = MOCK_USER_STATE + val userId = MOCK_USER_STATE.activeUserId + val cipherId = "mockId-1" + val cipherView = createMockCipherView(number = 1) + val encryptionContext = createMockEncryptionContext(number = 1) + val error = CookieRedirectException("test.host") + coEvery { + vaultSdkSource.encryptCipher(userId = userId, cipherView = cipherView) + } returns encryptionContext.asSuccess() + coEvery { + ciphersService.softDeleteCipher(cipherId = cipherId) + } returns error.asFailure() + + val result = cipherManager.softDeleteCipher( + cipherId = cipherId, + cipherView = cipherView, + ) + + assertEquals( + DeleteCipherResult.Error( + errorMessage = "Your request was interrupted because " + + "the app needed to re-authenticate. Please try again.", + error = error, + ), + result, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `restoreCipher with CookieRedirectException should return RestoreCipherResult Error with errorMessage`() = + runTest { + fakeAuthDiskSource.userState = MOCK_USER_STATE + val cipherId = "mockId-1" + val cipherView = createMockCipherView(number = 1) + val error = CookieRedirectException("test.host") + coEvery { + ciphersService.restoreCipher(cipherId = cipherId) + } returns error.asFailure() + + val result = cipherManager.restoreCipher( + cipherId = cipherId, + cipherView = cipherView, + ) + + assertEquals( + RestoreCipherResult.Error( + errorMessage = "Your request was interrupted because " + + "the app needed to re-authenticate. Please try again.", + error = error, + ), + result, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `archiveCipher with CookieRedirectException should return ArchiveCipherResult Error with errorMessage`() = + runTest { + fakeAuthDiskSource.userState = MOCK_USER_STATE + val cipherId = "mockId-1" + val error = CookieRedirectException("test.host") + coEvery { + ciphersService.archiveCipher(cipherId = cipherId) + } returns error.asFailure() + + val result = cipherManager.archiveCipher( + cipherId = cipherId, + cipherView = createMockCipherView(number = 1), + ) + + assertEquals( + ArchiveCipherResult.Error( + errorMessage = "Your request was interrupted because " + + "the app needed to re-authenticate. Please try again.", + error = error, + ), + result, + ) + } + private fun setupMockUri( url: String, queryParams: Map = emptyMap(), diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt index 5b8b59593e..2454333910 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt @@ -398,6 +398,46 @@ class SearchViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `ArchiveClick with ArchiveCipherResult error with errorMessage should display that message`() = + runTest { + val cipherView = createMockCipherView(number = 1, clock = clock) + + val viewModel = createViewModel(initialState = null) + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepository.archiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns ArchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction( + SearchAction.OverflowOptionClick( + overflowAction = ListingItemOverflowAction.VaultAction.ArchiveClick( + cipherId = "mockId-1", + ), + ), + ) + + assertEquals( + DEFAULT_STATE.copy( + dialogState = SearchState.DialogState.Error( + title = BitwardenString.an_error_has_occurred.asText(), + message = errorMessage.asText(), + throwable = error, + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `UnarchiveClick with UnarchiveCipherResult Success should emit a ShowSnackbar event`() = runTest { @@ -456,6 +496,46 @@ class SearchViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `UnarchiveClick with UnarchiveCipherResult error with errorMessage should display that message`() = + runTest { + val cipherView = createMockCipherView(number = 1, clock = clock) + + val viewModel = createViewModel(initialState = null) + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepository.unarchiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns UnarchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction( + SearchAction.OverflowOptionClick( + overflowAction = ListingItemOverflowAction.VaultAction.UnarchiveClick( + cipherId = "mockId-1", + ), + ), + ) + + assertEquals( + DEFAULT_STATE.copy( + dialogState = SearchState.DialogState.Error( + title = BitwardenString.an_error_has_occurred.asText(), + message = errorMessage.asText(), + throwable = error, + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `AutofillItemClick should call emitAccessibilitySelection`() = runTest { val cipherView = setupForAutofill( diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index c1d358e681..a005dce22f 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -735,6 +735,80 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `ConfirmDeleteClick with DeleteCipherResult error with errorMessage should display that message`() = + runTest { + val cipherListView = createMockCipherListView(number = 1) + val cipherView = createMockCipherView(number = 1) + val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID) + val initState = createVaultAddItemState(vaultAddEditType = vaultAddEditType) + mutableVaultDataFlow.value = DataState.Loaded( + data = createVaultData(cipherListView = cipherListView), + ) + + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initState, + vaultAddEditType = vaultAddEditType, + vaultItemCipherType = VaultItemCipherType.LOGIN, + ), + ) + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepository.softDeleteCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns DeleteCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction(VaultAddEditAction.Common.ConfirmDeleteClick) + + assertEquals( + createVaultAddItemState( + vaultAddEditType = vaultAddEditType, + dialogState = VaultAddEditState.DialogState.Generic( + message = errorMessage.asText(), + error = error, + ), + commonContentViewState = createCommonContentViewState( + name = "mockName-1", + originalCipher = createMockCipherView(number = 1), + notes = "mockNotes-1", + customFieldData = listOf( + VaultAddEditState.Custom.HiddenField( + itemId = "testId", + name = "mockName-1", + value = "mockValue-1", + ), + ), + ), + typeContentViewState = createLoginTypeContentViewState( + username = "mockUsername-1", + password = "mockPassword-1", + uri = listOf( + UriItem( + id = "testId", + uri = "www.mockuri1.com", + match = UriMatchType.HOST, + checksum = "mockUriChecksum-1", + ), + ), + totpCode = "mockTotp-1", + canViewPassword = true, + fido2CredentialCreationDateTime = null, + ) + .copy(totp = "mockTotp-1"), + ), + viewModel.stateFlow.value, + ) + } + @Suppress("MaxLineLength") @Test fun `in add mode, SaveClick should show dialog, remove it once an item is saved, and emit NavigateBack`() = @@ -2476,6 +2550,96 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `ArchiveClick with ArchiveCipherResult error with errorMessage should display that message`() = + runTest { + mutableUserStateFlow.update { + it?.copy( + accounts = it.accounts.map { account -> + account.copy(isPremium = true) + }, + ) + } + val cipherListView = + createMockCipherListView(number = 1, isArchived = false) + val cipherView = + createMockCipherView(number = 1, isArchived = false) + val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID) + val initState = createVaultAddItemState( + vaultAddEditType = vaultAddEditType, + commonContentViewState = createCommonContentViewState( + originalCipher = cipherView, + ), + hasPremium = true, + ) + mutableVaultDataFlow.value = DataState.Loaded( + data = createVaultData(cipherListView = cipherListView), + ) + + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initState, + vaultAddEditType = vaultAddEditType, + vaultItemCipherType = VaultItemCipherType.LOGIN, + ), + ) + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepository.archiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns ArchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction(VaultAddEditAction.Common.ArchiveClick) + + assertEquals( + createVaultAddItemState( + hasPremium = true, + vaultAddEditType = vaultAddEditType, + dialogState = VaultAddEditState.DialogState.Generic( + message = errorMessage.asText(), + error = error, + ), + commonContentViewState = createCommonContentViewState( + name = "mockName-1", + originalCipher = createMockCipherView(number = 1), + notes = "mockNotes-1", + customFieldData = listOf( + VaultAddEditState.Custom.HiddenField( + itemId = "testId", + name = "mockName-1", + value = "mockValue-1", + ), + ), + ), + typeContentViewState = createLoginTypeContentViewState( + username = "mockUsername-1", + password = "mockPassword-1", + uri = listOf( + UriItem( + id = "testId", + uri = "www.mockuri1.com", + match = UriMatchType.HOST, + checksum = "mockUriChecksum-1", + ), + ), + totpCode = "mockTotp-1", + canViewPassword = true, + fido2CredentialCreationDateTime = null, + ) + .copy(totp = "mockTotp-1"), + ), + viewModel.stateFlow.value, + ) + } + @Suppress("MaxLineLength") @Test fun `UnarchiveClick with UnarchiveCipherResult Success should send snackbar event and NavigateBack`() = @@ -2588,6 +2752,95 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `UnarchiveClick with UnarchiveCipherResult error with errorMessage should display that message`() = + runTest { + mutableUserStateFlow.update { + it?.copy( + accounts = it.accounts.map { account -> + account.copy(isPremium = true) + }, + ) + } + val cipherListView = + createMockCipherListView(number = 1, isArchived = false) + val cipherView = + createMockCipherView(number = 1, isArchived = false) + val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID) + val initState = createVaultAddItemState( + vaultAddEditType = vaultAddEditType, + commonContentViewState = createCommonContentViewState( + originalCipher = cipherView, + ), + hasPremium = true, + ) + mutableVaultDataFlow.value = DataState.Loaded( + data = createVaultData(cipherListView = cipherListView), + ) + + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initState, + vaultAddEditType = vaultAddEditType, + vaultItemCipherType = VaultItemCipherType.LOGIN, + ), + ) + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepository.unarchiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns UnarchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction(VaultAddEditAction.Common.UnarchiveClick) + + assertEquals( + createVaultAddItemState( + hasPremium = true, + vaultAddEditType = vaultAddEditType, + dialogState = VaultAddEditState.DialogState.Generic( + message = errorMessage.asText(), + error = error, + ), + commonContentViewState = createCommonContentViewState( + name = "mockName-1", + originalCipher = createMockCipherView(number = 1), + notes = "mockNotes-1", + customFieldData = listOf( + VaultAddEditState.Custom.HiddenField( + itemId = "testId", + name = "mockName-1", + value = "mockValue-1", + ), + ), + ), + typeContentViewState = createLoginTypeContentViewState( + username = "mockUsername-1", + password = "mockPassword-1", + uri = listOf( + UriItem( + id = "testId", + uri = "www.mockuri1.com", + match = UriMatchType.HOST, + checksum = "mockUriChecksum-1", + ), + ), + totpCode = "mockTotp-1", + canViewPassword = true, + fido2CredentialCreationDateTime = null, + ), + ), + viewModel.stateFlow.value, + ) + } + @Nested inner class VaultAddEditLoginTypeItemActions { private lateinit var viewModel: VaultAddEditViewModel diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsViewModelTest.kt index 86a15b816e..3945d38598 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsViewModelTest.kt @@ -480,6 +480,55 @@ class AttachmentsViewModelTest : BaseViewModelTest() { } } + @Suppress("MaxLineLength") + @Test + fun `DeleteClick with deleteCipherAttachment error with errorMessage should display that message`() = + runTest { + val cipherId = "mockId-1" + val attachmentId = "mockId-1" + val cipherView = createMockCipherView(number = 1) + val initialState = DEFAULT_STATE.copy( + viewState = DEFAULT_CONTENT_WITH_ATTACHMENTS, + ) + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Fail") + coEvery { + vaultRepository.deleteCipherAttachment( + cipherId = cipherId, + attachmentId = attachmentId, + cipherView = cipherView, + ) + } returns DeleteAttachmentResult.Error( + errorMessage = errorMessage, + error = error, + ) + mutableVaultItemStateFlow.value = DataState.Loaded(cipherView) + + val viewModel = createViewModel() + viewModel.stateFlow.test { + assertEquals(initialState, awaitItem()) + viewModel.trySendAction(AttachmentsAction.DeleteClick(attachmentId)) + assertEquals( + initialState.copy( + dialogState = AttachmentsState.DialogState.Loading( + message = BitwardenString.deleting.asText(), + ), + ), + awaitItem(), + ) + assertEquals( + initialState.copy( + dialogState = AttachmentsState.DialogState.Error( + title = BitwardenString.an_error_has_occurred.asText(), + message = errorMessage.asText(), + throwable = error, + ), + ), + awaitItem(), + ) + } + } + @Test fun `DeleteClick with deleteCipherAttachment success should emit ShowSnackbar`() = runTest { val cipherId = "mockId-1" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt index 4575e6b1c6..75a5e7806e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt @@ -355,6 +355,59 @@ class VaultItemViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `ArchiveClick with ArchiveCipherResult error with errorMessage should display that message`() = + runTest { + val cipherView = createMockCipherView(number = 1, isArchived = false) + every { + cipherView.toViewState( + previousState = null, + isPremiumUser = true, + totpCodeItemData = null, + canDelete = true, + canRestore = false, + canAssignToCollections = true, + canEdit = true, + baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl, + isIconLoadingDisabled = false, + relatedLocations = persistentListOf(), + hasOrganizations = true, + ) + } returns DEFAULT_VIEW_STATE + mutableVaultItemFlow.value = DataState.Loaded(data = cipherView) + mutableAuthCodeItemFlow.value = DataState.Loaded(data = null) + mutableCollectionsStateFlow.value = DataState.Loaded(emptyList()) + mutableFoldersStateFlow.value = DataState.Loaded(emptyList()) + + val viewModel = createViewModel(state = null) + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepo.archiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns ArchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction(VaultItemAction.Common.ArchiveClick) + + assertEquals( + DEFAULT_STATE.copy( + viewState = DEFAULT_VIEW_STATE, + dialog = VaultItemState.DialogState.Generic( + message = errorMessage.asText(), + error = error, + ), + ), + viewModel.stateFlow.value, + ) + } + @Suppress("MaxLineLength") @Test fun `UnarchiveClick with UnarchiveCipherResult Success should send snackbar event and NavigateBack`() = @@ -457,6 +510,69 @@ class VaultItemViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `UnarchiveClick with UnarchiveCipherResult error with errorMessage should display that message`() = + runTest { + val cipherView = createMockCipherView(number = 1, isArchived = true) + every { + cipherView.toViewState( + previousState = null, + isPremiumUser = true, + totpCodeItemData = null, + canDelete = true, + canRestore = false, + canAssignToCollections = true, + canEdit = true, + baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl, + isIconLoadingDisabled = false, + relatedLocations = persistentListOf(), + hasOrganizations = true, + ) + } returns DEFAULT_VIEW_STATE.copy( + common = DEFAULT_COMMON.copy( + archived = true, + currentCipher = cipherView, + ), + ) + mutableVaultItemFlow.value = DataState.Loaded(data = cipherView) + mutableAuthCodeItemFlow.value = DataState.Loaded(data = null) + mutableCollectionsStateFlow.value = DataState.Loaded(emptyList()) + mutableFoldersStateFlow.value = DataState.Loaded(emptyList()) + + val viewModel = createViewModel(state = null) + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepo.unarchiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns UnarchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction(VaultItemAction.Common.UnarchiveClick) + + assertEquals( + DEFAULT_STATE.copy( + dialog = VaultItemState.DialogState.Generic( + message = errorMessage.asText(), + error = error, + ), + viewState = DEFAULT_VIEW_STATE.copy( + common = DEFAULT_COMMON.copy( + archived = true, + currentCipher = cipherView, + ), + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `DeleteClick should update state`() = runTest { @@ -631,6 +747,57 @@ class VaultItemViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `ConfirmDeleteClick with DeleteCipherResult error with errorMessage should display that message`() = + runTest { + every { + mockCipherView.toViewState( + previousState = null, + isPremiumUser = true, + totpCodeItemData = null, + canDelete = true, + canRestore = false, + canAssignToCollections = true, + canEdit = true, + baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl, + isIconLoadingDisabled = false, + relatedLocations = persistentListOf(), + hasOrganizations = true, + ) + } returns DEFAULT_VIEW_STATE + mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView) + mutableAuthCodeItemFlow.value = DataState.Loaded(data = null) + mutableCollectionsStateFlow.value = DataState.Loaded(emptyList()) + mutableFoldersStateFlow.value = DataState.Loaded(emptyList()) + + val viewModel = createViewModel(state = DEFAULT_STATE) + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepo.softDeleteCipher( + cipherId = VAULT_ITEM_ID, + cipherView = createMockCipherView(number = 1), + ) + } returns DeleteCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction(VaultItemAction.Common.ConfirmDeleteClick) + + assertEquals( + DEFAULT_STATE.copy( + viewState = DEFAULT_VIEW_STATE, + dialog = VaultItemState.DialogState.Generic( + message = errorMessage.asText(), + error = error, + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `ConfirmDeleteClick with deleted cipher should should invoke hardDeleteCipher`() = runTest { @@ -830,6 +997,57 @@ class VaultItemViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `ConfirmRestoreClick with RestoreCipherResult error with errorMessage should display that message`() { + every { + mockCipherView.toViewState( + previousState = null, + isPremiumUser = true, + totpCodeItemData = createTotpCodeData(), + canDelete = true, + canRestore = false, + canAssignToCollections = true, + canEdit = true, + baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl, + isIconLoadingDisabled = false, + relatedLocations = persistentListOf(), + hasOrganizations = true, + ) + } returns DEFAULT_VIEW_STATE + mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView) + mutableAuthCodeItemFlow.value = + DataState.Loaded(data = createVerificationCodeItem()) + mutableCollectionsStateFlow.value = DataState.Loaded(emptyList()) + mutableFoldersStateFlow.value = DataState.Loaded(emptyList()) + + val viewModel = createViewModel(state = DEFAULT_STATE) + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Fail") + coEvery { + vaultRepo.restoreCipher( + cipherId = VAULT_ITEM_ID, + cipherView = createMockCipherView(number = 1), + ) + } returns RestoreCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction(VaultItemAction.Common.ConfirmRestoreClick) + + assertEquals( + DEFAULT_STATE.copy( + viewState = DEFAULT_VIEW_STATE, + dialog = VaultItemState.DialogState.Generic( + message = errorMessage.asText(), + error = error, + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `on EditClick should navigate password`() = runTest { val loginViewState = createViewState() diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index dcb48d24fd..0e0f2236e5 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -1155,6 +1155,46 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `ArchiveClick with ArchiveCipherResult error with errorMessage should display that message`() = + runTest { + val cipherView = createMockCipherView(number = 1, clock = clock) + + val viewModel = createVaultItemListingViewModel() + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepository.archiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns ArchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction( + VaultItemListingsAction.OverflowOptionClick( + action = ListingItemOverflowAction.VaultAction.ArchiveClick( + cipherId = "mockId-1", + ), + ), + ) + + assertEquals( + createVaultItemListingState( + dialogState = VaultItemListingState.DialogState.Error( + title = BitwardenString.an_error_has_occurred.asText(), + message = errorMessage.asText(), + throwable = error, + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `UnarchiveClick with UnarchiveCipherResult Success should emit a ShowSnackbar event`() = runTest { @@ -1215,6 +1255,46 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `UnarchiveClick with UnarchiveCipherResult error with errorMessage should display that message`() = + runTest { + val cipherView = createMockCipherView(number = 1, clock = clock) + + val viewModel = createVaultItemListingViewModel() + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepository.unarchiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns UnarchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction( + VaultItemListingsAction.OverflowOptionClick( + action = ListingItemOverflowAction.VaultAction.UnarchiveClick( + cipherId = "mockId-1", + ), + ), + ) + + assertEquals( + createVaultItemListingState( + dialogState = VaultItemListingState.DialogState.Error( + title = BitwardenString.an_error_has_occurred.asText(), + message = errorMessage.asText(), + throwable = error, + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `MasterPasswordRepromptSubmit for a request Error should show a generic error dialog`() = runTest { diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt index 67bd86ba84..a1c6d30c59 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/movetoorganization/VaultMoveToOrganizationViewModelTest.kt @@ -385,6 +385,78 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() { } } + @Suppress("MaxLineLength") + @Test + fun `MoveClick with shareCipher error with errorMessage should show error dialog with that message`() = + runTest { + val viewModel = createViewModel() + mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS)) + mutableVaultItemFlow.tryEmit( + value = DataState.Loaded(createMockCipherView(number = 1)), + ) + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Fail") + coEvery { + vaultRepository.shareCipher( + cipherId = "mockCipherId", + organizationId = "mockOrganizationId-1", + cipherView = createMockCipherView(number = 1), + collectionIds = listOf("mockId-1"), + ) + } returns ShareCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + viewModel.stateFlow.test { + assertEquals( + initialState.copy( + viewState = VaultMoveToOrganizationState.ViewState.Content( + organizations = createMockOrganizationList(), + selectedOrganizationId = "mockOrganizationId-1", + cipherToMove = createMockCipherView(number = 1), + ), + ), + awaitItem(), + ) + viewModel.trySendAction(VaultMoveToOrganizationAction.MoveClick) + assertEquals( + initialState.copy( + dialogState = VaultMoveToOrganizationState.DialogState.Loading( + message = BitwardenString.saving.asText(), + ), + viewState = VaultMoveToOrganizationState.ViewState.Content( + organizations = createMockOrganizationList(), + selectedOrganizationId = "mockOrganizationId-1", + cipherToMove = createMockCipherView(number = 1), + ), + ), + awaitItem(), + ) + assertEquals( + initialState.copy( + dialogState = VaultMoveToOrganizationState.DialogState.Error( + message = errorMessage.asText(), + throwable = error, + ), + viewState = VaultMoveToOrganizationState.ViewState.Content( + organizations = createMockOrganizationList(), + selectedOrganizationId = "mockOrganizationId-1", + cipherToMove = createMockCipherView(number = 1), + ), + ), + awaitItem(), + ) + } + coVerify { + vaultRepository.shareCipher( + cipherId = "mockCipherId", + organizationId = "mockOrganizationId-1", + cipherView = createMockCipherView(number = 1), + collectionIds = listOf("mockId-1"), + ) + } + } + @Test fun `MoveClick with shareCipher success should emit NavigateBack`() = runTest { val viewModel = createViewModel() 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 978e480b82..11aa427304 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 @@ -750,6 +750,46 @@ class VaultViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `ArchiveClick with ArchiveCipherResult error with errorMessage should display that message`() = + runTest { + val cipherView = createMockCipherView(number = 1, clock = clock) + + val viewModel = createViewModel() + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepository.archiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns ArchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction( + VaultAction.OverflowOptionClick( + overflowAction = ListingItemOverflowAction.VaultAction.ArchiveClick( + cipherId = "mockId-1", + ), + ), + ) + + assertEquals( + DEFAULT_STATE.copy( + dialog = VaultState.DialogState.Error( + title = BitwardenString.an_error_has_occurred.asText(), + message = errorMessage.asText(), + error = error, + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `UnarchiveClick with UnarchiveCipherResult Success should emit a ShowSnackbar event`() = runTest { @@ -808,6 +848,46 @@ class VaultViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `UnarchiveClick with UnarchiveCipherResult error with errorMessage should display that message`() = + runTest { + val cipherView = createMockCipherView(number = 1, clock = clock) + + val viewModel = createViewModel() + + val errorMessage = "You do not have permission to edit this." + val error = Throwable("Oh dang.") + coEvery { + vaultRepository.unarchiveCipher( + cipherId = "mockId-1", + cipherView = cipherView, + ) + } returns UnarchiveCipherResult.Error( + errorMessage = errorMessage, + error = error, + ) + + viewModel.trySendAction( + VaultAction.OverflowOptionClick( + overflowAction = ListingItemOverflowAction.VaultAction.UnarchiveClick( + cipherId = "mockId-1", + ), + ), + ) + + assertEquals( + DEFAULT_STATE.copy( + dialog = VaultState.DialogState.Error( + title = BitwardenString.an_error_has_occurred.asText(), + message = errorMessage.asText(), + error = error, + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `on LockAccountClick should call lockVault for the given account`() { val accountUserId = "userId"