Add tests.
This commit is contained in:
parent
e1e984cfb0
commit
dbe7796704
7 changed files with 180 additions and 77 deletions
|
|
@ -59,6 +59,7 @@ dependencies {
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.tests.testutils)
|
||||||
|
|
||||||
androidTestImplementation(libs.test.junitext)
|
androidTestImplementation(libs.test.junitext)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,11 @@ class DefaultHomeserverResolver @Inject constructor(
|
||||||
emit(Async.Uninitialized)
|
emit(Async.Uninitialized)
|
||||||
// Debounce
|
// Debounce
|
||||||
delay(300)
|
delay(300)
|
||||||
val clean = userInput.trim()
|
val trimmedUserInput = userInput.trim()
|
||||||
if (clean.length < 4) return@flow
|
if (trimmedUserInput.length < 4) return@flow
|
||||||
emit(Async.Loading())
|
emit(Async.Loading())
|
||||||
val list = getUrlCandidate(clean.ensureProtocol().removeSuffix("/"))
|
val candidateBase = trimmedUserInput.ensureProtocol().removeSuffix("/")
|
||||||
|
val list = getUrlCandidates(candidateBase)
|
||||||
val currentList = Collections.synchronizedList(mutableListOf<HomeserverData>())
|
val currentList = Collections.synchronizedList(mutableListOf<HomeserverData>())
|
||||||
// Run all the requests in parallel
|
// Run all the requests in parallel
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.io) {
|
||||||
|
|
@ -77,14 +78,14 @@ class DefaultHomeserverResolver @Inject constructor(
|
||||||
}
|
}
|
||||||
}.awaitAll()
|
}.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 (currentList.isEmpty()) {
|
||||||
if (userInput.isValidUrl()) {
|
if (trimmedUserInput.isValidUrl()) {
|
||||||
emit(
|
emit(
|
||||||
Async.Success(
|
Async.Success(
|
||||||
listOf(
|
listOf(
|
||||||
HomeserverData(
|
HomeserverData(
|
||||||
homeserverUrl = userInput,
|
homeserverUrl = trimmedUserInput,
|
||||||
isWellknownValid = false,
|
isWellknownValid = false,
|
||||||
supportSlidingSync = 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 {
|
return buildList {
|
||||||
if (data.contains(".")) {
|
if (data.contains(".")) {
|
||||||
// TLD detected?
|
// TLD detected?
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,19 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package io.element.android.features.login.impl.changeaccountprovider.form.network
|
package io.element.android.features.login.impl.changeaccountprovider.form.network
|
||||||
|
|
||||||
import io.element.android.libraries.network.RetrofitFactory
|
interface WellknownRequest {
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class WellknownRequest @Inject constructor(
|
|
||||||
private val retrofitFactory: RetrofitFactory,
|
|
||||||
) {
|
|
||||||
/**
|
/**
|
||||||
* Return the WellKnown data, if found.
|
* Return the WellKnown data, or throw an error if not found.
|
||||||
* @param baseUrl for instance https://matrix.org
|
* @param baseUrl for instance https://matrix.org
|
||||||
*/
|
*/
|
||||||
suspend fun execute(baseUrl: String): WellKnown {
|
suspend fun execute(baseUrl: String): WellKnown
|
||||||
val wellknownApi = retrofitFactory.create(baseUrl)
|
|
||||||
.create(WellknownAPI::class.java)
|
|
||||||
return wellknownApi.getWellKnown()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,22 +21,27 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.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.features.login.impl.datasource.AccountProviderDataSource
|
||||||
import io.element.android.libraries.architecture.Async
|
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.libraries.matrix.test.auth.FakeAuthenticationService
|
||||||
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class ChangeAccountProviderFormPresenterTest {
|
class ChangeAccountProviderFormPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
val homeServerResolver = FakeHomeServerResolver()
|
val fakeWellknownRequest = FakeWellknownRequest()
|
||||||
val changeServerPresenter = ChangeServerPresenter(
|
val changeServerPresenter = ChangeServerPresenter(
|
||||||
FakeAuthenticationService(),
|
FakeAuthenticationService(),
|
||||||
AccountProviderDataSource()
|
AccountProviderDataSource()
|
||||||
)
|
)
|
||||||
val presenter = ChangeAccountProviderFormPresenter(
|
val presenter = ChangeAccountProviderFormPresenter(
|
||||||
homeServerResolver,
|
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||||
changeServerPresenter
|
changeServerPresenter
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
|
@ -50,13 +55,13 @@ class ChangeAccountProviderFormPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - enter text no result`() = runTest {
|
fun `present - enter text no result`() = runTest {
|
||||||
val homeServerResolver = FakeHomeServerResolver()
|
val fakeWellknownRequest = FakeWellknownRequest()
|
||||||
val changeServerPresenter = ChangeServerPresenter(
|
val changeServerPresenter = ChangeServerPresenter(
|
||||||
FakeAuthenticationService(),
|
FakeAuthenticationService(),
|
||||||
AccountProviderDataSource()
|
AccountProviderDataSource()
|
||||||
)
|
)
|
||||||
val presenter = ChangeAccountProviderFormPresenter(
|
val presenter = ChangeAccountProviderFormPresenter(
|
||||||
homeServerResolver,
|
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||||
changeServerPresenter
|
changeServerPresenter
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
|
@ -73,12 +78,41 @@ class ChangeAccountProviderFormPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - enter text one then two results`() = runTest {
|
fun `present - enter valid url no wellknown`() = runTest {
|
||||||
val homeServerResolver = FakeHomeServerResolver()
|
val fakeWellknownRequest = FakeWellknownRequest()
|
||||||
homeServerResolver.givenResult(
|
val changeServerPresenter = ChangeServerPresenter(
|
||||||
listOf(
|
FakeAuthenticationService(),
|
||||||
listOf(aHomeserverData()),
|
AccountProviderDataSource()
|
||||||
listOf(aHomeserverData(), aHomeserverData()),
|
)
|
||||||
|
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(
|
val changeServerPresenter = ChangeServerPresenter(
|
||||||
|
|
@ -86,7 +120,7 @@ class ChangeAccountProviderFormPresenterTest {
|
||||||
AccountProviderDataSource()
|
AccountProviderDataSource()
|
||||||
)
|
)
|
||||||
val presenter = ChangeAccountProviderFormPresenter(
|
val presenter = ChangeAccountProviderFormPresenter(
|
||||||
homeServerResolver,
|
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||||
changeServerPresenter
|
changeServerPresenter
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
|
@ -98,8 +132,62 @@ class ChangeAccountProviderFormPresenterTest {
|
||||||
assertThat(withInputState.userInput).isEqualTo("test")
|
assertThat(withInputState.userInput).isEqualTo("test")
|
||||||
assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized)
|
assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized)
|
||||||
assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java)
|
assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java)
|
||||||
assertThat(awaitItem().userInputResult).isEqualTo(Async.Success(listOf(aHomeserverData())))
|
assertThat(awaitItem().userInputResult).isEqualTo(
|
||||||
assertThat(awaitItem().userInputResult).isEqualTo(Async.Success(listOf(aHomeserverData(), aHomeserverData())))
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue