mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-04 20:34:14 +01:00
Handle additional error cases in regV5.
This commit is contained in:
+5
-1
@@ -185,7 +185,11 @@ fun PhoneNumberScreen(
|
||||
onEvent(PhoneNumberEntryScreenEvents.ConsumeOneTimeEvent)
|
||||
when (state.oneTimeEvent) {
|
||||
OneTimeEvent.NetworkError -> simpleErrorMessage = resources.getString(R.string.VerificationCodeScreen__network_error)
|
||||
is OneTimeEvent.RateLimited -> simpleErrorMessage = resources.getString(R.string.VerificationCodeScreen__too_many_attempts_try_again_in_s, state.oneTimeEvent.retryAfter.toString())
|
||||
is OneTimeEvent.RateLimited -> simpleErrorMessage = if (state.oneTimeEvent.retryAfter.isPositive()) {
|
||||
resources.getString(R.string.VerificationCodeScreen__too_many_attempts_try_again_in_s, state.oneTimeEvent.retryAfter.toString())
|
||||
} else {
|
||||
resources.getString(R.string.VerificationCodeScreen__too_many_attempts)
|
||||
}
|
||||
OneTimeEvent.UnknownError -> simpleErrorMessage = resources.getString(R.string.VerificationCodeScreen__an_unexpected_error_occurred)
|
||||
OneTimeEvent.CouldNotRequestCodeWithSelectedTransport -> simpleErrorMessage = resources.getString(R.string.VerificationCodeScreen__could_not_send_code_via_selected_method)
|
||||
OneTimeEvent.UnableToSendSms -> simpleErrorMessage = resources.getString(R.string.VerificationCodeScreen__unable_to_send_sms)
|
||||
|
||||
+28
@@ -51,6 +51,7 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
@@ -92,11 +93,38 @@ fun PinCreationScreen(
|
||||
) {
|
||||
val activePin = remember { mutableStateOf("") }
|
||||
val canSubmitPin = activePin.value.length >= 4
|
||||
val resources = LocalResources.current
|
||||
var errorMessage: String? by remember { mutableStateOf(null) }
|
||||
|
||||
BackHandler(enabled = state.isConfirmEnabled) {
|
||||
onEvent(PinCreationScreenEvents.BackToPinEntry)
|
||||
}
|
||||
|
||||
LaunchedEffect(state.oneTimeEvent) {
|
||||
val event = state.oneTimeEvent ?: return@LaunchedEffect
|
||||
onEvent(PinCreationScreenEvents.ConsumeOneTimeEvent)
|
||||
errorMessage = when (event) {
|
||||
is PinCreationState.OneTimeEvent.ServiceError -> {
|
||||
resources.getString(R.string.PinCreationScreen__service_error)
|
||||
}
|
||||
is PinCreationState.OneTimeEvent.NetworkError -> {
|
||||
if (event.retryAfter != null) {
|
||||
resources.getString(R.string.PinCreationScreen__network_error_try_again_in_s, event.retryAfter.toString())
|
||||
} else {
|
||||
resources.getString(R.string.PinCreationScreen__network_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errorMessage?.let { message ->
|
||||
Dialogs.SimpleMessageDialog(
|
||||
message = message,
|
||||
dismiss = stringResource(android.R.string.ok),
|
||||
onDismiss = { errorMessage = null }
|
||||
)
|
||||
}
|
||||
|
||||
when (val params = RegistrationScaffold.rememberLayoutParams()) {
|
||||
is RegistrationScaffold.Params.OnePane -> OnePaneLayout(
|
||||
params = params,
|
||||
|
||||
+1
@@ -13,4 +13,5 @@ sealed class PinCreationScreenEvents {
|
||||
data object LearnMore : PinCreationScreenEvents()
|
||||
data object OptOut : PinCreationScreenEvents()
|
||||
data object BackToPinEntry : PinCreationScreenEvents()
|
||||
data object ConsumeOneTimeEvent : PinCreationScreenEvents()
|
||||
}
|
||||
|
||||
+9
-2
@@ -7,6 +7,7 @@ package org.signal.registration.screens.pincreation
|
||||
|
||||
import org.signal.core.models.AccountEntropyPool
|
||||
import org.signal.core.util.censor
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class PinCreationState(
|
||||
val isAlphanumericKeyboard: Boolean = false,
|
||||
@@ -14,9 +15,15 @@ data class PinCreationState(
|
||||
val pinMismatch: Boolean = false,
|
||||
val loading: Boolean = false,
|
||||
val firstPin: String? = null,
|
||||
val accountEntropyPool: AccountEntropyPool? = null
|
||||
val accountEntropyPool: AccountEntropyPool? = null,
|
||||
val oneTimeEvent: OneTimeEvent? = null
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "PinCreationState(isAlphanumericKeyboard=$isAlphanumericKeyboard, isConfirmEnabled=$isConfirmEnabled, pinMismatch=$pinMismatch, loading=$loading, firstPin=${firstPin?.let { "${it.length} chars" }}, accountEntropyPool=${accountEntropyPool?.displayValue?.censor()})"
|
||||
return "PinCreationState(isAlphanumericKeyboard=$isAlphanumericKeyboard, isConfirmEnabled=$isConfirmEnabled, pinMismatch=$pinMismatch, loading=$loading, firstPin=${firstPin?.let { "${it.length} chars" }}, accountEntropyPool=${accountEntropyPool?.displayValue?.censor()}, oneTimeEvent=$oneTimeEvent)"
|
||||
}
|
||||
|
||||
sealed interface OneTimeEvent {
|
||||
data object ServiceError : OneTimeEvent
|
||||
data class NetworkError(val retryAfter: Duration?) : OneTimeEvent
|
||||
}
|
||||
}
|
||||
|
||||
+7
-6
@@ -23,6 +23,7 @@ import org.signal.registration.RegistrationFlowState
|
||||
import org.signal.registration.RegistrationRepository
|
||||
import org.signal.registration.RestoreDecision
|
||||
import org.signal.registration.screens.EventDrivenViewModel
|
||||
import kotlin.time.toKotlinDuration
|
||||
|
||||
/**
|
||||
* ViewModel for the PIN creation screen.
|
||||
@@ -91,6 +92,9 @@ class PinCreationViewModel(
|
||||
_state.value = state.copy(isConfirmEnabled = false)
|
||||
applyOptOut()
|
||||
}
|
||||
is PinCreationScreenEvents.ConsumeOneTimeEvent -> {
|
||||
_state.value = state.copy(oneTimeEvent = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +133,7 @@ class PinCreationViewModel(
|
||||
when (val error = result.error) {
|
||||
is NetworkController.BackupMasterKeyError.EnclaveNotFound -> {
|
||||
Log.w(TAG, "[PinSubmitted] SVR enclave not found.")
|
||||
// TODO [registration] - Report to UI and indicate to library user that pin could not be created
|
||||
throw NotImplementedError("Report to UI and indicate to library user that pin could not be created")
|
||||
state.copy(loading = false, oneTimeEvent = PinCreationState.OneTimeEvent.ServiceError)
|
||||
}
|
||||
|
||||
is NetworkController.BackupMasterKeyError.NotRegistered -> {
|
||||
@@ -143,14 +146,12 @@ class PinCreationViewModel(
|
||||
|
||||
is RequestResult.RetryableNetworkError -> {
|
||||
Log.w(TAG, "[PinSubmitted] Network error when backing up master key.", result.networkError)
|
||||
// TODO [registration] - Report to UI and indicate to library user that pin could not be created
|
||||
throw NotImplementedError("Report to UI and indicate to library user that pin could not be created")
|
||||
state.copy(loading = false, oneTimeEvent = PinCreationState.OneTimeEvent.NetworkError(result.retryAfter?.toKotlinDuration()))
|
||||
}
|
||||
|
||||
is RequestResult.ApplicationError -> {
|
||||
Log.w(TAG, "[PinSubmitted] Application error when backing up master key.", result.cause)
|
||||
// TODO [registration] - Report to UI and indicate to library user that pin could not be created
|
||||
throw NotImplementedError("Report to UI and indicate to library user that pin could not be created")
|
||||
state.copy(loading = false, oneTimeEvent = PinCreationState.OneTimeEvent.ServiceError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-7
@@ -23,6 +23,7 @@ import org.signal.registration.RegistrationFlowState
|
||||
import org.signal.registration.RegistrationRepository
|
||||
import org.signal.registration.RegistrationRoute
|
||||
import org.signal.registration.screens.EventDrivenViewModel
|
||||
import org.signal.registration.screens.util.navigateBack
|
||||
import org.signal.registration.screens.util.navigateTo
|
||||
|
||||
/**
|
||||
@@ -90,7 +91,13 @@ class PinEntryForRegistrationLockViewModel(
|
||||
return when (val error = restoreResult.error) {
|
||||
is NetworkController.RestoreMasterKeyError.WrongPin -> {
|
||||
Log.w(TAG, "[PinEntered] Wrong PIN. Tries remaining: ${error.triesRemaining}")
|
||||
state.copy(loading = false, triesRemaining = error.triesRemaining)
|
||||
if (error.triesRemaining <= 0) {
|
||||
Log.w(TAG, "[PinEntered] Out of PIN attempts. Account is locked.")
|
||||
parentEventEmitter.navigateTo(RegistrationRoute.AccountLocked(timeRemainingMs = timeRemaining))
|
||||
state
|
||||
} else {
|
||||
state.copy(loading = false, triesRemaining = error.triesRemaining)
|
||||
}
|
||||
}
|
||||
is NetworkController.RestoreMasterKeyError.NoDataFound -> {
|
||||
Log.w(TAG, "[PinEntered] No SVR data found. Account is locked.")
|
||||
@@ -144,9 +151,9 @@ class PinEntryForRegistrationLockViewModel(
|
||||
is RequestResult.NonSuccess -> {
|
||||
when (val error = registerResult.error) {
|
||||
is NetworkController.RegisterAccountError.SessionNotFoundOrNotVerified -> {
|
||||
Log.w(TAG, "[PinEntered] Session not found or verified: ${error.message}")
|
||||
// TODO [registration] - Handle session not found or verified.
|
||||
throw NotImplementedError("Handle session not found or verified")
|
||||
Log.w(TAG, "[PinEntered] Session not found or verified: ${error.message}. Resetting.")
|
||||
parentEventEmitter(RegistrationFlowEvent.ResetState)
|
||||
state
|
||||
}
|
||||
is NetworkController.RegisterAccountError.RegistrationLock -> {
|
||||
Log.w(TAG, "[PinEntered] Still getting registration lock error after providing token. This shouldn't happen. Resetting state.")
|
||||
@@ -166,9 +173,10 @@ class PinEntryForRegistrationLockViewModel(
|
||||
state.copy(loading = false, oneTimeEvent = PinEntryState.OneTimeEvent.UnknownError)
|
||||
}
|
||||
is NetworkController.RegisterAccountError.RegistrationRecoveryPasswordIncorrect -> {
|
||||
Log.w(TAG, "[PinEntered] Registration recovery password incorrect: ${error.message}")
|
||||
// TODO [registration] - Handle incorrect password
|
||||
throw NotImplementedError("Handle incorrect password")
|
||||
Log.w(TAG, "[PinEntered] Registration recovery password incorrect: ${error.message}. Marking recovery password invalid and navigating back.")
|
||||
parentEventEmitter(RegistrationFlowEvent.RecoveryPasswordInvalid)
|
||||
parentEventEmitter.navigateBack()
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-11
@@ -307,9 +307,8 @@ class VerificationCodeViewModel(
|
||||
return state.copy(oneTimeEvent = OneTimeEvent.IncorrectVerificationCode, incorrectCodeAttempts = newAttempts, digits = VerificationCodeState.emptyDigits(), focusedDigitIndex = 0)
|
||||
}
|
||||
is NetworkController.SubmitVerificationCodeError.SessionNotFound -> {
|
||||
Log.w(TAG, "[SubmitCode] Session not found: ${error.message}")
|
||||
// TODO don't start over, go back to phone number entry
|
||||
parentEventEmitter(RegistrationFlowEvent.ResetState)
|
||||
Log.w(TAG, "[SubmitCode] Session not found: ${error.message}. Navigating back to phone number entry.")
|
||||
parentEventEmitter.navigateBack()
|
||||
return state
|
||||
}
|
||||
is NetworkController.SubmitVerificationCodeError.SessionAlreadyVerifiedOrNoCodeRequested -> {
|
||||
@@ -365,8 +364,9 @@ class VerificationCodeViewModel(
|
||||
is RequestResult.NonSuccess -> {
|
||||
when (val error = registerResult.error) {
|
||||
is NetworkController.RegisterAccountError.SessionNotFoundOrNotVerified -> {
|
||||
// TODO [registration] Handle session not found or not verified case.
|
||||
throw NotImplementedError("Handle session not found or not verified case.")
|
||||
Log.w(TAG, "[Register] Session not found or not verified: ${error.message}. Navigating back to phone number entry.")
|
||||
parentEventEmitter.navigateBack()
|
||||
state
|
||||
}
|
||||
is NetworkController.RegisterAccountError.DeviceTransferPossible -> {
|
||||
error("[Register] Got told a device transfer is possible. We should never get into this state. Resetting.")
|
||||
@@ -454,9 +454,8 @@ class VerificationCodeViewModel(
|
||||
)
|
||||
}
|
||||
is NetworkController.RequestVerificationCodeError.InvalidSessionId -> {
|
||||
Log.w(TAG, "[RequestCode][$transport] Invalid session ID: ${error.message}")
|
||||
// TODO don't start over, go back to phone number entry
|
||||
parentEventEmitter(RegistrationFlowEvent.ResetState)
|
||||
Log.w(TAG, "[RequestCode][$transport] Invalid session ID: ${error.message}. Navigating back to phone number entry.")
|
||||
parentEventEmitter.navigateBack()
|
||||
state
|
||||
}
|
||||
is NetworkController.RequestVerificationCodeError.MissingRequestInformationOrAlreadyVerified -> {
|
||||
@@ -469,9 +468,8 @@ class VerificationCodeViewModel(
|
||||
)
|
||||
}
|
||||
is NetworkController.RequestVerificationCodeError.SessionNotFound -> {
|
||||
Log.w(TAG, "[RequestCode][$transport] Session not found: ${error.message}")
|
||||
// TODO don't start over, go back to phone number entry
|
||||
parentEventEmitter(RegistrationFlowEvent.ResetState)
|
||||
Log.w(TAG, "[RequestCode][$transport] Session not found: ${error.message}. Navigating back to phone number entry.")
|
||||
parentEventEmitter.navigateBack()
|
||||
state
|
||||
}
|
||||
is NetworkController.RequestVerificationCodeError.ThirdPartyServiceError -> {
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
<string name="VerificationCodeScreen__network_error">Unable to connect. Please check your network and try again.</string>
|
||||
<!-- Snackbar shown when rate limited. Placeholder is the retry duration -->
|
||||
<string name="VerificationCodeScreen__too_many_attempts_try_again_in_s">Too many attempts. Try again in %s.</string>
|
||||
<!-- Snackbar shown when rate limited without a specific retry duration -->
|
||||
<string name="VerificationCodeScreen__too_many_attempts">Too many attempts. Please try again later.</string>
|
||||
<!-- Snackbar shown for generic/unknown errors -->
|
||||
<string name="VerificationCodeScreen__an_unexpected_error_occurred">An unexpected error occurred. Please try again.</string>
|
||||
<!-- Snackbar shown when we are unable to send an SMS to the user\'s number -->
|
||||
@@ -361,6 +363,12 @@
|
||||
<string name="PinCreationScreen__disable_pin">Disable PIN</string>
|
||||
<!-- Labels the button that cancels disabling the Signal PIN. -->
|
||||
<string name="PinCreationScreen__cancel">Cancel</string>
|
||||
<!-- Dialog message shown when the PIN could not be created because of a problem reaching the service. -->
|
||||
<string name="PinCreationScreen__service_error">There was an issue connecting to the service.</string>
|
||||
<!-- Dialog message shown when the PIN could not be created because of a network error. -->
|
||||
<string name="PinCreationScreen__network_error">Encountered a network error.</string>
|
||||
<!-- Dialog message shown when the PIN could not be created because of a network error, and the user may retry after a delay. Placeholder is the wait duration. -->
|
||||
<string name="PinCreationScreen__network_error_try_again_in_s">Encountered a network error. You can try again in %1$s.</string>
|
||||
|
||||
<!-- PIN entry screen title when registration lock is active. -->
|
||||
<string name="PinEntryScreen__registration_lock">Registration Lock</string>
|
||||
|
||||
+68
@@ -9,8 +9,11 @@ import assertk.assertThat
|
||||
import assertk.assertions.hasSize
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isFalse
|
||||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.isNotNull
|
||||
import assertk.assertions.isNull
|
||||
import assertk.assertions.isTrue
|
||||
import assertk.assertions.prop
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
@@ -34,6 +37,9 @@ import org.signal.registration.RegistrationFlowEvent
|
||||
import org.signal.registration.RegistrationFlowState
|
||||
import org.signal.registration.RegistrationRepository
|
||||
import org.signal.registration.RestoreDecision
|
||||
import java.io.IOException
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class PinCreationViewModelTest {
|
||||
@@ -167,6 +173,68 @@ class PinCreationViewModelTest {
|
||||
assertThat(emittedParentEvents.first()).isEqualTo(RegistrationFlowEvent.ResetState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PinSubmitted with EnclaveNotFound error surfaces a service error and stops loading`() = runTest(testDispatcher) {
|
||||
val states = collectStates()
|
||||
val aep = AccountEntropyPool.generate()
|
||||
val confirmState = PinCreationState(accountEntropyPool = aep, isConfirmEnabled = true, firstPin = "123456")
|
||||
|
||||
coEvery { mockRepository.setNewlyCreatedPin(any(), any(), any<MasterKey>()) } returns
|
||||
RequestResult.NonSuccess(NetworkController.BackupMasterKeyError.EnclaveNotFound)
|
||||
|
||||
viewModel.applyEvent(confirmState, PinCreationScreenEvents.PinSubmitted("123456"))
|
||||
|
||||
assertThat(emittedParentEvents).hasSize(0)
|
||||
assertThat(states.last().oneTimeEvent).isEqualTo(PinCreationState.OneTimeEvent.ServiceError)
|
||||
assertThat(states.last().loading).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PinSubmitted with application error surfaces a service error and stops loading`() = runTest(testDispatcher) {
|
||||
val states = collectStates()
|
||||
val aep = AccountEntropyPool.generate()
|
||||
val confirmState = PinCreationState(accountEntropyPool = aep, isConfirmEnabled = true, firstPin = "123456")
|
||||
|
||||
coEvery { mockRepository.setNewlyCreatedPin(any(), any(), any<MasterKey>()) } returns
|
||||
RequestResult.ApplicationError(RuntimeException("Unexpected"))
|
||||
|
||||
viewModel.applyEvent(confirmState, PinCreationScreenEvents.PinSubmitted("123456"))
|
||||
|
||||
assertThat(emittedParentEvents).hasSize(0)
|
||||
assertThat(states.last().oneTimeEvent).isEqualTo(PinCreationState.OneTimeEvent.ServiceError)
|
||||
assertThat(states.last().loading).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PinSubmitted with retryable network error surfaces a network error with retryAfter`() = runTest(testDispatcher) {
|
||||
val states = collectStates()
|
||||
val aep = AccountEntropyPool.generate()
|
||||
val confirmState = PinCreationState(accountEntropyPool = aep, isConfirmEnabled = true, firstPin = "123456")
|
||||
val retryAfter = 30.seconds
|
||||
|
||||
coEvery { mockRepository.setNewlyCreatedPin(any(), any(), any<MasterKey>()) } returns
|
||||
RequestResult.RetryableNetworkError(IOException("Network error"), retryAfter.toJavaDuration())
|
||||
|
||||
viewModel.applyEvent(confirmState, PinCreationScreenEvents.PinSubmitted("123456"))
|
||||
|
||||
assertThat(emittedParentEvents).hasSize(0)
|
||||
assertThat(states.last().oneTimeEvent).isNotNull()
|
||||
.isInstanceOf<PinCreationState.OneTimeEvent.NetworkError>()
|
||||
.prop(PinCreationState.OneTimeEvent.NetworkError::retryAfter)
|
||||
.isEqualTo(retryAfter)
|
||||
assertThat(states.last().loading).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ConsumeOneTimeEvent clears the one-time event`() = runTest(testDispatcher) {
|
||||
val states = collectStates()
|
||||
val stateWithEvent = PinCreationState(oneTimeEvent = PinCreationState.OneTimeEvent.ServiceError)
|
||||
|
||||
viewModel.applyEvent(stateWithEvent, PinCreationScreenEvents.ConsumeOneTimeEvent)
|
||||
|
||||
assertThat(states.last().oneTimeEvent).isNull()
|
||||
}
|
||||
|
||||
// ==================== OptOut Tests ====================
|
||||
|
||||
@Test
|
||||
|
||||
+59
@@ -106,6 +106,26 @@ class PinEntryForRegistrationLockViewModelTest {
|
||||
assertThat(emittedStates.last().loading).isEqualTo(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PinEntered with wrong PIN and no tries remaining navigates to AccountLocked`() = runTest {
|
||||
val initialState = PinEntryState(mode = PinEntryState.Mode.RegistrationLock)
|
||||
|
||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), forRegistrationLock = true) } returns
|
||||
RequestResult.NonSuccess(
|
||||
NetworkController.RestoreMasterKeyError.WrongPin(0)
|
||||
)
|
||||
|
||||
viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("wrong-pin"), parentEventEmitter, stateEmitter)
|
||||
|
||||
assertThat(emittedParentEvents).hasSize(1)
|
||||
assertThat(emittedParentEvents.first())
|
||||
.isInstanceOf<RegistrationFlowEvent.NavigateToScreen>()
|
||||
.prop(RegistrationFlowEvent.NavigateToScreen::route)
|
||||
.isInstanceOf<RegistrationRoute.AccountLocked>()
|
||||
.prop(RegistrationRoute.AccountLocked::timeRemainingMs)
|
||||
.isEqualTo(testTimeRemaining)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PinEntered with no SVR data navigates to AccountLocked`() = runTest {
|
||||
val initialState = PinEntryState(mode = PinEntryState.Mode.RegistrationLock)
|
||||
@@ -197,6 +217,45 @@ class PinEntryForRegistrationLockViewModelTest {
|
||||
|
||||
// ==================== Registration Error Tests ====================
|
||||
|
||||
@Test
|
||||
fun `PinEntered with session not found during registration emits ResetState`() = runTest {
|
||||
val masterKey = mockk<MasterKey>(relaxed = true)
|
||||
val initialState = PinEntryState(mode = PinEntryState.Mode.RegistrationLock)
|
||||
|
||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), forRegistrationLock = true) } returns
|
||||
RequestResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
||||
coEvery { mockRepository.registerAccountWithSession(any(), any(), any(), any()) } returns
|
||||
RequestResult.NonSuccess(
|
||||
NetworkController.RegisterAccountError.SessionNotFoundOrNotVerified("Session not found")
|
||||
)
|
||||
|
||||
viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("123456"), parentEventEmitter, stateEmitter)
|
||||
|
||||
assertThat(emittedParentEvents).hasSize(2)
|
||||
assertThat(emittedParentEvents[0]).isInstanceOf<RegistrationFlowEvent.MasterKeyRestoredFromSvr>()
|
||||
assertThat(emittedParentEvents[1]).isEqualTo(RegistrationFlowEvent.ResetState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PinEntered with recovery password incorrect during registration marks it invalid and navigates back`() = runTest {
|
||||
val masterKey = mockk<MasterKey>(relaxed = true)
|
||||
val initialState = PinEntryState(mode = PinEntryState.Mode.RegistrationLock)
|
||||
|
||||
coEvery { mockRepository.restoreMasterKeyFromSvr(any(), any(), forRegistrationLock = true) } returns
|
||||
RequestResult.Success(NetworkController.MasterKeyResponse(masterKey))
|
||||
coEvery { mockRepository.registerAccountWithSession(any(), any(), any(), any()) } returns
|
||||
RequestResult.NonSuccess(
|
||||
NetworkController.RegisterAccountError.RegistrationRecoveryPasswordIncorrect("Wrong password")
|
||||
)
|
||||
|
||||
viewModel.applyEvent(initialState, PinEntryScreenEvents.PinEntered("123456"), parentEventEmitter, stateEmitter)
|
||||
|
||||
assertThat(emittedParentEvents).hasSize(3)
|
||||
assertThat(emittedParentEvents[0]).isInstanceOf<RegistrationFlowEvent.MasterKeyRestoredFromSvr>()
|
||||
assertThat(emittedParentEvents[1]).isEqualTo(RegistrationFlowEvent.RecoveryPasswordInvalid)
|
||||
assertThat(emittedParentEvents[2]).isEqualTo(RegistrationFlowEvent.NavigateBack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PinEntered with registration lock error during registration emits ResetState`() = runTest {
|
||||
val masterKey = mockk<MasterKey>(relaxed = true)
|
||||
|
||||
+6
-6
@@ -579,7 +579,7 @@ class VerificationCodeViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CodeEntered with session not found emits ResetState`() = runTest {
|
||||
fun `CodeEntered with session not found navigates back to phone number entry`() = runTest {
|
||||
val sessionMetadata = createSessionMetadata()
|
||||
val initialState = VerificationCodeState(
|
||||
sessionMetadata = sessionMetadata,
|
||||
@@ -594,7 +594,7 @@ class VerificationCodeViewModelTest {
|
||||
viewModel.applyEvent(initialState, VerificationCodeScreenEvents.CodeEntered("123456"), stateEmitter)
|
||||
|
||||
assertThat(emittedEvents).hasSize(1)
|
||||
assertThat(emittedEvents.first()).isEqualTo(RegistrationFlowEvent.ResetState)
|
||||
assertThat(emittedEvents.first()).isEqualTo(RegistrationFlowEvent.NavigateBack)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -953,7 +953,7 @@ class VerificationCodeViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ResendSms with InvalidSessionId emits ResetState`() = runTest {
|
||||
fun `ResendSms with InvalidSessionId navigates back to phone number entry`() = runTest {
|
||||
val sessionMetadata = createSessionMetadata()
|
||||
val initialState = VerificationCodeState(sessionMetadata = sessionMetadata)
|
||||
|
||||
@@ -965,11 +965,11 @@ class VerificationCodeViewModelTest {
|
||||
viewModel.applyEvent(initialState, VerificationCodeScreenEvents.ResendSms, stateEmitter)
|
||||
|
||||
assertThat(emittedEvents).hasSize(1)
|
||||
assertThat(emittedEvents.first()).isEqualTo(RegistrationFlowEvent.ResetState)
|
||||
assertThat(emittedEvents.first()).isEqualTo(RegistrationFlowEvent.NavigateBack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ResendSms with SessionNotFound emits ResetState`() = runTest {
|
||||
fun `ResendSms with SessionNotFound navigates back to phone number entry`() = runTest {
|
||||
val sessionMetadata = createSessionMetadata()
|
||||
val initialState = VerificationCodeState(sessionMetadata = sessionMetadata)
|
||||
|
||||
@@ -981,7 +981,7 @@ class VerificationCodeViewModelTest {
|
||||
viewModel.applyEvent(initialState, VerificationCodeScreenEvents.ResendSms, stateEmitter)
|
||||
|
||||
assertThat(emittedEvents).hasSize(1)
|
||||
assertThat(emittedEvents.first()).isEqualTo(RegistrationFlowEvent.ResetState)
|
||||
assertThat(emittedEvents.first()).isEqualTo(RegistrationFlowEvent.NavigateBack)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user