adding test case around invalid deeplinks within the onboarding flow
This commit is contained in:
parent
797e0ee706
commit
75d038b058
|
@ -187,7 +187,6 @@ object VectorStaticModule {
|
|||
return analyticsConfig
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesBuildMeta() = BuildMeta()
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.app.core.extensions
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.ConnectivityManager
|
||||
|
@ -30,11 +31,13 @@ import androidx.annotation.ColorRes
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import dagger.hilt.EntryPoints
|
||||
import im.vector.app.core.datastore.dataStoreProvider
|
||||
import im.vector.app.core.di.SingletonEntryPoint
|
||||
import im.vector.app.core.resources.BuildMeta
|
||||
import java.io.OutputStream
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
@ -88,9 +91,10 @@ fun Context.safeOpenOutputStream(uri: Uri): OutputStream? {
|
|||
* @return true if no active connection is found
|
||||
*/
|
||||
@Suppress("deprecation")
|
||||
fun Context.inferNoConnectivity(): Boolean {
|
||||
val connectivityManager: ConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
|
||||
@SuppressLint("NewApi") // false positive
|
||||
fun Context.inferNoConnectivity(buildMeta: BuildMeta): Boolean {
|
||||
val connectivityManager = getSystemService<ConnectivityManager>()!!
|
||||
return if (buildMeta.sdkInt > Build.VERSION_CODES.M) {
|
||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||
when {
|
||||
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> false
|
||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.app.core.extensions.configureAndStart
|
|||
import im.vector.app.core.extensions.inferNoConnectivity
|
||||
import im.vector.app.core.extensions.vectorStore
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.BuildMeta
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.ensureProtocol
|
||||
import im.vector.app.core.utils.ensureTrailingSlash
|
||||
|
@ -81,7 +82,8 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
private val registrationActionHandler: RegistrationActionHandler,
|
||||
private val directLoginUseCase: DirectLoginUseCase,
|
||||
private val startAuthenticationFlowUseCase: StartAuthenticationFlowUseCase,
|
||||
private val vectorOverrides: VectorOverrides
|
||||
private val vectorOverrides: VectorOverrides,
|
||||
private val buildMeta: BuildMeta
|
||||
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -638,18 +640,24 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
|
||||
private fun onAuthenticationStartError(it: Throwable, trigger: OnboardingAction.HomeServerChange) {
|
||||
when {
|
||||
it.isHomeserverUnavailable() && applicationContext.inferNoConnectivity() -> _viewEvents.post(
|
||||
it.isHomeserverUnavailable() && applicationContext.inferNoConnectivity(buildMeta) -> _viewEvents.post(
|
||||
OnboardingViewEvents.Failure(it)
|
||||
)
|
||||
it.isHomeserverUnavailable() && trigger is OnboardingAction.HomeServerChange.SelectHomeServer -> _viewEvents.post(
|
||||
OnboardingViewEvents.DeeplinkAuthenticationFailure(retryAction = trigger.resetToDefaultUrl())
|
||||
deeplinkUrlIsUnavailable(it, trigger) -> _viewEvents.post(
|
||||
OnboardingViewEvents.DeeplinkAuthenticationFailure(
|
||||
retryAction = (trigger as OnboardingAction.HomeServerChange.SelectHomeServer).resetToDefaultUrl()
|
||||
)
|
||||
)
|
||||
else -> _viewEvents.post(
|
||||
else -> _viewEvents.post(
|
||||
OnboardingViewEvents.Failure(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deeplinkUrlIsUnavailable(error: Throwable, trigger: OnboardingAction.HomeServerChange) = error.isHomeserverUnavailable() &&
|
||||
loginConfig != null &&
|
||||
trigger is OnboardingAction.HomeServerChange.SelectHomeServer
|
||||
|
||||
private fun OnboardingAction.HomeServerChange.SelectHomeServer.resetToDefaultUrl() = copy(homeServerUrl = defaultHomeserverUrl)
|
||||
|
||||
private suspend fun onAuthenticationStartedSuccess(
|
||||
|
|
|
@ -17,17 +17,13 @@
|
|||
package im.vector.app.features.onboarding.ftueauth
|
||||
|
||||
import android.widget.Button
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hasContentFlow
|
||||
import im.vector.app.core.extensions.inferNoConnectivity
|
||||
import im.vector.app.features.login.SignMode
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
|
||||
|
||||
fun SignMode.toAuthenticateAction(login: String, password: String, initialDeviceName: String): OnboardingAction.AuthenticateAction {
|
||||
return when (this) {
|
||||
|
|
|
@ -18,6 +18,8 @@ package im.vector.app.features.onboarding
|
|||
|
||||
import android.net.Uri
|
||||
import com.airbnb.mvrx.test.MvRxTestRule
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.login.LoginConfig
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import im.vector.app.features.login.ReAuthHelper
|
||||
import im.vector.app.features.login.SignMode
|
||||
|
@ -38,6 +40,8 @@ import im.vector.app.test.fakes.FakeUri
|
|||
import im.vector.app.test.fakes.FakeUriFilenameResolver
|
||||
import im.vector.app.test.fakes.FakeVectorFeatures
|
||||
import im.vector.app.test.fakes.FakeVectorOverrides
|
||||
import im.vector.app.test.fakes.toTestString
|
||||
import im.vector.app.test.fixtures.aBuildMeta
|
||||
import im.vector.app.test.fixtures.aHomeServerCapabilities
|
||||
import im.vector.app.test.test
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
@ -242,6 +246,28 @@ class OnboardingViewModelTest {
|
|||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given unavailable deeplink, when selecting homeserver, then emits failure with default homeserver as retry action`() = runTest {
|
||||
fakeContext.givenHasConnection()
|
||||
fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, A_HOMESERVER_CONFIG)
|
||||
fakeStartAuthenticationFlowUseCase.givenHomeserverUnavailable(A_HOMESERVER_CONFIG)
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(OnboardingAction.InitWith(LoginConfig(A_HOMESERVER_URL, null)))
|
||||
viewModel.handle(OnboardingAction.HomeServerChange.SelectHomeServer(A_HOMESERVER_URL))
|
||||
|
||||
val expectedRetryAction = OnboardingAction.HomeServerChange.SelectHomeServer("${R.string.matrix_org_server_url.toTestString()}/")
|
||||
test
|
||||
.assertStatesChanges(
|
||||
initialState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(isLoading = false) }
|
||||
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.DeeplinkAuthenticationFailure(expectedRetryAction))
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given in the sign up flow, when editing homeserver, then updates selected homeserver state and emits edited event`() = runTest {
|
||||
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
||||
|
@ -457,7 +483,8 @@ class OnboardingViewModelTest {
|
|||
fakeRegisterActionHandler.instance,
|
||||
fakeDirectLoginUseCase.instance,
|
||||
fakeStartAuthenticationFlowUseCase.instance,
|
||||
FakeVectorOverrides()
|
||||
FakeVectorOverrides(),
|
||||
aBuildMeta()
|
||||
).also {
|
||||
viewModel = it
|
||||
initialState = state
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeConnectivityManager {
|
||||
val instance = mockk<ConnectivityManager>()
|
||||
|
||||
fun givenNoActiveConnection() {
|
||||
every { instance.activeNetwork } returns null
|
||||
}
|
||||
|
||||
fun givenHasActiveConnection() {
|
||||
val network = mockk<Network>()
|
||||
every { instance.activeNetwork } returns network
|
||||
|
||||
val networkCapabilities = FakeNetworkCapabilities()
|
||||
networkCapabilities.givenTransports(
|
||||
NetworkCapabilities.TRANSPORT_CELLULAR,
|
||||
NetworkCapabilities.TRANSPORT_WIFI,
|
||||
NetworkCapabilities.TRANSPORT_VPN
|
||||
)
|
||||
every { instance.getNetworkCapabilities(network) } returns networkCapabilities.instance
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ package im.vector.app.test.fakes
|
|||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import io.mockk.every
|
||||
|
@ -48,4 +49,21 @@ class FakeContext(
|
|||
fun givenMissingSafeOutputStreamFor(uri: Uri) {
|
||||
every { contentResolver.openOutputStream(uri, "wt") } returns null
|
||||
}
|
||||
|
||||
fun givenNoConnection() {
|
||||
val connectivityManager = FakeConnectivityManager()
|
||||
connectivityManager.givenNoActiveConnection()
|
||||
givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance)
|
||||
}
|
||||
|
||||
private fun <T> givenService(name: String, klass: Class<T>, service: T) {
|
||||
every { instance.getSystemService(name) } returns service
|
||||
every { instance.getSystemService(klass) } returns service
|
||||
}
|
||||
|
||||
fun givenHasConnection() {
|
||||
val connectivityManager = FakeConnectivityManager()
|
||||
connectivityManager.givenHasActiveConnection()
|
||||
givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 android.net.NetworkCapabilities
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeNetworkCapabilities {
|
||||
val instance = mockk<NetworkCapabilities>()
|
||||
|
||||
fun givenTransports(vararg type: Int) {
|
||||
every { instance.hasTransport(any()) } answers {
|
||||
val input = it.invocation.args.first() as Int
|
||||
type.contains(input)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,9 +18,11 @@ package im.vector.app.test.fakes
|
|||
|
||||
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase
|
||||
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
|
||||
import im.vector.app.test.fixtures.aHomeserverUnavailableError
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
|
||||
class FakeStartAuthenticationFlowUseCase {
|
||||
|
||||
|
@ -29,4 +31,8 @@ class FakeStartAuthenticationFlowUseCase {
|
|||
fun givenResult(config: HomeServerConnectionConfig, result: StartAuthenticationResult) {
|
||||
coEvery { instance.execute(config) } returns result
|
||||
}
|
||||
|
||||
fun givenHomeserverUnavailable(config: HomeServerConnectionConfig) {
|
||||
coEvery { instance.execute(config) } throws aHomeserverUnavailableError()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,11 @@ package im.vector.app.test.fixtures
|
|||
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import java.net.UnknownHostException
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
fun a401ServerError() = Failure.ServerError(
|
||||
MatrixError(MatrixError.M_UNAUTHORIZED, ""), HttpsURLConnection.HTTP_UNAUTHORIZED
|
||||
)
|
||||
|
||||
fun aHomeserverUnavailableError() = Failure.NetworkConnection(UnknownHostException())
|
||||
|
|
Loading…
Reference in New Issue