diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/api/SendsApi.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/api/SendsApi.kt index 64a2546304..44e1932395 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/api/SendsApi.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/api/SendsApi.kt @@ -35,4 +35,10 @@ interface SendsApi { */ @DELETE("sends/{sendId}") suspend fun deleteSend(@Path("sendId") sendId: String): Result + + /** + * Deletes a send. + */ + @PUT("sends/{sendId}/remove-password") + suspend fun removeSendPassword(@Path("sendId") sendId: String): Result } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsService.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsService.kt index 906c74d047..23e5d5307f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsService.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsService.kt @@ -24,9 +24,16 @@ interface SendsService { ): Result /** - * Attempt to delete a cipher. + * Attempt to delete a send. */ suspend fun deleteSend( sendId: String, ): Result + + /** + * Attempt to remove password protection from a send. + */ + suspend fun removeSendPassword( + sendId: String, + ): Result } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsServiceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsServiceImpl.kt index e2601ddf97..4b2470694f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsServiceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsServiceImpl.kt @@ -40,4 +40,18 @@ class SendsServiceImpl( override suspend fun deleteSend(sendId: String): Result = sendsApi.deleteSend(sendId = sendId) + + override suspend fun removeSendPassword(sendId: String): Result = + sendsApi + .removeSendPassword(sendId = sendId) + .map { UpdateSendResponseJson.Success(send = it) } + .recoverCatching { throwable -> + throwable + .toBitwardenError() + .parseErrorBodyOrNull( + code = 400, + json = json, + ) + ?: throw throwable + } } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt index 87c3bbfb2a..172c5f3513 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt @@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.vault.manager.VaultLockManager import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult +import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult @@ -151,4 +152,9 @@ interface VaultRepository : VaultLockManager { sendId: String, sendView: SendView, ): UpdateSendResult + + /** + * Attempt to remove the password from a send. + */ + suspend fun removePasswordSend(sendId: String): RemovePasswordSendResult } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt index 85b962318b..8dc26c89ba 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt @@ -30,6 +30,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource import com.x8bit.bitwarden.data.vault.manager.VaultLockManager import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult +import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult @@ -443,6 +444,34 @@ class VaultRepositoryImpl( ) } + override suspend fun removePasswordSend(sendId: String): RemovePasswordSendResult { + val userId = requireNotNull(activeUserId) + return sendsService + .removeSendPassword(sendId = sendId) + .fold( + onSuccess = { response -> + when (response) { + is UpdateSendResponseJson.Invalid -> { + RemovePasswordSendResult.Error(errorMessage = response.message) + } + + is UpdateSendResponseJson.Success -> { + vaultDiskSource.saveSend(userId = userId, send = response.send) + vaultSdkSource + .decryptSend( + userId = userId, + send = response.send.toEncryptedSdkSend(), + ) + .getOrNull() + ?.let { RemovePasswordSendResult.Success(sendView = it) } + ?: RemovePasswordSendResult.Error(errorMessage = null) + } + } + }, + onFailure = { RemovePasswordSendResult.Error(errorMessage = null) }, + ) + } + private fun storeProfileData( syncResponse: SyncResponseJson, ) { diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/RemovePasswordSendResult.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/RemovePasswordSendResult.kt new file mode 100644 index 0000000000..8e2d6c0722 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/RemovePasswordSendResult.kt @@ -0,0 +1,21 @@ +package com.x8bit.bitwarden.data.vault.repository.model + +import com.bitwarden.core.SendView + +/** + * Models result of removing the password protection from a send. + */ +sealed class RemovePasswordSendResult { + + /** + * Send has had the password protection successfully removed and contains the decrypted + * [SendView]. + */ + data class Success(val sendView: SendView) : RemovePasswordSendResult() + + /** + * Generic error while removing the password protection from a send. The optional + * [errorMessage] may be displayed directly in the UI when present. + */ + data class Error(val errorMessage: String?) : RemovePasswordSendResult() +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreen.kt index 082a8d1e09..c6eecb418d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreen.kt @@ -32,7 +32,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers -import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList /** * Displays new send UX. @@ -92,7 +92,7 @@ fun AddSendScreen( ) if (!state.isAddMode) { BitwardenOverflowActionItem( - menuItemDataList = persistentListOf( + menuItemDataList = listOfNotNull( OverflowMenuItemData( text = stringResource(id = R.string.remove_password), onClick = remember(viewModel) { @@ -102,7 +102,8 @@ fun AddSendScreen( ) } }, - ), + ) + .takeIf { state.hasPassword }, OverflowMenuItemData( text = stringResource(id = R.string.copy_link), onClick = remember(viewModel) { @@ -121,7 +122,8 @@ fun AddSendScreen( { viewModel.trySendAction(AddSendAction.DeleteClick) } }, ), - ), + ) + .toPersistentList(), ) } }, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt index 6b53ba5420..209fd63c21 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt @@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult +import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.util.Text @@ -70,6 +71,7 @@ class AddSendViewModel @Inject constructor( .plusWeeks(1), expirationDate = null, sendUrl = null, + hasPassword = false, ), selectedType = AddSendState.ViewState.Content.SendType.Text( input = "", @@ -139,6 +141,10 @@ class AddSendViewModel @Inject constructor( private fun handleInternalAction(action: AddSendAction.Internal): Unit = when (action) { is AddSendAction.Internal.CreateSendResultReceive -> handleCreateSendResultReceive(action) is AddSendAction.Internal.UpdateSendResultReceive -> handleUpdateSendResultReceive(action) + is AddSendAction.Internal.RemovePasswordResultReceive -> handleRemovePasswordResultReceive( + action, + ) + is AddSendAction.Internal.UserStateReceive -> handleUserStateReceive(action) is AddSendAction.Internal.SendDataReceive -> handleSendDataReceive(action) } @@ -200,6 +206,32 @@ class AddSendViewModel @Inject constructor( } } + private fun handleRemovePasswordResultReceive( + action: AddSendAction.Internal.RemovePasswordResultReceive, + ) { + when (val result = action.result) { + is RemovePasswordSendResult.Error -> { + mutableStateFlow.update { + it.copy( + dialogState = AddSendState.DialogState.Error( + title = R.string.an_error_has_occurred.asText(), + message = result + .errorMessage + ?.asText() + ?: R.string.generic_error_message.asText(), + ), + ) + } + } + + is RemovePasswordSendResult.Success -> { + updateCommonContent { it.copy(hasPassword = false) } + mutableStateFlow.update { it.copy(dialogState = null) } + sendEvent(AddSendEvent.ShowToast(message = R.string.send_password_removed.asText())) + } + } + } + private fun handleUserStateReceive(action: AddSendAction.Internal.UserStateReceive) { mutableStateFlow.update { it.copy(isPremiumUser = action.userState?.activeAccount?.isPremium == true) @@ -288,8 +320,22 @@ class AddSendViewModel @Inject constructor( } private fun handleRemovePasswordClick() { - // TODO Add remove password support (BIT-1435) - sendEvent(AddSendEvent.ShowToast("Not yet implemented".asText())) + when (val addSendType = state.addSendType) { + AddSendType.AddItem -> Unit + is AddSendType.EditItem -> { + mutableStateFlow.update { + it.copy( + dialogState = AddSendState.DialogState.Loading( + message = R.string.removing_send_password.asText(), + ), + ) + } + viewModelScope.launch { + val result = vaultRepo.removePasswordSend(addSendType.sendItemId) + sendAction(AddSendAction.Internal.RemovePasswordResultReceive(result)) + } + } + } } private fun handleShareLinkClick() { @@ -516,6 +562,12 @@ data class AddSendState( */ val isAddMode: Boolean get() = addSendType is AddSendType.AddItem + /** + * Helper to determine if the currently displayed send has a password already set. + */ + val hasPassword: Boolean + get() = (viewState as? ViewState.Content)?.common?.hasPassword == true + /** * Represents the specific view states for the [AddSendScreen]. */ @@ -566,6 +618,7 @@ data class AddSendState( val deletionDate: ZonedDateTime, val expirationDate: ZonedDateTime?, val sendUrl: String?, + val hasPassword: Boolean, ) : Parcelable { val dateFormatPattern: String get() = "M/d/yyyy" @@ -767,6 +820,11 @@ sealed class AddSendAction { */ data class UpdateSendResultReceive(val result: UpdateSendResult) : Internal() + /** + * Indicates a result for removing the password from a send has been received. + */ + data class RemovePasswordResultReceive(val result: RemovePasswordSendResult) : Internal() + /** * Indicates that the send item data has been received. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensions.kt index a183fb1d18..1f12d93264 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensions.kt @@ -28,6 +28,7 @@ fun SendView.toViewState( deletionDate = ZonedDateTime.ofInstant(this.deletionDate, clock.zone), expirationDate = this.expirationDate?.let { ZonedDateTime.ofInstant(it, clock.zone) }, sendUrl = this.toSendUrl(baseWebSendUrl), + hasPassword = this.hasPassword, ), selectedType = when (type) { SendType.TEXT -> { diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsServiceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsServiceTest.kt index e8efe9079b..2f7b29a281 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsServiceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SendsServiceTest.kt @@ -70,6 +70,32 @@ class SendsServiceTest : BaseServiceTest() { val result = sendsService.deleteSend(sendId = "send-id-1") assertEquals(Unit, result.getOrThrow()) } + + @Test + fun `removeSendPassword with success response should return a Success with the correct send`() = + runTest { + server.enqueue(MockResponse().setBody(CREATE_UPDATE_SEND_SUCCESS_JSON)) + val result = sendsService.removeSendPassword(sendId = "send-id-1") + assertEquals( + UpdateSendResponseJson.Success(send = createMockSend(number = 1)), + result.getOrThrow(), + ) + } + + @Suppress("MaxLineLength") + @Test + fun `removeSendPassword with an invalid response should return an Invalid with the correct data`() = + runTest { + server.enqueue(MockResponse().setResponseCode(400).setBody(UPDATE_SEND_INVALID_JSON)) + val result = sendsService.removeSendPassword(sendId = "send-id-1") + assertEquals( + UpdateSendResponseJson.Invalid( + message = "You do not have permission to edit this.", + validationErrors = null, + ), + result.getOrThrow(), + ) + } } private const val CREATE_UPDATE_SEND_SUCCESS_JSON = """ diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt index 315461036e..3ef2fc41f6 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryTest.kt @@ -49,6 +49,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView import com.x8bit.bitwarden.data.vault.manager.VaultLockManager import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult +import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult @@ -1591,6 +1592,65 @@ class VaultRepositoryTest { assertEquals(UpdateSendResult.Success(mockSendViewResult), result) } + @Test + @Suppress("MaxLineLength") + fun `removePasswordSend with sendsService removeSendPassword Error should return RemovePasswordSendResult Error`() = + runTest { + fakeAuthDiskSource.userState = MOCK_USER_STATE + val sendId = "sendId1234" + val mockSendView = createMockSendView(number = 1) + coEvery { + sendsService.removeSendPassword(sendId = sendId) + } returns Throwable("Fail").asFailure() + + val result = vaultRepository.removePasswordSend(sendId = sendId) + + assertEquals(RemovePasswordSendResult.Error(errorMessage = null), result) + } + + @Test + @Suppress("MaxLineLength") + fun `removePasswordSend with sendsService removeSendPassword Success and vaultSdkSource decryptSend Failure should return RemovePasswordSendResult Error`() = + runTest { + fakeAuthDiskSource.userState = MOCK_USER_STATE + val userId = "mockId-1" + val sendId = "sendId1234" + val mockSend = createMockSend(number = 1) + coEvery { + sendsService.removeSendPassword(sendId = sendId) + } returns UpdateSendResponseJson.Success(send = mockSend).asSuccess() + coEvery { + vaultSdkSource.decryptSend(userId = userId, send = createMockSdkSend(number = 1)) + } returns Throwable("Fail").asFailure() + coEvery { vaultDiskSource.saveSend(userId = userId, send = mockSend) } just runs + + val result = vaultRepository.removePasswordSend(sendId = sendId) + + assertEquals(RemovePasswordSendResult.Error(errorMessage = null), result) + } + + @Test + @Suppress("MaxLineLength") + fun `removePasswordSend with sendsService removeSendPassword Success should return RemovePasswordSendResult success`() = + runTest { + fakeAuthDiskSource.userState = MOCK_USER_STATE + val userId = "mockId-1" + val sendId = "sendId1234" + val mockSendView = createMockSendView(number = 1) + val mockSend = createMockSend(number = 1) + coEvery { + sendsService.removeSendPassword(sendId = sendId) + } returns UpdateSendResponseJson.Success(send = mockSend).asSuccess() + coEvery { + vaultSdkSource.decryptSend(userId = userId, send = createMockSdkSend(number = 1)) + } returns mockSendView.asSuccess() + coEvery { vaultDiskSource.saveSend(userId = userId, send = mockSend) } just runs + + val result = vaultRepository.removePasswordSend(sendId = sendId) + + assertEquals(RemovePasswordSendResult.Success(mockSendView), result) + } + //region Helper functions /** diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt index 572921d74f..c510430d9e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt @@ -124,6 +124,24 @@ class AddSendScreenTest : BaseComposeTest() { .isDisplayed() } + @Test + fun `overflow remove password button should be hidden when hasPassword is false`() { + mutableStateFlow.value = DEFAULT_STATE.copy( + addSendType = AddSendType.EditItem(sendItemId = "sendId"), + viewState = DEFAULT_VIEW_STATE.copy( + common = DEFAULT_COMMON_STATE.copy(hasPassword = false), + ), + ) + + composeTestRule + .onNodeWithContentDescription("More") + .performClick() + + composeTestRule + .onNodeWithText("Remove password") + .assertDoesNotExist() + } + @Test fun `on overflow remove password button click should send RemovePasswordClick`() { mutableStateFlow.value = DEFAULT_STATE.copy( @@ -790,6 +808,7 @@ class AddSendScreenTest : BaseComposeTest() { deletionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"), expirationDate = null, sendUrl = null, + hasPassword = true, ) private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt index 30a01f0059..db69e90e47 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt @@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult +import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.util.asText @@ -292,18 +293,127 @@ class AddSendViewModelTest : BaseViewModelTest() { } @Test - fun `RemovePasswordClick should send ShowToast`() = runTest { - val viewModel = createViewModel( - state = DEFAULT_STATE.copy(addSendType = AddSendType.EditItem("sendId")), - addSendType = AddSendType.EditItem("sendId"), - ) + fun `in add item state, RemovePasswordClick should do nothing`() = runTest { + val viewModel = createViewModel() viewModel.eventFlow.test { viewModel.trySendAction(AddSendAction.RemovePasswordClick) - assertEquals(AddSendEvent.ShowToast("Not yet implemented".asText()), awaitItem()) + expectNoEvents() } } + @Suppress("MaxLineLength") + @Test + fun `in edit item state, RemovePasswordClick vaultRepository removePasswordSend Error without message should show default error dialog`() = + runTest { + val sendId = "mockId-1" + coEvery { + vaultRepository.removePasswordSend(sendId) + } returns RemovePasswordSendResult.Error(errorMessage = null) + val initialState = DEFAULT_STATE.copy( + addSendType = AddSendType.EditItem(sendItemId = sendId), + ) + val mockSendView = createMockSendView(number = 1) + every { + mockSendView.toViewState(clock, DEFAULT_ENVIRONMENT_URL) + } returns DEFAULT_VIEW_STATE + mutableSendDataStateFlow.value = DataState.Loaded(mockSendView) + val viewModel = createViewModel( + state = initialState, + addSendType = AddSendType.EditItem(sendItemId = sendId), + ) + + viewModel.stateFlow.test { + assertEquals(initialState, awaitItem()) + viewModel.trySendAction(AddSendAction.RemovePasswordClick) + assertEquals( + initialState.copy( + dialogState = AddSendState.DialogState.Loading( + message = R.string.removing_send_password.asText(), + ), + ), + awaitItem(), + ) + assertEquals( + initialState.copy( + dialogState = AddSendState.DialogState.Error( + title = R.string.an_error_has_occurred.asText(), + message = R.string.generic_error_message.asText(), + ), + ), + awaitItem(), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `in edit item state, RemovePasswordClick vaultRepository removePasswordSend Error with message should show error dialog with message`() = + runTest { + val sendId = "mockId-1" + val errorMessage = "Fail" + coEvery { + vaultRepository.removePasswordSend(sendId) + } returns RemovePasswordSendResult.Error(errorMessage = errorMessage) + val mockSendView = createMockSendView(number = 1) + every { + mockSendView.toViewState(clock, DEFAULT_ENVIRONMENT_URL) + } returns DEFAULT_VIEW_STATE + mutableSendDataStateFlow.value = DataState.Loaded(mockSendView) + val initialState = DEFAULT_STATE.copy( + addSendType = AddSendType.EditItem(sendItemId = sendId), + ) + val viewModel = createViewModel( + state = initialState, + addSendType = AddSendType.EditItem(sendItemId = sendId), + ) + + viewModel.stateFlow.test { + assertEquals(initialState, awaitItem()) + viewModel.trySendAction(AddSendAction.RemovePasswordClick) + assertEquals( + initialState.copy( + dialogState = AddSendState.DialogState.Loading( + message = R.string.removing_send_password.asText(), + ), + ), + awaitItem(), + ) + assertEquals( + initialState.copy( + dialogState = AddSendState.DialogState.Error( + title = R.string.an_error_has_occurred.asText(), + message = errorMessage.asText(), + ), + ), + awaitItem(), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `in edit item state, RemovePasswordClick vaultRepository removePasswordSend Success should show toast`() = + runTest { + val sendId = "mockId-1" + val mockSendView = createMockSendView(number = 1) + coEvery { + vaultRepository.removePasswordSend(sendId) + } returns RemovePasswordSendResult.Success(mockSendView) + val viewModel = createViewModel( + state = DEFAULT_STATE.copy(addSendType = AddSendType.EditItem(sendItemId = sendId)), + addSendType = AddSendType.EditItem(sendItemId = sendId), + ) + + viewModel.eventFlow.test { + viewModel.trySendAction(AddSendAction.RemovePasswordClick) + assertEquals( + AddSendEvent.ShowToast(R.string.send_password_removed.asText()), + awaitItem(), + ) + } + } + @Test fun `ShareLinkClick should send ShowToast`() = runTest { val viewModel = createViewModel( @@ -605,6 +715,7 @@ class AddSendViewModelTest : BaseViewModelTest() { deletionDate = ZonedDateTime.parse("2023-11-03T00:00Z"), expirationDate = null, sendUrl = null, + hasPassword = false, ) private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt index 91a103ecbe..337080c545 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt @@ -70,6 +70,7 @@ private val DEFAULT_COMMON_STATE = AddSendState.ViewState.Content.Common( deletionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"), expirationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"), sendUrl = null, + hasPassword = false, ) private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensionsTest.kt index 1f625a890a..3f9e17fff7 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensionsTest.kt @@ -60,6 +60,7 @@ private val DEFAULT_COMMON: AddSendState.ViewState.Content.Common = ZoneOffset.UTC, ), sendUrl = "www.test.com/mockAccessId-1/mockKey-1", + hasPassword = true, ) private val DEFAULT_TEXT_TYPE: AddSendState.ViewState.Content.SendType.Text =