Add tests.

This commit is contained in:
Benoit Marty 2023-06-08 22:56:36 +02:00
parent 319d74b12b
commit 563f8d3403
7 changed files with 180 additions and 77 deletions

View file

@ -59,6 +59,7 @@ dependencies {
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.tests.testutils)
androidTestImplementation(libs.test.junitext)
}

View file

@ -49,10 +49,11 @@ class DefaultHomeserverResolver @Inject constructor(
emit(Async.Uninitialized)
// Debounce
delay(300)
val clean = userInput.trim()
if (clean.length < 4) return@flow
val trimmedUserInput = userInput.trim()
if (trimmedUserInput.length < 4) return@flow
emit(Async.Loading())
val list = getUrlCandidate(clean.ensureProtocol().removeSuffix("/"))
val candidateBase = trimmedUserInput.ensureProtocol().removeSuffix("/")
val list = getUrlCandidates(candidateBase)
val currentList = Collections.synchronizedList(mutableListOf<HomeserverData>())
// Run all the requests in parallel
withContext(dispatchers.io) {
@ -77,14 +78,14 @@ class DefaultHomeserverResolver @Inject constructor(
}
}.awaitAll()
}
// If list is empty, and the user as entered an URL, do not block the user.
// If list is empty, and the user has entered an URL, do not block the user.
if (currentList.isEmpty()) {
if (userInput.isValidUrl()) {
if (trimmedUserInput.isValidUrl()) {
emit(
Async.Success(
listOf(
HomeserverData(
homeserverUrl = userInput,
homeserverUrl = trimmedUserInput,
isWellknownValid = false,
supportSlidingSync = false,
)
@ -97,7 +98,7 @@ class DefaultHomeserverResolver @Inject constructor(
}
}
private fun getUrlCandidate(data: String): List<String> {
private fun getUrlCandidates(data: String): List<String> {
return buildList {
if (data.contains(".")) {
// TLD detected?

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2023 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 io.element.android.features.login.impl.changeaccountprovider.form.network
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.network.RetrofitFactory
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultWellknownRequest @Inject constructor(
private val retrofitFactory: RetrofitFactory,
) : WellknownRequest {
/**
* Return the WellKnown data, if found.
* @param baseUrl for instance https://matrix.org
*/
override suspend fun execute(baseUrl: String): WellKnown {
val wellknownApi = retrofitFactory.create(baseUrl)
.create(WellknownAPI::class.java)
return wellknownApi.getWellKnown()
}
}

View file

@ -15,19 +15,10 @@
*/
package io.element.android.features.login.impl.changeaccountprovider.form.network
import io.element.android.libraries.network.RetrofitFactory
import javax.inject.Inject
class WellknownRequest @Inject constructor(
private val retrofitFactory: RetrofitFactory,
) {
interface WellknownRequest {
/**
* Return the WellKnown data, if found.
* Return the WellKnown data, or throw an error if not found.
* @param baseUrl for instance https://matrix.org
*/
suspend fun execute(baseUrl: String): WellKnown {
val wellknownApi = retrofitFactory.create(baseUrl)
.create(WellknownAPI::class.java)
return wellknownApi.getWellKnown()
}
suspend fun execute(baseUrl: String): WellKnown
}

View file

@ -21,22 +21,27 @@ import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnown
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnownBaseConfig
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnownSlidingSyncConfig
import io.element.android.features.login.impl.datasource.AccountProviderDataSource
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.runTest
import org.junit.Test
class ChangeAccountProviderFormPresenterTest {
@Test
fun `present - initial state`() = runTest {
val homeServerResolver = FakeHomeServerResolver()
val fakeWellknownRequest = FakeWellknownRequest()
val changeServerPresenter = ChangeServerPresenter(
FakeAuthenticationService(),
AccountProviderDataSource()
)
val presenter = ChangeAccountProviderFormPresenter(
homeServerResolver,
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter
)
moleculeFlow(RecompositionClock.Immediate) {
@ -50,13 +55,13 @@ class ChangeAccountProviderFormPresenterTest {
@Test
fun `present - enter text no result`() = runTest {
val homeServerResolver = FakeHomeServerResolver()
val fakeWellknownRequest = FakeWellknownRequest()
val changeServerPresenter = ChangeServerPresenter(
FakeAuthenticationService(),
AccountProviderDataSource()
)
val presenter = ChangeAccountProviderFormPresenter(
homeServerResolver,
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter
)
moleculeFlow(RecompositionClock.Immediate) {
@ -73,12 +78,41 @@ class ChangeAccountProviderFormPresenterTest {
}
@Test
fun `present - enter text one then two results`() = runTest {
val homeServerResolver = FakeHomeServerResolver()
homeServerResolver.givenResult(
listOf(
listOf(aHomeserverData()),
listOf(aHomeserverData(), aHomeserverData()),
fun `present - enter valid url no wellknown`() = runTest {
val fakeWellknownRequest = FakeWellknownRequest()
val changeServerPresenter = ChangeServerPresenter(
FakeAuthenticationService(),
AccountProviderDataSource()
)
val presenter = ChangeAccountProviderFormPresenter(
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("https://test.org"))
val withInputState = awaitItem()
assertThat(withInputState.userInput).isEqualTo("https://test.org")
assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized)
assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java)
assertThat(awaitItem().userInputResult).isEqualTo(
Async.Success(
listOf(
aHomeserverData(homeserverUrl = "https://test.org", isWellknownValid = false, supportSlidingSync = false)
)
)
)
}
}
@Test
fun `present - enter text one result no sliding sync`() = runTest {
val fakeWellknownRequest = FakeWellknownRequest()
fakeWellknownRequest.givenResultMap(
mapOf(
"https://test.org" to aWellKnown().copy(slidingSyncProxy = null),
)
)
val changeServerPresenter = ChangeServerPresenter(
@ -86,7 +120,7 @@ class ChangeAccountProviderFormPresenterTest {
AccountProviderDataSource()
)
val presenter = ChangeAccountProviderFormPresenter(
homeServerResolver,
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter
)
moleculeFlow(RecompositionClock.Immediate) {
@ -98,8 +132,62 @@ class ChangeAccountProviderFormPresenterTest {
assertThat(withInputState.userInput).isEqualTo("test")
assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized)
assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java)
assertThat(awaitItem().userInputResult).isEqualTo(Async.Success(listOf(aHomeserverData())))
assertThat(awaitItem().userInputResult).isEqualTo(Async.Success(listOf(aHomeserverData(), aHomeserverData())))
assertThat(awaitItem().userInputResult).isEqualTo(
Async.Success(
listOf(
aHomeserverData(homeserverUrl = "https://test.org", isWellknownValid = true, supportSlidingSync = false)
)
)
)
}
}
@Test
fun `present - enter text one result with sliding sync`() = runTest {
val fakeWellknownRequest = FakeWellknownRequest()
fakeWellknownRequest.givenResultMap(
mapOf(
"https://test.io" to aWellKnown(),
)
)
val changeServerPresenter = ChangeServerPresenter(
FakeAuthenticationService(),
AccountProviderDataSource()
)
val presenter = ChangeAccountProviderFormPresenter(
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test"))
val withInputState = awaitItem()
assertThat(withInputState.userInput).isEqualTo("test")
assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized)
assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java)
assertThat(awaitItem().userInputResult).isEqualTo(
Async.Success(
listOf(
aHomeserverData(homeserverUrl = "https://test.io")
)
)
)
}
}
private fun aWellKnown(): WellKnown {
return WellKnown(
homeServer = WellKnownBaseConfig(
baseURL = A_HOMESERVER_URL
),
identityServer = WellKnownBaseConfig(
baseURL = A_HOMESERVER_URL
),
slidingSyncProxy = WellKnownSlidingSyncConfig(
url = A_HOMESERVER_URL
)
)
}
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2023 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 io.element.android.features.login.impl.changeaccountprovider.form
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.test.FAKE_DELAY_IN_MS
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class FakeHomeServerResolver : HomeserverResolver {
private var pendingResult: List<List<HomeserverData>> = emptyList()
fun givenResult(result: List<List<HomeserverData>>) {
pendingResult = result
}
override suspend fun resolve(userInput: String): Flow<Async<List<HomeserverData>>> = flow {
emit(Async.Uninitialized)
delay(FAKE_DELAY_IN_MS)
emit(Async.Loading())
// Sending the pending result
if (pendingResult.isEmpty()) {
emit(Async.Uninitialized)
} else {
pendingResult.forEach {
delay(FAKE_DELAY_IN_MS)
emit(Async.Success(it))
}
}
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 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 io.element.android.features.login.impl.changeaccountprovider.form
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnown
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellknownRequest
class FakeWellknownRequest : WellknownRequest {
private var resultMap: Map<String, WellKnown> = emptyMap()
fun givenResultMap(map: Map<String, WellKnown>) {
resultMap = map
}
override suspend fun execute(baseUrl: String): WellKnown {
return resultMap[baseUrl] ?: error("No result provided for $baseUrl")
}
}