From dbe5bb767b64f55212056d36bd874603532b4e73 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 8 Aug 2025 12:21:36 +0200 Subject: [PATCH] Extract code to retrieve .well-known files to its own modules. --- features/login/impl/build.gradle.kts | 5 +- .../DefaultAccountProviderAccessControl.kt | 7 +- .../ElementWellknownRetriever.kt | 42 -------- .../login/impl/resolver/HomeserverResolver.kt | 11 ++- .../network/DefaultWellknownRequest.kt | 27 ------ .../impl/resolver/network/WellknownRequest.kt | 15 --- .../WebClientUrlForAuthenticationRetriever.kt | 20 +--- ...DefaultAccountProviderAccessControlTest.kt | 7 +- .../FakeElementWellknownRetriever.kt | 19 ---- .../changeserver/ChangeServerPresenterTest.kt | 18 ++-- .../resolver/network/FakeWellknownRequest.kt | 19 ---- .../onboarding/OnBoardingPresenterTest.kt | 8 +- .../qrcode/scan/QrCodeScanPresenterTest.kt | 8 +- .../SearchAccountProviderPresenterTest.kt | 97 ++++++++++++++++--- libraries/wellknown/api/build.gradle.kts | 13 +++ .../wellknown/api/ElementWellKnown.kt | 13 +++ .../libraries/wellknown/api/WellKnown.kt | 17 ++++ .../wellknown/api/WellknownRetriever.kt | 13 +++ libraries/wellknown/impl/build.gradle.kts | 32 ++++++ .../impl/DefaultWellknownRetriever.kt | 69 +++++++++++++ .../impl/InternalElementWellKnown.kt | 6 +- .../wellknown/impl/InternalWellKnown.kt | 15 +-- .../impl/InternalWellKnownBaseConfig.kt | 4 +- .../libraries/wellknown/impl}/WellknownAPI.kt | 6 +- libraries/wellknown/test/build.gradle.kts | 19 ++++ .../wellknown/test/FakeWellknownRetriever.kt | 26 +++++ 26 files changed, 337 insertions(+), 199 deletions(-) delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/ElementWellknownRetriever.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/DefaultWellknownRequest.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownRequest.kt delete mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/FakeElementWellknownRetriever.kt delete mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/resolver/network/FakeWellknownRequest.kt create mode 100644 libraries/wellknown/api/build.gradle.kts create mode 100644 libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt create mode 100644 libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt create mode 100644 libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt create mode 100644 libraries/wellknown/impl/build.gradle.kts create mode 100644 libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/ElementWellKnown.kt => libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt (80%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt => libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt (65%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt => libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt (84%) rename {features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network => libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl}/WellknownAPI.kt (67%) create mode 100644 libraries/wellknown/test/build.gradle.kts create mode 100644 libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index a4f20fb97d..ec01f6012a 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -36,7 +36,6 @@ dependencies { implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api) - implementation(projects.libraries.network) implementation(projects.libraries.designsystem) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) @@ -44,10 +43,9 @@ dependencies { implementation(projects.libraries.qrcode) implementation(projects.libraries.oidc.api) implementation(projects.libraries.uiUtils) + implementation(projects.libraries.wellknown.api) implementation(libs.androidx.browser) - implementation(platform(libs.network.retrofit.bom)) implementation(libs.androidx.webkit) - implementation(libs.network.retrofit) implementation(libs.serialization.json) api(projects.features.login.api) @@ -65,6 +63,7 @@ dependencies { testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.oidc.test) testImplementation(projects.libraries.permissions.test) + testImplementation(projects.libraries.wellknown.test) testImplementation(projects.tests.testutils) testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt index d591e97f6f..bda1fa1c4b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt @@ -13,12 +13,13 @@ import io.element.android.features.login.api.accesscontrol.AccountProviderAccess import io.element.android.features.login.impl.changeserver.AccountProviderAccessException import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.di.AppScope +import io.element.android.libraries.wellknown.api.WellknownRetriever import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultAccountProviderAccessControl @Inject constructor( private val enterpriseService: EnterpriseService, - private val elementWellknownRetriever: ElementWellknownRetriever, + private val wellknownRetriever: WellknownRetriever, ) : AccountProviderAccessControl { override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String) = try { assertIsAllowedToConnectToAccountProvider( @@ -37,8 +38,8 @@ class DefaultAccountProviderAccessControl @Inject constructor( ) { if (enterpriseService.isEnterpriseBuild.not()) { // Ensure that Element Pro is not required for this account provider - val wellKnown = elementWellknownRetriever.retrieve( - accountProviderUrl = accountProviderUrl.ensureProtocol(), + val wellKnown = wellknownRetriever.getElementWellKnown( + baseUrl = accountProviderUrl.ensureProtocol(), ) if (wellKnown?.enforceElementPro == true) { throw AccountProviderAccessException.NeedElementProException( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/ElementWellknownRetriever.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/ElementWellknownRetriever.kt deleted file mode 100644 index e68809df07..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/ElementWellknownRetriever.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.login.impl.accesscontrol - -import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.features.login.impl.resolver.network.ElementWellKnown -import io.element.android.features.login.impl.resolver.network.WellknownAPI -import io.element.android.libraries.di.AppScope -import io.element.android.libraries.network.RetrofitFactory -import timber.log.Timber -import javax.inject.Inject - -interface ElementWellknownRetriever { - suspend fun retrieve(accountProviderUrl: String): ElementWellKnown? -} - -@ContributesBinding(AppScope::class) -class DefaultElementWellknownRetriever @Inject constructor( - private val retrofitFactory: RetrofitFactory, -) : ElementWellknownRetriever { - override suspend fun retrieve(accountProviderUrl: String): ElementWellKnown? { - val wellknownApi = try { - retrofitFactory.create(accountProviderUrl) - .create(WellknownAPI::class.java) - } catch (e: Exception) { - // If the base URL is not valid, we cannot retrieve the well-known data - Timber.e(e, "Failed to create Retrofit instance for $accountProviderUrl") - return null - } - return try { - wellknownApi.getElementWellKnown() - } catch (e: Exception) { - Timber.e(e, "Failed to retrieve Element well-known data for $accountProviderUrl") - null - } - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt index 6190d6ff6b..56b7391102 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt @@ -7,13 +7,14 @@ package io.element.android.features.login.impl.resolver -import io.element.android.features.login.impl.resolver.network.WellknownRequest import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.parallelMap import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.core.uri.isValidUrl +import io.element.android.libraries.wellknown.api.WellKnown +import io.element.android.libraries.wellknown.api.WellknownRetriever import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -27,7 +28,7 @@ import javax.inject.Inject */ class HomeserverResolver @Inject constructor( private val dispatchers: CoroutineDispatchers, - private val wellknownRequest: WellknownRequest, + private val wellknownRetriever: WellknownRetriever, ) { fun resolve(userInput: String): Flow> = flow { val flowContext = currentCoroutineContext() @@ -41,7 +42,7 @@ class HomeserverResolver @Inject constructor( list.parallelMap { url -> val wellKnown = tryOrNull { withTimeout(5000) { - wellknownRequest.execute(url) + wellknownRetriever.getWellKnown(url) } } val isValid = wellKnown?.isValid().orFalse() @@ -86,3 +87,7 @@ class HomeserverResolver @Inject constructor( } } } + +private fun WellKnown.isValid(): Boolean { + return homeServer?.baseURL?.isNotBlank().orFalse() +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/DefaultWellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/DefaultWellknownRequest.kt deleted file mode 100644 index d217b4ca9e..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/DefaultWellknownRequest.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ -package io.element.android.features.login.impl.resolver.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() - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownRequest.kt deleted file mode 100644 index b734850989..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownRequest.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ -package io.element.android.features.login.impl.resolver.network - -interface WellknownRequest { - /** - * Return the WellKnown data, or throw an error if not found. - * @param baseUrl for instance https://matrix.org - */ - suspend fun execute(baseUrl: String): WellKnown -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt index 3793f3a53b..8046576347 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt @@ -10,12 +10,10 @@ package io.element.android.features.login.impl.web import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding import io.element.android.appconfig.AuthenticationConfig -import io.element.android.features.login.impl.resolver.network.WellknownAPI import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported import io.element.android.libraries.di.AppScope -import io.element.android.libraries.network.RetrofitFactory +import io.element.android.libraries.wellknown.api.WellknownRetriever import timber.log.Timber -import java.net.HttpURLConnection import javax.inject.Inject interface WebClientUrlForAuthenticationRetriever { @@ -24,24 +22,16 @@ interface WebClientUrlForAuthenticationRetriever { @ContributesBinding(AppScope::class) class DefaultWebClientUrlForAuthenticationRetriever @Inject constructor( - private val retrofitFactory: RetrofitFactory, + private val wellknownRetriever: WellknownRetriever, ) : WebClientUrlForAuthenticationRetriever { override suspend fun retrieve(homeServerUrl: String): String { if (homeServerUrl != AuthenticationConfig.MATRIX_ORG_URL) { Timber.w("Temporary account creation flow is only supported on matrix.org") throw AccountCreationNotSupported() } - val wellknownApi = retrofitFactory.create(homeServerUrl) - .create(WellknownAPI::class.java) - val result = try { - wellknownApi.getElementWellKnown() - } catch (e: retrofit2.HttpException) { - throw when { - e.code() == HttpURLConnection.HTTP_NOT_FOUND -> AccountCreationNotSupported() - else -> e - } - } - val registrationHelperUrl = result.registrationHelperUrl + val wellknown = wellknownRetriever.getElementWellKnown(homeServerUrl) + ?: throw AccountCreationNotSupported() + val registrationHelperUrl = wellknown.registrationHelperUrl return if (registrationHelperUrl != null) { registrationHelperUrl.toUri() .buildUpon() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt index 87ec750b36..b0ee59cea8 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControlTest.kt @@ -10,10 +10,11 @@ package io.element.android.features.login.impl.accesscontrol import com.google.common.truth.Truth.assertThat import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.changeserver.AccountProviderAccessException -import io.element.android.features.login.impl.resolver.network.ElementWellKnown +import io.element.android.features.wellknown.test.FakeWellknownRetriever import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2 import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL +import io.element.android.libraries.wellknown.api.ElementWellKnown import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Test @@ -152,8 +153,8 @@ class DefaultAccountProviderAccessControlTest { isAllowedToConnectToHomeserverResult = { isAllowedToConnectToHomeserver }, defaultHomeserverListResult = { allowedAccountProviders }, ), - elementWellknownRetriever = FakeElementWellknownRetriever( - retrieveResult = { elementWellKnown } + wellknownRetriever = FakeWellknownRetriever( + getElementWellKnownResult = { elementWellKnown }, ), ) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/FakeElementWellknownRetriever.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/FakeElementWellknownRetriever.kt deleted file mode 100644 index 70854302c8..0000000000 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accesscontrol/FakeElementWellknownRetriever.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.login.impl.accesscontrol - -import io.element.android.features.login.impl.resolver.network.ElementWellKnown -import io.element.android.tests.testutils.simulateLongTask - -class FakeElementWellknownRetriever( - private val retrieveResult: (String) -> ElementWellKnown? = { null }, -) : ElementWellknownRetriever { - override suspend fun retrieve(accountProviderUrl: String): ElementWellKnown? = simulateLongTask { - retrieveResult(accountProviderUrl) - } -} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index a096024141..dc2b552ff5 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -11,17 +11,17 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl -import io.element.android.features.login.impl.accesscontrol.ElementWellknownRetriever -import io.element.android.features.login.impl.accesscontrol.FakeElementWellknownRetriever import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.error.ChangeServerError -import io.element.android.features.login.impl.resolver.network.ElementWellKnown +import io.element.android.features.wellknown.test.FakeWellknownRetriever import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService +import io.element.android.libraries.wellknown.api.ElementWellKnown +import io.element.android.libraries.wellknown.api.WellknownRetriever import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -113,14 +113,14 @@ class ChangeServerPresenterTest { @Test fun `present - change server element pro required error`() = runTest { - val retrieveResult = lambdaRecorder { + val getElementWellKnownResult = lambdaRecorder { ElementWellKnown( enforceElementPro = true, ) } createPresenter( - elementWellknownRetriever = FakeElementWellknownRetriever( - retrieveResult = retrieveResult, + wellknownRetriever = FakeWellknownRetriever( + getElementWellKnownResult = getElementWellKnownResult, ), ).test { val initialState = awaitItem() @@ -136,7 +136,7 @@ class ChangeServerPresenterTest { assertThat( (failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).applicationId ).isEqualTo("io.element.enterprise") - retrieveResult.assertions() + getElementWellKnownResult.assertions() .isCalledOnce() .with(value(A_HOMESERVER_URL.ensureProtocol())) } @@ -146,13 +146,13 @@ class ChangeServerPresenterTest { authenticationService: FakeMatrixAuthenticationService = FakeMatrixAuthenticationService(), accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()), enterpriseService: EnterpriseService = FakeEnterpriseService(), - elementWellknownRetriever: ElementWellknownRetriever = FakeElementWellknownRetriever(), + wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), ) = ChangeServerPresenter( authenticationService = authenticationService, accountProviderDataSource = accountProviderDataSource, defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl( enterpriseService = enterpriseService, - elementWellknownRetriever = elementWellknownRetriever, + wellknownRetriever = wellknownRetriever, ), ) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/resolver/network/FakeWellknownRequest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/resolver/network/FakeWellknownRequest.kt deleted file mode 100644 index e629ebe3a5..0000000000 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/resolver/network/FakeWellknownRequest.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.login.impl.resolver.network - -class FakeWellknownRequest : WellknownRequest { - private var resultMap: Map = emptyMap() - fun givenResultMap(map: Map) { - resultMap = map - } - - override suspend fun execute(baseUrl: String): WellKnown { - return resultMap[baseUrl] ?: error("No result provided for $baseUrl") - } -} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt index ae00099687..035be8f31b 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt @@ -13,11 +13,10 @@ import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl -import io.element.android.features.login.impl.accesscontrol.ElementWellknownRetriever -import io.element.android.features.login.impl.accesscontrol.FakeElementWellknownRetriever import io.element.android.features.login.impl.login.LoginHelper import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever +import io.element.android.features.wellknown.test.FakeWellknownRetriever import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -34,6 +33,7 @@ import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationSer import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.oidc.api.OidcActionFlow import io.element.android.libraries.oidc.test.customtab.FakeOidcActionFlow +import io.element.android.libraries.wellknown.api.WellknownRetriever import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest @@ -238,7 +238,7 @@ private fun createPresenter( buildMeta: BuildMeta = aBuildMeta(), featureFlagService: FeatureFlagService = FakeFeatureFlagService(), enterpriseService: EnterpriseService = FakeEnterpriseService(), - elementWellknownRetriever: ElementWellknownRetriever = FakeElementWellknownRetriever(), + wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), rageshakeFeatureAvailability: () -> Boolean = { true }, loginHelper: LoginHelper = createLoginHelper(), ) = OnBoardingPresenter( @@ -248,7 +248,7 @@ private fun createPresenter( enterpriseService = enterpriseService, defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl( enterpriseService = enterpriseService, - elementWellknownRetriever = elementWellknownRetriever, + wellknownRetriever = wellknownRetriever, ), rageshakeFeatureAvailability = rageshakeFeatureAvailability, loginHelper = loginHelper, diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt index 2d6cdf71dc..a4d594399f 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt @@ -14,15 +14,15 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl -import io.element.android.features.login.impl.accesscontrol.ElementWellknownRetriever -import io.element.android.features.login.impl.accesscontrol.FakeElementWellknownRetriever import io.element.android.features.login.impl.changeserver.AccountProviderAccessException import io.element.android.features.login.impl.qrcode.FakeQrCodeLoginManager +import io.element.android.features.wellknown.test.FakeWellknownRetriever import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginDataFactory +import io.element.android.libraries.wellknown.api.WellknownRetriever import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers @@ -162,14 +162,14 @@ class QrCodeScanPresenterTest { coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(), qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(), enterpriseService: EnterpriseService = FakeEnterpriseService(), - elementWellknownRetriever: ElementWellknownRetriever = FakeElementWellknownRetriever(), + wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), ) = QrCodeScanPresenter( qrCodeLoginDataFactory = qrCodeLoginDataFactory, qrCodeLoginManager = qrCodeLoginManager, coroutineDispatchers = coroutineDispatchers, defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl( enterpriseService = enterpriseService, - elementWellknownRetriever = elementWellknownRetriever, + wellknownRetriever = wellknownRetriever, ), ) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt index 96e539a2d8..b679c92ee1 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt @@ -13,12 +13,14 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.login.impl.changeserver.aChangeServerState import io.element.android.features.login.impl.resolver.HomeserverResolver -import io.element.android.features.login.impl.resolver.network.FakeWellknownRequest -import io.element.android.features.login.impl.resolver.network.WellKnown -import io.element.android.features.login.impl.resolver.network.WellKnownBaseConfig +import io.element.android.features.wellknown.test.FakeWellknownRetriever import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.test.A_HOMESERVER_URL +import io.element.android.libraries.wellknown.api.WellKnown +import io.element.android.libraries.wellknown.api.WellKnownBaseConfig import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -30,9 +32,9 @@ class SearchAccountProviderPresenterTest { @Test fun `present - initial state`() = runTest { - val fakeWellknownRequest = FakeWellknownRequest() + val fakeWellknownRetriever = FakeWellknownRetriever() val presenter = SearchAccountProviderPresenter( - homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever), changeServerPresenter = { aChangeServerState() } ) moleculeFlow(RecompositionMode.Immediate) { @@ -46,9 +48,9 @@ class SearchAccountProviderPresenterTest { @Test fun `present - enter text no result`() = runTest { - val fakeWellknownRequest = FakeWellknownRequest() + val fakeWellknownRetriever = FakeWellknownRetriever() val presenter = SearchAccountProviderPresenter( - homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever), changeServerPresenter = { aChangeServerState() } ) moleculeFlow(RecompositionMode.Immediate) { @@ -66,9 +68,9 @@ class SearchAccountProviderPresenterTest { @Test fun `present - enter valid url no wellknown`() = runTest { - val fakeWellknownRequest = FakeWellknownRequest() + val fakeWellknownRetriever = FakeWellknownRetriever() val presenter = SearchAccountProviderPresenter( - homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever), changeServerPresenter = { aChangeServerState() } ) moleculeFlow(RecompositionMode.Immediate) { @@ -92,14 +94,20 @@ class SearchAccountProviderPresenterTest { @Test fun `present - enter text one result with wellknown`() = runTest { - val fakeWellknownRequest = FakeWellknownRequest() - fakeWellknownRequest.givenResultMap( - mapOf( - "https://test.io" to aWellKnown(), - ) + val getWellKnownResult = lambdaRecorder { + when (it) { + "https://test.org" -> error("not found") + "https://test.com" -> error("not found") + "https://test.io" -> aWellKnown() + "https://test" -> error("not found") + else -> error("should not happen") + } + } + val fakeWellknownRetriever = FakeWellknownRetriever( + getWellKnownResult = getWellKnownResult, ) val presenter = SearchAccountProviderPresenter( - homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever), changeServerPresenter = { aChangeServerState() } ) moleculeFlow(RecompositionMode.Immediate) { @@ -118,6 +126,65 @@ class SearchAccountProviderPresenterTest { ) ) ) + getWellKnownResult.assertions().isCalledExactly(4) + .withSequence( + listOf(value("https://test.org")), + listOf(value("https://test.com")), + listOf(value("https://test.io")), + listOf(value("https://test")), + ) + } + } + + @Test + fun `present - enter text two results with wellknown`() = runTest { + val getWellKnownResult = lambdaRecorder { + when (it) { + "https://test.org" -> aWellKnown() + "https://test.com" -> error("not found") + "https://test.io" -> aWellKnown() + "https://test" -> error("not found") + else -> error("should not happen") + } + } + val fakeWellknownRetriever = FakeWellknownRetriever( + getWellKnownResult = getWellKnownResult, + ) + val presenter = SearchAccountProviderPresenter( + homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever), + changeServerPresenter = { aChangeServerState() } + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test")) + val withInputState = awaitItem() + assertThat(withInputState.userInput).isEqualTo("test") + assertThat(initialState.userInputResult).isEqualTo(AsyncData.Uninitialized) + assertThat(awaitItem().userInputResult).isInstanceOf(AsyncData.Loading::class.java) + assertThat(awaitItem().userInputResult).isEqualTo( + AsyncData.Success( + listOf( + aHomeserverData(homeserverUrl = "https://test.org"), + ) + ) + ) + assertThat(awaitItem().userInputResult).isEqualTo( + AsyncData.Success( + listOf( + aHomeserverData(homeserverUrl = "https://test.org"), + aHomeserverData(homeserverUrl = "https://test.io"), + ) + ) + ) + getWellKnownResult.assertions().isCalledExactly(4) + .withSequence( + listOf(value("https://test.org")), + listOf(value("https://test.com")), + listOf(value("https://test.io")), + listOf(value("https://test")), + ) } } diff --git a/libraries/wellknown/api/build.gradle.kts b/libraries/wellknown/api/build.gradle.kts new file mode 100644 index 0000000000..3a805e38c8 --- /dev/null +++ b/libraries/wellknown/api/build.gradle.kts @@ -0,0 +1,13 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.wellknown.api" +} diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt new file mode 100644 index 0000000000..5d6563e3c3 --- /dev/null +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.wellknown.api + +data class ElementWellKnown( + val registrationHelperUrl: String? = null, + val enforceElementPro: Boolean? = null, +) diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt new file mode 100644 index 0000000000..9b41b52e2f --- /dev/null +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.wellknown.api + +data class WellKnown( + val homeServer: WellKnownBaseConfig? = null, + val identityServer: WellKnownBaseConfig? = null, +) + +data class WellKnownBaseConfig( + val baseURL: String? = null +) diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt new file mode 100644 index 0000000000..e617bc8e13 --- /dev/null +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.wellknown.api + +interface WellknownRetriever { + suspend fun getWellKnown(baseUrl: String): WellKnown? + suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? +} diff --git a/libraries/wellknown/impl/build.gradle.kts b/libraries/wellknown/impl/build.gradle.kts new file mode 100644 index 0000000000..a9c1a1e9bf --- /dev/null +++ b/libraries/wellknown/impl/build.gradle.kts @@ -0,0 +1,32 @@ +import extension.setupAnvil + +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") + id("kotlin-parcelize") + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "io.element.android.libraries.wellknown.impl" +} + +setupAnvil() + +dependencies { + implementation(libs.androidx.annotationjvm) + implementation(libs.coroutines.core) + implementation(platform(libs.network.retrofit.bom)) + implementation(libs.network.retrofit) + implementation(libs.serialization.json) + implementation(projects.libraries.architecture) + implementation(projects.libraries.core) + implementation(projects.libraries.network) + implementation(projects.libraries.wellknown.api) +} diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt new file mode 100644 index 0000000000..4149d24df6 --- /dev/null +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.wellknown.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.uri.ensureProtocol +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.network.RetrofitFactory +import io.element.android.libraries.wellknown.api.ElementWellKnown +import io.element.android.libraries.wellknown.api.WellKnown +import io.element.android.libraries.wellknown.api.WellKnownBaseConfig +import io.element.android.libraries.wellknown.api.WellknownRetriever +import timber.log.Timber +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultWellknownRetriever @Inject constructor( + private val retrofitFactory: RetrofitFactory, +) : WellknownRetriever { + override suspend fun getWellKnown(baseUrl: String): WellKnown? { + val wellknownApi = buildWellknownApi(baseUrl) ?: return null + return try { + wellknownApi.getWellKnown().map() + } catch (e: Exception) { + Timber.e(e, "Failed to retrieve well-known data for $baseUrl") + null + } + } + + override suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? { + val wellknownApi = buildWellknownApi(baseUrl) ?: return null + return try { + wellknownApi.getElementWellKnown().map() + } catch (e: Exception) { + Timber.e(e, "Failed to retrieve Element well-known data for $baseUrl") + null + } + } + + private fun buildWellknownApi(accountProviderUrl: String): WellknownAPI? { + return try { + retrofitFactory.create(accountProviderUrl.ensureProtocol()) + .create(WellknownAPI::class.java) + } catch (e: Exception) { + // If the base URL is not valid, we cannot retrieve the well-known data + Timber.e(e, "Failed to create Retrofit instance for $accountProviderUrl") + null + } + } +} + +private fun InternalElementWellKnown.map() = ElementWellKnown( + registrationHelperUrl = registrationHelperUrl, + enforceElementPro = enforceElementPro, +) + +private fun InternalWellKnown.map() = WellKnown( + homeServer = homeServer?.map(), + identityServer = identityServer?.map(), +) + +private fun InternalWellKnownBaseConfig.map() = WellKnownBaseConfig( + baseURL = baseURL, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/ElementWellKnown.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt similarity index 80% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/ElementWellKnown.kt rename to libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt index 4f0073455a..9e902d9b92 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/ElementWellKnown.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt @@ -1,11 +1,11 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright 2025 New Vector Ltd. * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.login.impl.resolver.network +package io.element.android.libraries.wellknown.impl import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -20,7 +20,7 @@ import kotlinx.serialization.Serializable * . */ @Serializable -data class ElementWellKnown( +data class InternalElementWellKnown( @SerialName("registration_helper_url") val registrationHelperUrl: String? = null, @SerialName("enforce_element_pro") diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt similarity index 65% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt rename to libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt index 5c2843bb3d..8915359771 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt @@ -5,9 +5,8 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.login.impl.resolver.network +package io.element.android.libraries.wellknown.impl -import io.element.android.libraries.core.bool.orFalse import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -26,13 +25,9 @@ import kotlinx.serialization.Serializable * . */ @Serializable -data class WellKnown( +data class InternalWellKnown( @SerialName("m.homeserver") - val homeServer: WellKnownBaseConfig? = null, + val homeServer: InternalWellKnownBaseConfig? = null, @SerialName("m.identity_server") - val identityServer: WellKnownBaseConfig? = null, -) { - fun isValid(): Boolean { - return homeServer?.baseURL?.isNotBlank().orFalse() - } -} + val identityServer: InternalWellKnownBaseConfig? = null, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt similarity index 84% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt rename to libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt index b67ec9cb95..76f1a2af4e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.login.impl.resolver.network +package io.element.android.libraries.wellknown.impl import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -20,7 +20,7 @@ import kotlinx.serialization.Serializable * . */ @Serializable -data class WellKnownBaseConfig( +data class InternalWellKnownBaseConfig( @SerialName("base_url") val baseURL: String? = null ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownAPI.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt similarity index 67% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownAPI.kt rename to libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt index 29d0b83beb..d109aa7a43 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownAPI.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt @@ -5,14 +5,14 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.login.impl.resolver.network +package io.element.android.libraries.wellknown.impl import retrofit2.http.GET internal interface WellknownAPI { @GET(".well-known/matrix/client") - suspend fun getWellKnown(): WellKnown + suspend fun getWellKnown(): InternalWellKnown @GET(".well-known/element/element.json") - suspend fun getElementWellKnown(): ElementWellKnown + suspend fun getElementWellKnown(): InternalElementWellKnown } diff --git a/libraries/wellknown/test/build.gradle.kts b/libraries/wellknown/test/build.gradle.kts new file mode 100644 index 0000000000..7c7f4a368b --- /dev/null +++ b/libraries/wellknown/test/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.wellknown.test" +} + +dependencies { + implementation(projects.libraries.wellknown.api) + implementation(projects.tests.testutils) +} diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt new file mode 100644 index 0000000000..c8bbbde26d --- /dev/null +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.wellknown.test + +import io.element.android.libraries.wellknown.api.ElementWellKnown +import io.element.android.libraries.wellknown.api.WellKnown +import io.element.android.libraries.wellknown.api.WellknownRetriever +import io.element.android.tests.testutils.simulateLongTask + +class FakeWellknownRetriever( + private val getWellKnownResult: (String) -> WellKnown? = { null }, + private val getElementWellKnownResult: (String) -> ElementWellKnown? = { null }, +) : WellknownRetriever { + override suspend fun getWellKnown(baseUrl: String): WellKnown? = simulateLongTask { + getWellKnownResult(baseUrl) + } + + override suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? = simulateLongTask { + getElementWellKnownResult(baseUrl) + } +}