mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 01:16:02 +03:00
PM-11387 on new create account with email verification, attempt login… (#3842)
This commit is contained in:
parent
3c39d8beac
commit
17c579bfc2
5 changed files with 171 additions and 97 deletions
|
@ -54,7 +54,7 @@ fun NavGraphBuilder.completeRegistrationDestination(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onNavigateToPasswordGuidance: () -> Unit,
|
onNavigateToPasswordGuidance: () -> Unit,
|
||||||
onNavigateToPreventAccountLockout: () -> Unit,
|
onNavigateToPreventAccountLockout: () -> Unit,
|
||||||
onNavigateToLogin: (email: String, token: String) -> Unit,
|
onNavigateToLogin: (email: String, token: String?) -> Unit,
|
||||||
) {
|
) {
|
||||||
composableWithSlideTransitions(
|
composableWithSlideTransitions(
|
||||||
route = COMPLETE_REGISTRATION_ROUTE,
|
route = COMPLETE_REGISTRATION_ROUTE,
|
||||||
|
|
|
@ -73,7 +73,7 @@ fun CompleteRegistrationScreen(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onNavigateToPasswordGuidance: () -> Unit,
|
onNavigateToPasswordGuidance: () -> Unit,
|
||||||
onNavigateToPreventAccountLockout: () -> Unit,
|
onNavigateToPreventAccountLockout: () -> Unit,
|
||||||
onNavigateToLogin: (email: String, token: String) -> Unit,
|
onNavigateToLogin: (email: String, token: String?) -> Unit,
|
||||||
viewModel: CompleteRegistrationViewModel = hiltViewModel(),
|
viewModel: CompleteRegistrationViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
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.PasswordStrengthResult
|
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
|
@ -106,18 +107,14 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
|
|
||||||
override fun handleAction(action: CompleteRegistrationAction) {
|
override fun handleAction(action: CompleteRegistrationAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
|
is Internal -> handleInternalAction(action)
|
||||||
is ConfirmPasswordInputChange -> handleConfirmPasswordInputChanged(action)
|
is ConfirmPasswordInputChange -> handleConfirmPasswordInputChanged(action)
|
||||||
is PasswordHintChange -> handlePasswordHintChanged(action)
|
is PasswordHintChange -> handlePasswordHintChanged(action)
|
||||||
is PasswordInputChange -> handlePasswordInputChanged(action)
|
is PasswordInputChange -> handlePasswordInputChanged(action)
|
||||||
is BackClick -> handleBackClicked()
|
is BackClick -> handleBackClicked()
|
||||||
is ErrorDialogDismiss -> handleDialogDismiss()
|
is ErrorDialogDismiss -> handleDialogDismiss()
|
||||||
is CheckDataBreachesToggle -> handleCheckDataBreachesToggle(action)
|
is CheckDataBreachesToggle -> handleCheckDataBreachesToggle(action)
|
||||||
is Internal.ReceiveRegisterResult -> {
|
|
||||||
handleReceiveRegisterAccountResult(action)
|
|
||||||
}
|
|
||||||
|
|
||||||
ContinueWithBreachedPasswordClick -> handleContinueWithBreachedPasswordClick()
|
ContinueWithBreachedPasswordClick -> handleContinueWithBreachedPasswordClick()
|
||||||
is ReceivePasswordStrengthResult -> handlePasswordStrengthResult(action)
|
|
||||||
CompleteRegistrationAction.LearnToPreventLockoutClick -> {
|
CompleteRegistrationAction.LearnToPreventLockoutClick -> {
|
||||||
handlePreventAccountLockoutClickAction()
|
handlePreventAccountLockoutClickAction()
|
||||||
}
|
}
|
||||||
|
@ -127,10 +124,16 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
CompleteRegistrationAction.CallToActionClick -> handleCallToActionClick()
|
CompleteRegistrationAction.CallToActionClick -> handleCallToActionClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleInternalAction(action: Internal) {
|
||||||
|
when (action) {
|
||||||
|
is Internal.GeneratedPasswordResult -> handleGeneratedPasswordResult(action)
|
||||||
|
is ReceivePasswordStrengthResult -> handlePasswordStrengthResult(action)
|
||||||
|
is Internal.ReceiveRegisterResult -> handleReceiveRegisterAccountResult(action)
|
||||||
is Internal.UpdateOnboardingFeatureState -> handleUpdateOnboardingFeatureState(action)
|
is Internal.UpdateOnboardingFeatureState -> handleUpdateOnboardingFeatureState(action)
|
||||||
is Internal.GeneratedPasswordResult -> handleGeneratedPasswordResult(
|
is Internal.ReceiveLoginResult -> handleLoginResult(action)
|
||||||
action,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,13 +218,19 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is RegisterResult.Success -> {
|
is RegisterResult.Success -> {
|
||||||
mutableStateFlow.update { it.copy(dialog = null) }
|
viewModelScope.launch {
|
||||||
sendEvent(
|
val loginResult = authRepository.login(
|
||||||
CompleteRegistrationEvent.NavigateToLogin(
|
|
||||||
email = state.userEmail,
|
email = state.userEmail,
|
||||||
|
password = state.passwordInput,
|
||||||
captchaToken = registerAccountResult.captchaToken,
|
captchaToken = registerAccountResult.captchaToken,
|
||||||
),
|
)
|
||||||
)
|
sendAction(
|
||||||
|
Internal.ReceiveLoginResult(
|
||||||
|
loginResult = loginResult,
|
||||||
|
captchaToken = registerAccountResult.captchaToken,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterResult.DataBreachFound -> {
|
RegisterResult.DataBreachFound -> {
|
||||||
|
@ -259,6 +268,25 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleLoginResult(action: Internal.ReceiveLoginResult) {
|
||||||
|
clearDialogState()
|
||||||
|
sendEvent(
|
||||||
|
CompleteRegistrationEvent.ShowToast(
|
||||||
|
message = R.string.account_created_success.asText(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// If the login result is Success the state based navigation will take care of it.
|
||||||
|
// otherwise we need to navigate to the login screen.
|
||||||
|
if (action.loginResult !is LoginResult.Success) {
|
||||||
|
sendEvent(
|
||||||
|
CompleteRegistrationEvent.NavigateToLogin(
|
||||||
|
email = state.userEmail,
|
||||||
|
captchaToken = action.captchaToken,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleCheckDataBreachesToggle(action: CheckDataBreachesToggle) {
|
private fun handleCheckDataBreachesToggle(action: CheckDataBreachesToggle) {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(isCheckDataBreachesToggled = action.newState)
|
it.copy(isCheckDataBreachesToggled = action.newState)
|
||||||
|
@ -266,9 +294,7 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDialogDismiss() {
|
private fun handleDialogDismiss() {
|
||||||
mutableStateFlow.update {
|
clearDialogState()
|
||||||
it.copy(dialog = null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBackClicked() {
|
private fun handleBackClicked() {
|
||||||
|
@ -393,6 +419,12 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun clearDialogState() {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(dialog = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -509,7 +541,7 @@ sealed class CompleteRegistrationEvent {
|
||||||
*/
|
*/
|
||||||
data class NavigateToLogin(
|
data class NavigateToLogin(
|
||||||
val email: String,
|
val email: String,
|
||||||
val captchaToken: String,
|
val captchaToken: String?,
|
||||||
) : CompleteRegistrationEvent()
|
) : CompleteRegistrationEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,5 +627,17 @@ sealed class CompleteRegistrationAction {
|
||||||
* Indicates a generated password has been received.
|
* Indicates a generated password has been received.
|
||||||
*/
|
*/
|
||||||
data class GeneratedPasswordResult(val generatedPassword: String) : Internal()
|
data class GeneratedPasswordResult(val generatedPassword: String) : Internal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates registration was successful and will now attempt to login and unlock the vault.
|
||||||
|
* @property captchaToken The captcha token to use for login. With the login function this
|
||||||
|
* is possible to be negative.
|
||||||
|
*
|
||||||
|
* @see [AuthRepository.login]
|
||||||
|
*/
|
||||||
|
data class ReceiveLoginResult(
|
||||||
|
val loginResult: LoginResult,
|
||||||
|
val captchaToken: String?,
|
||||||
|
) : Internal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
<string name="yes">Yes</string>
|
<string name="yes">Yes</string>
|
||||||
<string name="account">Account</string>
|
<string name="account">Account</string>
|
||||||
<string name="account_created">Your new account has been created! You may now log in.</string>
|
<string name="account_created">Your new account has been created! You may now log in.</string>
|
||||||
|
<string name="account_created_success">Your new account has been created!</string>
|
||||||
<string name="add_an_item">Add an Item</string>
|
<string name="add_an_item">Add an Item</string>
|
||||||
<string name="app_extension">App extension</string>
|
<string name="app_extension">App extension</string>
|
||||||
<string name="autofill_accessibility_description">Use the Bitwarden accessibility service to auto-fill your logins across apps and the web.</string>
|
<string name="autofill_accessibility_description">Use the Bitwarden accessibility service to auto-fill your logins across apps and the web.</string>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_3
|
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_3
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_4
|
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_4
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
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.PasswordStrengthResult
|
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||||
|
@ -58,6 +59,25 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||||
private val mockAuthRepository = mockk<AuthRepository>() {
|
private val mockAuthRepository = mockk<AuthRepository>() {
|
||||||
every { userStateFlow } returns mutableUserStateFlow
|
every { userStateFlow } returns mutableUserStateFlow
|
||||||
|
coEvery {
|
||||||
|
login(
|
||||||
|
email = any(),
|
||||||
|
password = any(),
|
||||||
|
captchaToken = any(),
|
||||||
|
)
|
||||||
|
} returns LoginResult.Success
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
register(
|
||||||
|
email = any(),
|
||||||
|
masterPassword = any(),
|
||||||
|
masterPasswordHint = any(),
|
||||||
|
emailVerificationToken = any(),
|
||||||
|
captchaToken = any(),
|
||||||
|
shouldCheckDataBreaches = any(),
|
||||||
|
isMasterPasswordStrong = any(),
|
||||||
|
)
|
||||||
|
} returns RegisterResult.Success(captchaToken = CAPTCHA_BYPASS_TOKEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
||||||
|
@ -119,10 +139,10 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `CallToActionClick with all inputs valid should show and hide loading dialog`() = runTest {
|
fun `CallToActionClick with all inputs valid should account created toast and hide dialog`() =
|
||||||
val repo = mockk<AuthRepository> {
|
runTest {
|
||||||
coEvery {
|
coEvery {
|
||||||
register(
|
mockAuthRepository.register(
|
||||||
email = EMAIL,
|
email = EMAIL,
|
||||||
masterPassword = PASSWORD,
|
masterPassword = PASSWORD,
|
||||||
masterPasswordHint = null,
|
masterPasswordHint = null,
|
||||||
|
@ -132,45 +152,39 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
isMasterPasswordStrong = true,
|
isMasterPasswordStrong = true,
|
||||||
)
|
)
|
||||||
} returns RegisterResult.Success(captchaToken = CAPTCHA_BYPASS_TOKEN)
|
} returns RegisterResult.Success(captchaToken = CAPTCHA_BYPASS_TOKEN)
|
||||||
|
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE)
|
||||||
|
turbineScope {
|
||||||
|
val stateFlow = viewModel.stateFlow.testIn(backgroundScope)
|
||||||
|
val eventFlow = viewModel.eventFlow.testIn(backgroundScope)
|
||||||
|
assertEquals(VALID_INPUT_STATE, stateFlow.awaitItem())
|
||||||
|
viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick)
|
||||||
|
assertEquals(
|
||||||
|
VALID_INPUT_STATE.copy(dialog = CompleteRegistrationDialog.Loading),
|
||||||
|
stateFlow.awaitItem(),
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
CompleteRegistrationEvent.ShowToast(R.string.account_created_success.asText()),
|
||||||
|
eventFlow.awaitItem(),
|
||||||
|
)
|
||||||
|
// Make sure loading dialog is hidden:
|
||||||
|
assertEquals(VALID_INPUT_STATE, stateFlow.awaitItem())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE, repo)
|
|
||||||
turbineScope {
|
|
||||||
val stateFlow = viewModel.stateFlow.testIn(backgroundScope)
|
|
||||||
val eventFlow = viewModel.eventFlow.testIn(backgroundScope)
|
|
||||||
assertEquals(VALID_INPUT_STATE, stateFlow.awaitItem())
|
|
||||||
viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick)
|
|
||||||
assertEquals(
|
|
||||||
VALID_INPUT_STATE.copy(dialog = CompleteRegistrationDialog.Loading),
|
|
||||||
stateFlow.awaitItem(),
|
|
||||||
)
|
|
||||||
assertEquals(
|
|
||||||
CompleteRegistrationEvent.NavigateToLogin(
|
|
||||||
EMAIL,
|
|
||||||
CAPTCHA_BYPASS_TOKEN,
|
|
||||||
),
|
|
||||||
eventFlow.awaitItem(),
|
|
||||||
)
|
|
||||||
// Make sure loading dialog is hidden:
|
|
||||||
assertEquals(VALID_INPUT_STATE, stateFlow.awaitItem())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `CallToActionClick register returns error should update errorDialogState`() = runTest {
|
fun `CallToActionClick register returns error should update errorDialogState`() = runTest {
|
||||||
val repo = mockk<AuthRepository> {
|
coEvery {
|
||||||
coEvery {
|
mockAuthRepository.register(
|
||||||
register(
|
email = EMAIL,
|
||||||
email = EMAIL,
|
masterPassword = PASSWORD,
|
||||||
masterPassword = PASSWORD,
|
masterPasswordHint = null,
|
||||||
masterPasswordHint = null,
|
emailVerificationToken = TOKEN,
|
||||||
emailVerificationToken = TOKEN,
|
captchaToken = null,
|
||||||
captchaToken = null,
|
shouldCheckDataBreaches = false,
|
||||||
shouldCheckDataBreaches = false,
|
isMasterPasswordStrong = true,
|
||||||
isMasterPasswordStrong = true,
|
)
|
||||||
)
|
} returns RegisterResult.Error(errorMessage = "mock_error")
|
||||||
} returns RegisterResult.Error(errorMessage = "mock_error")
|
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE)
|
||||||
}
|
|
||||||
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE, repo)
|
|
||||||
viewModel.stateFlow.test {
|
viewModel.stateFlow.test {
|
||||||
assertEquals(VALID_INPUT_STATE, awaitItem())
|
assertEquals(VALID_INPUT_STATE, awaitItem())
|
||||||
viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick)
|
viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick)
|
||||||
|
@ -193,52 +207,68 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `CallToActionClick register returns Success should emit NavigateToLogin`() = runTest {
|
fun `CallToActionClick register returns Success should attempt login`() = runTest {
|
||||||
val repo = mockk<AuthRepository> {
|
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE)
|
||||||
coEvery {
|
viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick)
|
||||||
register(
|
coVerify {
|
||||||
email = EMAIL,
|
mockAuthRepository.login(
|
||||||
masterPassword = PASSWORD,
|
email = EMAIL,
|
||||||
masterPasswordHint = null,
|
password = PASSWORD,
|
||||||
emailVerificationToken = TOKEN,
|
captchaToken = CAPTCHA_BYPASS_TOKEN,
|
||||||
captchaToken = null,
|
|
||||||
shouldCheckDataBreaches = false,
|
|
||||||
isMasterPasswordStrong = true,
|
|
||||||
)
|
|
||||||
} returns RegisterResult.Success(captchaToken = CAPTCHA_BYPASS_TOKEN)
|
|
||||||
}
|
|
||||||
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE, repo)
|
|
||||||
viewModel.eventFlow.test {
|
|
||||||
viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick)
|
|
||||||
assertEquals(
|
|
||||||
CompleteRegistrationEvent.NavigateToLogin(
|
|
||||||
EMAIL,
|
|
||||||
CAPTCHA_BYPASS_TOKEN,
|
|
||||||
),
|
|
||||||
awaitItem(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ContinueWithBreachedPasswordClick should call repository with checkDataBreaches false`() {
|
fun `when login attempt returns success should wait for state based navigation`() = runTest {
|
||||||
val repo = mockk<AuthRepository> {
|
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE)
|
||||||
coEvery {
|
viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick)
|
||||||
register(
|
viewModel.eventFlow.test {
|
||||||
email = EMAIL,
|
assertTrue(awaitItem() is CompleteRegistrationEvent.ShowToast)
|
||||||
masterPassword = PASSWORD,
|
expectNoEvents()
|
||||||
masterPasswordHint = null,
|
|
||||||
emailVerificationToken = TOKEN,
|
|
||||||
captchaToken = null,
|
|
||||||
shouldCheckDataBreaches = false,
|
|
||||||
isMasterPasswordStrong = true,
|
|
||||||
)
|
|
||||||
} returns RegisterResult.Error(null)
|
|
||||||
}
|
}
|
||||||
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE, repo)
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `when login attempt returns anything other than success should send navigate to login event`() =
|
||||||
|
runTest {
|
||||||
|
coEvery {
|
||||||
|
mockAuthRepository.login(
|
||||||
|
email = EMAIL,
|
||||||
|
password = PASSWORD,
|
||||||
|
captchaToken = CAPTCHA_BYPASS_TOKEN,
|
||||||
|
)
|
||||||
|
} returns LoginResult.TwoFactorRequired
|
||||||
|
|
||||||
|
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE)
|
||||||
|
viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick)
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
assertTrue(awaitItem() is CompleteRegistrationEvent.ShowToast)
|
||||||
|
assertEquals(
|
||||||
|
CompleteRegistrationEvent.NavigateToLogin(EMAIL, CAPTCHA_BYPASS_TOKEN),
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ContinueWithBreachedPasswordClick should call repository with checkDataBreaches false`() {
|
||||||
|
coEvery {
|
||||||
|
mockAuthRepository.register(
|
||||||
|
email = EMAIL,
|
||||||
|
masterPassword = PASSWORD,
|
||||||
|
masterPasswordHint = null,
|
||||||
|
emailVerificationToken = TOKEN,
|
||||||
|
captchaToken = null,
|
||||||
|
shouldCheckDataBreaches = false,
|
||||||
|
isMasterPasswordStrong = true,
|
||||||
|
)
|
||||||
|
} returns RegisterResult.Error(null)
|
||||||
|
val viewModel = createCompleteRegistrationViewModel(VALID_INPUT_STATE)
|
||||||
viewModel.trySendAction(CompleteRegistrationAction.ContinueWithBreachedPasswordClick)
|
viewModel.trySendAction(CompleteRegistrationAction.ContinueWithBreachedPasswordClick)
|
||||||
coVerify {
|
coVerify {
|
||||||
repo.register(
|
mockAuthRepository.register(
|
||||||
email = EMAIL,
|
email = EMAIL,
|
||||||
masterPassword = PASSWORD,
|
masterPassword = PASSWORD,
|
||||||
masterPasswordHint = null,
|
masterPasswordHint = null,
|
||||||
|
@ -632,14 +662,13 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
private fun createCompleteRegistrationViewModel(
|
private fun createCompleteRegistrationViewModel(
|
||||||
completeRegistrationState: CompleteRegistrationState? = DEFAULT_STATE,
|
completeRegistrationState: CompleteRegistrationState? = DEFAULT_STATE,
|
||||||
authRepository: AuthRepository = mockAuthRepository,
|
|
||||||
): CompleteRegistrationViewModel = CompleteRegistrationViewModel(
|
): CompleteRegistrationViewModel = CompleteRegistrationViewModel(
|
||||||
savedStateHandle = SavedStateHandle(
|
savedStateHandle = SavedStateHandle(
|
||||||
mapOf(
|
mapOf(
|
||||||
"state" to completeRegistrationState,
|
"state" to completeRegistrationState,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
authRepository = authRepository,
|
authRepository = mockAuthRepository,
|
||||||
environmentRepository = fakeEnvironmentRepository,
|
environmentRepository = fakeEnvironmentRepository,
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
featureFlagManager = featureFlagManager,
|
featureFlagManager = featureFlagManager,
|
||||||
|
|
Loading…
Reference in a new issue