diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreen.kt index dd8e486d3..0efad71f3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreen.kt @@ -37,7 +37,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.auth.feature.createaccount.PasswordStrengthIndicator import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect -import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledTonalButton import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState @@ -127,7 +126,7 @@ fun ExportVaultScreen( is ExportVaultState.DialogState.Error -> { BitwardenBasicDialog( visibilityState = BasicDialogState.Shown( - title = dialog.title ?: R.string.an_error_has_occurred.asText(), + title = dialog.title, message = dialog.message, ), onDismissRequest = remember(viewModel) { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt index 0874a1261..89f198fc3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt @@ -118,15 +118,30 @@ class ExportVaultViewModel @Inject constructor( /** * Verify the master password after confirming exporting the vault. */ + @Suppress("ReturnCount") private fun handleConfirmExportVaultClicked() { // Display an error alert if the user hasn't entered a password. if (mutableStateFlow.value.passwordInput.isBlank()) { updateStateWithError( - R.string.validation_field_required.asText( + message = R.string.validation_field_required.asText( R.string.master_password.asText(), ), ) return + } else if (state.exportFormat == ExportVaultFormat.JSON_ENCRYPTED) { + if (state.filePasswordInput.isBlank()) { + updateStateWithError( + message = R.string.validation_field_required + .asText(R.string.file_password.asText()), + ) + return + } else if (state.confirmFilePasswordInput.isBlank()) { + updateStateWithError( + message = R.string.validation_field_required + .asText(R.string.confirm_file_password.asText()), + ) + return + } } // Otherwise, validate the password. @@ -335,7 +350,7 @@ class ExportVaultViewModel @Inject constructor( mutableStateFlow.update { it.copy( dialogState = ExportVaultState.DialogState.Error( - title = null, + title = R.string.an_error_has_occurred.asText(), message = message, ), ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt index ce1aac4ad..ca4a5b2f5 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt @@ -115,7 +115,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() { assertEquals( DEFAULT_STATE.copy( dialogState = ExportVaultState.DialogState.Error( - title = null, + title = R.string.an_error_has_occurred.asText(), message = R.string.validation_field_required.asText( R.string.master_password.asText(), ), @@ -132,6 +132,89 @@ class ExportVaultViewModelTest : BaseViewModelTest() { } } + @Suppress("MaxLineLength") + @Test + fun `ConfirmExportVaultClicked blank file password should show an error when export type is JSON_ENCRYPTED`() = + runTest { + val password = "password" + val viewModel = createViewModel() + viewModel.trySendAction( + ExportVaultAction.ExportFormatOptionSelect(ExportVaultFormat.JSON_ENCRYPTED), + ) + viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password)) + viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked) + assertEquals( + DEFAULT_STATE.copy( + dialogState = ExportVaultState.DialogState.Error( + title = R.string.an_error_has_occurred.asText(), + message = R.string.validation_field_required.asText( + R.string.file_password.asText(), + ), + ), + exportFormat = ExportVaultFormat.JSON_ENCRYPTED, + passwordInput = password, + ), + viewModel.stateFlow.value, + ) + + viewModel.trySendAction(ExportVaultAction.DialogDismiss) + assertEquals( + DEFAULT_STATE.copy( + exportFormat = ExportVaultFormat.JSON_ENCRYPTED, + passwordInput = password, + ), + viewModel.stateFlow.value, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `ConfirmExportVaultClicked blank confirm file password should show an error when export type is JSON_ENCRYPTED`() = + runTest { + val password = "password" + coEvery { + authRepository.getPasswordStrength( + email = EMAIL_ADDRESS, + password = password, + ) + } returns PasswordStrengthResult.Success( + passwordStrength = PasswordStrength.LEVEL_4, + ) + val viewModel = createViewModel() + viewModel.trySendAction( + ExportVaultAction.ExportFormatOptionSelect(ExportVaultFormat.JSON_ENCRYPTED), + ) + viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password)) + viewModel.trySendAction(ExportVaultAction.FilePasswordInputChange(password)) + viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked) + assertEquals( + DEFAULT_STATE.copy( + dialogState = ExportVaultState.DialogState.Error( + title = R.string.an_error_has_occurred.asText(), + message = R.string.validation_field_required.asText( + R.string.confirm_file_password.asText(), + ), + ), + exportFormat = ExportVaultFormat.JSON_ENCRYPTED, + filePasswordInput = password, + passwordInput = password, + passwordStrengthState = PasswordStrengthState.STRONG, + ), + viewModel.stateFlow.value, + ) + + viewModel.trySendAction(ExportVaultAction.DialogDismiss) + assertEquals( + DEFAULT_STATE.copy( + exportFormat = ExportVaultFormat.JSON_ENCRYPTED, + filePasswordInput = password, + passwordInput = password, + passwordStrengthState = PasswordStrengthState.STRONG, + ), + viewModel.stateFlow.value, + ) + } + @Test fun `ConfirmExportVaultClicked invalid password should show an error`() = runTest { val password = "password" @@ -149,7 +232,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() { assertEquals( DEFAULT_STATE.copy( dialogState = ExportVaultState.DialogState.Error( - title = null, + title = R.string.an_error_has_occurred.asText(), message = R.string.invalid_master_password.asText(), ), passwordInput = password, @@ -181,7 +264,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() { assertEquals( DEFAULT_STATE.copy( dialogState = ExportVaultState.DialogState.Error( - title = null, + title = R.string.an_error_has_occurred.asText(), message = R.string.generic_error_message.asText(), ), passwordInput = password, @@ -278,7 +361,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() { assertEquals( DEFAULT_STATE.copy( dialogState = ExportVaultState.DialogState.Error( - title = null, + title = R.string.an_error_has_occurred.asText(), message = R.string.export_vault_failure.asText(), ), ), @@ -401,7 +484,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() { assertEquals( DEFAULT_STATE.copy( dialogState = ExportVaultState.DialogState.Error( - title = null, + title = R.string.an_error_has_occurred.asText(), message = R.string.export_vault_failure.asText(), ), ), @@ -433,7 +516,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() { DEFAULT_STATE.copy( exportData = exportData, dialogState = ExportVaultState.DialogState.Error( - title = null, + title = R.string.an_error_has_occurred.asText(), message = R.string.export_vault_failure.asText(), ), ),