diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt index f6379bef30..524a1fb9dd 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.graphics.Color import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.libraries.matrix.api.GetUrlResolver import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.tests.testutils.lambda.lambdaError @@ -31,8 +32,9 @@ class FakeEnterpriseService( private val firebasePushGatewayResult: () -> String? = { lambdaError() }, private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() }, private val getNoisyNotificationChannelIdResult: (SessionId?) -> String? = { lambdaError() }, - private val tweakMasUrlResult: (String, String) -> String = { _, _ -> lambdaError() }, + private val tweakMasUrlResult: (String, String, GetUrlResolver) -> String = { _, _, _ -> lambdaError() }, private val overrideWellKnownResult: () -> ElementWellKnown? = { lambdaError() }, + private val essConfigEndpointUrlResult: (String) -> String = { lambdaError() }, ) : EnterpriseService { private val brandColorState = MutableStateFlow(initialBrandColor) private val semanticColorsState = MutableStateFlow(initialSemanticColors) @@ -41,8 +43,8 @@ class FakeEnterpriseService( isEnterpriseUserResult(sessionId) } - override suspend fun tweakMasUrl(url: String, homeserver: String): String = simulateLongTask { - tweakMasUrlResult(url, homeserver) + override suspend fun tweakMasUrl(url: String, homeserver: String, getUrlResolver: GetUrlResolver): String = simulateLongTask { + tweakMasUrlResult(url, homeserver, getUrlResolver) } override fun defaultHomeserverList(): List { @@ -85,4 +87,8 @@ class FakeEnterpriseService( override fun overriddenElementWellKnown(): ElementWellKnown? { return overrideWellKnownResult() } + + override fun essConfigEndpointUrl(domain: String): String { + return essConfigEndpointUrlResult(domain) + } } diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProvider.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProvider.kt new file mode 100644 index 0000000000..2c57dbb530 --- /dev/null +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProvider.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2026 Element Creations 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.enterprise.test.remoteconfig + +import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfig +import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult + +class FakeRemoteEnterpriseConfigProvider( + private val getResult: (SessionId) -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound } +) : RemoteEnterpriseConfigProvider { + override suspend fun get(sessionId: SessionId): WellknownRetrieverResult { + return getResult(sessionId) + } +} diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProviderFactory.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProviderFactory.kt new file mode 100644 index 0000000000..1ba699cfed --- /dev/null +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/remoteconfig/FakeRemoteEnterpriseConfigProviderFactory.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 Element Creations 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.enterprise.test.remoteconfig + +import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider +import io.element.android.libraries.matrix.api.GetUrlResolver + +class FakeRemoteEnterpriseConfigProviderFactory( + private val remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider = FakeRemoteEnterpriseConfigProvider(), +) : RemoteEnterpriseConfigProvider.Factory { + override fun create(getUrlResolver: GetUrlResolver): RemoteEnterpriseConfigProvider = remoteEnterpriseConfigProvider +} 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 e3cf1c0ea7..ee4cea0532 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 @@ -12,10 +12,12 @@ 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.wellknown.test.FakeWellknownRetriever +import io.element.android.features.wellknown.test.FakeWellknownRetrieverFactory import io.element.android.features.wellknown.test.anElementWellKnown 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.matrix.test.FakeTemporaryMatrixClientFactory import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import kotlinx.coroutines.test.runTest @@ -156,15 +158,18 @@ class DefaultAccountProviderAccessControlTest { isAllowedToConnectToHomeserverResult = { isAllowedToConnectToHomeserver }, defaultHomeserverListResult = { allowedAccountProviders }, ), - wellknownRetriever = FakeWellknownRetriever( - getElementWellKnownResult = { - if (elementWellKnown == null) { - WellknownRetrieverResult.NotFound - } else { - WellknownRetrieverResult.Success(elementWellKnown) - } - }, + wellknownRetrieverFactory = FakeWellknownRetrieverFactory( + wellknownRetriever = FakeWellknownRetriever( + getElementWellKnownResult = { _, _ -> + if (elementWellKnown == null) { + WellknownRetrieverResult.NotFound + } else { + WellknownRetrieverResult.Success(elementWellKnown) + } + }, + ), ), + temporaryMatrixClientFactory = FakeTemporaryMatrixClientFactory(), ) private fun DefaultAccountProviderAccessControl.expectNeedElementProException() { 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 5d9040adac..c28fee77ad 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 @@ -17,18 +17,19 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderDat import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate import io.element.android.features.wellknown.test.FakeWellknownRetriever +import io.element.android.features.wellknown.test.FakeWellknownRetrieverFactory import io.element.android.features.wellknown.test.anElementWellKnown import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_HOMESERVER_URL +import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClientFactory import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory -import io.element.android.libraries.wellknown.api.ElementWellKnown +import io.element.android.libraries.wellknown.api.EnterpriseRemoteConfigSource import io.element.android.libraries.wellknown.api.WellknownRetriever import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.WarmUpRule @@ -158,7 +159,8 @@ class ChangeServerPresenterTest { @Test fun `present - change server element pro required error`() = runTest { - val getElementWellKnownResult = lambdaRecorder> { + val getElementWellKnownResult = lambdaRecorder { homeserver: String, _: EnterpriseRemoteConfigSource -> + assertThat(homeserver).isEqualTo(A_HOMESERVER_URL) WellknownRetrieverResult.Success( anElementWellKnown( enforceElementPro = true, @@ -174,8 +176,6 @@ class ChangeServerPresenterTest { assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) val anAccountProvider = AccountProvider(url = A_HOMESERVER_URL) initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(anAccountProvider)) - val loadingState = awaitItem() - assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() assertThat( (failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).unauthorisedAccountProviderTitle @@ -183,9 +183,7 @@ class ChangeServerPresenterTest { assertThat( (failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).applicationId ).isEqualTo("io.element.enterprise") - getElementWellKnownResult.assertions() - .isCalledOnce() - .with(value(A_HOMESERVER_URL.ensureProtocol())) + getElementWellKnownResult.assertions().isCalledOnce() } } @@ -269,7 +267,8 @@ class ChangeServerPresenterTest { accountProviderDataSource = accountProviderDataSource, defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl( enterpriseService = enterpriseService, - wellknownRetriever = wellknownRetriever, + wellknownRetrieverFactory = FakeWellknownRetrieverFactory(wellknownRetriever = wellknownRetriever), + temporaryMatrixClientFactory = FakeTemporaryMatrixClientFactory(), ), localNetworkPermissionGate = LocalNetworkPermissionGate( advisor = localNetworkPermissionAdvisor, 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 79268aa1e4..782ff00ec0 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 @@ -20,6 +20,7 @@ import io.element.android.features.login.impl.login.LoginModePresenter 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.features.wellknown.test.FakeWellknownRetrieverFactory import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -30,11 +31,15 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2 import io.element.android.libraries.matrix.test.A_LOGIN_HINT +import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClientFactory import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.oauth.api.OAuthActionFlow import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow +import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionAdvisor +import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor +import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.aSessionData @@ -304,7 +309,8 @@ private fun createPresenter( enterpriseService = enterpriseService, defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl( enterpriseService = enterpriseService, - wellknownRetriever = wellknownRetriever, + wellknownRetrieverFactory = FakeWellknownRetrieverFactory(wellknownRetriever = wellknownRetriever), + temporaryMatrixClientFactory = FakeTemporaryMatrixClientFactory(), ), rageshakeFeatureAvailability = rageshakeFeatureAvailability, loginModePresenter = loginModePresenter, @@ -318,9 +324,9 @@ fun createLoginModePresenter( authenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(), webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(), localNetworkPermissionAdvisor: LocalNetworkPermissionAdvisor = - io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor(), - permissionsPresenterFactory: io.element.android.libraries.permissions.api.PermissionsPresenter.Factory = - io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory(), + FakeLocalNetworkPermissionAdvisor(), + permissionsPresenterFactory: PermissionsPresenter.Factory = + FakePermissionsPresenterFactory(), ): LoginModePresenter = LoginModePresenter( oAuthActionFlow = oAuthActionFlow, authenticationService = authenticationService, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt index d1536d949e..5e5000aedc 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt @@ -52,7 +52,7 @@ fun TestScope.createRustMatrixClientFactory( ), clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(), workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(), - contentScannerUrlProvider: ContentScannerUrlProvider = { Result.success(null) }, + contentScannerUrlProviderFactory: ContentScannerUrlProvider.Factory = { ContentScannerUrlProvider { Result.success(null) } }, ) = RustMatrixClientFactory( cacheDirectory = cacheDirectory, appCoroutineScope = backgroundScope, @@ -67,5 +67,5 @@ fun TestScope.createRustMatrixClientFactory( clientBuilderProvider = clientBuilderProvider, sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(), workManagerScheduler = workManagerScheduler, - contentScannerUrlProvider = contentScannerUrlProvider, + contentScannerUrlProviderFactory = contentScannerUrlProviderFactory, ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeTemporaryMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeTemporaryMatrixClient.kt new file mode 100644 index 0000000000..83bde4653a --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeTemporaryMatrixClient.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026 Element Creations 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.matrix.test + +import io.element.android.libraries.matrix.api.TemporaryMatrixClient +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeTemporaryMatrixClient( + private val getUrlResult: (String) -> Result = { lambdaError() }, + private val closeLambda: () -> Unit = {}, +) : TemporaryMatrixClient { + override suspend fun getUrl(url: String): Result = getUrlResult(url) + + override fun close() = closeLambda() +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeTemporaryMatrixClientFactory.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeTemporaryMatrixClientFactory.kt new file mode 100644 index 0000000000..1d8dc0c26c --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeTemporaryMatrixClientFactory.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 Element Creations 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.matrix.test + +import io.element.android.libraries.matrix.api.TemporaryMatrixClientFactory +import io.element.android.libraries.matrix.api.TemporaryMatrixClient + +class FakeTemporaryMatrixClientFactory( + private val createResult: (String) -> Result = { Result.success(FakeTemporaryMatrixClient()) }, +) : TemporaryMatrixClientFactory { + override suspend fun createTemporaryMatrixClient(homeServerUrl: String): Result = createResult(homeServerUrl) +} diff --git a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStoreTest.kt b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStoreTest.kt index b53041faf8..dc101c41a0 100644 --- a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStoreTest.kt +++ b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellKnownStoreTest.kt @@ -148,11 +148,25 @@ class DefaultWellKnownStoreTest { assertThat(result).isEqualTo(WellknownRetrieverResult.Success(wellKnown.map())) } + @Test + fun `prefix is used`() = runTest { + val prefix = "test-prefix" + val clock = FakeSystemClock(epochMillisResult = A_FAKE_TIMESTAMP) + val cacheStore = InMemoryCacheStore( + initialData = mapOf("$prefix-$A_DOMAIN" to CacheData("{}", A_FAKE_TIMESTAMP)) + ) + val sut = createDefaultWellKnownStore(prefix = prefix, cacheStore = cacheStore, systemClock = clock) + + assertThat(sut.get(A_DOMAIN)).isInstanceOf(WellknownRetrieverResult.Success::class.java) + } + private fun createDefaultWellKnownStore( + prefix: String? = null, cacheStore: InMemoryCacheStore = InMemoryCacheStore(), systemClock: FakeSystemClock = FakeSystemClock(), elementWellKnownParser: ElementWellKnownParser = DefaultElementWellKnownParser(jsonProvider), ) = DefaultWellKnownStore( + prefix = prefix, cacheStore = cacheStore, systemClock = systemClock, elementWellKnownParser = elementWellKnownParser, diff --git a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt index 96ae4c2b17..020abf55ca 100644 --- a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt +++ b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt @@ -14,275 +14,93 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.wellknown.test.FakeElementWellknownStore import io.element.android.features.wellknown.test.anElementWellKnown -import io.element.android.libraries.androidutils.json.DefaultJsonProvider -import io.element.android.libraries.androidutils.json.JsonProvider -import io.element.android.libraries.network.RetrofitFactory -import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase +import io.element.android.libraries.matrix.api.GetUrlResolver +import io.element.android.libraries.matrix.api.exception.ClientException import io.element.android.libraries.wellknown.api.ElementWellKnown +import io.element.android.libraries.wellknown.api.ElementWellKnownParser +import io.element.android.libraries.wellknown.api.EnterpriseRemoteConfigSource import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.lambda.lambdaRecorder -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest -import okhttp3.Call -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import okio.IOException import org.junit.Test -import java.net.PortUnreachableException class DefaultWellknownRetrieverTest { @Test - fun `get empty element wellknown`() = runTest { - val mockCall = mockCall(defaultResponse(body = "{}")) + fun `get element wellknown calls resolver and parser`() = runTest { + val parser = lambdaRecorder> { Result.success(anElementWellKnown()) } + val resolver = SpyGetUrlResolver { Result.success(WELLKNOWN_CONTENT.toByteArray()) } val sut = createDefaultWellknownRetriever( - callFactory = { mockCall }, + elementWellKnownParser = parser, + urlResolver = resolver, ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo( - WellknownRetrieverResult.Success( - ElementWellKnown( - registrationHelperUrl = null, - enforceElementPro = null, - rageshakeUrl = null, - brandColor = null, - notificationSound = null, - identityProviderAppScheme = null, - customRecoveryPassphrase = null, - contentScannerUrl = null, - forceDisableE2EE = null, - ) - ) - ) - verify(exactly = 1) { mockCall.enqueue(any()) } + + sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT) + + resolver.assertions().isCalledOnce().with(value(WELLKNOWN_URL)) + parser.assertions().isCalledOnce().with(value(WELLKNOWN_CONTENT)) } @Test - fun `get element wellknown with full content`() = runTest { + fun `get element wellknown parser error`() = runTest { val sut = createDefaultWellknownRetriever( - callFactory = { mockCall(defaultResponse()) } + elementWellKnownParser = { Result.failure(IllegalStateException("Invalid JSON")) }, ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo( - WellknownRetrieverResult.Success( - ElementWellKnown( - registrationHelperUrl = "a_registration_url", - enforceElementPro = true, - rageshakeUrl = "a_rageshake_url", - brandColor = "#FF0000", - notificationSound = "a_notification_sound.flac", - identityProviderAppScheme = "an_app_scheme", - customRecoveryPassphrase = null, - contentScannerUrl = "https://content-scanner.example.com", - forceDisableE2EE = false, - ) - ) - ) - } - - @Test - fun `get element wellknown with unknown key`() = runTest { - val sut = createDefaultWellknownRetriever( - callFactory = { - mockCall(defaultResponse(body = """{ - "registration_helper_url": "a_registration_url", - "enforce_element_pro": true, - "rageshake_url": "a_rageshake_url", - // Note the trailing comma, and the comment! - "other": true, - }""".trimIndent())) - } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo( - WellknownRetrieverResult.Success( - ElementWellKnown( - registrationHelperUrl = "a_registration_url", - enforceElementPro = true, - rageshakeUrl = "a_rageshake_url", - brandColor = null, - notificationSound = null, - identityProviderAppScheme = null, - contentScannerUrl = null, - customRecoveryPassphrase = null, - forceDisableE2EE = null, - ) - ) - ) - } - - @Test - fun `get element wellknown with custom recovery passphrase settings`() = runTest { - val sut = createDefaultWellknownRetriever( - callFactory = { - mockCall(defaultResponse(body = """{ - "custom_recovery_passphrase": { - "min_character_count": 8 - } - }""".trimIndent())) - } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo( - WellknownRetrieverResult.Success( - anElementWellKnown( - customRecoveryPassphrase = CustomRecoveryPassphrase(minCharacterCount = 8) - ) - ) - ) - } - - @Test - fun `get element wellknown with custom recovery passphrase settings missing min character count floors to 1`() = runTest { - val sut = createDefaultWellknownRetriever( - callFactory = { - mockCall(defaultResponse(body = """{ - "custom_recovery_passphrase": {} - }""".trimIndent())) - } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo( - WellknownRetrieverResult.Success( - anElementWellKnown( - customRecoveryPassphrase = CustomRecoveryPassphrase(minCharacterCount = 1) - ) - ) - ) - } - - @Test - fun `get element wellknown with zero min character count floors to 1`() = runTest { - val sut = createDefaultWellknownRetriever( - callFactory = { - mockCall( - defaultResponse( - body = """{ - "custom_recovery_passphrase": { - "min_character_count": 0 - } - }""".trimIndent() - ) - ) - } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo( - WellknownRetrieverResult.Success( - anElementWellKnown( - customRecoveryPassphrase = CustomRecoveryPassphrase(minCharacterCount = 1) - ) - ) - ) - } - - @Test - fun `get element wellknown with negative min character count floors to 1`() = runTest { - val sut = createDefaultWellknownRetriever( - callFactory = { - mockCall( - defaultResponse( - body = """{ - "custom_recovery_passphrase": { - "min_character_count": -5 - } - }""".trimIndent() - ) - ) - } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo( - WellknownRetrieverResult.Success( - anElementWellKnown( - customRecoveryPassphrase = CustomRecoveryPassphrase(minCharacterCount = 1) - ) - ) - ) - } - - @Test - fun `get element wellknown json error`() = runTest { - val sut = createDefaultWellknownRetriever( - callFactory = { mockCall(defaultResponse(status = 500)) } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isInstanceOf(WellknownRetrieverResult.Error::class.java) + assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) + .isInstanceOf(WellknownRetrieverResult.Error::class.java) } @Test fun `get element wellknown network error`() = runTest { - val sut = createDefaultWellknownRetriever( - callFactory = { mockCall(error = PortUnreachableException()) } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isInstanceOf(WellknownRetrieverResult.Error::class.java) + val resolver = SpyGetUrlResolver { Result.failure(IllegalStateException("Network error")) } + val sut = createDefaultWellknownRetriever(urlResolver = resolver) + assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) + .isInstanceOf(WellknownRetrieverResult.Error::class.java) } @Test fun `get element wellknown 404 http error counts as not found`() = runTest { - val sut = createDefaultWellknownRetriever( - callFactory = { mockCall(defaultResponse(status = 404)) } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isInstanceOf(WellknownRetrieverResult.NotFound::class.java) - } - - @Test - fun `get element wellknown hitting cache containing invalid json`() = runTest { - val cacheStore = FakeElementWellknownStore( - initialData = mapOf( - WELLKNOWN_URL to WellknownRetrieverResult.Error(IllegalStateException("Invalid JSON")) - ) - ) - val sut = createDefaultWellknownRetriever( - callFactory = { mockCall(defaultResponse(body = "invalid json")) }, - cacheStore = cacheStore, - jsonProvider = JsonProvider { error("Failed to parse JSON") } - ) - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isInstanceOf(WellknownRetrieverResult.Error::class.java) - // Ensure that the cache is deleted after the failure to parse it - assertThat(cacheStore.get(WELLKNOWN_URL)).isEqualTo(WellknownRetrieverResult.NotFound) + val resolver = SpyGetUrlResolver { Result.failure(ClientException.Generic("Http error: 404", null)) } + val sut = createDefaultWellknownRetriever(urlResolver = resolver) + assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)) + .isInstanceOf(WellknownRetrieverResult.NotFound::class.java) } @Test fun `get element wellknown was overridden`() = runTest { - val getLambda = lambdaRecorder { mockCall() } val wellKnown = anElementWellKnown() + val resolver = SpyGetUrlResolver { Result.success(WELLKNOWN_CONTENT.toByteArray()) } + val sut = createDefaultWellknownRetriever( - callFactory = getLambda, enterpriseService = FakeEnterpriseService( - overrideWellKnownResult = { wellKnown } - ) + overrideWellKnownResult = { wellKnown }, + ), + urlResolver = resolver, ) // The overridden value is returned - assertThat(sut.getElementWellKnown(WELLKNOWN_URL)).isEqualTo( + assertThat(sut.getElementWellKnown(WELLKNOWN_URL, EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT)).isEqualTo( WellknownRetrieverResult.Success(wellKnown) ) // And the endpoint is never hit - getLambda.assertions().isNeverCalled() + resolver.assertions().isNeverCalled() } - private fun defaultResponse( - body: String = WELLKNOWN_CONTENT, - status: Int = 200, - ) = Response.Builder() - .code(status) - .message("OK") - .protocol(okhttp3.Protocol.HTTP_1_1) - .request(Request.Builder().url(WELLKNOWN_URL).build()) - .body(body.toByteArray().toResponseBody()) - .build() - - private fun createDefaultWellknownRetriever( - callFactory: Call.Factory = Call.Factory { _: Request -> mockCall(defaultResponse()) }, + private fun TestScope.createDefaultWellknownRetriever( cacheStore: FakeElementWellknownStore = FakeElementWellknownStore(), - jsonProvider: JsonProvider = DefaultJsonProvider(), + elementWellKnownParser: ElementWellKnownParser = { Result.success(expectedElementWellKnown) }, enterpriseService: FakeEnterpriseService = FakeEnterpriseService(overrideWellKnownResult = { null }), + urlResolver: GetUrlResolver = SpyGetUrlResolver { Result.success(WELLKNOWN_CONTENT.toByteArray()) }, ) = DefaultWellknownRetriever( - retrofitFactory = RetrofitFactory( - callFactory = { callFactory }, - json = { jsonProvider } - ), - jsonProvider = jsonProvider, - elementWellknownStore = cacheStore, + elementWellknownStoreFactory = { cacheStore }, enterpriseService = enterpriseService, + elementWellKnownParser = elementWellKnownParser, + getUrlResolver = urlResolver, + coroutineScope = backgroundScope, ) companion object { @@ -297,24 +115,27 @@ class DefaultWellknownRetrieverTest { "content_scanner_url": "https://content-scanner.example.com", "force_disable_e2ee": false }""" + + private val expectedElementWellKnown = ElementWellKnown( + registrationHelperUrl = "a_registration_url", + enforceElementPro = true, + rageshakeUrl = "a_rageshake_url", + brandColor = "#FF0000", + notificationSound = "a_notification_sound.flac", + identityProviderAppScheme = "an_app_scheme", + contentScannerUrl = "https://content-scanner.example.com", + customRecoveryPassphrase = null, + forceDisableE2EE = false, + ) } } -private fun mockCall( - response: Response? = null, - error: IOException? = null, -): Call = mockk { - if (error != null) { - every { enqueue(any()) } answers { - val callback = firstArg() - callback.onFailure(this@mockk, error) - } - } else if (response != null) { - every { enqueue(any()) } answers { - val callback = firstArg() - callback.onResponse(this@mockk, response) - } - } else { - error("Either response or error must be provided") +private class SpyGetUrlResolver(lambda: (String) -> Result) : GetUrlResolver { + private val spy = lambdaRecorder> { lambda(it) } + + override suspend fun getUrl(url: String): Result { + return spy(url) } + + fun assertions() = spy.assertions() } diff --git a/libraries/wellknown/test/build.gradle.kts b/libraries/wellknown/test/build.gradle.kts index 8eb47ef5ca..7f12f380c7 100644 --- a/libraries/wellknown/test/build.gradle.kts +++ b/libraries/wellknown/test/build.gradle.kts @@ -17,6 +17,7 @@ android { dependencies { implementation(libs.serialization.json) implementation(projects.libraries.androidutils) + implementation(projects.libraries.matrix.api) implementation(projects.libraries.wellknown.api) implementation(projects.libraries.wellknown.impl) implementation(projects.tests.testutils) diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt deleted file mode 100644 index 3c675beed2..0000000000 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations 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.wellknown.test - -import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.SessionWellknownRetriever -import io.element.android.libraries.wellknown.api.WellknownRetrieverResult -import io.element.android.tests.testutils.simulateLongTask - -class FakeSessionWellknownRetriever( - private val getElementWellKnownResult: () -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound }, -) : SessionWellknownRetriever { - override suspend fun getElementWellKnown(): WellknownRetrieverResult = simulateLongTask { - getElementWellKnownResult() - } -} 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 index 139332667e..e08969d0f9 100644 --- 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 @@ -9,14 +9,16 @@ package io.element.android.features.wellknown.test import io.element.android.libraries.wellknown.api.ElementWellKnown +import io.element.android.libraries.wellknown.api.EnterpriseRemoteConfigSource import io.element.android.libraries.wellknown.api.WellknownRetriever import io.element.android.libraries.wellknown.api.WellknownRetrieverResult -import io.element.android.tests.testutils.simulateLongTask class FakeWellknownRetriever( - private val getElementWellKnownResult: (String) -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound }, + private val getElementWellKnownResult: (String, EnterpriseRemoteConfigSource) -> WellknownRetrieverResult = { _, _ -> + WellknownRetrieverResult.NotFound + }, ) : WellknownRetriever { - override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult = simulateLongTask { - getElementWellKnownResult(baseUrl) + override suspend fun getElementWellKnown(host: String, source: EnterpriseRemoteConfigSource): WellknownRetrieverResult { + return getElementWellKnownResult(host, source) } } diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetrieverFactory.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetrieverFactory.kt new file mode 100644 index 0000000000..c0e804835e --- /dev/null +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetrieverFactory.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 Element Creations 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.matrix.api.GetUrlResolver +import io.element.android.libraries.wellknown.api.WellknownRetriever + +class FakeWellknownRetrieverFactory( + private val wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), +) : WellknownRetriever.Factory { + override fun create(getUrlResolver: GetUrlResolver): WellknownRetriever = wellknownRetriever +}