mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Merge pull request #7591 from element-hq/bma/iterateOnClientCreation
Iterate on client creation
This commit is contained in:
+10
-11
@@ -35,7 +35,6 @@ import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
import org.matrix.rustcomponents.sdk.ClientBuilder
|
||||
import org.matrix.rustcomponents.sdk.CrossProcessLockConfig
|
||||
import org.matrix.rustcomponents.sdk.RequestConfig
|
||||
@@ -100,7 +99,12 @@ class RustMatrixClientFactory(
|
||||
)
|
||||
.homeserverUrl(sessionData.homeserverUrl)
|
||||
.enableAutomaticBackPagination(featureFlagService.isFeatureEnabled(FeatureFlags.AutomaticBackPagination))
|
||||
.let { (clientBuilderEnterpriseHook(RustMatrixClientBuilder(it), SessionId(sessionData.userId)) as RustMatrixClientBuilder).inner }
|
||||
.let {
|
||||
(clientBuilderEnterpriseHook.beforeClientCreationWithSession(
|
||||
clientBuilder = RustMatrixClientBuilder(it),
|
||||
sessionId = SessionId(sessionData.userId),
|
||||
) as RustMatrixClientBuilder).inner
|
||||
}
|
||||
.use { it.build() }
|
||||
|
||||
client.setMediaRetentionPolicy(
|
||||
@@ -118,14 +122,6 @@ class RustMatrixClientFactory(
|
||||
|
||||
client.restoreSession(sessionData.toSession())
|
||||
|
||||
create(client, sessionData, isMessageSearchAvailable)
|
||||
}
|
||||
|
||||
suspend fun create(
|
||||
client: Client,
|
||||
sessionData: SessionData,
|
||||
isMessageSearchAvailable: Boolean,
|
||||
): RustMatrixClient {
|
||||
val (anonymizedAccessToken, anonymizedRefreshToken) = client.session().anonymizedTokens()
|
||||
|
||||
client.setUtdDelegate(UtdTracker(analyticsService))
|
||||
@@ -135,7 +131,7 @@ class RustMatrixClientFactory(
|
||||
.withOfflineMode()
|
||||
.finish()
|
||||
|
||||
return RustMatrixClient(
|
||||
RustMatrixClient(
|
||||
sessionPaths = sessionData.getSessionPaths(),
|
||||
innerClient = client,
|
||||
sessionStore = sessionStore,
|
||||
@@ -227,6 +223,9 @@ class RustMatrixClientFactory(
|
||||
// Workaround for non-nullable proxy parameter in the SDK, since each call to the ClientBuilder returns a new reference we need to keep
|
||||
proxyProvider.provides()?.let { proxy(it) } ?: this
|
||||
}
|
||||
.let {
|
||||
(clientBuilderEnterpriseHook.beforeClientCreation(RustMatrixClientBuilder(it)) as RustMatrixClientBuilder).inner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-54
@@ -44,6 +44,7 @@ import io.element.android.libraries.matrix.impl.keys.SecretGenerator
|
||||
import io.element.android.libraries.matrix.impl.mapper.toSessionData
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
|
||||
import io.element.android.libraries.sessionstorage.api.LoginType
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.flow.first
|
||||
@@ -101,17 +102,7 @@ class RustMatrixAuthenticationService(
|
||||
runCatchingExceptions {
|
||||
val sessionData = sessionStore.getSession(sessionId.value)
|
||||
if (sessionData != null) {
|
||||
if (sessionData.isTokenValid) {
|
||||
// Use the sessionData.passphrase, which can be null for a previously created session
|
||||
if (sessionData.passphrase == null) {
|
||||
Timber.w("Restoring a session without a passphrase")
|
||||
} else {
|
||||
Timber.w("Restoring a session with a passphrase")
|
||||
}
|
||||
rustMatrixClientFactory.create(sessionData)
|
||||
} else {
|
||||
throw SessionRestorationException.InvalidToken()
|
||||
}
|
||||
restoreSession(sessionData)
|
||||
} else {
|
||||
throw SessionRestorationException.MissingSession(sessionId)
|
||||
}
|
||||
@@ -120,6 +111,19 @@ class RustMatrixAuthenticationService(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun restoreSession(sessionData: SessionData): MatrixClient {
|
||||
if (!sessionData.isTokenValid) {
|
||||
throw SessionRestorationException.InvalidToken()
|
||||
}
|
||||
// Use the sessionData.passphrase, which can be null for a previously created session
|
||||
if (sessionData.passphrase == null) {
|
||||
Timber.w("Restoring a session without a passphrase")
|
||||
} else {
|
||||
Timber.w("Restoring a session with a passphrase")
|
||||
}
|
||||
return rustMatrixClientFactory.create(sessionData)
|
||||
}
|
||||
|
||||
private fun getDatabaseKey(): ClientSecret {
|
||||
Timber.d("New sessions will be encrypted with a raw key")
|
||||
return secretGenerator.generateKey()
|
||||
@@ -137,7 +141,7 @@ class RustMatrixAuthenticationService(
|
||||
|
||||
client.homeserverLoginDetails().map()
|
||||
}.onFailure {
|
||||
clear(destroyClient = true)
|
||||
clear()
|
||||
}.mapFailure { failure ->
|
||||
Timber.e(failure, "Failed to set homeserver to $homeserver")
|
||||
failure.mapAuthenticationException()
|
||||
@@ -165,18 +169,7 @@ class RustMatrixAuthenticationService(
|
||||
passphrase = pendingKey.formattedAsString(),
|
||||
sessionPaths = currentSessionPaths,
|
||||
)
|
||||
val matrixClient = rustMatrixClientFactory.create(client, sessionData, isMessageSearchAvailable())
|
||||
|
||||
// Apply enterprise hooks to the newly created client as soon as possible
|
||||
clientEnterpriseHook(matrixClient)
|
||||
|
||||
newMatrixClientObservers.forEach { it.invoke(matrixClient) }
|
||||
sessionStore.addSession(sessionData)
|
||||
|
||||
// Clean up the strong reference held here since it's no longer necessary
|
||||
clear(destroyClient = false)
|
||||
|
||||
SessionId(sessionData.userId)
|
||||
finalizeClientCreation(sessionData)
|
||||
}.mapFailure { failure ->
|
||||
Timber.e(failure, "Failed to login")
|
||||
failure.mapAuthenticationException()
|
||||
@@ -305,20 +298,7 @@ class RustMatrixAuthenticationService(
|
||||
passphrase = pendingKey.formattedAsString(),
|
||||
sessionPaths = currentSessionPaths,
|
||||
)
|
||||
val matrixClient = rustMatrixClientFactory.create(client, sessionData, isMessageSearchAvailable())
|
||||
|
||||
// Apply enterprise hooks to the newly created client as soon as possible
|
||||
clientEnterpriseHook(matrixClient)
|
||||
|
||||
matrixClient.waitForKnownVerificationState()
|
||||
|
||||
newMatrixClientObservers.forEach { it.invoke(matrixClient) }
|
||||
sessionStore.addSession(sessionData)
|
||||
|
||||
// Clean up the strong reference held here since it's no longer necessary
|
||||
clear(destroyClient = false)
|
||||
|
||||
SessionId(sessionData.userId)
|
||||
finalizeClientCreation(sessionData)
|
||||
}.mapFailure { failure ->
|
||||
Timber.e(failure, "Failed to login with OAuth")
|
||||
failure.mapAuthenticationException()
|
||||
@@ -357,6 +337,7 @@ class RustMatrixAuthenticationService(
|
||||
sessionPaths = emptySessionPaths,
|
||||
qrCodeData = sdkQrCodeLoginData,
|
||||
)
|
||||
currentClient = client
|
||||
client.newLoginWithQrCodeHandler(
|
||||
oauthConfiguration = oAuthConfiguration,
|
||||
).use {
|
||||
@@ -374,18 +355,7 @@ class RustMatrixAuthenticationService(
|
||||
passphrase = pendingKey.formattedAsString(),
|
||||
sessionPaths = emptySessionPaths,
|
||||
)
|
||||
val matrixClient = rustMatrixClientFactory.create(client, sessionData, isMessageSearchAvailable())
|
||||
|
||||
// Apply enterprise hooks to the newly created client as soon as possible
|
||||
clientEnterpriseHook(matrixClient)
|
||||
|
||||
newMatrixClientObservers.forEach { it.invoke(matrixClient) }
|
||||
sessionStore.addSession(sessionData)
|
||||
|
||||
// Clean up the strong reference held here since it's no longer necessary
|
||||
clear(destroyClient = false)
|
||||
|
||||
SessionId(sessionData.userId)
|
||||
finalizeClientCreation(sessionData)
|
||||
}.mapFailure {
|
||||
when (it) {
|
||||
is QrCodeDecodeException -> QrErrorMapper.map(it)
|
||||
@@ -393,6 +363,8 @@ class RustMatrixAuthenticationService(
|
||||
else -> it
|
||||
}
|
||||
}.onFailure { throwable ->
|
||||
// A QR code login always builds its own client, so it can be disposed of on failure.
|
||||
clear()
|
||||
if (throwable is CancellationException) {
|
||||
throw throwable
|
||||
}
|
||||
@@ -400,6 +372,27 @@ class RustMatrixAuthenticationService(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun finalizeClientCreation(sessionData: SessionData): SessionId {
|
||||
// Close the client which was used to perform the login before creating the final client.
|
||||
// Both use the same session paths, so their SQLite stores must never be opened at the same time.
|
||||
clear()
|
||||
|
||||
val matrixClient = restoreSession(sessionData)
|
||||
// Apply enterprise hooks to the newly created client as soon as possible
|
||||
clientEnterpriseHook(matrixClient)
|
||||
|
||||
matrixClient.waitForKnownVerificationState()
|
||||
|
||||
newMatrixClientObservers.forEach { it.invoke(matrixClient) }
|
||||
sessionStore.addSession(sessionData)
|
||||
|
||||
// The session paths now hold the data of the account which has just been logged in, so forget
|
||||
// them: they must not be deleted by the rotateSessionPath() of the next login attempt.
|
||||
sessionPaths = null
|
||||
|
||||
return SessionId(sessionData.userId)
|
||||
}
|
||||
|
||||
private suspend fun makeClient(
|
||||
sessionPaths: SessionPaths,
|
||||
config: suspend ClientBuilder.() -> ClientBuilder,
|
||||
@@ -449,10 +442,8 @@ class RustMatrixAuthenticationService(
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun clear(destroyClient: Boolean) {
|
||||
if (destroyClient) {
|
||||
currentClient?.close()
|
||||
}
|
||||
private fun clear() {
|
||||
currentClient?.close()
|
||||
currentClient = null
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -9,11 +9,13 @@
|
||||
package io.element.android.libraries.matrix.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.test.FakeClientBuilderEnterpriseHook
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
|
||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
|
||||
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilderProvider
|
||||
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
@@ -51,6 +53,7 @@ fun TestScope.createRustMatrixClientFactory(
|
||||
),
|
||||
clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(),
|
||||
workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(),
|
||||
sqliteStoreBuilderProvider: SqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
||||
) = RustMatrixClientFactory(
|
||||
cacheDirectory = cacheDirectory,
|
||||
appCoroutineScope = backgroundScope,
|
||||
@@ -63,7 +66,7 @@ fun TestScope.createRustMatrixClientFactory(
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
timelineEventFilterFactory = FakeTimelineEventFilterFactory(),
|
||||
clientBuilderProvider = clientBuilderProvider,
|
||||
sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
||||
sqliteStoreBuilderProvider = sqliteStoreBuilderProvider,
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
clientBuilderEnterpriseHook = { builder, _ -> builder },
|
||||
clientBuilderEnterpriseHook = FakeClientBuilderEnterpriseHook(),
|
||||
)
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@
|
||||
package io.element.android.libraries.matrix.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.test.FakeClientBuilderEnterpriseHook
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
|
||||
@@ -77,6 +78,6 @@ class RustTemporaryMatrixClientFactoryTest {
|
||||
clientBuilderProvider = clientBuilderProvider,
|
||||
sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
clientBuilderEnterpriseHook = { builder, _ -> builder },
|
||||
clientBuilderEnterpriseHook = FakeClientBuilderEnterpriseHook(),
|
||||
)
|
||||
}
|
||||
|
||||
+187
-5
@@ -12,25 +12,44 @@ 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.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.paths.SessionPaths
|
||||
import io.element.android.libraries.matrix.impl.ClientBuilderProvider
|
||||
import io.element.android.libraries.matrix.impl.FakeClientBuilderProvider
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData
|
||||
import io.element.android.libraries.matrix.impl.createRustMatrixClientFactory
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClientBuilder
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiHomeserverLoginDetails
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiLoginWithQrCodeHandler
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiQrCodeData
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
|
||||
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilder
|
||||
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
|
||||
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilder
|
||||
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilderProvider
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.auth.FakeOAuthRedirectUrlProvider
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
import org.matrix.rustcomponents.sdk.ClientBuilder
|
||||
import org.matrix.rustcomponents.sdk.HumanQrLoginException
|
||||
import java.io.File
|
||||
|
||||
class RustMatrixAuthenticationServiceTest {
|
||||
@get:Rule
|
||||
val temporaryFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun `setHomeserver is successful`() = runTest {
|
||||
val sut = createRustMatrixAuthenticationService(
|
||||
@@ -74,20 +93,183 @@ class RustMatrixAuthenticationServiceTest {
|
||||
closeResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login closes the client used to log in before building the client of the session`() = runTest {
|
||||
val events = mutableListOf<String>()
|
||||
val sut = createRustMatrixAuthenticationService(
|
||||
clientBuilderProvider = FakeSequentialClientBuilderProvider(
|
||||
{
|
||||
events.add("build login client")
|
||||
FakeFfiClient(
|
||||
homeserverLoginDetailsResult = { FakeFfiHomeserverLoginDetails() },
|
||||
loginResult = { _, _ -> },
|
||||
closeResult = { events.add("close login client") },
|
||||
)
|
||||
},
|
||||
{
|
||||
events.add("build session client")
|
||||
FakeFfiClient(withUtdHook = {})
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
|
||||
assertThat(sut.login("alice", "password").getOrNull()).isEqualTo(A_USER_ID)
|
||||
|
||||
// The two clients share the same session paths, so the login one must be closed first.
|
||||
assertThat(events).containsExactly("build login client", "close login client", "build session client").inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loginWithQrCode closes the client used to log in before building the client of the session`() = runTest {
|
||||
val events = mutableListOf<String>()
|
||||
val sut = createRustMatrixAuthenticationService(
|
||||
clientBuilderProvider = FakeSequentialClientBuilderProvider(
|
||||
{
|
||||
events.add("build login client")
|
||||
FakeFfiClient(
|
||||
newLoginWithQrCodeHandlerResult = { FakeFfiLoginWithQrCodeHandler() },
|
||||
closeResult = { events.add("close login client") },
|
||||
)
|
||||
},
|
||||
{
|
||||
events.add("build session client")
|
||||
FakeFfiClient(withUtdHook = {})
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
val result = sut.loginWithQrCode(aSdkQrCodeLoginData()) {}
|
||||
|
||||
assertThat(result.getOrNull()).isEqualTo(A_USER_ID)
|
||||
assertThat(events).containsExactly("build login client", "close login client", "build session client").inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loginWithQrCode closes the client it created when the login fails`() = runTest {
|
||||
val closeResult = lambdaRecorder<Unit> {}
|
||||
val sut = createRustMatrixAuthenticationService(
|
||||
clientBuilderProvider = FakeClientBuilderProvider(
|
||||
provideResult = {
|
||||
FakeFfiClientBuilder(
|
||||
buildResult = {
|
||||
FakeFfiClient(
|
||||
newLoginWithQrCodeHandlerResult = {
|
||||
FakeFfiLoginWithQrCodeHandler(
|
||||
scanResult = { throw HumanQrLoginException.Unknown() },
|
||||
)
|
||||
},
|
||||
closeResult = closeResult,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(sut.loginWithQrCode(aSdkQrCodeLoginData()) {}.isFailure).isTrue()
|
||||
closeResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
private fun aSdkQrCodeLoginData() = SdkQrCodeLoginData(
|
||||
FakeFfiQrCodeData(
|
||||
baseUrlResult = { A_HOMESERVER_URL },
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* A [ClientBuilderProvider] handing out one [Client] per call, in order.
|
||||
*/
|
||||
private class FakeSequentialClientBuilderProvider(
|
||||
private vararg val clients: () -> Client,
|
||||
) : ClientBuilderProvider {
|
||||
private var index = 0
|
||||
override fun provide(): ClientBuilder = FakeFfiClientBuilder(buildResult = clients[index++])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a new login does not delete the session data of the account which has just been logged in`() = runTest {
|
||||
val storeBuilderProvider = SessionDirectoryCreatingSqliteStoreBuilderProvider()
|
||||
val sut = createRustMatrixAuthenticationService(
|
||||
clientBuilderProvider = FakeSequentialClientBuilderProvider(
|
||||
{ aLoginFakeFfiClient() },
|
||||
{ FakeFfiClient(withUtdHook = {}) },
|
||||
{ aLoginFakeFfiClient() },
|
||||
),
|
||||
sessionPathsFactory = SessionPathsFactory(temporaryFolder.newFolder("base"), temporaryFolder.newFolder("cache")),
|
||||
sqliteStoreBuilderProvider = storeBuilderProvider,
|
||||
)
|
||||
|
||||
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
|
||||
assertThat(sut.login("alice", "password").getOrNull()).isEqualTo(A_USER_ID)
|
||||
val loggedInSessionPaths = storeBuilderProvider.providedSessionPaths.first()
|
||||
|
||||
// Adding another account rotates the session paths, which must not touch the previous account.
|
||||
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
|
||||
|
||||
assertThat(loggedInSessionPaths.fileDirectory.exists()).isTrue()
|
||||
assertThat(loggedInSessionPaths.cacheDirectory.exists()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a new login deletes the session data of a previous failed login attempt`() = runTest {
|
||||
val storeBuilderProvider = SessionDirectoryCreatingSqliteStoreBuilderProvider()
|
||||
val sut = createRustMatrixAuthenticationService(
|
||||
clientBuilderProvider = FakeSequentialClientBuilderProvider(
|
||||
{ aLoginFakeFfiClient() },
|
||||
{ throw IllegalStateException("Failed to build the client of the session") },
|
||||
{ aLoginFakeFfiClient() },
|
||||
),
|
||||
sessionPathsFactory = SessionPathsFactory(temporaryFolder.newFolder("base"), temporaryFolder.newFolder("cache")),
|
||||
sqliteStoreBuilderProvider = storeBuilderProvider,
|
||||
)
|
||||
|
||||
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
|
||||
assertThat(sut.login("alice", "password").isFailure).isTrue()
|
||||
val abandonedSessionPaths = storeBuilderProvider.providedSessionPaths.first()
|
||||
|
||||
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
|
||||
|
||||
assertThat(abandonedSessionPaths.fileDirectory.exists()).isFalse()
|
||||
assertThat(abandonedSessionPaths.cacheDirectory.exists()).isFalse()
|
||||
}
|
||||
|
||||
private fun aLoginFakeFfiClient() = FakeFfiClient(
|
||||
homeserverLoginDetailsResult = { FakeFfiHomeserverLoginDetails() },
|
||||
loginResult = { _, _ -> },
|
||||
)
|
||||
|
||||
/**
|
||||
* A [SqliteStoreBuilderProvider] which creates the session directories, like the SDK does when it
|
||||
* opens its stores, and records them so that a test can assert on their lifecycle.
|
||||
*/
|
||||
private class SessionDirectoryCreatingSqliteStoreBuilderProvider : SqliteStoreBuilderProvider {
|
||||
val providedSessionPaths = mutableListOf<SessionPaths>()
|
||||
|
||||
override fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder {
|
||||
sessionPaths.fileDirectory.mkdirs()
|
||||
sessionPaths.cacheDirectory.mkdirs()
|
||||
providedSessionPaths.add(sessionPaths)
|
||||
return FakeSqliteStoreBuilder()
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createRustMatrixAuthenticationService(
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
sessionStore: SessionStore = InMemorySessionStore(updateUserProfileResult = { _, _, _ -> }),
|
||||
clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
sessionPathsFactory: SessionPathsFactory = SessionPathsFactory(File("/base"), File("/cache")),
|
||||
sqliteStoreBuilderProvider: SqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
||||
): RustMatrixAuthenticationService {
|
||||
val baseDirectory = File("/base")
|
||||
val cacheDirectory = File("/cache")
|
||||
val rustMatrixClientFactory = createRustMatrixClientFactory(
|
||||
cacheDirectory = cacheDirectory,
|
||||
cacheDirectory = File("/cache"),
|
||||
sessionStore = sessionStore,
|
||||
clientBuilderProvider = clientBuilderProvider,
|
||||
workManagerScheduler = FakeWorkManagerScheduler(submitLambda = {}),
|
||||
sqliteStoreBuilderProvider = sqliteStoreBuilderProvider,
|
||||
)
|
||||
return RustMatrixAuthenticationService(
|
||||
sessionPathsFactory = SessionPathsFactory(baseDirectory, cacheDirectory),
|
||||
sessionPathsFactory = sessionPathsFactory,
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
sessionStore = sessionStore,
|
||||
rustMatrixClientFactory = rustMatrixClientFactory,
|
||||
|
||||
+12
@@ -25,10 +25,12 @@ import org.matrix.rustcomponents.sdk.Encryption
|
||||
import org.matrix.rustcomponents.sdk.HomeserverCapabilities
|
||||
import org.matrix.rustcomponents.sdk.HomeserverLoginDetails
|
||||
import org.matrix.rustcomponents.sdk.IgnoredUsersListener
|
||||
import org.matrix.rustcomponents.sdk.LoginWithQrCodeHandler
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
import org.matrix.rustcomponents.sdk.NotificationClient
|
||||
import org.matrix.rustcomponents.sdk.NotificationProcessSetup
|
||||
import org.matrix.rustcomponents.sdk.NotificationSettings
|
||||
import org.matrix.rustcomponents.sdk.OAuthConfiguration
|
||||
import org.matrix.rustcomponents.sdk.ProfileListener
|
||||
import org.matrix.rustcomponents.sdk.PusherIdentifiers
|
||||
import org.matrix.rustcomponents.sdk.PusherKind
|
||||
@@ -57,6 +59,8 @@ class FakeFfiClient(
|
||||
private val withUtdHook: (UnableToDecryptDelegate) -> Unit = { lambdaError() },
|
||||
private val getProfileResult: (String) -> UserProfile = { aRustUserProfile() },
|
||||
private val homeserverLoginDetailsResult: () -> HomeserverLoginDetails = { lambdaError() },
|
||||
private val loginResult: (String, String) -> Unit = { _, _ -> lambdaError() },
|
||||
private val newLoginWithQrCodeHandlerResult: () -> LoginWithQrCodeHandler = { lambdaError() },
|
||||
private val getStoreSizesResult: () -> StoreSizes = { lambdaError() },
|
||||
private val createRoomResult: (CreateRoomParameters) -> String = { lambdaError() },
|
||||
private val homeserverCapabilities: HomeserverCapabilities = FakeFfiHomeserverCapabilities(),
|
||||
@@ -122,6 +126,14 @@ class FakeFfiClient(
|
||||
return homeserverLoginDetailsResult()
|
||||
}
|
||||
|
||||
override suspend fun login(username: String, password: String, initialDeviceName: String?, deviceId: String?) {
|
||||
loginResult(username, password)
|
||||
}
|
||||
|
||||
override fun newLoginWithQrCodeHandler(oauthConfiguration: OAuthConfiguration): LoginWithQrCodeHandler {
|
||||
return newLoginWithQrCodeHandlerResult()
|
||||
}
|
||||
|
||||
override suspend fun setMediaRetentionPolicy(policy: MediaRetentionPolicy) {}
|
||||
|
||||
override suspend fun getStoreSizes(): StoreSizes {
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.GeneratedQrLoginProgressListener
|
||||
import org.matrix.rustcomponents.sdk.LoginWithQrCodeHandler
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
import org.matrix.rustcomponents.sdk.QrCodeData
|
||||
import org.matrix.rustcomponents.sdk.QrLoginProgress
|
||||
import org.matrix.rustcomponents.sdk.QrLoginProgressListener
|
||||
|
||||
class FakeFfiLoginWithQrCodeHandler(
|
||||
private val generateResult: suspend () -> Unit = {},
|
||||
private val scanResult: suspend (QrCodeData) -> Unit = {},
|
||||
) : LoginWithQrCodeHandler(NoHandle) {
|
||||
private var scanProgressListener: QrLoginProgressListener? = null
|
||||
|
||||
override suspend fun generate(progressListener: GeneratedQrLoginProgressListener) {
|
||||
generateResult()
|
||||
}
|
||||
|
||||
override suspend fun scan(qrCodeData: QrCodeData, progressListener: QrLoginProgressListener) {
|
||||
scanProgressListener = progressListener
|
||||
scanResult(qrCodeData)
|
||||
}
|
||||
|
||||
fun emitScanProgress(progress: QrLoginProgress) {
|
||||
scanProgressListener?.onUpdate(progress)
|
||||
}
|
||||
|
||||
override fun close() = Unit
|
||||
}
|
||||
+5
@@ -14,12 +14,17 @@ import org.matrix.rustcomponents.sdk.QrCodeData
|
||||
|
||||
class FakeFfiQrCodeData(
|
||||
private val serverNameResult: () -> String? = { lambdaError() },
|
||||
private val baseUrlResult: () -> String? = { lambdaError() },
|
||||
private val toBytesResult: () -> ByteArray = { lambdaError() },
|
||||
) : QrCodeData(NoHandle) {
|
||||
override fun serverName(): String? {
|
||||
return serverNameResult()
|
||||
}
|
||||
|
||||
override fun baseUrl(): String? {
|
||||
return baseUrlResult()
|
||||
}
|
||||
|
||||
override fun toBytes(): ByteArray {
|
||||
return toBytesResult()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user