Stop waiting for a room alias that the remote server never resolves (#7446)

Tapping a room alias whose server is down left the resolver screen on a
spinner for as long as the screen stayed open, with no way forward other
than going back.

Bound the resolution with the ten second timeout already used when
joining a room by address, so the retry dialog is shown instead. The fake
client's resolve lambda becomes suspending so a server that never answers
can be simulated.
This commit is contained in:
hayyaksi
2026-08-20 10:34:15 +03:00
committed by GitHub
parent 48f26ae33e
commit 918015b8bb
3 changed files with 28 additions and 2 deletions
@@ -24,7 +24,10 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import java.util.concurrent.TimeoutException
import kotlin.jvm.optionals.getOrElse
import kotlin.time.Duration.Companion.seconds
@AssistedInject
class RoomAliasResolverPresenter(
@@ -61,9 +64,14 @@ class RoomAliasResolverPresenter(
private fun CoroutineScope.resolveAlias(resolveState: MutableState<AsyncData<ResolvedRoomAlias>>) = launch {
suspend {
matrixClient.resolveRoomAlias(roomAlias)
val result = withTimeoutOrNull(ALIAS_RESOLVE_TIMEOUT_IN_SECONDS.seconds) {
matrixClient.resolveRoomAlias(roomAlias)
} ?: throw TimeoutException("Timed out while resolving ${roomAlias.value}")
result
.getOrThrow()
.getOrElse { throw RoomAliasResolverFailures.UnknownAlias }
}.runCatchingUpdatingState(resolveState)
}
}
private const val ALIAS_RESOLVE_TIMEOUT_IN_SECONDS = 10
@@ -22,10 +22,12 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SERVER_LIST
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import java.util.Optional
import java.util.concurrent.TimeoutException
class RoomAliasHelperPresenterTest {
@get:Rule
@@ -60,6 +62,22 @@ class RoomAliasHelperPresenterTest {
}
}
@Test
fun `present - a server that never answers ends up in an error state`() = runTest {
val client = FakeMatrixClient(
resolveRoomAliasResult = { awaitCancellation() }
)
val presenter = createPresenter(matrixClient = client)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
assertThat(awaitItem().resolveState.isUninitialized()).isTrue()
assertThat(awaitItem().resolveState.isLoading()).isTrue()
val resultState = awaitItem()
assertThat(resultState.resolveState.errorOrNull()).isInstanceOf(TimeoutException::class.java)
}
}
@Test
fun `present - resolve alias error and retry`() = runTest {
val client = FakeMatrixClient(
@@ -100,7 +100,7 @@ class FakeMatrixClient(
override val roomMembershipObserver: RoomMembershipObserver = RoomMembershipObserver(),
private val homeserverCapabilitiesProvider: FakeHomeserverCapabilitiesProvider = FakeHomeserverCapabilitiesProvider(),
private val accountManagementUrlResult: (AccountManagementAction?) -> Result<String?> = { lambdaError() },
private val resolveRoomAliasResult: (RoomAlias) -> Result<Optional<ResolvedRoomAlias>> = {
private val resolveRoomAliasResult: suspend (RoomAlias) -> Result<Optional<ResolvedRoomAlias>> = {
Result.success(
Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList()))
)