diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/E2EIClientProvider.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/E2EIClientProvider.kt index 2be10426cfa..92c17cc4cb7 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/E2EIClientProvider.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/E2EIClientProvider.kt @@ -30,21 +30,22 @@ import com.wire.kalium.logic.feature.CurrentClientIdProvider import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.fold +import com.wire.kalium.logic.kaliumLogger import com.wire.kalium.util.KaliumDispatcher import com.wire.kalium.util.KaliumDispatcherImpl import kotlinx.coroutines.withContext -interface E2EClientProvider { +interface E2EIClientProvider { suspend fun getE2EIClient(clientId: ClientId? = null): Either } -internal class E2EIClientProviderImpl( +internal class EI2EIClientProviderImpl( private val userId: UserId, private val currentClientIdProvider: CurrentClientIdProvider, private val mlsClientProvider: MLSClientProvider, private val userRepository: UserRepository, private val dispatchers: KaliumDispatcher = KaliumDispatcherImpl -) : E2EClientProvider { +) : E2EIClientProvider { private var e2EIClient: E2EIClient? = null @@ -62,11 +63,21 @@ internal class E2EIClientProviderImpl( } ?: run { getSelfUserInfo().flatMap { selfUser -> mlsClientProvider.getMLSClient(currentClientId).flatMap { - val newE2EIClient = it.newAcmeEnrollment( - e2eiClientId, - selfUser.first, - selfUser.second - ) + val newE2EIClient = if (it.isE2EIEnabled()) { + kaliumLogger.e("initial E2EI client for MLS client without e2ei") + it.e2eiNewRotateEnrollment( + e2eiClientId, + selfUser.first, + selfUser.second + ) + } else { + kaliumLogger.e("initial E2EI client for mls client that already has e2ei enabled") + it.e2eiNewActivationEnrollment( + e2eiClientId, + selfUser.first, + selfUser.second + ) + } e2EIClient = newE2EIClient Either.Right(newE2EIClient) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepository.kt index 8498c7c96ee..fc01a914709 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepository.kt @@ -24,7 +24,7 @@ import com.wire.kalium.cryptography.AcmeDirectory import com.wire.kalium.cryptography.NewAcmeAuthz import com.wire.kalium.cryptography.NewAcmeOrder import com.wire.kalium.logic.CoreFailure -import com.wire.kalium.logic.data.client.E2EClientProvider +import com.wire.kalium.logic.data.client.E2EIClientProvider import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.feature.CurrentClientIdProvider import com.wire.kalium.logic.functional.Either @@ -65,7 +65,7 @@ interface E2EIRepository { class E2EIRepositoryImpl( private val e2EIApi: E2EIApi, private val acmeApi: ACMEApi, - private val e2EClientProvider: E2EClientProvider, + private val e2EIClientProvider: E2EIClientProvider, private val mlsClientProvider: MLSClientProvider, private val currentClientIdProvider: CurrentClientIdProvider ) : E2EIRepository { @@ -73,7 +73,7 @@ class E2EIRepositoryImpl( override suspend fun loadACMEDirectories(): Either = wrapApiRequest { acmeApi.getACMEDirectories() }.flatMap { directories -> - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> wrapE2EIRequest { e2eiClient.directoryResponse(Json.encodeToString(directories).encodeToByteArray()) } @@ -85,7 +85,7 @@ class E2EIRepositoryImpl( } override suspend fun createNewAccount(prevNonce: String, createAccountEndpoint: String) = - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> val accountRequest = e2eiClient.getNewAccountRequest(prevNonce) wrapApiRequest { acmeApi.sendACMERequest(createAccountEndpoint, accountRequest) @@ -96,7 +96,7 @@ class E2EIRepositoryImpl( } override suspend fun createNewOrder(prevNonce: String, createOrderEndpoint: String) = - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> val orderRequest = e2eiClient.getNewOrderRequest(prevNonce) wrapApiRequest { acmeApi.sendACMERequest(createOrderEndpoint, orderRequest) @@ -107,7 +107,7 @@ class E2EIRepositoryImpl( } override suspend fun createAuthz(prevNonce: String, authzEndpoint: String) = - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> val authzRequest = e2eiClient.getNewAuthzRequest(authzEndpoint, prevNonce) wrapApiRequest { acmeApi.sendACMERequest(authzEndpoint, authzRequest) @@ -129,12 +129,12 @@ class E2EIRepositoryImpl( } } - override suspend fun getDPoPToken(wireNonce: String) = e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + override suspend fun getDPoPToken(wireNonce: String) = e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> Either.Right(e2eiClient.createDpopToken(wireNonce)) } override suspend fun validateDPoPChallenge(accessToken: String, prevNonce: String, acmeChallenge: AcmeChallenge) = - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> val challengeRequest = e2eiClient.getNewDpopChallengeRequest(accessToken, prevNonce) wrapApiRequest { acmeApi.sendChallengeRequest(acmeChallenge.url, challengeRequest) @@ -145,7 +145,7 @@ class E2EIRepositoryImpl( } override suspend fun validateOIDCChallenge(idToken: String, prevNonce: String, acmeChallenge: AcmeChallenge) = - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> val challengeRequest = e2eiClient.getNewOidcChallengeRequest(idToken, prevNonce) wrapApiRequest { acmeApi.sendChallengeRequest(acmeChallenge.url, challengeRequest) @@ -156,13 +156,13 @@ class E2EIRepositoryImpl( } override suspend fun validateChallenge(challengeResponse: ChallengeResponse) = - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> e2eiClient.setChallengeResponse(Json.encodeToString(challengeResponse).encodeToByteArray()) Either.Right(Unit) } override suspend fun checkOrderRequest(location: String, prevNonce: String) = - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> val checkOrderRequest = e2eiClient.checkOrderRequest(location, prevNonce) wrapApiRequest { acmeApi.sendACMERequest(location, checkOrderRequest) @@ -173,7 +173,7 @@ class E2EIRepositoryImpl( } override suspend fun finalize(location: String, prevNonce: String) = - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> val finalizeRequest = e2eiClient.finalizeRequest(prevNonce) wrapApiRequest { acmeApi.sendACMERequest(location, finalizeRequest) @@ -184,7 +184,7 @@ class E2EIRepositoryImpl( } override suspend fun certificateRequest(location: String, prevNonce: String) = - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> val certificateRequest = e2eiClient.certificateRequest(prevNonce) wrapApiRequest { acmeApi.sendACMERequest(location, certificateRequest) @@ -192,7 +192,7 @@ class E2EIRepositoryImpl( } override suspend fun initMLSClientWithCertificate(certificateChain: String) { - e2EClientProvider.getE2EIClient().flatMap { e2eiClient -> + e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> mlsClientProvider.getMLSClient().map { it.e2eiMlsInitOnly(e2eiClient, certificateChain) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt index 3d366244990..f1b6eb31fc6 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt @@ -77,7 +77,7 @@ import kotlinx.serialization.json.Json import kotlin.time.Duration.Companion.minutes @Suppress("TooManyFunctions") -internal interface UserRepository { +interface UserRepository { suspend fun fetchSelfUser(): Either /** diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index db2cdd1dde4..004a920be70 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -44,8 +44,8 @@ import com.wire.kalium.logic.data.call.VideoStateCheckerImpl import com.wire.kalium.logic.data.call.mapper.CallMapper import com.wire.kalium.logic.data.client.ClientDataSource import com.wire.kalium.logic.data.client.ClientRepository -import com.wire.kalium.logic.data.client.E2EClientProvider -import com.wire.kalium.logic.data.client.E2EIClientProviderImpl +import com.wire.kalium.logic.data.client.E2EIClientProvider +import com.wire.kalium.logic.data.client.EI2EIClientProviderImpl import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.data.client.MLSClientProviderImpl import com.wire.kalium.logic.data.client.remote.ClientRemoteDataSource @@ -595,8 +595,8 @@ class UserSessionScope internal constructor( clientIdProvider ) - private val e2EIClientProvider: E2EClientProvider by lazy { - E2EIClientProviderImpl( + private val e2EIClientProvider: E2EIClientProvider by lazy { + EI2EIClientProviderImpl( userId = userId, currentClientIdProvider = clientIdProvider, mlsClientProvider = mlsClientProvider, diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/client/E2EIClientProviderTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/client/E2EIClientProviderTest.kt new file mode 100644 index 00000000000..5a593830c3f --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/client/E2EIClientProviderTest.kt @@ -0,0 +1,142 @@ +/* + * Wire + * Copyright (C) 2023 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.client + +import com.wire.kalium.logic.data.client.E2EIClientProvider +import com.wire.kalium.logic.data.client.EI2EIClientProviderImpl +import com.wire.kalium.logic.framework.TestClient +import com.wire.kalium.logic.framework.TestUser +import com.wire.kalium.logic.util.arrangement.provider.E2EIClientProviderArrangement +import com.wire.kalium.logic.util.arrangement.provider.E2EIClientProviderArrangementImpl +import com.wire.kalium.logic.util.shouldFail +import com.wire.kalium.logic.util.shouldSucceed +import io.mockative.any +import io.mockative.once +import io.mockative.verify +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class E2EIClientProviderTest { + @Test + fun givenMLSClientWithoutE2EI_whenGettingE2EIClient_callsNewRotateEnrollment() = runTest{ + val (arrangement, e2eiClientProvider) = Arrangement() + .arrange { + withGetMLSClientSuccessful() + withE2EINewActivationEnrollmentSuccessful() + withSelfUser(TestUser.SELF) + withE2EIEnabled(false) + } + + e2eiClientProvider.getE2EIClient(TestClient.CLIENT_ID).shouldSucceed() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::getSelfUser) + .wasInvoked(exactly = once) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiNewActivationEnrollment) + .with(any(), any(), any()) + .wasInvoked(exactly = once) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiNewRotateEnrollment) + .with(any(),any(),any()) + .wasNotInvoked() + } + + @Test + fun givenMLSClientWithE2EI_whenGettingE2EIClient_callsNewActivationEnrollment() = runTest{ + val (arrangement, e2eiClientProvider) = Arrangement() + .arrange { + withGetMLSClientSuccessful() + withE2EINewRotationEnrollmentSuccessful() + withSelfUser(TestUser.SELF) + withE2EIEnabled(true) + } + + e2eiClientProvider.getE2EIClient(TestClient.CLIENT_ID).shouldSucceed() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::getSelfUser) + .wasInvoked(exactly = once) + + verify(arrangement.mlsClientProvider) + .suspendFunction(arrangement.mlsClientProvider::getMLSClient) + .with(any()) + .wasInvoked(exactly = once) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiNewRotateEnrollment) + .with(any(),any(),any()) + .wasInvoked(exactly = once) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiNewActivationEnrollment) + .with(any(), any(), any()) + .wasNotInvoked() + } + + @Test + fun givenSelfUserNotFound_whenGettingE2EIClient_ReturnsError() = runTest{ + val (arrangement, e2eiClientProvider) = Arrangement() + .arrange { + withGetMLSClientSuccessful() + withE2EINewRotationEnrollmentSuccessful() + withSelfUser(null) + withE2EIEnabled(true) + } + + e2eiClientProvider.getE2EIClient(TestClient.CLIENT_ID).shouldFail() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::getSelfUser) + .wasInvoked(exactly = once) + + verify(arrangement.mlsClientProvider) + .suspendFunction(arrangement.mlsClientProvider::getMLSClient) + .with(any()) + .wasNotInvoked() + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiNewRotateEnrollment) + .with(any(),any(),any()) + .wasNotInvoked() + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiNewActivationEnrollment) + .with(any(), any(), any()) + .wasNotInvoked() + } + + private class Arrangement : + E2EIClientProviderArrangement by E2EIClientProviderArrangementImpl() { + private lateinit var e2eiClientProvider: E2EIClientProvider + + fun arrange(block: Arrangement.() -> Unit): Pair { + apply(block) + e2eiClientProvider = EI2EIClientProviderImpl( + TestUser.USER_ID, + currentClientIdProvider, + mlsClientProvider, + userRepository + ) + + return this to e2eiClientProvider + } + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepositoryTest.kt index da3d3b5cb46..e4ba7558118 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepositoryTest.kt @@ -18,23 +18,18 @@ package com.wire.kalium.logic.data.e2ei import com.wire.kalium.cryptography.* -import com.wire.kalium.cryptography.utils.generateRandomAES256Key -import com.wire.kalium.logic.data.client.E2EClientProvider +import com.wire.kalium.logic.data.client.E2EIClientProvider import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.data.e2ei.E2EIRepositoryTest.Arrangement.Companion.ACME_CHALLENGE -import com.wire.kalium.logic.data.e2ei.E2EIRepositoryTest.Arrangement.Companion.ACME_DIRECTORIES -import com.wire.kalium.logic.data.e2ei.E2EIRepositoryTest.Arrangement.Companion.ACME_DIRECTORIES_RESPONSE import com.wire.kalium.logic.data.e2ei.E2EIRepositoryTest.Arrangement.Companion.RANDOM_ACCESS_TOKEN import com.wire.kalium.logic.data.e2ei.E2EIRepositoryTest.Arrangement.Companion.RANDOM_ID_TOKEN import com.wire.kalium.logic.data.e2ei.E2EIRepositoryTest.Arrangement.Companion.RANDOM_NONCE import com.wire.kalium.logic.data.e2ei.E2EIRepositoryTest.Arrangement.Companion.RANDOM_URL -import com.wire.kalium.logic.data.featureConfig.* import com.wire.kalium.logic.feature.CurrentClientIdProvider import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.util.shouldFail import com.wire.kalium.logic.util.shouldSucceed import com.wire.kalium.network.api.base.authenticated.e2ei.E2EIApi -import com.wire.kalium.network.api.base.authenticated.featureConfigs.* import com.wire.kalium.network.api.base.model.ErrorResponse import com.wire.kalium.network.api.base.unbound.acme.ACMEApi import com.wire.kalium.network.api.base.unbound.acme.ACMEResponse @@ -42,14 +37,9 @@ import com.wire.kalium.network.api.base.unbound.acme.AcmeDirectoriesResponse import com.wire.kalium.network.api.base.unbound.acme.ChallengeResponse import com.wire.kalium.network.exceptions.KaliumException import com.wire.kalium.network.utils.NetworkResponse -import com.wire.kalium.util.serialization.toJsonElement -import io.ktor.util.* import io.mockative.* import kotlinx.coroutines.test.runTest -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json import kotlin.test.Test -import kotlin.test.assertEquals class E2EIRepositoryTest { @Test @@ -768,7 +758,7 @@ class E2EIRepositoryTest { val acmeApi: ACMEApi = mock(classOf()) @Mock - val e2eiClientProvider: E2EClientProvider = mock(classOf()) + val e2eiClientProvider: E2EIClientProvider = mock(classOf()) @Mock val e2eiClient = mock(classOf()) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestUser.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestUser.kt index 31ad4feb87d..ed6f09619e9 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestUser.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestUser.kt @@ -44,7 +44,7 @@ import com.wire.kalium.persistence.dao.UserEntity import com.wire.kalium.persistence.dao.UserTypeEntity object TestUser { - private const val value = "value" + private const val value = "41d2b365-f4a9-4ba1-bddf-5afb8aca6786" private const val domain = "domain" val USER_ID = UserId(value, domain) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/provider/E2EIClientProviderArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/provider/E2EIClientProviderArrangement.kt new file mode 100644 index 00000000000..4e957dc223e --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/provider/E2EIClientProviderArrangement.kt @@ -0,0 +1,102 @@ +/* + * Wire + * Copyright (C) 2023 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.util.arrangement.provider + +import com.wire.kalium.cryptography.E2EIClient +import com.wire.kalium.cryptography.E2EIQualifiedClientId +import com.wire.kalium.cryptography.MLSClient +import com.wire.kalium.logic.data.client.MLSClientProvider +import com.wire.kalium.logic.data.user.SelfUser +import com.wire.kalium.logic.data.user.UserRepository +import com.wire.kalium.logic.feature.CurrentClientIdProvider +import com.wire.kalium.logic.functional.Either +import io.mockative.Mock +import io.mockative.anything +import io.mockative.classOf +import io.mockative.given +import io.mockative.mock + +interface E2EIClientProviderArrangement { + @Mock + val mlsClientProvider: MLSClientProvider + + @Mock + val mlsClient: MLSClient + + @Mock + val e2eiClient: E2EIClient + + @Mock + val userRepository: UserRepository + + @Mock + val currentClientIdProvider: CurrentClientIdProvider + + fun withGetMLSClientSuccessful() + + fun withE2EINewActivationEnrollmentSuccessful() + + fun withE2EINewRotationEnrollmentSuccessful() + + + fun withE2EIEnabled(isEnabled: Boolean) + + fun withSelfUser(selfUser: SelfUser?) +} + +class E2EIClientProviderArrangementImpl : E2EIClientProviderArrangement { + override val mlsClientProvider: MLSClientProvider = mock(classOf()) + override val mlsClient: MLSClient = mock(classOf()) + override val e2eiClient: E2EIClient = mock(classOf()) + override val userRepository: UserRepository = mock(classOf()) + override val currentClientIdProvider = mock(classOf()) + override fun withGetMLSClientSuccessful() { + given(mlsClientProvider) + .suspendFunction(mlsClientProvider::getMLSClient) + .whenInvokedWith(anything()) + .then { Either.Right(mlsClient) } + } + + override fun withE2EINewActivationEnrollmentSuccessful() { + given(mlsClient) + .suspendFunction(mlsClient::e2eiNewActivationEnrollment) + .whenInvokedWith(anything(), anything(), anything()) + .thenReturn(e2eiClient) + } + override fun withE2EINewRotationEnrollmentSuccessful() { + given(mlsClient) + .suspendFunction(mlsClient::e2eiNewRotateEnrollment) + .whenInvokedWith(anything(), anything(), anything()) + .thenReturn(e2eiClient) + } + + override fun withE2EIEnabled(isEnabled: Boolean) { + given(mlsClient) + .suspendFunction(mlsClient::isE2EIEnabled) + .whenInvoked() + .thenReturn(isEnabled) + } + + override fun withSelfUser(selfUser: SelfUser?) { + given(userRepository) + .suspendFunction(userRepository::getSelfUser) + .whenInvoked() + .thenReturn(selfUser) + } + +}