extracting registration steps to separate handler to make testing the flow simpler
This commit is contained in:
parent
4225f62120
commit
3fa415007c
|
@ -22,7 +22,6 @@ import im.vector.app.features.login.LoginConfig
|
|||
import im.vector.app.features.login.ServerType
|
||||
import im.vector.app.features.login.SignMode
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.internal.network.ssl.Fingerprint
|
||||
|
||||
sealed interface OnboardingAction : VectorViewModelAction {
|
||||
|
@ -42,22 +41,9 @@ sealed interface OnboardingAction : VectorViewModelAction {
|
|||
|
||||
// Login or Register, depending on the signMode
|
||||
data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction
|
||||
object StopEmailValidationCheck : OnboardingAction
|
||||
|
||||
// Register actions
|
||||
open class RegisterAction : OnboardingAction
|
||||
|
||||
data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction()
|
||||
object SendAgainThreePid : RegisterAction()
|
||||
|
||||
// TODO Confirm Email (from link in the email, open in the phone, intercepted by the app)
|
||||
data class ValidateThreePid(val code: String) : RegisterAction()
|
||||
|
||||
data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction()
|
||||
object StopEmailValidationCheck : RegisterAction()
|
||||
|
||||
data class CaptchaDone(val captchaResponse: String) : RegisterAction()
|
||||
object AcceptTerms : RegisterAction()
|
||||
object RegisterDummy : RegisterAction()
|
||||
data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction
|
||||
|
||||
// Reset actions
|
||||
open class ResetAction : OnboardingAction
|
||||
|
|
|
@ -83,6 +83,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
private val vectorFeatures: VectorFeatures,
|
||||
private val analyticsTracker: AnalyticsTracker,
|
||||
private val uriFilenameResolver: UriFilenameResolver,
|
||||
private val registrationActionHandler: RegistrationActionHandler,
|
||||
private val vectorOverrides: VectorOverrides
|
||||
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {
|
||||
|
||||
|
@ -116,16 +117,16 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
|
||||
private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
|
||||
|
||||
private val registrationWizard: RegistrationWizard
|
||||
get() = authenticationService.getRegistrationWizard()
|
||||
|
||||
val currentThreePid: String?
|
||||
get() = registrationWizard?.currentThreePid
|
||||
get() = registrationWizard.currentThreePid
|
||||
|
||||
// True when login and password has been sent with success to the homeserver
|
||||
val isRegistrationStarted: Boolean
|
||||
get() = authenticationService.isRegistrationStarted
|
||||
|
||||
private val registrationWizard: RegistrationWizard?
|
||||
get() = authenticationService.getRegistrationWizard()
|
||||
|
||||
private val loginWizard: LoginWizard?
|
||||
get() = authenticationService.getLoginWizard()
|
||||
|
||||
|
@ -153,7 +154,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||
is OnboardingAction.ResetPassword -> handleResetPassword(action)
|
||||
is OnboardingAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
|
||||
is OnboardingAction.RegisterAction -> handleRegisterAction(action)
|
||||
is OnboardingAction.PostRegisterAction -> handleRegisterAction(action.registerAction)
|
||||
is OnboardingAction.ResetAction -> handleResetAction(action)
|
||||
is OnboardingAction.UserAcceptCertificate -> handleUserAcceptCertificate(action)
|
||||
OnboardingAction.ClearHomeServerHistory -> handleClearHomeServerHistory()
|
||||
|
@ -164,6 +165,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
is OnboardingAction.ProfilePictureSelected -> handleProfilePictureSelected(action)
|
||||
OnboardingAction.SaveSelectedProfilePicture -> updateProfilePicture()
|
||||
is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
|
||||
OnboardingAction.StopEmailValidationCheck -> currentJob = null
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
@ -266,131 +268,36 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleRegisterAction(action: OnboardingAction.RegisterAction) {
|
||||
when (action) {
|
||||
is OnboardingAction.CaptchaDone -> handleCaptchaDone(action)
|
||||
is OnboardingAction.AcceptTerms -> handleAcceptTerms()
|
||||
is OnboardingAction.RegisterDummy -> handleRegisterDummy()
|
||||
is OnboardingAction.AddThreePid -> handleAddThreePid(action)
|
||||
is OnboardingAction.SendAgainThreePid -> handleSendAgainThreePid()
|
||||
is OnboardingAction.ValidateThreePid -> handleValidateThreePid(action)
|
||||
is OnboardingAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action)
|
||||
is OnboardingAction.StopEmailValidationCheck -> handleStopEmailValidationCheck()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCheckIfEmailHasBeenValidated(action: OnboardingAction.CheckIfEmailHasBeenValidated) {
|
||||
// We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state
|
||||
currentJob = executeRegistrationStep(withLoading = false) {
|
||||
it.checkIfEmailHasBeenValidated(action.delayMillis)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleStopEmailValidationCheck() {
|
||||
currentJob = null
|
||||
}
|
||||
|
||||
private fun handleValidateThreePid(action: OnboardingAction.ValidateThreePid) {
|
||||
currentJob = executeRegistrationStep {
|
||||
it.handleValidateThreePid(action.code)
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeRegistrationStep(withLoading: Boolean = true,
|
||||
block: suspend (RegistrationWizard) -> RegistrationResult): Job {
|
||||
if (withLoading) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
}
|
||||
return viewModelScope.launch {
|
||||
try {
|
||||
registrationWizard?.let { block(it) }
|
||||
/*
|
||||
// Simulate registration disabled
|
||||
throw Failure.ServerError(MatrixError(
|
||||
code = MatrixError.FORBIDDEN,
|
||||
message = "Registration is disabled"
|
||||
), 403))
|
||||
*/
|
||||
} catch (failure: Throwable) {
|
||||
if (failure !is CancellationException) {
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(failure))
|
||||
}
|
||||
null
|
||||
}
|
||||
?.let { data ->
|
||||
when (data) {
|
||||
is RegistrationResult.Success -> onSessionCreated(data.session, isAccountCreated = true)
|
||||
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
|
||||
}
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddThreePid(action: OnboardingAction.AddThreePid) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
private fun handleRegisterAction(action: RegisterAction) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
registrationWizard?.addThreePid(action.threePid)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(failure))
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendAgainThreePid() {
|
||||
if (action.hasLoadingState()) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
registrationWizard?.sendAgainThreePid()
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(failure))
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
kotlin.runCatching { registrationActionHandler.handleRegisterAction(registrationWizard, action) }
|
||||
.fold(
|
||||
onSuccess = {
|
||||
when (it) {
|
||||
is RegistrationResult.Success -> onSessionCreated(it.session, isAccountCreated = true)
|
||||
is RegistrationResult.FlowResponse -> onFlowResponse(it.flowResult)
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
if (it !is CancellationException) {
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(it))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAcceptTerms() {
|
||||
currentJob = executeRegistrationStep {
|
||||
it.acceptTerms()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRegisterDummy() {
|
||||
currentJob = executeRegistrationStep {
|
||||
it.dummy()
|
||||
setState { copy(asyncRegistration = Uninitialized) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRegisterWith(action: OnboardingAction.LoginOrRegister) {
|
||||
reAuthHelper.data = action.password
|
||||
currentJob = executeRegistrationStep {
|
||||
it.createAccount(
|
||||
handleRegisterAction(RegisterAction.CreateAccount(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCaptchaDone(action: OnboardingAction.CaptchaDone) {
|
||||
currentJob = executeRegistrationStep {
|
||||
it.performReCaptcha(action.captchaResponse)
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
private fun handleResetAction(action: OnboardingAction.ResetAction) {
|
||||
|
@ -461,7 +368,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
when (action.signMode) {
|
||||
SignMode.SignUp -> startRegistrationFlow()
|
||||
SignMode.SignUp -> handleRegisterAction(RegisterAction.RegisterDummy)
|
||||
SignMode.SignIn -> startAuthenticationFlow()
|
||||
SignMode.SignInWithMatrixId -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId))
|
||||
SignMode.Unknown -> Unit
|
||||
|
@ -499,7 +406,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
|
||||
// If there is a pending email validation continue on this step
|
||||
try {
|
||||
if (registrationWizard?.isRegistrationStarted == true) {
|
||||
if (registrationWizard.isRegistrationStarted) {
|
||||
currentThreePid?.let {
|
||||
handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendEmailSuccess(it)))
|
||||
}
|
||||
|
@ -730,12 +637,6 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun startRegistrationFlow() {
|
||||
currentJob = executeRegistrationStep {
|
||||
it.getRegistrationFlow()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAuthenticationFlow() {
|
||||
// Ensure Wizard is ready
|
||||
loginWizard
|
||||
|
@ -745,8 +646,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
|
||||
private fun onFlowResponse(flowResult: FlowResult) {
|
||||
// If dummy stage is mandatory, and password is already sent, do the dummy stage now
|
||||
if (isRegistrationStarted &&
|
||||
flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) {
|
||||
if (isRegistrationStarted && flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) {
|
||||
handleRegisterDummy()
|
||||
} else {
|
||||
// Notify the user
|
||||
|
@ -754,6 +654,10 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleRegisterDummy() {
|
||||
handleRegisterAction(RegisterAction.RegisterDummy)
|
||||
}
|
||||
|
||||
private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) {
|
||||
val state = awaitState()
|
||||
state.useCase?.let { useCase ->
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.onboarding
|
||||
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import javax.inject.Inject
|
||||
|
||||
class RegistrationActionHandler @Inject constructor() {
|
||||
|
||||
suspend fun handleRegisterAction(registrationWizard: RegistrationWizard, action: RegisterAction): RegistrationResult {
|
||||
return when (action) {
|
||||
RegisterAction.StartRegistration -> registrationWizard.getRegistrationFlow()
|
||||
is RegisterAction.CaptchaDone -> registrationWizard.performReCaptcha(action.captchaResponse)
|
||||
is RegisterAction.AcceptTerms -> registrationWizard.acceptTerms()
|
||||
is RegisterAction.RegisterDummy -> registrationWizard.dummy()
|
||||
is RegisterAction.AddThreePid -> registrationWizard.addThreePid(action.threePid)
|
||||
is RegisterAction.SendAgainThreePid -> registrationWizard.sendAgainThreePid()
|
||||
is RegisterAction.ValidateThreePid -> registrationWizard.handleValidateThreePid(action.code)
|
||||
is RegisterAction.CheckIfEmailHasBeenValidated -> registrationWizard.checkIfEmailHasBeenValidated(action.delayMillis)
|
||||
is RegisterAction.CreateAccount -> registrationWizard.createAccount(action.username, action.password, action.initialDeviceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface RegisterAction {
|
||||
object StartRegistration : RegisterAction
|
||||
data class CreateAccount(val username: String, val password: String, val initialDeviceName: String) : RegisterAction
|
||||
|
||||
data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction
|
||||
object SendAgainThreePid : RegisterAction
|
||||
|
||||
// TODO Confirm Email (from link in the email, open in the phone, intercepted by the app)
|
||||
data class ValidateThreePid(val code: String) : RegisterAction
|
||||
|
||||
data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction
|
||||
|
||||
data class CaptchaDone(val captchaResponse: String) : RegisterAction
|
||||
object AcceptTerms : RegisterAction
|
||||
object RegisterDummy : RegisterAction
|
||||
}
|
||||
|
||||
fun RegisterAction.hasLoadingState() = when (this) {
|
||||
is RegisterAction.CheckIfEmailHasBeenValidated -> false
|
||||
else -> true
|
||||
}
|
|
@ -39,6 +39,7 @@ import im.vector.app.databinding.FragmentLoginCaptchaBinding
|
|||
import im.vector.app.features.login.JavascriptResponse
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import timber.log.Timber
|
||||
|
@ -181,7 +182,7 @@ class FtueAuthCaptchaFragment @Inject constructor(
|
|||
|
||||
val response = javascriptResponse?.response
|
||||
if (javascriptResponse?.action == "verifyCallback" && response != null) {
|
||||
viewModel.handle(OnboardingAction.CaptchaDone(response))
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(response)))
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -37,6 +37,7 @@ import im.vector.app.databinding.FragmentLoginGenericTextInputFormBinding
|
|||
import im.vector.app.features.login.TextInputFormFragmentMode
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
@ -138,7 +139,7 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA
|
|||
private fun onOtherButtonClicked() {
|
||||
when (params.mode) {
|
||||
TextInputFormFragmentMode.ConfirmMsisdn -> {
|
||||
viewModel.handle(OnboardingAction.SendAgainThreePid)
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.SendAgainThreePid))
|
||||
}
|
||||
else -> {
|
||||
// Should not happen, button is not displayed
|
||||
|
@ -152,19 +153,19 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA
|
|||
|
||||
if (text.isEmpty()) {
|
||||
// Perform dummy action
|
||||
viewModel.handle(OnboardingAction.RegisterDummy)
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.RegisterDummy))
|
||||
} else {
|
||||
when (params.mode) {
|
||||
TextInputFormFragmentMode.SetEmail -> {
|
||||
viewModel.handle(OnboardingAction.AddThreePid(RegisterThreePid.Email(text)))
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AddThreePid(RegisterThreePid.Email(text))))
|
||||
}
|
||||
TextInputFormFragmentMode.SetMsisdn -> {
|
||||
getCountryCodeOrShowError(text)?.let { countryCode ->
|
||||
viewModel.handle(OnboardingAction.AddThreePid(RegisterThreePid.Msisdn(text, countryCode)))
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AddThreePid(RegisterThreePid.Msisdn(text, countryCode))))
|
||||
}
|
||||
}
|
||||
TextInputFormFragmentMode.ConfirmMsisdn -> {
|
||||
viewModel.handle(OnboardingAction.ValidateThreePid(text))
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.ValidateThreePid(text)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.airbnb.mvrx.args
|
|||
import im.vector.app.R
|
||||
import im.vector.app.databinding.FragmentLoginWaitForEmailBinding
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.failure.is401
|
||||
import javax.inject.Inject
|
||||
|
@ -54,7 +55,7 @@ class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragm
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
viewModel.handle(OnboardingAction.CheckIfEmailHasBeenValidated(0))
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(0)))
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
@ -70,7 +71,7 @@ class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragm
|
|||
override fun onError(throwable: Throwable) {
|
||||
if (throwable.is401()) {
|
||||
// Try again, with a delay
|
||||
viewModel.handle(OnboardingAction.CheckIfEmailHasBeenValidated(10_000))
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(10_000)))
|
||||
} else {
|
||||
super.onError(throwable)
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.app.features.login.terms.LoginTermsViewState
|
|||
import im.vector.app.features.login.terms.PolicyController
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import im.vector.app.features.onboarding.ftueauth.AbstractFtueAuthFragment
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms
|
||||
|
@ -111,7 +112,7 @@ class FtueAuthTermsFragment @Inject constructor(
|
|||
}
|
||||
|
||||
private fun submit() {
|
||||
viewModel.handle(OnboardingAction.AcceptTerms)
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AcceptTerms))
|
||||
}
|
||||
|
||||
override fun updateWithState(state: OnboardingViewState) {
|
||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.app.test.fakes.FakeAuthenticationService
|
|||
import im.vector.app.test.fakes.FakeContext
|
||||
import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory
|
||||
import im.vector.app.test.fakes.FakeHomeServerHistoryService
|
||||
import im.vector.app.test.fakes.FakeRegisterActionHandler
|
||||
import im.vector.app.test.fakes.FakeRegistrationWizard
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.test.fakes.FakeStringProvider
|
||||
|
@ -41,6 +42,9 @@ import kotlinx.coroutines.test.runBlockingTest
|
|||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
|
||||
private const val A_DISPLAY_NAME = "a display name"
|
||||
|
@ -50,6 +54,7 @@ private val AN_UNSUPPORTED_PERSONALISATION_STATE = PersonalizationState(
|
|||
supportsChangingDisplayName = false,
|
||||
supportsChangingProfilePicture = false
|
||||
)
|
||||
private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration
|
||||
|
||||
class OnboardingViewModelTest {
|
||||
|
||||
|
@ -63,6 +68,7 @@ class OnboardingViewModelTest {
|
|||
private val fakeUriFilenameResolver = FakeUriFilenameResolver()
|
||||
private val fakeActiveSessionHolder = FakeActiveSessionHolder(fakeSession)
|
||||
private val fakeAuthenticationService = FakeAuthenticationService()
|
||||
private val fakeRegisterActionHandler = FakeRegisterActionHandler()
|
||||
|
||||
lateinit var viewModel: OnboardingViewModel
|
||||
|
||||
|
@ -108,28 +114,67 @@ class OnboardingViewModelTest {
|
|||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given register action requires more steps when handling action then posts next steps`() = runBlockingTest {
|
||||
val test = viewModel.test(this)
|
||||
val flowResult = FlowResult(missingStages = listOf(Stage.Email(true)), completedStages = listOf(Stage.Email(true)))
|
||||
givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.FlowResponse(flowResult))
|
||||
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION))
|
||||
|
||||
test
|
||||
.assertStatesWithPrevious(
|
||||
initialState,
|
||||
{ copy(asyncRegistration = Loading()) },
|
||||
{ copy(asyncRegistration = Uninitialized) }
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.RegistrationFlowResult(flowResult, isRegistrationStarted = true))
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given registration has started and has dummy step to do when handling action then ignores other steps and executes dummy`() = runBlockingTest {
|
||||
val test = viewModel.test(this)
|
||||
|
||||
val homeServerCapabilities = HomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true)
|
||||
fakeSession.fakeHomeServerCapabilitiesService.givenCapabilities(homeServerCapabilities)
|
||||
val flowResult = FlowResult(missingStages = listOf(Stage.Dummy(mandatory = true), Stage.Email(true)), completedStages = emptyList())
|
||||
givenRegistrationResultsFor(listOf(
|
||||
A_LOADABLE_REGISTER_ACTION to RegistrationResult.FlowResponse(flowResult),
|
||||
RegisterAction.RegisterDummy to RegistrationResult.Success(fakeSession)
|
||||
))
|
||||
givenSuccessfullyCreatesAccount()
|
||||
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION))
|
||||
|
||||
test
|
||||
.assertStatesWithPrevious(
|
||||
initialState,
|
||||
{ copy(asyncRegistration = Loading()) },
|
||||
{ copy(asyncLoginAction = Success(Unit), personalizationState = homeServerCapabilities.toPersonalisationState()) },
|
||||
{ copy(asyncRegistration = Uninitialized) },
|
||||
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnAccountCreated)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given homeserver does not support personalisation when registering account then updates state and emits account created event`() = runBlockingTest {
|
||||
fakeSession.fakeHomeServerCapabilitiesService.givenCapabilities(HomeServerCapabilities(canChangeDisplayName = false, canChangeAvatar = false))
|
||||
val homeServerCapabilities = HomeServerCapabilities(canChangeDisplayName = false, canChangeAvatar = false)
|
||||
fakeSession.fakeHomeServerCapabilitiesService.givenCapabilities(homeServerCapabilities)
|
||||
givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession))
|
||||
givenSuccessfullyCreatesAccount()
|
||||
val test = viewModel.test(this)
|
||||
|
||||
viewModel.handle(OnboardingAction.RegisterDummy)
|
||||
viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION))
|
||||
|
||||
test
|
||||
.assertStates(
|
||||
.assertStatesWithPrevious(
|
||||
initialState,
|
||||
initialState.copy(asyncRegistration = Loading()),
|
||||
initialState.copy(
|
||||
asyncLoginAction = Success(Unit),
|
||||
asyncRegistration = Loading(),
|
||||
personalizationState = AN_UNSUPPORTED_PERSONALISATION_STATE
|
||||
),
|
||||
initialState.copy(
|
||||
asyncLoginAction = Success(Unit),
|
||||
asyncRegistration = Uninitialized,
|
||||
personalizationState = AN_UNSUPPORTED_PERSONALISATION_STATE
|
||||
)
|
||||
{ copy(asyncRegistration = Loading()) },
|
||||
{ copy(asyncLoginAction = Success(Unit), personalizationState = homeServerCapabilities.toPersonalisationState()) },
|
||||
{ copy(asyncLoginAction = Success(Unit), asyncRegistration = Uninitialized) }
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnAccountCreated)
|
||||
.finish()
|
||||
|
@ -173,10 +218,10 @@ class OnboardingViewModelTest {
|
|||
viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
|
||||
|
||||
test
|
||||
.assertStates(
|
||||
.assertStatesWithPrevious(
|
||||
initialState,
|
||||
initialState.copy(asyncDisplayName = Loading()),
|
||||
initialState.copy(asyncDisplayName = Fail(AN_ERROR)),
|
||||
{ copy(asyncDisplayName = Loading()) },
|
||||
{ copy(asyncDisplayName = Fail(AN_ERROR)) },
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.Failure(AN_ERROR))
|
||||
.finish()
|
||||
|
@ -264,6 +309,7 @@ class OnboardingViewModelTest {
|
|||
FakeVectorFeatures(),
|
||||
FakeAnalyticsTracker(),
|
||||
fakeUriFilenameResolver.instance,
|
||||
fakeRegisterActionHandler.instance,
|
||||
FakeVectorOverrides()
|
||||
)
|
||||
}
|
||||
|
@ -286,14 +332,6 @@ class OnboardingViewModelTest {
|
|||
state.copy(asyncProfilePicture = Fail(cause))
|
||||
)
|
||||
|
||||
private fun givenSuccessfullyCreatesAccount() {
|
||||
fakeActiveSessionHolder.expectSetsActiveSession(fakeSession)
|
||||
val registrationWizard = FakeRegistrationWizard().also { it.givenSuccessfulDummy(fakeSession) }
|
||||
fakeAuthenticationService.givenRegistrationWizard(registrationWizard)
|
||||
fakeAuthenticationService.expectReset()
|
||||
fakeSession.expectStartsSyncing()
|
||||
}
|
||||
|
||||
private fun expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState: OnboardingViewState): List<OnboardingViewState> {
|
||||
return listOf(
|
||||
personalisedInitialState,
|
||||
|
@ -304,4 +342,26 @@ class OnboardingViewModelTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun givenSuccessfullyCreatesAccount() {
|
||||
fakeActiveSessionHolder.expectSetsActiveSession(fakeSession)
|
||||
fakeAuthenticationService.expectReset()
|
||||
fakeSession.expectStartsSyncing()
|
||||
}
|
||||
|
||||
private fun givenRegistrationResultFor(action: RegisterAction, result: RegistrationResult) {
|
||||
givenRegistrationResultsFor(listOf(action to result))
|
||||
}
|
||||
|
||||
private fun givenRegistrationResultsFor(results: List<Pair<RegisterAction, RegistrationResult>>) {
|
||||
fakeAuthenticationService.givenRegistrationStarted(true)
|
||||
val registrationWizard = FakeRegistrationWizard()
|
||||
fakeAuthenticationService.givenRegistrationWizard(registrationWizard)
|
||||
fakeRegisterActionHandler.givenResultsFor(registrationWizard, results)
|
||||
}
|
||||
}
|
||||
|
||||
private fun HomeServerCapabilities.toPersonalisationState() = PersonalizationState(
|
||||
supportsChangingDisplayName = canChangeDisplayName,
|
||||
supportsChangingProfilePicture = canChangeAvatar
|
||||
)
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.onboarding
|
||||
|
||||
import im.vector.app.test.fakes.FakeRegistrationWizard
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
|
||||
private val A_SESSION = FakeSession()
|
||||
private val AN_EXPECTED_RESULT = RegistrationResult.Success(A_SESSION)
|
||||
private const val A_USERNAME = "a username"
|
||||
private const val A_PASSWORD = "a password"
|
||||
private const val AN_INITIAL_DEVICE_NAME = "a device name"
|
||||
private const val A_CAPTCHA_RESPONSE = "a captcha response"
|
||||
private const val A_PID_CODE = "a pid code"
|
||||
private const val EMAIL_VALIDATED_DELAY = 10000L
|
||||
private val A_PID_TO_REGISTER = RegisterThreePid.Email("an email")
|
||||
|
||||
class RegistrationActionHandlerTest {
|
||||
|
||||
private val fakeRegistrationWizard = FakeRegistrationWizard()
|
||||
private val registrationActionHandler = RegistrationActionHandler()
|
||||
|
||||
@Test
|
||||
fun `when handling register action then delegates to wizard`() = runBlockingTest {
|
||||
val cases = listOf(
|
||||
case(RegisterAction.StartRegistration) { getRegistrationFlow() },
|
||||
case(RegisterAction.CaptchaDone(A_CAPTCHA_RESPONSE)) { performReCaptcha(A_CAPTCHA_RESPONSE) },
|
||||
case(RegisterAction.AcceptTerms) { acceptTerms() },
|
||||
case(RegisterAction.RegisterDummy) { dummy() },
|
||||
case(RegisterAction.AddThreePid(A_PID_TO_REGISTER)) { addThreePid(A_PID_TO_REGISTER) },
|
||||
case(RegisterAction.SendAgainThreePid) { sendAgainThreePid() },
|
||||
case(RegisterAction.ValidateThreePid(A_PID_CODE)) { handleValidateThreePid(A_PID_CODE) },
|
||||
case(RegisterAction.CheckIfEmailHasBeenValidated(EMAIL_VALIDATED_DELAY)) { checkIfEmailHasBeenValidated(EMAIL_VALIDATED_DELAY) },
|
||||
case(RegisterAction.CreateAccount(A_USERNAME, A_PASSWORD, AN_INITIAL_DEVICE_NAME)) {
|
||||
createAccount(A_USERNAME, A_PASSWORD, AN_INITIAL_DEVICE_NAME)
|
||||
}
|
||||
)
|
||||
|
||||
cases.forEach { testSuccessfulActionDelegation(it) }
|
||||
}
|
||||
|
||||
private suspend fun testSuccessfulActionDelegation(case: Case) {
|
||||
fakeRegistrationWizard.givenSuccessFor(result = A_SESSION, case.expect)
|
||||
|
||||
val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, case.action)
|
||||
|
||||
result shouldBeEqualTo AN_EXPECTED_RESULT
|
||||
}
|
||||
}
|
||||
|
||||
private fun case(action: RegisterAction, expect: suspend RegistrationWizard.() -> RegistrationResult) = Case(action, expect)
|
||||
|
||||
private class Case(val action: RegisterAction, val expect: suspend RegistrationWizard.() -> RegistrationResult)
|
|
@ -23,10 +23,15 @@ import org.matrix.android.sdk.api.auth.AuthenticationService
|
|||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
|
||||
class FakeAuthenticationService : AuthenticationService by mockk() {
|
||||
|
||||
fun givenRegistrationWizard(registrationWizard: RegistrationWizard) {
|
||||
every { getRegistrationWizard() } returns registrationWizard
|
||||
}
|
||||
|
||||
fun givenRegistrationStarted(started: Boolean) {
|
||||
every { isRegistrationStarted } returns started
|
||||
}
|
||||
|
||||
fun expectReset() {
|
||||
coJustRun { reset() }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.test.fakes
|
||||
|
||||
import im.vector.app.features.onboarding.RegisterAction
|
||||
import im.vector.app.features.onboarding.RegistrationActionHandler
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
|
||||
class FakeRegisterActionHandler {
|
||||
|
||||
val instance = mockk<RegistrationActionHandler>()
|
||||
|
||||
fun givenResultFor(wizard: RegistrationWizard, action: RegisterAction, result: RegistrationResult) {
|
||||
coEvery { instance.handleRegisterAction(wizard, action) } answers {
|
||||
it.invocation.args.first()
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fun givenResultsFor(wizard: RegistrationWizard, result: List<Pair<RegisterAction, RegistrationResult>>) {
|
||||
coEvery { instance.handleRegisterAction(wizard, any()) } answers {
|
||||
val actionArg = it.invocation.args[1] as RegisterAction
|
||||
result.first { it.first == actionArg }.second
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,14 @@ import org.matrix.android.sdk.api.session.Session
|
|||
class FakeRegistrationWizard : RegistrationWizard by mockk() {
|
||||
|
||||
fun givenSuccessfulDummy(session: Session) {
|
||||
coEvery { dummy() } returns RegistrationResult.Success(session)
|
||||
givenSuccessFor(session) { dummy() }
|
||||
}
|
||||
|
||||
fun givenSuccessFor(result: Session, expect: suspend RegistrationWizard.() -> RegistrationResult) {
|
||||
coEvery { expect(this@FakeRegistrationWizard) } returns RegistrationResult.Success(result)
|
||||
}
|
||||
|
||||
fun givenSuccessfulAcceptTerms(session: Session) {
|
||||
coEvery { acceptTerms() } returns RegistrationResult.Success(session)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue