mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-918: Resend notification emails (#792)
This commit is contained in:
parent
555ff1dcd2
commit
5fa49c8b53
12 changed files with 309 additions and 9 deletions
|
@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJso
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailJsonRequest
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
|
@ -12,7 +13,6 @@ import retrofit2.http.POST
|
|||
* Defines raw calls under the /accounts API.
|
||||
*/
|
||||
interface AccountsApi {
|
||||
|
||||
@POST("/accounts/prelogin")
|
||||
suspend fun preLogin(@Body body: PreLoginRequestJson): Result<PreLoginResponseJson>
|
||||
|
||||
|
@ -23,4 +23,9 @@ interface AccountsApi {
|
|||
suspend fun passwordHintRequest(
|
||||
@Body body: PasswordHintRequestJson,
|
||||
): Result<Unit>
|
||||
|
||||
@POST("/two-factor/send-email-login")
|
||||
suspend fun resendVerificationCodeEmail(
|
||||
@Body body: ResendEmailJsonRequest,
|
||||
): Result<Unit>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Hold the information necessary to resend the email with the
|
||||
* two-factor verification code.
|
||||
*
|
||||
* @property deviceIdentifier The device identifier.
|
||||
* @property email The user's email address.
|
||||
* @property passwordHash The master password hash, if the user is logging
|
||||
* in via the master password.
|
||||
* @property ssoToken The sso token, if the user is logging in via single sign on.
|
||||
*/
|
||||
@Serializable
|
||||
data class ResendEmailJsonRequest(
|
||||
@SerialName("DeviceIdentifier")
|
||||
val deviceIdentifier: String,
|
||||
|
||||
@SerialName("Email")
|
||||
val email: String,
|
||||
|
||||
@SerialName("MasterPasswordHash")
|
||||
val passwordHash: String?,
|
||||
|
||||
@SerialName("SsoEmail2FaSessionToken")
|
||||
val ssoToken: String?,
|
||||
)
|
|
@ -4,6 +4,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRespon
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailJsonRequest
|
||||
|
||||
/**
|
||||
* Provides an API for querying accounts endpoints.
|
||||
|
@ -29,4 +30,9 @@ interface AccountsService {
|
|||
* Request a password hint.
|
||||
*/
|
||||
suspend fun requestPasswordHint(email: String): Result<PasswordHintResponseJson>
|
||||
|
||||
/**
|
||||
* Resend the email with the two-factor verification code.
|
||||
*/
|
||||
suspend fun resendVerificationCodeEmail(body: ResendEmailJsonRequest): Result<Unit>
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJso
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailJsonRequest
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
||||
import kotlinx.serialization.json.Json
|
||||
|
@ -58,4 +59,7 @@ class AccountsServiceImpl constructor(
|
|||
)
|
||||
?: throw throwable
|
||||
}
|
||||
|
||||
override suspend fun resendVerificationCodeEmail(body: ResendEmailJsonRequest): Result<Unit> =
|
||||
accountsApi.resendVerificationCodeEmail(body = body)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.PasswordHintResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PrevalidateSsoResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserFingerprintResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -106,6 +107,11 @@ interface AuthRepository : AuthenticatorProvider {
|
|||
*/
|
||||
fun logout()
|
||||
|
||||
/**
|
||||
* Resend the email with the two-factor verification code.
|
||||
*/
|
||||
suspend fun resendVerificationCodeEmail(): ResendEmailResult
|
||||
|
||||
/**
|
||||
* Switches to the account corresponding to the given [userId] if possible.
|
||||
*/
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRespon
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailJsonRequest
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
|
@ -34,6 +35,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.PasswordHintResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PrevalidateSsoResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserFingerprintResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -96,6 +98,11 @@ class AuthRepositoryImpl(
|
|||
*/
|
||||
private var identityTokenAuthModel: IdentityTokenAuthModel? = null
|
||||
|
||||
/**
|
||||
* The information necessary to resend the verification code email for two-factor login.
|
||||
*/
|
||||
private var resendEmailJsonRequest: ResendEmailJsonRequest? = null
|
||||
|
||||
/**
|
||||
* A scope intended for use when simply collecting multiple flows in order to combine them. The
|
||||
* use of [Dispatchers.Unconfined] allows for this to happen synchronously whenever any of
|
||||
|
@ -272,9 +279,22 @@ class AuthRepositoryImpl(
|
|||
onSuccess = { loginResponse ->
|
||||
when (loginResponse) {
|
||||
is CaptchaRequired -> LoginResult.CaptchaRequired(loginResponse.captchaKey)
|
||||
|
||||
is TwoFactorRequired -> {
|
||||
// Cache the data necessary for the remaining two-factor auth flow.
|
||||
identityTokenAuthModel = authModel
|
||||
twoFactorResponse = loginResponse
|
||||
resendEmailJsonRequest = ResendEmailJsonRequest(
|
||||
deviceIdentifier = authDiskSource.uniqueAppId,
|
||||
email = email,
|
||||
passwordHash = authModel.password,
|
||||
ssoToken = loginResponse.ssoToken,
|
||||
)
|
||||
|
||||
// If this error was received, it also means any cached two-factor
|
||||
// token is invalid.
|
||||
authDiskSource.storeTwoFactorToken(email, null)
|
||||
|
||||
LoginResult.TwoFactorRequired
|
||||
}
|
||||
|
||||
|
@ -300,6 +320,7 @@ class AuthRepositoryImpl(
|
|||
// Remove any cached data after successfully logging in.
|
||||
identityTokenAuthModel = null
|
||||
twoFactorResponse = null
|
||||
resendEmailJsonRequest = null
|
||||
|
||||
// Attempt to unlock the vault if possible.
|
||||
password?.let {
|
||||
|
@ -373,6 +394,14 @@ class AuthRepositoryImpl(
|
|||
if (wasActiveUser) vaultRepository.clearUnlockedData()
|
||||
}
|
||||
|
||||
override suspend fun resendVerificationCodeEmail(): ResendEmailResult =
|
||||
resendEmailJsonRequest?.let { jsonRequest ->
|
||||
accountsService.resendVerificationCodeEmail(body = jsonRequest).fold(
|
||||
onFailure = { ResendEmailResult.Error(message = it.message) },
|
||||
onSuccess = { ResendEmailResult.Success },
|
||||
)
|
||||
} ?: ResendEmailResult.Error(message = null)
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override fun switchAccount(userId: String): SwitchAccountResult {
|
||||
val currentUserState = authDiskSource.userState
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.model
|
||||
|
||||
/**
|
||||
* Models result of resend email request.
|
||||
*/
|
||||
sealed class ResendEmailResult {
|
||||
|
||||
/**
|
||||
* Resend email request success
|
||||
*/
|
||||
data object Success : ResendEmailResult()
|
||||
|
||||
/**
|
||||
* There was an error.
|
||||
*/
|
||||
data class Error(val message: String?) : ResendEmailResult()
|
||||
}
|
|
@ -81,7 +81,7 @@ fun TwoFactorLoginScreen(
|
|||
}
|
||||
|
||||
is TwoFactorLoginEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, event.message(context.resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.util.preferredAuthMethod
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.util.twoFactorDisplayEmail
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
|
@ -31,6 +32,7 @@ private const val KEY_STATE = "state"
|
|||
* Manages application state for the Two-Factor Login screen.
|
||||
*/
|
||||
@HiltViewModel
|
||||
@Suppress("TooManyFunctions")
|
||||
class TwoFactorLoginViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
|
@ -83,6 +85,9 @@ class TwoFactorLoginViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is TwoFactorLoginAction.Internal.ReceiveLoginResult -> handleReceiveLoginResult(action)
|
||||
is TwoFactorLoginAction.Internal.ReceiveResendEmailResult -> {
|
||||
handleReceiveResendEmailResult(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,6 +218,39 @@ class TwoFactorLoginViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the resend email result.
|
||||
*/
|
||||
private fun handleReceiveResendEmailResult(
|
||||
action: TwoFactorLoginAction.Internal.ReceiveResendEmailResult,
|
||||
) {
|
||||
// Dismiss the loading overlay.
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
|
||||
when (action.resendEmailResult) {
|
||||
// Display a dialog for an error result.
|
||||
is ResendEmailResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = TwoFactorLoginState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.verification_email_not_sent.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Display a toast for a successful result.
|
||||
ResendEmailResult.Success -> {
|
||||
sendEvent(
|
||||
TwoFactorLoginEvent.ShowToast(
|
||||
message = R.string.verification_email_sent.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state with the new toggle value.
|
||||
*/
|
||||
|
@ -228,8 +266,29 @@ class TwoFactorLoginViewModel @Inject constructor(
|
|||
* Resend the verification code email.
|
||||
*/
|
||||
private fun handleResendEmailClick() {
|
||||
// TODO: Finish implementation (BIT-918)
|
||||
sendEvent(TwoFactorLoginEvent.ShowToast("Not yet implemented"))
|
||||
// Ensure that the user is in fact verifying with email.
|
||||
if (mutableStateFlow.value.authMethod != TwoFactorAuthMethod.EMAIL) {
|
||||
return
|
||||
}
|
||||
|
||||
// Show the loading overlay.
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = TwoFactorLoginState.DialogState.Loading(
|
||||
message = R.string.submitting.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Resend the email notification.
|
||||
viewModelScope.launch {
|
||||
val result = authRepository.resendVerificationCodeEmail()
|
||||
sendAction(
|
||||
TwoFactorLoginAction.Internal.ReceiveResendEmailResult(
|
||||
resendEmailResult = result,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -312,7 +371,7 @@ sealed class TwoFactorLoginEvent {
|
|||
* Shows a toast with the given [message].
|
||||
*/
|
||||
data class ShowToast(
|
||||
val message: String,
|
||||
val message: Text,
|
||||
) : TwoFactorLoginEvent()
|
||||
}
|
||||
|
||||
|
@ -378,5 +437,12 @@ sealed class TwoFactorLoginAction {
|
|||
data class ReceiveLoginResult(
|
||||
val loginResult: LoginResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates a resend email result has been received.
|
||||
*/
|
||||
data class ReceiveResendEmailResult(
|
||||
val resendEmailResult: ResendEmailResult,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRespon
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailJsonRequest
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.Json
|
||||
|
@ -211,6 +212,21 @@ class AccountsServiceTest : BaseServiceTest() {
|
|||
assertEquals(PasswordHintResponseJson.Success, result.getOrNull())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resendVerificationCodeEmail with empty response is success`() = runTest {
|
||||
val response = MockResponse().setBody("")
|
||||
server.enqueue(response)
|
||||
val result = service.resendVerificationCodeEmail(
|
||||
body = ResendEmailJsonRequest(
|
||||
deviceIdentifier = "3",
|
||||
email = "example@email.com",
|
||||
passwordHash = "37y4d8r379r4789nt387r39k3dr87nr93",
|
||||
ssoToken = null,
|
||||
),
|
||||
)
|
||||
assertTrue(result.isSuccess)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EMAIL = "email"
|
||||
private val registerRequestBody = RegisterRequestJson(
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PrevalidateSsoResp
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailJsonRequest
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
|
@ -45,6 +46,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.PasswordHintResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PrevalidateSsoResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserFingerprintResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations
|
||||
|
@ -1418,6 +1420,72 @@ class AuthRepositoryTest {
|
|||
verify(exactly = 0) { vaultRepository.clearUnlockedData() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resendVerificationCodeEmail uses cached request data to make api call`() = runTest {
|
||||
// Attempt a normal login with a two factor error first, so that the necessary
|
||||
// data will be cached.
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns Result.success(PRE_LOGIN_SUCCESS)
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
} returns Result.success(
|
||||
GetTokenResponseJson.TwoFactorRequired(
|
||||
TWO_FACTOR_AUTH_METHODS_DATA, null, null,
|
||||
),
|
||||
)
|
||||
val firstResult = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.TwoFactorRequired, firstResult)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
}
|
||||
|
||||
// Resend the verification code email.
|
||||
coEvery {
|
||||
accountsService.resendVerificationCodeEmail(
|
||||
body = ResendEmailJsonRequest(
|
||||
deviceIdentifier = UNIQUE_APP_ID,
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
ssoToken = null,
|
||||
),
|
||||
)
|
||||
} returns Result.success(Unit)
|
||||
val resendEmailResult = repository.resendVerificationCodeEmail()
|
||||
assertEquals(ResendEmailResult.Success, resendEmailResult)
|
||||
coVerify {
|
||||
accountsService.resendVerificationCodeEmail(
|
||||
body = ResendEmailJsonRequest(
|
||||
deviceIdentifier = UNIQUE_APP_ID,
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
ssoToken = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resendVerificationCodeEmail returns error if no request data cached`() = runTest {
|
||||
val result = repository.resendVerificationCodeEmail()
|
||||
assertEquals(ResendEmailResult.Error(message = null), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `switchAccount when there is no saved UserState should do nothing`() {
|
||||
val updatedUserId = USER_ID_2
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMetho
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
|
@ -239,7 +240,7 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `ContinueButtonClick login returns Error should update errorStateDialog`() = runTest {
|
||||
fun `ContinueButtonClick login returns Error should update dialogState`() = runTest {
|
||||
coEvery {
|
||||
authRepository.login(
|
||||
email = "example@email.com",
|
||||
|
@ -309,17 +310,70 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `ResendEmailClick should emit ShowToast`() = runTest {
|
||||
fun `ResendEmailClick returns success should emit ShowToast`() = runTest {
|
||||
coEvery {
|
||||
authRepository.resendVerificationCodeEmail()
|
||||
} returns ResendEmailResult.Success
|
||||
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(TwoFactorLoginAction.ResendEmailClick)
|
||||
viewModel.actionChannel.trySend(
|
||||
TwoFactorLoginAction.SelectAuthMethod(
|
||||
TwoFactorAuthMethod.EMAIL,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
TwoFactorLoginEvent.ShowToast("Not yet implemented"),
|
||||
DEFAULT_STATE.copy(authMethod = TwoFactorAuthMethod.EMAIL),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(TwoFactorLoginAction.ResendEmailClick)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(authMethod = TwoFactorAuthMethod.EMAIL),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
TwoFactorLoginEvent.ShowToast(message = R.string.verification_email_sent.asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ResendEmailClick returns error should update dialogState`() = runTest {
|
||||
coEvery {
|
||||
authRepository.resendVerificationCodeEmail()
|
||||
} returns ResendEmailResult.Error(message = null)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(
|
||||
TwoFactorLoginAction.SelectAuthMethod(
|
||||
TwoFactorAuthMethod.EMAIL,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(authMethod = TwoFactorAuthMethod.EMAIL),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(TwoFactorLoginAction.ResendEmailClick)
|
||||
|
||||
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(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SelectAuthMethod with RECOVERY_CODE should launch the NavigateToRecoveryCode event`() =
|
||||
runTest {
|
||||
|
|
Loading…
Reference in a new issue