adding tests around the editing of the homeserver url

This commit is contained in:
Adam Brown 2022-04-04 14:26:55 +01:00
parent e9f50038a4
commit 8b2e2a16e2
12 changed files with 260 additions and 20 deletions

View File

@ -18,3 +18,5 @@ package im.vector.app.core.extensions
inline fun <reified T> List<T>.nextOrNull(index: Int) = getOrNull(index + 1)
inline fun <reified T> List<T>.prevOrNull(index: Int) = getOrNull(index - 1)
fun <T> List<T>.containsAll(vararg items: T) = this.containsAll(items.toList())

View File

@ -140,8 +140,8 @@ class OnboardingViewModel @AssistedInject constructor(
is OnboardingAction.UpdateServerType -> handleUpdateServerType(action)
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
is OnboardingAction.InitWith -> handleInitWith(action)
is OnboardingAction.SelectHomeServer -> handleHomeserverChange(action.homeServerUrl).also { lastAction = action }
is OnboardingAction.EditHomeServer -> handleHomeserverChange(action.homeServerUrl).also { lastAction = action }
is OnboardingAction.SelectHomeServer -> run { lastAction = action }.also { handleHomeserverChange(action.homeServerUrl) }
is OnboardingAction.EditHomeServer -> run { lastAction = action }.also { handleHomeserverChange(action.homeServerUrl) }
is OnboardingAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
is OnboardingAction.Register -> handleRegisterWith(action).also { lastAction = action }
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)

View File

@ -67,7 +67,6 @@ data class SelectedHomeserverState(
val sourceUrl: String? = null,
val declaredUrl: String? = null,
val preferredLoginMode: LoginMode = LoginMode.Unknown,
// Supported types for the login. We cannot use a sealed class for LoginType because it is not serializable
val supportedLoginTypes: List<String> = emptyList(),
) : Parcelable

View File

@ -17,6 +17,7 @@
package im.vector.app.features.onboarding
import im.vector.app.R
import im.vector.app.core.extensions.containsAll
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.features.login.LoginMode
@ -37,7 +38,7 @@ class StartAuthenticationFlowUseCase @Inject constructor(
val preferredLoginMode = authFlow.findPreferredLoginMode()
val selection = createSelectedHomeserverState(authFlow, config, preferredLoginMode)
val isOutdated = (preferredLoginMode == LoginMode.Password && !authFlow.isLoginAndRegistrationSupported) || authFlow.isOutdatedHomeserver
return StartAuthenticationResult(isOutdated, selection, preferredLoginMode, authFlow.supportedLoginTypes.toList())
return StartAuthenticationResult(isOutdated, selection)
}
private fun createSelectedHomeserverState(authFlow: LoginFlowResult, config: HomeServerConnectionConfig, preferredLoginMode: LoginMode): SelectedHomeserverState {
@ -55,18 +56,14 @@ class StartAuthenticationFlowUseCase @Inject constructor(
}
private fun LoginFlowResult.findPreferredLoginMode() = when {
// SSO login is taken first
supportedLoginTypes.contains(LoginFlowTypes.SSO) &&
supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders)
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders)
supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
supportedLoginTypes.containsAll(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders)
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders)
supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}
data class StartAuthenticationResult(
val isHomeserverOutdated: Boolean,
val selectedHomeserverState: SelectedHomeserverState,
val loginMode: LoginMode,
val supportedLoginTypes: List<String>,
val selectedHomeserverState: SelectedHomeserverState
)
}

View File

@ -18,8 +18,10 @@ package im.vector.app.features.onboarding
import android.net.Uri
import com.airbnb.mvrx.test.MvRxTestRule
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.login.SignMode
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakeAnalyticsTracker
import im.vector.app.test.fakes.FakeAuthenticationService
@ -30,6 +32,7 @@ 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.FakeStartAuthenticationFlowUseCase
import im.vector.app.test.fakes.FakeStringProvider
import im.vector.app.test.fakes.FakeUri
import im.vector.app.test.fakes.FakeUriFilenameResolver
@ -41,6 +44,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
@ -58,6 +62,9 @@ private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplay
private val AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList())
private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT)
private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name")
private const val A_HOMESERVER_URL = "https://edited-homeserver.org"
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password)
class OnboardingViewModelTest {
@ -74,6 +81,9 @@ class OnboardingViewModelTest {
private val fakeRegisterActionHandler = FakeRegisterActionHandler()
private val fakeDirectLoginUseCase = FakeDirectLoginUseCase()
private val fakeVectorFeatures = FakeVectorFeatures()
private val fakeHomeServerConnectionConfigFactory = FakeHomeServerConnectionConfigFactory()
private val fakeStartAuthenticationFlowUseCase = FakeStartAuthenticationFlowUseCase()
private val fakeHomeServerHistoryService = FakeHomeServerHistoryService()
lateinit var viewModel: OnboardingViewModel
@ -224,6 +234,25 @@ class OnboardingViewModelTest {
.finish()
}
@Test
fun `given when editing homeserver, then updates selected homeserver state and emits edited event`() = runTest {
val test = viewModel.test()
fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, A_HOMESERVER_CONFIG)
fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(false, SELECTED_HOMESERVER_STATE))
fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString())
viewModel.handle(OnboardingAction.EditHomeServer(A_HOMESERVER_URL))
test
.assertStatesChanges(
initialState,
{ copy(isLoading = true) },
{ copy(isLoading = false, selectedHomeserver = SELECTED_HOMESERVER_STATE) },
)
.assertEvents(OnboardingViewEvents.OnHomeserverEdited)
.finish()
}
@Test
fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest {
fakeVectorFeatures.givenPersonalisationEnabled()
@ -383,16 +412,16 @@ class OnboardingViewModelTest {
fakeContext.instance,
fakeAuthenticationService,
fakeActiveSessionHolder.instance,
FakeHomeServerConnectionConfigFactory().instance,
fakeHomeServerConnectionConfigFactory.instance,
ReAuthHelper(),
FakeStringProvider().instance,
FakeHomeServerHistoryService(),
fakeHomeServerHistoryService,
fakeVectorFeatures,
FakeAnalyticsTracker(),
fakeUriFilenameResolver.instance,
fakeRegisterActionHandler.instance,
fakeDirectLoginUseCase.instance,
StartAuthenticationFlowUseCase(fakeAuthenticationService, FakeStringProvider().instance),
fakeStartAuthenticationFlowUseCase.instance,
FakeVectorOverrides()
)
}

View File

