BIT-1965: Send email 2FA verification when switching to screen. (#1096)

This commit is contained in:
Ramsey Smith 2024-03-06 11:54:04 -07:00 committed by Álison Fernandes
parent 4dddd1e210
commit a8f76488da
2 changed files with 128 additions and 18 deletions

View file

@ -298,11 +298,13 @@ class TwoFactorLoginViewModel @Inject constructor(
// Display a toast for a successful result.
ResendEmailResult.Success -> {
sendEvent(
TwoFactorLoginEvent.ShowToast(
message = R.string.verification_email_sent.asText(),
),
)
if (action.isUserInitiated) {
sendEvent(
TwoFactorLoginEvent.ShowToast(
message = R.string.verification_email_sent.asText(),
),
)
}
}
}
}
@ -342,6 +344,7 @@ class TwoFactorLoginViewModel @Inject constructor(
sendAction(
TwoFactorLoginAction.Internal.ReceiveResendEmailResult(
resendEmailResult = result,
isUserInitiated = true,
),
)
}
@ -351,13 +354,35 @@ class TwoFactorLoginViewModel @Inject constructor(
* Update the state with the auth method or opens the url for the recovery code.
*/
private fun handleSelectAuthMethod(action: TwoFactorLoginAction.SelectAuthMethod) {
if (action.authMethod == TwoFactorAuthMethod.RECOVERY_CODE) {
sendEvent(TwoFactorLoginEvent.NavigateToRecoveryCode)
} else {
mutableStateFlow.update {
it.copy(
authMethod = action.authMethod,
)
when (action.authMethod) {
TwoFactorAuthMethod.RECOVERY_CODE -> {
sendEvent(TwoFactorLoginEvent.NavigateToRecoveryCode)
}
TwoFactorAuthMethod.EMAIL -> {
if (state.authMethod != TwoFactorAuthMethod.EMAIL) {
viewModelScope.launch {
val result = authRepository.resendVerificationCodeEmail()
sendAction(
TwoFactorLoginAction.Internal.ReceiveResendEmailResult(
resendEmailResult = result,
isUserInitiated = false,
),
)
}
}
mutableStateFlow.update { it.copy(authMethod = action.authMethod) }
}
TwoFactorAuthMethod.AUTHENTICATOR_APP,
TwoFactorAuthMethod.DUO,
TwoFactorAuthMethod.YUBI_KEY,
TwoFactorAuthMethod.U2F,
TwoFactorAuthMethod.REMEMBER,
TwoFactorAuthMethod.DUO_ORGANIZATION,
TwoFactorAuthMethod.FIDO_2_WEB_APP,
-> {
mutableStateFlow.update { it.copy(authMethod = action.authMethod) }
}
}
}
@ -592,6 +617,7 @@ sealed class TwoFactorLoginAction {
*/
data class ReceiveResendEmailResult(
val resendEmailResult: ResendEmailResult,
val isUserInitiated: Boolean,
) : Internal()
}
}

View file

@ -467,7 +467,11 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
} returns ResendEmailResult.Error(message = null)
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE,
awaitItem(),
)
viewModel.actionChannel.trySend(
TwoFactorLoginAction.SelectAuthMethod(
TwoFactorAuthMethod.EMAIL,
@ -475,11 +479,8 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
)
assertEquals(
DEFAULT_STATE.copy(authMethod = TwoFactorAuthMethod.EMAIL),
viewModel.stateFlow.value,
awaitItem(),
)
viewModel.actionChannel.trySend(TwoFactorLoginAction.ResendEmailClick)
assertEquals(
DEFAULT_STATE.copy(
authMethod = TwoFactorAuthMethod.EMAIL,
@ -488,7 +489,29 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
message = R.string.verification_email_not_sent.asText(),
),
),
viewModel.stateFlow.value,
awaitItem(),
)
viewModel.actionChannel.trySend(TwoFactorLoginAction.ResendEmailClick)
assertEquals(
DEFAULT_STATE.copy(
authMethod = TwoFactorAuthMethod.EMAIL,
dialogState = TwoFactorLoginState.DialogState.Loading(
message = R.string.submitting.asText(),
),
),
awaitItem(),
)
assertEquals(
DEFAULT_STATE.copy(
authMethod = TwoFactorAuthMethod.EMAIL,
dialogState = TwoFactorLoginState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.verification_email_not_sent.asText(),
),
),
awaitItem(),
)
}
}
@ -528,6 +551,67 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
}
}
@Test
@Suppress("MaxLineLength")
fun `ReceiveResendEmailResult with ResendEmailResult Success and isUserInitiated true should ShowToast`() =
runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(
TwoFactorLoginAction.Internal.ReceiveResendEmailResult(
resendEmailResult = ResendEmailResult.Success,
isUserInitiated = true,
),
)
assertEquals(
TwoFactorLoginEvent.ShowToast(
message = R.string.verification_email_sent.asText(),
),
awaitItem(),
)
}
}
@Test
@Suppress("MaxLineLength")
fun `ReceiveResendEmailResult with ResendEmailResult Success and isUserInitiated false should not emit any events`() =
runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(
TwoFactorLoginAction.Internal.ReceiveResendEmailResult(
resendEmailResult = ResendEmailResult.Success,
isUserInitiated = false,
),
)
expectNoEvents()
}
}
@Test
fun `ReceiveResendEmailResult with ResendEmailResult Error should not emit any events`() =
runTest {
val viewModel = createViewModel()
viewModel.stateFlow.test {
assertEquals(DEFAULT_STATE, awaitItem())
viewModel.actionChannel.trySend(
TwoFactorLoginAction.Internal.ReceiveResendEmailResult(
resendEmailResult = ResendEmailResult.Error(message = null),
isUserInitiated = true,
),
)
assertEquals(
DEFAULT_STATE.copy(
dialogState = TwoFactorLoginState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.verification_email_not_sent.asText(),
),
),
awaitItem(),
)
}
}
private fun createViewModel(
state: TwoFactorLoginState? = null,
): TwoFactorLoginViewModel =