From 87254b443695576411ef606cfdcaf43e07570549 Mon Sep 17 00:00:00 2001 From: Caleb Derosier <125901828+caleb-livefront@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:36:57 -0600 Subject: [PATCH] Fix incorrect VM state access & unnecessary eventFlow usages (#1218) --- .../createaccount/CreateAccountViewModel.kt | 22 +- .../environment/EnvironmentViewModel.kt | 2 - .../auth/feature/landing/LandingViewModel.kt | 6 +- .../ui/auth/feature/login/LoginViewModel.kt | 10 +- .../vaultunlock/VaultUnlockViewModel.kt | 4 +- .../loginapproval/LoginApprovalViewModel.kt | 12 +- .../PendingRequestsViewModel.kt | 2 +- .../exportvault/ExportVaultViewModel.kt | 4 +- .../feature/generator/GeneratorViewModel.kt | 4 +- .../feature/addedit/VaultAddEditViewModel.kt | 4 +- .../ResetPasswordViewModelTest.kt | 277 ++++++------- .../TwoFactorLoginViewModelTest.kt | 98 +++-- .../exportvault/ExportVaultViewModelTest.kt | 366 +++++++++--------- 13 files changed, 381 insertions(+), 430 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModel.kt index 07dc3d4372..92eb3c690c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModel.kt @@ -191,7 +191,7 @@ class CreateAccountViewModel @Inject constructor( mutableStateFlow.update { it.copy(dialog = null) } sendEvent( CreateAccountEvent.NavigateToLogin( - email = mutableStateFlow.value.emailInput, + email = state.emailInput, captchaToken = registerAccountResult.captchaToken, ), ) @@ -251,7 +251,7 @@ class CreateAccountViewModel @Inject constructor( } else { passwordStrengthJob = viewModelScope.launch { val result = authRepository.getPasswordStrength( - email = mutableStateFlow.value.emailInput, + email = state.emailInput, password = action.input, ) trySendAction(ReceivePasswordStrengthResult(result)) @@ -264,7 +264,7 @@ class CreateAccountViewModel @Inject constructor( } private fun handleSubmitClick() = when { - mutableStateFlow.value.emailInput.isBlank() -> { + state.emailInput.isBlank() -> { val dialog = BasicDialogState.Shown( title = R.string.an_error_has_occurred.asText(), message = R.string.validation_field_required @@ -273,7 +273,7 @@ class CreateAccountViewModel @Inject constructor( mutableStateFlow.update { it.copy(dialog = CreateAccountDialog.Error(dialog)) } } - !mutableStateFlow.value.emailInput.isValidEmail() -> { + !state.emailInput.isValidEmail() -> { val dialog = BasicDialogState.Shown( title = R.string.an_error_has_occurred.asText(), message = R.string.invalid_email.asText(), @@ -281,7 +281,7 @@ class CreateAccountViewModel @Inject constructor( mutableStateFlow.update { it.copy(dialog = CreateAccountDialog.Error(dialog)) } } - mutableStateFlow.value.passwordInput.length < MIN_PASSWORD_LENGTH -> { + state.passwordInput.length < MIN_PASSWORD_LENGTH -> { val dialog = BasicDialogState.Shown( title = R.string.an_error_has_occurred.asText(), message = R.string.master_password_length_val_message_x.asText(MIN_PASSWORD_LENGTH), @@ -289,7 +289,7 @@ class CreateAccountViewModel @Inject constructor( mutableStateFlow.update { it.copy(dialog = CreateAccountDialog.Error(dialog)) } } - mutableStateFlow.value.passwordInput != mutableStateFlow.value.confirmPasswordInput -> { + state.passwordInput != state.confirmPasswordInput -> { val dialog = BasicDialogState.Shown( title = R.string.an_error_has_occurred.asText(), message = R.string.master_password_confirmation_val_message.asText(), @@ -297,7 +297,7 @@ class CreateAccountViewModel @Inject constructor( mutableStateFlow.update { it.copy(dialog = CreateAccountDialog.Error(dialog)) } } - !mutableStateFlow.value.isAcceptPoliciesToggled -> { + !state.isAcceptPoliciesToggled -> { val dialog = BasicDialogState.Shown( title = R.string.an_error_has_occurred.asText(), message = R.string.accept_policies_error.asText(), @@ -307,7 +307,7 @@ class CreateAccountViewModel @Inject constructor( else -> { submitRegisterAccountRequest( - shouldCheckForDataBreaches = mutableStateFlow.value.isCheckDataBreachesToggled, + shouldCheckForDataBreaches = state.isCheckDataBreachesToggled, captchaToken = null, ) } @@ -327,9 +327,9 @@ class CreateAccountViewModel @Inject constructor( viewModelScope.launch { val result = authRepository.register( shouldCheckDataBreaches = shouldCheckForDataBreaches, - email = mutableStateFlow.value.emailInput, - masterPassword = mutableStateFlow.value.passwordInput, - masterPasswordHint = mutableStateFlow.value.passwordHintInput.ifBlank { null }, + email = state.emailInput, + masterPassword = state.passwordInput, + masterPasswordHint = state.passwordHintInput.ifBlank { null }, captchaToken = captchaToken, ) sendAction( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/environment/EnvironmentViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/environment/EnvironmentViewModel.kt index 2ce1dd5f8c..da03560319 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/environment/EnvironmentViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/environment/EnvironmentViewModel.kt @@ -72,8 +72,6 @@ class EnvironmentViewModel @Inject constructor( } private fun handleSaveClickAction() { - val state = mutableStateFlow.value - val urlsAreAllNullOrValid = listOf( state.serverUrl, state.webVaultServerUrl, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt index da82f3df8a..9f8d49679a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt @@ -128,7 +128,7 @@ class LandingViewModel @Inject constructor( } private fun handleContinueButtonClicked() { - if (!mutableStateFlow.value.emailInput.isValidEmail()) { + if (!state.emailInput.isValidEmail()) { mutableStateFlow.update { it.copy( dialog = LandingState.DialogState.Error( @@ -150,8 +150,8 @@ class LandingViewModel @Inject constructor( return } - val email = mutableStateFlow.value.emailInput - val isRememberMeEnabled = mutableStateFlow.value.isRememberMeEnabled + val email = state.emailInput + val isRememberMeEnabled = state.isRememberMeEnabled // Update the remembered email address authRepository.rememberedEmailAddress = email.takeUnless { !isRememberMeEnabled } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt index 5bee24c28a..cc10a74d89 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt @@ -232,9 +232,9 @@ class LoginViewModel @Inject constructor( } viewModelScope.launch { val result = authRepository.login( - email = mutableStateFlow.value.emailAddress, - password = mutableStateFlow.value.passwordInput, - captchaToken = mutableStateFlow.value.captchaToken, + email = state.emailAddress, + password = state.passwordInput, + captchaToken = state.captchaToken, ) sendAction( LoginAction.Internal.ReceiveLoginResult( @@ -245,7 +245,7 @@ class LoginViewModel @Inject constructor( } private fun handleMasterPasswordHintClicked() { - val email = mutableStateFlow.value.emailAddress + val email = state.emailAddress sendEvent(LoginEvent.NavigateToMasterPasswordHint(email)) } @@ -254,7 +254,7 @@ class LoginViewModel @Inject constructor( } private fun handleSingleSignOnClicked() { - val email = mutableStateFlow.value.emailAddress + val email = state.emailAddress sendEvent(LoginEvent.NavigateToEnterpriseSignOn(email)) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModel.kt index cf4ff20acb..0218369968 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/vaultunlock/VaultUnlockViewModel.kt @@ -160,13 +160,13 @@ class VaultUnlockViewModel @Inject constructor( val vaultUnlockResult = when (state.vaultUnlockType) { VaultUnlockType.MASTER_PASSWORD -> { vaultRepo.unlockVaultWithMasterPassword( - mutableStateFlow.value.input, + state.input, ) } VaultUnlockType.PIN -> { vaultRepo.unlockVaultWithPin( - mutableStateFlow.value.input, + state.input, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModel.kt index a46c3e0596..0c414a600d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/loginapproval/LoginApprovalViewModel.kt @@ -97,9 +97,9 @@ class LoginApprovalViewModel @Inject constructor( trySendAction( LoginApprovalAction.Internal.ApproveRequestResultReceive( result = authRepository.updateAuthRequest( - requestId = mutableStateFlow.value.requestId, - masterPasswordHash = mutableStateFlow.value.masterPasswordHash, - publicKey = mutableStateFlow.value.publicKey, + requestId = state.requestId, + masterPasswordHash = state.masterPasswordHash, + publicKey = state.publicKey, isApproved = true, ), ), @@ -116,9 +116,9 @@ class LoginApprovalViewModel @Inject constructor( trySendAction( LoginApprovalAction.Internal.DeclineRequestResultReceive( result = authRepository.updateAuthRequest( - requestId = mutableStateFlow.value.requestId, - masterPasswordHash = mutableStateFlow.value.masterPasswordHash, - publicKey = mutableStateFlow.value.publicKey, + requestId = state.requestId, + masterPasswordHash = state.masterPasswordHash, + publicKey = state.publicKey, isApproved = false, ), ), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModel.kt index 770b0f4ec9..7c7f1f82de 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/pendingrequests/PendingRequestsViewModel.kt @@ -76,7 +76,7 @@ class PendingRequestsViewModel @Inject constructor( viewState = PendingRequestsState.ViewState.Loading, ) } - mutableStateFlow.value.authRequests.forEach { request -> + state.authRequests.forEach { request -> authRepository.updateAuthRequest( requestId = request.id, masterPasswordHash = request.masterPasswordHash, 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 89f198fc3b..21f4e8a9a9 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 @@ -121,7 +121,7 @@ class ExportVaultViewModel @Inject constructor( @Suppress("ReturnCount") private fun handleConfirmExportVaultClicked() { // Display an error alert if the user hasn't entered a password. - if (mutableStateFlow.value.passwordInput.isBlank()) { + if (state.passwordInput.isBlank()) { updateStateWithError( message = R.string.validation_field_required.asText( R.string.master_password.asText(), @@ -149,7 +149,7 @@ class ExportVaultViewModel @Inject constructor( sendAction( ExportVaultAction.Internal.ReceiveValidatePasswordResult( result = authRepository.validatePassword( - password = mutableStateFlow.value.passwordInput, + password = state.passwordInput, ), ), ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt index c33d6d8a34..9ccf6f94a5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt @@ -550,7 +550,7 @@ class GeneratorViewModel @Inject constructor( private fun handleRegenerationClick() { // Go through the update process with the current state to trigger a // regeneration of the generated text for the same state. - updateGeneratorMainType(forceRegeneration = true) { mutableStateFlow.value.selectedType } + updateGeneratorMainType(forceRegeneration = true) { state.selectedType } } private fun handleCopyClick() { @@ -1294,7 +1294,7 @@ class GeneratorViewModel @Inject constructor( forceRegeneration: Boolean = false, crossinline block: (GeneratorState.MainType) -> GeneratorState.MainType?, ) { - val currentSelectedType = mutableStateFlow.value.selectedType + val currentSelectedType = state.selectedType val updatedMainType = block(currentSelectedType) ?: return mutableStateFlow.update { it.copy(selectedType = updatedMainType) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index 9dd12741b9..6d7fa2c035 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -434,7 +434,7 @@ class VaultAddEditViewModel @Inject constructor( when (action.customFieldAction) { CustomFieldAction.MOVE_UP -> { val items = - (mutableStateFlow.value.viewState as VaultAddEditState.ViewState.Content) + (state.viewState as VaultAddEditState.ViewState.Content) .common .customFieldData .toMutableList() @@ -455,7 +455,7 @@ class VaultAddEditViewModel @Inject constructor( CustomFieldAction.MOVE_DOWN -> { val items = - (mutableStateFlow.value.viewState as VaultAddEditState.ViewState.Content) + (state.viewState as VaultAddEditState.ViewState.Content) .common .customFieldData .toMutableList() diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/resetPassword/ResetPasswordViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/resetPassword/ResetPasswordViewModelTest.kt index 40da87c72a..ad613e2acb 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/resetPassword/ResetPasswordViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/resetPassword/ResetPasswordViewModelTest.kt @@ -42,75 +42,67 @@ class ResetPasswordViewModelTest : BaseViewModelTest() { } @Test - fun `CurrentPasswordInputChanged should update the current password input in the state`() = - runTest { - val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.CurrentPasswordInputChanged("Test123")) - - assertEquals( - DEFAULT_STATE.copy( - currentPasswordInput = "Test123", - ), - viewModel.stateFlow.value, - ) - } - } - - @Test - fun `SubmitClicked with blank password shows error alert`() = runTest { + fun `CurrentPasswordInputChanged should update the current password input in the state`() { val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.SubmitClick) + viewModel.trySendAction(ResetPasswordAction.CurrentPasswordInputChanged("Test123")) - assertEquals( - DEFAULT_STATE.copy( - dialogState = ResetPasswordState.DialogState.Error( - title = null, - message = R.string.validation_field_required - .asText(R.string.master_password.asText()), - ), - ), - viewModel.stateFlow.value, - ) - - // Dismiss the alert. - viewModel.trySendAction(ResetPasswordAction.DialogDismiss) - assertEquals( - DEFAULT_STATE, - viewModel.stateFlow.value, - ) - } + assertEquals( + DEFAULT_STATE.copy( + currentPasswordInput = "Test123", + ), + viewModel.stateFlow.value, + ) } @Test - fun `SubmitClicked with invalid password shows error alert for weak password reason`() = - runTest { - val password = "Test123" - coEvery { - authRepository.validatePasswordAgainstPolicies(password) - } returns false + fun `SubmitClicked with blank password shows error alert`() { + val viewModel = createViewModel() + viewModel.trySendAction(ResetPasswordAction.SubmitClick) - val viewModel = createViewModel() - viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password)) - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.SubmitClick) + assertEquals( + DEFAULT_STATE.copy( + dialogState = ResetPasswordState.DialogState.Error( + title = null, + message = R.string.validation_field_required + .asText(R.string.master_password.asText()), + ), + ), + viewModel.stateFlow.value, + ) - assertEquals( - DEFAULT_STATE.copy( - dialogState = ResetPasswordState.DialogState.Error( - title = R.string.master_password_policy_validation_title.asText(), - message = R.string.master_password_policy_validation_message.asText(), - ), - passwordInput = password, - ), - viewModel.stateFlow.value, - ) - } - } + // Dismiss the alert. + viewModel.trySendAction(ResetPasswordAction.DialogDismiss) + assertEquals( + DEFAULT_STATE, + viewModel.stateFlow.value, + ) + } @Test - fun `SubmitClicked with invalid password shows error alert for admin reset reason`() = runTest { + fun `SubmitClicked with invalid password shows error alert for weak password reason`() { + val password = "Test123" + coEvery { + authRepository.validatePasswordAgainstPolicies(password) + } returns false + + val viewModel = createViewModel() + viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password)) + viewModel.trySendAction(ResetPasswordAction.SubmitClick) + + assertEquals( + DEFAULT_STATE.copy( + dialogState = ResetPasswordState.DialogState.Error( + title = R.string.master_password_policy_validation_title.asText(), + message = R.string.master_password_policy_validation_message.asText(), + ), + passwordInput = password, + ), + viewModel.stateFlow.value, + ) + } + + @Test + fun `SubmitClicked with invalid password shows error alert for admin reset reason`() { val password = "Test123" every { authRepository.passwordResetReason @@ -118,26 +110,24 @@ class ResetPasswordViewModelTest : BaseViewModelTest() { val viewModel = createViewModel() viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password)) - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.SubmitClick) + viewModel.trySendAction(ResetPasswordAction.SubmitClick) - assertEquals( - DEFAULT_STATE.copy( - resetReason = ForcePasswordResetReason.ADMIN_FORCE_PASSWORD_RESET, - dialogState = ResetPasswordState.DialogState.Error( - title = null, - message = R.string.master_password_length_val_message_x - .asText(MIN_PASSWORD_LENGTH), - ), - passwordInput = password, + assertEquals( + DEFAULT_STATE.copy( + resetReason = ForcePasswordResetReason.ADMIN_FORCE_PASSWORD_RESET, + dialogState = ResetPasswordState.DialogState.Error( + title = null, + message = R.string.master_password_length_val_message_x + .asText(MIN_PASSWORD_LENGTH), ), - viewModel.stateFlow.value, - ) - } + passwordInput = password, + ), + viewModel.stateFlow.value, + ) } @Test - fun `SubmitClicked with non-matching retyped password shows error alert`() = runTest { + fun `SubmitClicked with non-matching retyped password shows error alert`() { val password = "Test123" coEvery { authRepository.validatePasswordAgainstPolicies(password) @@ -146,24 +136,22 @@ class ResetPasswordViewModelTest : BaseViewModelTest() { val viewModel = createViewModel() viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password)) - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.SubmitClick) + viewModel.trySendAction(ResetPasswordAction.SubmitClick) - assertEquals( - DEFAULT_STATE.copy( - dialogState = ResetPasswordState.DialogState.Error( - title = null, - message = R.string.master_password_confirmation_val_message.asText(), - ), - passwordInput = password, + assertEquals( + DEFAULT_STATE.copy( + dialogState = ResetPasswordState.DialogState.Error( + title = null, + message = R.string.master_password_confirmation_val_message.asText(), ), - viewModel.stateFlow.value, - ) - } + passwordInput = password, + ), + viewModel.stateFlow.value, + ) } @Test - fun `SubmitClicked with error for validating current password shows error alert`() = runTest { + fun `SubmitClicked with error for validating current password shows error alert`() { val currentPassword = "CurrentTest123" val password = "Test123" coEvery { @@ -178,26 +166,24 @@ class ResetPasswordViewModelTest : BaseViewModelTest() { viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password)) viewModel.trySendAction(ResetPasswordAction.RetypePasswordInputChanged(password)) - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.SubmitClick) + viewModel.trySendAction(ResetPasswordAction.SubmitClick) - assertEquals( - DEFAULT_STATE.copy( - dialogState = ResetPasswordState.DialogState.Error( - title = null, - message = R.string.generic_error_message.asText(), - ), - currentPasswordInput = currentPassword, - passwordInput = password, - retypePasswordInput = password, + assertEquals( + DEFAULT_STATE.copy( + dialogState = ResetPasswordState.DialogState.Error( + title = null, + message = R.string.generic_error_message.asText(), ), - viewModel.stateFlow.value, - ) - } + currentPasswordInput = currentPassword, + passwordInput = password, + retypePasswordInput = password, + ), + viewModel.stateFlow.value, + ) } @Test - fun `SubmitClicked with invalid current password shows alert`() = runTest { + fun `SubmitClicked with invalid current password shows alert`() { val currentPassword = "CurrentTest123" val password = "Test123" coEvery { @@ -212,22 +198,20 @@ class ResetPasswordViewModelTest : BaseViewModelTest() { viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password)) viewModel.trySendAction(ResetPasswordAction.RetypePasswordInputChanged(password)) - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.SubmitClick) + viewModel.trySendAction(ResetPasswordAction.SubmitClick) - assertEquals( - DEFAULT_STATE.copy( - dialogState = ResetPasswordState.DialogState.Error( - title = null, - message = R.string.invalid_master_password.asText(), - ), - currentPasswordInput = currentPassword, - passwordInput = password, - retypePasswordInput = password, + assertEquals( + DEFAULT_STATE.copy( + dialogState = ResetPasswordState.DialogState.Error( + title = null, + message = R.string.invalid_master_password.asText(), ), - viewModel.stateFlow.value, - ) - } + currentPasswordInput = currentPassword, + passwordInput = password, + retypePasswordInput = password, + ), + viewModel.stateFlow.value, + ) } @Test @@ -289,49 +273,42 @@ class ResetPasswordViewModelTest : BaseViewModelTest() { } @Test - fun `PasswordInputChanged should update the password input in the state`() = runTest { + fun `PasswordInputChanged should update the password input in the state`() { val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged("Test123")) + viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged("Test123")) - assertEquals( - DEFAULT_STATE.copy( - passwordInput = "Test123", - ), - viewModel.stateFlow.value, - ) - } + assertEquals( + DEFAULT_STATE.copy( + passwordInput = "Test123", + ), + viewModel.stateFlow.value, + ) } @Test - fun `RetypePasswordInputChanged should update the retype password input in the state`() = - runTest { - val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.RetypePasswordInputChanged("Test123")) + fun `RetypePasswordInputChanged should update the retype password input in the state`() { + val viewModel = createViewModel() + viewModel.trySendAction(ResetPasswordAction.RetypePasswordInputChanged("Test123")) - assertEquals( - DEFAULT_STATE.copy( - retypePasswordInput = "Test123", - ), - viewModel.stateFlow.value, - ) - } - } + assertEquals( + DEFAULT_STATE.copy( + retypePasswordInput = "Test123", + ), + viewModel.stateFlow.value, + ) + } @Test - fun `PasswordHintInputChanged should update the password hint input in the state`() = runTest { + fun `PasswordHintInputChanged should update the password hint input in the state`() { val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(ResetPasswordAction.PasswordHintInputChanged("Test123")) + viewModel.trySendAction(ResetPasswordAction.PasswordHintInputChanged("Test123")) - assertEquals( - DEFAULT_STATE.copy( - passwordHintInput = "Test123", - ), - viewModel.stateFlow.value, - ) - } + assertEquals( + DEFAULT_STATE.copy( + passwordHintInput = "Test123", + ), + viewModel.stateFlow.value, + ) } private fun createViewModel(): ResetPasswordViewModel = diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt index 1b80769744..f8e7a968cf 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/twofactorlogin/TwoFactorLoginViewModelTest.kt @@ -162,24 +162,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() { runTest { val input = "123456" val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(TwoFactorLoginAction.CodeInputChanged(input)) - assertEquals( - DEFAULT_STATE.copy( - codeInput = input, - isContinueButtonEnabled = true, - ), - viewModel.stateFlow.value, - ) - } - } - - @Test - fun `CodeInputChanged should update input and disable button if code is blank`() = runTest { - val input = "123456" - val viewModel = createViewModel() - viewModel.eventFlow.test { - // Set it to true. viewModel.trySendAction(TwoFactorLoginAction.CodeInputChanged(input)) assertEquals( DEFAULT_STATE.copy( @@ -188,17 +170,31 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() { ), viewModel.stateFlow.value, ) - - // Set it to false. - viewModel.trySendAction(TwoFactorLoginAction.CodeInputChanged("")) - assertEquals( - DEFAULT_STATE.copy( - codeInput = "", - isContinueButtonEnabled = false, - ), - viewModel.stateFlow.value, - ) } + + @Test + fun `CodeInputChanged should update input and disable button if code is blank`() { + val input = "123456" + val viewModel = createViewModel() + // Set it to true. + viewModel.trySendAction(TwoFactorLoginAction.CodeInputChanged(input)) + assertEquals( + DEFAULT_STATE.copy( + codeInput = input, + isContinueButtonEnabled = true, + ), + viewModel.stateFlow.value, + ) + + // Set it to false. + viewModel.trySendAction(TwoFactorLoginAction.CodeInputChanged("")) + assertEquals( + DEFAULT_STATE.copy( + codeInput = "", + isContinueButtonEnabled = false, + ), + viewModel.stateFlow.value, + ) } @Test @@ -415,17 +411,15 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() { } @Test - fun `RememberMeToggle should update the state`() = runTest { + fun `RememberMeToggle should update the state`() { val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(TwoFactorLoginAction.RememberMeToggle(true)) - assertEquals( - DEFAULT_STATE.copy( - isRememberMeEnabled = true, - ), - viewModel.stateFlow.value, - ) - } + viewModel.trySendAction(TwoFactorLoginAction.RememberMeToggle(true)) + assertEquals( + DEFAULT_STATE.copy( + isRememberMeEnabled = true, + ), + viewModel.stateFlow.value, + ) } @Test @@ -534,21 +528,19 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() { } @Test - fun `SelectAuthMethod with other method should update the state`() = runTest { + fun `SelectAuthMethod with other method should update the state`() { val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction( - TwoFactorLoginAction.SelectAuthMethod( - TwoFactorAuthMethod.AUTHENTICATOR_APP, - ), - ) - assertEquals( - DEFAULT_STATE.copy( - authMethod = TwoFactorAuthMethod.AUTHENTICATOR_APP, - ), - viewModel.stateFlow.value, - ) - } + viewModel.trySendAction( + TwoFactorLoginAction.SelectAuthMethod( + TwoFactorAuthMethod.AUTHENTICATOR_APP, + ), + ) + assertEquals( + DEFAULT_STATE.copy( + authMethod = TwoFactorAuthMethod.AUTHENTICATOR_APP, + ), + viewModel.stateFlow.value, + ) } @Test @@ -569,8 +561,8 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() { ), awaitItem(), ) + } } - } @Test @Suppress("MaxLineLength") 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 ca4a5b2f50..a0f12e4f02 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 @@ -86,137 +86,130 @@ class ExportVaultViewModelTest : BaseViewModelTest() { } @Test - fun `ConfirmExportVaultClicked correct password should call exportVaultDataToString`() = - runTest { - val password = "password" - coEvery { - authRepository.validatePassword( - password = password, - ) - } returns ValidatePasswordResult.Success(isValid = true) + fun `ConfirmExportVaultClicked correct password should call exportVaultDataToString`() { + val password = "password" + coEvery { + authRepository.validatePassword( + password = password, + ) + } returns ValidatePasswordResult.Success(isValid = true) - val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password)) + val viewModel = createViewModel() + viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password)) - viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked) + viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked) - coVerify { - vaultRepository.exportVaultDataToString(any()) - } - } + coVerify { + vaultRepository.exportVaultDataToString(any()) } + } @Test - fun `ConfirmExportVaultClicked blank password should show an error`() = runTest { + fun `ConfirmExportVaultClicked blank password should show an error`() { val viewModel = createViewModel() - viewModel.eventFlow.test { - 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.master_password.asText(), - ), + 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.master_password.asText(), ), ), - viewModel.stateFlow.value, - ) + ), + viewModel.stateFlow.value, + ) - viewModel.trySendAction(ExportVaultAction.DialogDismiss) - assertEquals( - DEFAULT_STATE, - viewModel.stateFlow.value, - ) - } + viewModel.trySendAction(ExportVaultAction.DialogDismiss) + assertEquals( + DEFAULT_STATE, + viewModel.stateFlow.value, + ) } @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(), - ), + fun `ConfirmExportVaultClicked blank file password should show an error when export type is JSON_ENCRYPTED`() { + 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, - ) + 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, - ) - } + 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, + fun `ConfirmExportVaultClicked blank confirm file password should show an error when export type is JSON_ENCRYPTED`() { + val password = "password" + coEvery { + authRepository.getPasswordStrength( + email = EMAIL_ADDRESS, + password = password, ) - 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(), - ), + } 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, - ) + 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, - ) - } + 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 { + fun `ConfirmExportVaultClicked invalid password should show an error`() { val password = "password" coEvery { authRepository.validatePassword( @@ -225,21 +218,19 @@ class ExportVaultViewModelTest : BaseViewModelTest() { } returns ValidatePasswordResult.Success(isValid = false) val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password)) + 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.invalid_master_password.asText(), - ), - passwordInput = password, + viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked) + assertEquals( + DEFAULT_STATE.copy( + dialogState = ExportVaultState.DialogState.Error( + title = R.string.an_error_has_occurred.asText(), + message = R.string.invalid_master_password.asText(), ), - viewModel.stateFlow.value, - ) - } + passwordInput = password, + ), + viewModel.stateFlow.value, + ) coVerify { authRepository.validatePassword( password = password, @@ -248,7 +239,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() { } @Test - fun `ConfirmExportVaultClicked error checking password should show an error`() = runTest { + fun `ConfirmExportVaultClicked error checking password should show an error`() { val password = "password" coEvery { authRepository.validatePassword( @@ -257,40 +248,35 @@ class ExportVaultViewModelTest : BaseViewModelTest() { } returns ValidatePasswordResult.Error val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password)) + 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.generic_error_message.asText(), - ), - passwordInput = password, + viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked) + assertEquals( + DEFAULT_STATE.copy( + dialogState = ExportVaultState.DialogState.Error( + title = R.string.an_error_has_occurred.asText(), + message = R.string.generic_error_message.asText(), ), - viewModel.stateFlow.value, - ) - } + passwordInput = password, + ), + viewModel.stateFlow.value, + ) } @Test - fun `ExportFormatOptionSelect should update the selected export format in the state`() = - runTest { - val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction( - ExportVaultAction.ExportFormatOptionSelect(ExportVaultFormat.CSV), - ) + fun `ExportFormatOptionSelect should update the selected export format in the state`() { + val viewModel = createViewModel() + viewModel.trySendAction( + ExportVaultAction.ExportFormatOptionSelect(ExportVaultFormat.CSV), + ) - assertEquals( - DEFAULT_STATE.copy( - exportFormat = ExportVaultFormat.CSV, - ), - viewModel.stateFlow.value, - ) - } - } + assertEquals( + DEFAULT_STATE.copy( + exportFormat = ExportVaultFormat.CSV, + ), + viewModel.stateFlow.value, + ) + } @Test fun `ConfirmFilePasswordInputChanged should update the confirm password input in the state`() { @@ -348,26 +334,25 @@ class ExportVaultViewModelTest : BaseViewModelTest() { } @Test - fun `ReceiveExportVaultDataToStringResult should update state to error if result is error`() = - runTest { - val viewModel = createViewModel() + fun `ReceiveExportVaultDataToStringResult should update state to error if result is error`() { + val viewModel = createViewModel() - viewModel.trySendAction( - ExportVaultAction.Internal.ReceiveExportVaultDataToStringResult( - result = ExportVaultDataResult.Error, - ), - ) + viewModel.trySendAction( + ExportVaultAction.Internal.ReceiveExportVaultDataToStringResult( + result = ExportVaultDataResult.Error, + ), + ) - assertEquals( - DEFAULT_STATE.copy( - dialogState = ExportVaultState.DialogState.Error( - title = R.string.an_error_has_occurred.asText(), - message = R.string.export_vault_failure.asText(), - ), + assertEquals( + DEFAULT_STATE.copy( + dialogState = ExportVaultState.DialogState.Error( + title = R.string.an_error_has_occurred.asText(), + message = R.string.export_vault_failure.asText(), ), - viewModel.stateFlow.value, - ) - } + ), + viewModel.stateFlow.value, + ) + } @Suppress("MaxLineLength") @Test @@ -493,37 +478,36 @@ class ExportVaultViewModelTest : BaseViewModelTest() { } @Test - fun `ExportLocationReceive should update state to error if saving the data fails`() = - runTest { - val exportData = "TestExportVaultData" - val viewModel = createViewModel( - DEFAULT_STATE.copy( - exportData = exportData, - ), - ) - val uri = mockk() - coEvery { - fileManager.stringToUri(fileUri = any(), dataString = exportData) - } returns false + fun `ExportLocationReceive should update state to error if saving the data fails`() { + val exportData = "TestExportVaultData" + val viewModel = createViewModel( + DEFAULT_STATE.copy( + exportData = exportData, + ), + ) + val uri = mockk() + coEvery { + fileManager.stringToUri(fileUri = any(), dataString = exportData) + } returns false - viewModel.trySendAction(ExportVaultAction.ExportLocationReceive(fileUri = uri)) + viewModel.trySendAction(ExportVaultAction.ExportLocationReceive(fileUri = uri)) - coVerify { - fileManager.stringToUri(fileUri = any(), dataString = exportData) - } - - assertEquals( - DEFAULT_STATE.copy( - exportData = exportData, - dialogState = ExportVaultState.DialogState.Error( - title = R.string.an_error_has_occurred.asText(), - message = R.string.export_vault_failure.asText(), - ), - ), - viewModel.stateFlow.value, - ) + coVerify { + fileManager.stringToUri(fileUri = any(), dataString = exportData) } + assertEquals( + DEFAULT_STATE.copy( + exportData = exportData, + dialogState = ExportVaultState.DialogState.Error( + title = R.string.an_error_has_occurred.asText(), + message = R.string.export_vault_failure.asText(), + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `ExportLocationReceive should emit ShowToast on success`() = runTest { val exportData = "TestExportVaultData"