@ -0,0 +1,157 @@
/*
* 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.R
import im.vector.app.features.login.LoginMode
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
import im.vector.app.test.fakes.FakeAuthenticationService
import im.vector.app.test.fakes.FakeStringProvider
import im.vector.app.test.fakes.FakeUri
import im.vector.app.test.fakes.toTestString
import io.mockk.coVerifyOrder
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Before
import org.junit.Test
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
private const val MATRIX_ORG_URL = "https://any-value.org/"
private const val A_DECLARED_HOMESERVER_URL = "https://foo.bar"
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(homeServerUri = FakeUri().instance)
private val SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>()
class StartAuthenticationFlowUseCaseTest {
private val fakeAuthenticationService = FakeAuthenticationService()
private val fakeStringProvider = FakeStringProvider()
private val useCase = StartAuthenticationFlowUseCase(fakeAuthenticationService, fakeStringProvider.instance)
@Before
fun setUp() {
fakeAuthenticationService.expectedCancelsPendingLogin()
}
@Test
fun `given empty login result when starting authentication flow then returns empty result`() = runTest {
val loginResult = aLoginResult()
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
result shouldBeEqualTo expectedResult()
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
}
@Test
fun `given login supports SSO and Password when starting authentication flow then prefers SsoAndPassword`() = runTest {
val supportedLoginTypes = listOf(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD)
val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes)
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
result shouldBeEqualTo expectedResult(
supportedLoginTypes = supportedLoginTypes,
preferredLoginMode = LoginMode.SsoAndPassword(SSO_IDENTITY_PROVIDERS),
)
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
}
@Test
fun `given login supports SSO when starting authentication flow then prefers Sso`() = runTest {
val supportedLoginTypes = listOf(LoginFlowTypes.SSO)
val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes)
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
result shouldBeEqualTo expectedResult(
supportedLoginTypes = supportedLoginTypes,
preferredLoginMode = LoginMode.Sso(SSO_IDENTITY_PROVIDERS),
)
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
}
@Test
fun `given login supports Password when starting authentication flow then prefers Password`() = runTest {
val supportedLoginTypes = listOf(LoginFlowTypes.PASSWORD)
val loginResult = aLoginResult(supportedLoginTypes = supportedLoginTypes)
fakeAuthenticationService.givenLoginFlow(A_HOMESERVER_CONFIG, loginResult)
val result = useCase.execute(A_HOMESERVER_CONFIG)
result shouldBeEqualTo expectedResult(
supportedLoginTypes = supportedLoginTypes,
preferredLoginMode = LoginMode.Password,
)
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
}
@Test
fun `given matrix dot org url when starting authentication flow then provides description`() = runTest {
val matrixOrgConfig = HomeServerConnectionConfig(homeServerUri = FakeUri(MATRIX_ORG_URL).instance)
fakeStringProvider.given(R.string.matrix_org_server_url, result = MATRIX_ORG_URL)
fakeAuthenticationService.givenLoginFlow(matrixOrgConfig, aLoginResult())
val result = useCase.execute(matrixOrgConfig)
result shouldBeEqualTo expectedResult(
description = R.string.ftue_auth_create_account_matrix_dot_org_server_description.toTestString(),
homeserverSourceUrl = MATRIX_ORG_URL
)
verifyClearsAndThenStartsLogin(matrixOrgConfig)
}
private fun aLoginResult(
supportedLoginTypes: List<String> = emptyList()
) = LoginFlowResult(
supportedLoginTypes = supportedLoginTypes,
ssoIdentityProviders = SSO_IDENTITY_PROVIDERS,
isLoginAndRegistrationSupported = true,
homeServerUrl = A_DECLARED_HOMESERVER_URL,
isOutdatedHomeserver = false
)
private fun expectedResult(
isHomeserverOutdated: Boolean = false,
description: String? = null,
preferredLoginMode: LoginMode = LoginMode.Unsupported,
supportedLoginTypes: List<String> = emptyList(),
homeserverSourceUrl: String = A_HOMESERVER_CONFIG.homeServerUri.toString()
) = StartAuthenticationResult(
isHomeserverOutdated,
SelectedHomeserverState(
description = description,
sourceUrl = homeserverSourceUrl,
declaredUrl = A_DECLARED_HOMESERVER_URL,
preferredLoginMode = preferredLoginMode,
supportedLoginTypes = supportedLoginTypes
)
)
private fun verifyClearsAndThenStartsLogin(homeServerConnectionConfig: HomeServerConnectionConfig) {
coVerifyOrder {
fakeAuthenticationService.cancelPendingLoginOrRegistration()
fakeAuthenticationService.getLoginFlow(homeServerConnectionConfig)
}
}
}

View File

@ -22,6 +22,7 @@ import io.mockk.every
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
@ -35,10 +36,18 @@ class FakeAuthenticationService : AuthenticationService by mockk() {
every { isRegistrationStarted } returns started
}
fun givenLoginFlow(config: HomeServerConnectionConfig, result: LoginFlowResult) {
coEvery { getLoginFlow(config) } returns result
}
fun expectReset() {
coJustRun { reset() }
}
fun expectedCancelsPendingLogin() {
coJustRun { cancelPendingLoginOrRegistration() }
}
fun givenWellKnown(matrixId: String, config: HomeServerConnectionConfig?, result: WellknownResult) {
coEvery { getWellKnownData(matrixId, config) } returns result
}
@ -52,6 +61,6 @@ class FakeAuthenticationService : AuthenticationService by mockk() {
}
fun givenDirectAuthenticationThrows(config: HomeServerConnectionConfig, matrixId: String, password: String, deviceName: String, cause: Throwable) {
coEvery { directAuthentication(config, matrixId, password, deviceName) } throws cause
coEvery { directAuthentication(config, matrixId, password, deviceName) } throws cause
}
}

View File

@ -17,9 +17,14 @@
package im.vector.app.test.fakes
import im.vector.app.features.login.HomeServerConnectionConfigFactory
import io.mockk.every
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
class FakeHomeServerConnectionConfigFactory {
val instance: HomeServerConnectionConfigFactory = mockk()
fun givenConfigFor(url: String, config: HomeServerConnectionConfig) {
every { instance.create(url) } returns config
}
}

View File

@ -16,9 +16,13 @@
package im.vector.app.test.fakes
import io.mockk.justRun
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
class FakeHomeServerHistoryService : HomeServerHistoryService by mockk() {
override fun getKnownServersUrls() = emptyList<String>()
fun expectUrlToBeAdded(url: String) {
justRun { addHomeServerToHistory(url) }
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.StartAuthenticationFlowUseCase
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
import io.mockk.coEvery
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
class FakeStartAuthenticationFlowUseCase {
val instance = mockk<StartAuthenticationFlowUseCase>()
fun givenResult(config: HomeServerConnectionConfig, result: StartAuthenticationResult) {
coEvery { instance.execute(config) } returns result
}
}

View File

@ -21,7 +21,6 @@ import io.mockk.every
import io.mockk.mockk
class FakeStringProvider {
val instance = mockk<StringProvider>()
init {
@ -29,6 +28,10 @@ class FakeStringProvider {
"test-${args[0]}"
}
}
fun given(id: Int, result: String) {
every { instance.getString(id) } returns result
}
}
fun Int.toTestString() = "test-$this"

View File

@ -25,7 +25,10 @@ class FakeUri(contentEquals: String? = null) {
val instance = mockk<Uri>()
init {
contentEquals?.let { givenEquals(it) }
contentEquals?.let {
givenEquals(it)
every { instance.toString() } returns it
}
}
fun givenNonHierarchical() {