diff --git a/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt b/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt index 809c350af64..205049fbf48 100644 --- a/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt +++ b/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt @@ -194,7 +194,11 @@ class MLSClientImpl( TODO("Not yet implemented") } - override suspend fun e2eiRotateAll(enrollment: E2EIClient, certificateChain: CertificateChain, newMLSKeyPackageCount: UInt) { + override suspend fun e2eiRotateAll( + enrollment: E2EIClient, + certificateChain: CertificateChain, + newMLSKeyPackageCount: UInt + ): RotateBundle { TODO("Not yet implemented") } diff --git a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt index 335b15b6461..1d05fcae411 100644 --- a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt +++ b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/MLSClientImpl.kt @@ -260,11 +260,13 @@ class MLSClientImpl( enrollment: E2EIClient, certificateChain: CertificateChain, newMLSKeyPackageCount: UInt - ) { - coreCrypto.e2eiRotateAll( - (enrollment as E2EIClientImpl).wireE2eIdentity, - certificateChain, - newMLSKeyPackageCount + ): RotateBundle { + return toRotateBundle( + coreCrypto.e2eiRotateAll( + (enrollment as E2EIClientImpl).wireE2eIdentity, + certificateChain, + newMLSKeyPackageCount + ) ) } diff --git a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt index 3e62eefb721..b8d816ef26f 100644 --- a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt +++ b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/MLSClient.kt @@ -326,7 +326,7 @@ interface MLSClient { /** * Generate new keypackages after E2EI certificate issued */ - suspend fun e2eiRotateAll(enrollment: E2EIClient, certificateChain: CertificateChain, newMLSKeyPackageCount: UInt) + suspend fun e2eiRotateAll(enrollment: E2EIClient, certificateChain: CertificateChain, newMLSKeyPackageCount: UInt): RotateBundle /** * Conversation E2EI Verification Status diff --git a/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt b/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt index d8ee164a4e0..ade7142af2f 100644 --- a/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt +++ b/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/MLSClientImpl.kt @@ -136,7 +136,7 @@ class MLSClientImpl : MLSClient { enrollment: E2EIClient, certificateChain: CertificateChain, newMLSKeyPackageCount: UInt - ) { + ): RotateBundle { TODO("Not yet implemented") } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt index ce6b115943c..dc4e7351e7e 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt @@ -21,6 +21,7 @@ package com.wire.kalium.logic.data.conversation import com.wire.kalium.cryptography.CommitBundle import com.wire.kalium.cryptography.CryptoQualifiedClientId import com.wire.kalium.cryptography.CryptoQualifiedID +import com.wire.kalium.cryptography.E2EIClient import com.wire.kalium.logger.obfuscateId import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.MLSFailure @@ -43,6 +44,8 @@ import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.flatMapLeft import com.wire.kalium.logic.functional.flatten +import com.wire.kalium.logic.functional.fold +import com.wire.kalium.logic.functional.foldToEitherWhileRight import com.wire.kalium.logic.functional.map import com.wire.kalium.logic.functional.onFailure import com.wire.kalium.logic.functional.onSuccess @@ -108,6 +111,11 @@ interface MLSConversationRepository { suspend fun observeProposalTimers(): Flow suspend fun observeEpochChanges(): Flow suspend fun getConversationVerificationStatus(groupID: GroupID): Either + suspend fun rotateKeysAndMigrateConversations( + clientId: ClientId, + e2eiClient: E2EIClient, + certificateChain: String + ): Either } private enum class CommitStrategy { @@ -514,6 +522,33 @@ internal class MLSConversationDataSource( wrapMLSRequest { mlsClient.isGroupVerified(idMapper.toCryptoModel(groupID)) } }.map { it.toModel() } + override suspend fun rotateKeysAndMigrateConversations( + clientId: ClientId, + e2eiClient: E2EIClient, + certificateChain: String + ) = mlsClientProvider.getMLSClient().flatMap { mlsClient -> + wrapMLSRequest { + mlsClient.e2eiRotateAll(e2eiClient, certificateChain, 10U) + }.map { rotateBundle -> + // todo: make below API calls atomic when the backend does it in one request + // todo: store keypackages to drop, later drop them again + kaliumLogger.w("drop old key packages after conversations migration") + keyPackageRepository.deleteKeyPackages(clientId, rotateBundle.keyPackageRefsToRemove).flatMapLeft { + return Either.Left(it) + } + + kaliumLogger.w("upload new key packages including x509 certificate") + keyPackageRepository.uploadKeyPackages(clientId, rotateBundle.newKeyPackages).flatMapLeft { + return Either.Left(it) + } + + kaliumLogger.w("send migration commits after key rotations") + rotateBundle.commits.map { + sendCommitBundle(GroupID(it.key), it.value) + }.foldToEitherWhileRight(Unit) { value, _ -> value }.fold({ return Either.Left(it) }, { }) + } + } + private suspend fun retryOnCommitFailure( groupID: GroupID, retryOnClientMismatch: Boolean = true, 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 fc01a914709..0cb47ad3888 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 @@ -26,6 +26,7 @@ import com.wire.kalium.cryptography.NewAcmeOrder import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.data.client.E2EIClientProvider import com.wire.kalium.logic.data.client.MLSClientProvider +import com.wire.kalium.logic.data.conversation.MLSConversationRepository import com.wire.kalium.logic.feature.CurrentClientIdProvider import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.flatMap @@ -59,7 +60,7 @@ interface E2EIRepository { suspend fun finalize(location: String, prevNonce: String): Either> suspend fun checkOrderRequest(location: String, prevNonce: String): Either> suspend fun certificateRequest(location: String, prevNonce: String): Either - suspend fun initMLSClientWithCertificate(certificateChain: String) + suspend fun rotateKeysAndMigrateConversations(certificateChain: String): Either } class E2EIRepositoryImpl( @@ -67,7 +68,8 @@ class E2EIRepositoryImpl( private val acmeApi: ACMEApi, private val e2EIClientProvider: E2EIClientProvider, private val mlsClientProvider: MLSClientProvider, - private val currentClientIdProvider: CurrentClientIdProvider + private val currentClientIdProvider: CurrentClientIdProvider, + private val mlsConversationRepository: MLSConversationRepository ) : E2EIRepository { override suspend fun loadACMEDirectories(): Either = wrapApiRequest { @@ -191,11 +193,11 @@ class E2EIRepositoryImpl( }.map { it } } - override suspend fun initMLSClientWithCertificate(certificateChain: String) { + override suspend fun rotateKeysAndMigrateConversations(certificateChain: String) = e2EIClientProvider.getE2EIClient().flatMap { e2eiClient -> - mlsClientProvider.getMLSClient().map { - it.e2eiMlsInitOnly(e2eiClient, certificateChain) + currentClientIdProvider().flatMap { clientId -> + mlsConversationRepository.rotateKeysAndMigrateConversations(clientId, e2eiClient, certificateChain) } } - } + } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/keypackage/KeyPackageRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/keypackage/KeyPackageRepository.kt index d02a523561a..165194c8910 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/keypackage/KeyPackageRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/keypackage/KeyPackageRepository.kt @@ -43,6 +43,10 @@ interface KeyPackageRepository { suspend fun uploadNewKeyPackages(clientId: ClientId, amount: Int = 100): Either + suspend fun uploadKeyPackages(clientId: ClientId, keyPackages: List): Either + + suspend fun deleteKeyPackages(clientId: ClientId, keyPackages: List): Either + suspend fun getAvailableKeyPackageCount(clientId: ClientId): Either suspend fun validKeyPackageCount(clientId: ClientId): Either @@ -87,6 +91,22 @@ class KeyPackageDataSource( } } + override suspend fun uploadKeyPackages( + clientId: ClientId, + keyPackages: List + ): Either = + wrapApiRequest { + keyPackageApi.uploadKeyPackages(clientId.value, keyPackages.map { it.encodeBase64() }) + } + + override suspend fun deleteKeyPackages( + clientId: ClientId, + keyPackages: List + ): Either = + wrapApiRequest { + keyPackageApi.deleteKeyPackages(clientId.value, keyPackages.map { it.encodeBase64() }) + } + override suspend fun validKeyPackageCount(clientId: ClientId): Either = mlsClientProvider.getMLSClient(clientId).flatMap { mlsClient -> wrapMLSRequest { 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 b5f99acf6ab..c96785324c9 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 @@ -592,7 +592,8 @@ class UserSessionScope internal constructor( globalScope.unboundNetworkContainer.acmeApi, e2EIClientProvider, mlsClientProvider, - clientIdProvider + clientIdProvider, + mlsConversationRepository ) private val e2EIClientProvider: E2EIClientProvider by lazy { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/EnrollE2EIUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/EnrollE2EIUseCase.kt index a144f91d0fc..21c5dcff78b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/EnrollE2EIUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/EnrollE2EIUseCase.kt @@ -22,6 +22,7 @@ import com.wire.kalium.logic.E2EIFailure import com.wire.kalium.logic.data.e2ei.E2EIRepository import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.fold +import com.wire.kalium.logic.functional.onFailure /** * Issue an E2EI certificate and re-initiate the MLSClient @@ -111,8 +112,10 @@ class EnrollE2EIUseCaseImpl internal constructor( return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.Certificate, it).toEitherLeft() }, { it }) - // TODO(fix): init after fixing the MLS client initialization mechanism - // TODO(revert): e2EIRepository.initMLSClientWithCertificate(certificateRequest.response.decodeToString()) + e2EIRepository.rotateKeysAndMigrateConversations(certificateRequest.response.decodeToString()).onFailure { + return E2EIEnrollmentResult.Failed(E2EIEnrollmentResult.E2EIStep.ConversationMigration, it).toEitherLeft() + } + return Either.Right(E2EIEnrollmentResult.Success(certificateRequest.response.decodeToString())) } @@ -132,6 +135,7 @@ sealed interface E2EIEnrollmentResult { OIDCChallenge, CheckOrderRequest, FinalizeRequest, + ConversationMigration, Certificate } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt index 8fef1218627..bd1497d45db 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt @@ -19,13 +19,16 @@ package com.wire.kalium.logic.data.conversation import com.wire.kalium.cryptography.CommitBundle +import com.wire.kalium.cryptography.E2EIClient import com.wire.kalium.cryptography.E2EIConversationState import com.wire.kalium.cryptography.GroupInfoBundle import com.wire.kalium.cryptography.GroupInfoEncryptionType import com.wire.kalium.cryptography.MLSClient import com.wire.kalium.cryptography.RatchetTreeType +import com.wire.kalium.cryptography.RotateBundle import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.data.client.MLSClientProvider +import com.wire.kalium.logic.data.conversation.MLSConversationRepositoryTest.Arrangement.Companion.TEST_FAILURE import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.id.GroupID import com.wire.kalium.logic.data.id.QualifiedClientID @@ -35,6 +38,7 @@ import com.wire.kalium.logic.data.mlspublickeys.KeyType import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKey import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysRepository import com.wire.kalium.logic.di.MapperProvider +import com.wire.kalium.logic.framework.TestClient import com.wire.kalium.logic.framework.TestConversation import com.wire.kalium.logic.framework.TestUser import com.wire.kalium.logic.functional.Either @@ -1100,6 +1104,149 @@ class MLSConversationRepositoryTest { .wasNotInvoked() } + @Test + fun givenSuccessResponse_whenRotatingKeysAndMigratingConversation_thenReturnsSuccess() = runTest { + val (arrangement, mlsConversationRepository) = Arrangement() + .withGetMLSClientSuccessful() + .withRotateAllSuccessful() + .withSendCommitBundleSuccessful() + .withUploadKeyPackagesReturning(Either.Right(Unit)) + .withDeleteKeyPackagesReturning(Either.Right(Unit)) + .arrange() + + assertEquals( + Either.Right(Unit), + mlsConversationRepository.rotateKeysAndMigrateConversations(TestClient.CLIENT_ID, arrangement.e2eiClient, "") + ) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiRotateAll) + .with(any(), any(), any()) + .wasInvoked(once) + + verify(arrangement.keyPackageRepository) + .suspendFunction(arrangement.keyPackageRepository::deleteKeyPackages) + .with(any(), any()) + .wasInvoked(once) + + verify(arrangement.keyPackageRepository) + .suspendFunction(arrangement.keyPackageRepository::uploadKeyPackages) + .with(any(), any()) + .wasInvoked(once) + + verify(arrangement.mlsMessageApi) + .suspendFunction(arrangement.mlsMessageApi::sendCommitBundle) + .with(anyInstanceOf(MLSMessageApi.CommitBundle::class)) + .wasInvoked(once) + } + + @Test + fun givenDropKeypackagesFailed_whenRotatingKeysAndMigratingConversation_thenReturnsFailure() = runTest { + val (arrangement, mlsConversationRepository) = Arrangement() + .withGetMLSClientSuccessful() + .withRotateAllSuccessful() + .withUploadKeyPackagesReturning(Either.Right(Unit)) + .withDeleteKeyPackagesReturning(TEST_FAILURE) + .withSendCommitBundleSuccessful() + .arrange() + + assertEquals( + TEST_FAILURE, + mlsConversationRepository.rotateKeysAndMigrateConversations(TestClient.CLIENT_ID, arrangement.e2eiClient, "") + ) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiRotateAll) + .with(any(), any(), any()) + .wasInvoked(once) + + verify(arrangement.keyPackageRepository) + .suspendFunction(arrangement.keyPackageRepository::deleteKeyPackages) + .with(any(), any()) + .wasInvoked(once) + + verify(arrangement.keyPackageRepository) + .suspendFunction(arrangement.keyPackageRepository::uploadKeyPackages) + .with(any(), any()) + .wasNotInvoked() + + verify(arrangement.mlsMessageApi) + .suspendFunction(arrangement.mlsMessageApi::sendCommitBundle) + .with(anyInstanceOf(MLSMessageApi.CommitBundle::class)) + .wasNotInvoked() + } + + @Test + fun givenUploadKeypackagesFailed_whenRotatingKeysAndMigratingConversation_thenReturnsFailure() = runTest { + val (arrangement, mlsConversationRepository) = Arrangement() + .withGetMLSClientSuccessful() + .withRotateAllSuccessful() + .withUploadKeyPackagesReturning(TEST_FAILURE) + .withDeleteKeyPackagesReturning(Either.Right(Unit)) + .withSendCommitBundleSuccessful() + .arrange() + + assertEquals( + TEST_FAILURE, + mlsConversationRepository.rotateKeysAndMigrateConversations(TestClient.CLIENT_ID, arrangement.e2eiClient, "") + ) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiRotateAll) + .with(any(), any(), any()) + .wasInvoked(once) + + verify(arrangement.keyPackageRepository) + .suspendFunction(arrangement.keyPackageRepository::deleteKeyPackages) + .with(any(), any()) + .wasInvoked(once) + + verify(arrangement.keyPackageRepository) + .suspendFunction(arrangement.keyPackageRepository::uploadKeyPackages) + .with(any(), any()) + .wasInvoked(once) + + verify(arrangement.mlsMessageApi) + .suspendFunction(arrangement.mlsMessageApi::sendCommitBundle) + .with(anyInstanceOf(MLSMessageApi.CommitBundle::class)) + .wasNotInvoked() + } + + @Test + fun givenSendingCommitBundlesFails_whenRotatingKeysAndMigratingConversation_thenReturnsFailure() = runTest { + val (arrangement, mlsConversationRepository) = Arrangement() + .withGetMLSClientSuccessful() + .withRotateAllSuccessful() + .withUploadKeyPackagesReturning(Either.Right(Unit)) + .withDeleteKeyPackagesReturning(Either.Right(Unit)) + .withSendCommitBundleFailing(Arrangement.MLS_CLIENT_MISMATCH_ERROR, times = 1) + .arrange() + + + val result = mlsConversationRepository.rotateKeysAndMigrateConversations(TestClient.CLIENT_ID, arrangement.e2eiClient, "") + result.shouldFail() + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::e2eiRotateAll) + .with(any(), any(), any()) + .wasInvoked(once) + + verify(arrangement.keyPackageRepository) + .suspendFunction(arrangement.keyPackageRepository::deleteKeyPackages) + .with(any(), any()) + .wasInvoked(once) + + verify(arrangement.keyPackageRepository) + .suspendFunction(arrangement.keyPackageRepository::uploadKeyPackages) + .with(any(), any()) + .wasInvoked(once) + + verify(arrangement.mlsMessageApi) + .suspendFunction(arrangement.mlsMessageApi::sendCommitBundle) + .with(anyInstanceOf(MLSMessageApi.CommitBundle::class)) + .wasInvoked(once) + } + private class Arrangement { @Mock @@ -1126,6 +1273,9 @@ class MLSConversationRepositoryTest { @Mock val mlsClient = mock(classOf()) + @Mock + val e2eiClient = mock(classOf()) + @Mock val syncManager = mock(SyncManager::class) @@ -1172,6 +1322,20 @@ class MLSConversationRepositoryTest { .then { Either.Right(keyPackages) } } + fun withUploadKeyPackagesReturning(result: Either) = apply { + given(keyPackageRepository) + .suspendFunction(keyPackageRepository::uploadKeyPackages) + .whenInvokedWith(anything(), anything()) + .thenReturn(result) + } + + fun withDeleteKeyPackagesReturning(result: Either) = apply { + given(keyPackageRepository) + .suspendFunction(keyPackageRepository::deleteKeyPackages) + .whenInvokedWith(anything(), anything()) + .thenReturn(result) + } + fun withGetPublicKeysSuccessful() = apply { given(mlsPublicKeysRepository) .suspendFunction(mlsPublicKeysRepository::getKeys) @@ -1193,6 +1357,13 @@ class MLSConversationRepositoryTest { .then { Either.Left(failure) } } + fun withRotateAllSuccessful() = apply { + given(mlsClient) + .suspendFunction(mlsClient::e2eiRotateAll) + .whenInvokedWith(anything(), anything(), anything()) + .thenReturn(ROTATE_BUNDLE) + } + fun withAddMLSMemberSuccessful() = apply { given(mlsClient) .suspendFunction(mlsClient::addMember) @@ -1330,7 +1501,8 @@ class MLSConversationRepositoryTest { proposalTimersFlow ) - internal companion object { + companion object { + val TEST_FAILURE = Either.Left(CoreFailure.Unknown(Throwable("an error"))) const val EPOCH = 5UL const val RAW_GROUP_ID = "groupId" val TIME = DateTimeUtil.currentIsoDateTimeString() @@ -1359,6 +1531,7 @@ class MLSConversationRepositoryTest { PUBLIC_GROUP_STATE ) val COMMIT_BUNDLE = CommitBundle(COMMIT, WELCOME, PUBLIC_GROUP_STATE_BUNDLE) + val ROTATE_BUNDLE = RotateBundle(mapOf(RAW_GROUP_ID to COMMIT_BUNDLE), emptyList(), emptyList()) val DECRYPTED_MESSAGE_BUNDLE = com.wire.kalium.cryptography.DecryptedMessageBundle( message = null, commitDelay = null, 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 e4ba7558118..acb0e0a0e5b 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,14 +18,18 @@ package com.wire.kalium.logic.data.e2ei import com.wire.kalium.cryptography.* +import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.data.client.E2EIClientProvider import com.wire.kalium.logic.data.client.MLSClientProvider +import com.wire.kalium.logic.data.conversation.MLSConversationRepository import com.wire.kalium.logic.data.e2ei.E2EIRepositoryTest.Arrangement.Companion.ACME_CHALLENGE 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.e2ei.E2EIRepositoryTest.Arrangement.Companion.TEST_FAILURE import com.wire.kalium.logic.feature.CurrentClientIdProvider +import com.wire.kalium.logic.framework.TestClient import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.util.shouldFail import com.wire.kalium.logic.util.shouldSucceed @@ -38,6 +42,7 @@ 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 io.mockative.* +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -602,6 +607,67 @@ class E2EIRepositoryTest { .wasInvoked(once) } + @Test + fun givenCertificate_whenCallingRotateKeysAndMigrateConversation_thenItSuccess() = runTest { + // Given + val (arrangement, e2eiRepository) = Arrangement() + .withCurrentClientIdProviderSuccessful() + .withGetE2EIClientSuccessful() + .withRotateKeysAndMigrateConversationsReturns(Either.Right(Unit)) + .arrange() + + // When + val result = e2eiRepository.rotateKeysAndMigrateConversations("") + + // Then + result.shouldSucceed() + + verify(arrangement.e2eiClientProvider) + .suspendFunction(arrangement.e2eiClientProvider::getE2EIClient) + .with(anything()) + .wasInvoked(once) + + verify(arrangement.currentClientIdProvider) + .suspendFunction(arrangement.currentClientIdProvider::invoke) + .wasInvoked(once) + + verify(arrangement.mlsConversationRepository) + .suspendFunction(arrangement.mlsConversationRepository::rotateKeysAndMigrateConversations) + .with(anything(), anything(), anything()) + .wasInvoked(once) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun givenCertificate_whenCallingRotateKeysAndMigrateConversationFails_thenReturnFailure() = runTest { + // Given + val (arrangement, e2eiRepository) = Arrangement() + .withCurrentClientIdProviderSuccessful() + .withGetE2EIClientSuccessful() + .withRotateKeysAndMigrateConversationsReturns(TEST_FAILURE) + .arrange() + + // When + val result = e2eiRepository.rotateKeysAndMigrateConversations("") + + // Then + result.shouldFail() + + verify(arrangement.e2eiClientProvider) + .suspendFunction(arrangement.e2eiClientProvider::getE2EIClient) + .with(anything()) + .wasInvoked(once) + + verify(arrangement.currentClientIdProvider) + .suspendFunction(arrangement.currentClientIdProvider::invoke) + .wasInvoked(once) + + verify(arrangement.mlsConversationRepository) + .suspendFunction(arrangement.mlsConversationRepository::rotateKeysAndMigrateConversations) + .with(anything(), anything(), anything()) + .wasInvoked(once) + } + private class Arrangement { fun withGetE2EIClientSuccessful() = apply { @@ -660,6 +726,21 @@ class E2EIRepositoryTest { .thenReturn(RANDOM_BYTE_ARRAY) } + fun withRotateKeysAndMigrateConversationsReturns(result: Either) = apply { + given(mlsConversationRepository) + .suspendFunction(mlsConversationRepository::rotateKeysAndMigrateConversations) + .whenInvokedWith(anything(), anything(), anything()) + .thenReturn(result) + } + + fun withCurrentClientIdProviderSuccessful() = apply { + given(currentClientIdProvider) + .suspendFunction(currentClientIdProvider::invoke) + .whenInvoked() + .thenReturn(Either.Right(TestClient.CLIENT_ID)) + } + + fun withFinalizeResponseSuccessful() = apply { given(e2eiClient) .suspendFunction(e2eiClient::finalizeResponse) @@ -766,6 +847,9 @@ class E2EIRepositoryTest { @Mock val mlsClientProvider: MLSClientProvider = mock(classOf()) + @Mock + val mlsConversationRepository = mock(classOf()) + @Mock val mlsClient = mock(classOf()) @@ -773,9 +857,17 @@ class E2EIRepositoryTest { val currentClientIdProvider: CurrentClientIdProvider = mock(classOf()) fun arrange() = - this to E2EIRepositoryImpl(e2eiApi, acmeApi, e2eiClientProvider, mlsClientProvider, currentClientIdProvider) + this to E2EIRepositoryImpl( + e2eiApi, + acmeApi, + e2eiClientProvider, + mlsClientProvider, + currentClientIdProvider, + mlsConversationRepository + ) companion object { + val TEST_FAILURE = Either.Left(CoreFailure.Unknown(Throwable("an error"))) val INVALID_REQUEST_ERROR = KaliumException.InvalidRequestError(ErrorResponse(405, "", "")) val RANDOM_BYTE_ARRAY = "random-value".encodeToByteArray() val RANDOM_NONCE = "xxxxx" diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/EnrollE2EICertificateUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/EnrollE2EICertificateUseCaseTest.kt index c7e69aaf8da..47937899054 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/EnrollE2EICertificateUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/EnrollE2EICertificateUseCaseTest.kt @@ -43,13 +43,13 @@ class EnrollE2EICertificateUseCaseTest { fun givenLoadACMEDirectoriesFails_whenInvokeUseCase_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -111,7 +111,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -120,14 +120,14 @@ class EnrollE2EICertificateUseCaseTest { fun givenGetACMENonceFails_whenInvokeUseCase_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -190,7 +190,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -199,15 +199,15 @@ class EnrollE2EICertificateUseCaseTest { fun givenCreateNewAccountFails_whenInvokeUseCase_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -271,7 +271,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -280,16 +280,16 @@ class EnrollE2EICertificateUseCaseTest { fun givenUseCase_whenCreateNewOrderFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewOrderResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -354,7 +354,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -363,17 +363,17 @@ class EnrollE2EICertificateUseCaseTest { fun givenUseCase_whenCreateAuthzFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewOrderResulting(Either.Right(Triple(ACME_ORDER, RANDOM_NONCE, RANDOM_LOCATION))) arrangement.withCreateAuthzResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -439,7 +439,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -448,7 +448,7 @@ class EnrollE2EICertificateUseCaseTest { fun givenUseCase_whenGetWireNonceFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) @@ -456,10 +456,10 @@ class EnrollE2EICertificateUseCaseTest { arrangement.withCreateAuthzResulting(Either.Right(Triple(ACME_AUTHZ, RANDOM_NONCE, RANDOM_LOCATION))) arrangement.withGetWireNonceResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -526,7 +526,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -535,7 +535,7 @@ class EnrollE2EICertificateUseCaseTest { fun givenUseCase_whenGetDPoPTokenFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) @@ -544,10 +544,10 @@ class EnrollE2EICertificateUseCaseTest { arrangement.withGetWireNonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withGetDPoPTokenResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -616,7 +616,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -625,7 +625,7 @@ class EnrollE2EICertificateUseCaseTest { fun givenUseCase_whenGetWireAccessTokenFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) @@ -635,10 +635,10 @@ class EnrollE2EICertificateUseCaseTest { arrangement.withGetDPoPTokenResulting(Either.Right(RANDOM_DPoP_TOKEN)) arrangement.withGetWireAccessTokenResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -707,7 +707,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -716,7 +716,7 @@ class EnrollE2EICertificateUseCaseTest { fun givenUseCase_whenValidateDPoPChallengeFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) @@ -727,10 +727,10 @@ class EnrollE2EICertificateUseCaseTest { arrangement.withGetWireAccessTokenResulting(Either.Right(WIRE_ACCESS_TOKEN)) arrangement.withValidateDPoPChallengeResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -800,7 +800,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -809,7 +809,7 @@ class EnrollE2EICertificateUseCaseTest { fun givenUseCase_whenValidateOIDCChallengeFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) @@ -821,10 +821,10 @@ class EnrollE2EICertificateUseCaseTest { arrangement.withValidateDPoPChallengeResulting(Either.Right(ACME_CHALLENGE_RESPONSE)) arrangement.withValidateOIDCChallengeResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -895,7 +895,7 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() } @@ -904,7 +904,7 @@ class EnrollE2EICertificateUseCaseTest { fun givenUseCase_whenCheckOrderRequestFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) @@ -917,10 +917,10 @@ class EnrollE2EICertificateUseCaseTest { arrangement.withValidateOIDCChallengeResulting(Either.Right(ACME_CHALLENGE_RESPONSE)) arrangement.withCheckOrderRequestResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -992,16 +992,17 @@ class EnrollE2EICertificateUseCaseTest { .with() .wasNotInvoked() verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with() .wasNotInvoked() + } @Test fun givenUseCase_whenFinalizeFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) @@ -1015,10 +1016,10 @@ class EnrollE2EICertificateUseCaseTest { arrangement.withCheckOrderRequestResulting(Either.Right((ACME_RESPONSE to RANDOM_NONCE))) arrangement.withFinalizeResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -1086,17 +1087,107 @@ class EnrollE2EICertificateUseCaseTest { .function(arrangement.e2EIRepository::certificateRequest) .with() .wasNotInvoked() + + } + + @Test + fun givenUseCase_whenRotatingKeysAndMigratingConversationsFailing_thenReturnFailure() = runTest { + val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() + + // given + arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) + arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) + arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) + arrangement.withCreateNewOrderResulting(Either.Right(Triple(ACME_ORDER, RANDOM_NONCE, RANDOM_LOCATION))) + arrangement.withCreateAuthzResulting(Either.Right(Triple(ACME_AUTHZ, RANDOM_NONCE, RANDOM_LOCATION))) + arrangement.withGetWireNonceResulting(Either.Right(RANDOM_NONCE)) + arrangement.withGetDPoPTokenResulting(Either.Right(RANDOM_DPoP_TOKEN)) + arrangement.withGetWireAccessTokenResulting(Either.Right(WIRE_ACCESS_TOKEN)) + arrangement.withValidateDPoPChallengeResulting(Either.Right(ACME_CHALLENGE_RESPONSE)) + arrangement.withValidateOIDCChallengeResulting(Either.Right(ACME_CHALLENGE_RESPONSE)) + arrangement.withCheckOrderRequestResulting(Either.Right((ACME_RESPONSE to RANDOM_NONCE))) + arrangement.withFinalizeResulting(Either.Right((ACME_RESPONSE to RANDOM_NONCE))) + arrangement.withCertificateRequestResulting(Either.Right(ACME_RESPONSE)) + arrangement.withRotateKeysAndMigrateConversations(TEST_EITHER_LEFT) + + // when + val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) + + // then + result.shouldFail() + assertTrue(result is Either.Left) + verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::loadACMEDirectories) .with() - .wasNotInvoked() + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::getACMENonce) + .with(any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::createNewAccount) + .with(any(), any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::createNewOrder) + .with(any(), any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::createAuthz) + .with(any(), any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::getWireNonce) + .with() + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::getDPoPToken) + .with(any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::getWireAccessToken) + .with(any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::validateDPoPChallenge) + .with(any(), any(), any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::validateOIDCChallenge) + .with(any(), any(), any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::checkOrderRequest) + .with(any(), any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::finalize) + .with(any(), any()) + .wasInvoked(exactly = once) + + verify(arrangement.e2EIRepository) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) + .with(any()) + .wasInvoked(exactly = once) } @Test fun givenUseCase_whenCertificateRequestFailing_thenReturnFailure() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) @@ -1109,12 +1200,13 @@ class EnrollE2EICertificateUseCaseTest { arrangement.withValidateOIDCChallengeResulting(Either.Right(ACME_CHALLENGE_RESPONSE)) arrangement.withCheckOrderRequestResulting(Either.Right((ACME_RESPONSE to RANDOM_NONCE))) arrangement.withFinalizeResulting(Either.Right((ACME_RESPONSE to RANDOM_NONCE))) + arrangement.withRotateKeysAndMigrateConversations(Either.Right(Unit)) arrangement.withCertificateRequestResulting(TEST_EITHER_LEFT) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldFail() assertTrue(result is Either.Left) @@ -1183,17 +1275,13 @@ class EnrollE2EICertificateUseCaseTest { .with(any(), any()) .wasInvoked(exactly = once) - verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) - .with() - .wasNotInvoked() } @Test fun givenUseCase_whenEveryStepSucceed_thenShouldSucceed() = runTest { val (arrangement, enrollE2EICertificateUseCase) = Arrangement().arrange() - //given + // given arrangement.withLoadACMEDirectoriesResulting(Either.Right(ACME_DIRECTORIES)) arrangement.withGetACMENonceResulting(Either.Right(RANDOM_NONCE)) arrangement.withCreateNewAccountResulting(Either.Right(RANDOM_NONCE)) @@ -1207,11 +1295,12 @@ class EnrollE2EICertificateUseCaseTest { arrangement.withCheckOrderRequestResulting(Either.Right((ACME_RESPONSE to RANDOM_NONCE))) arrangement.withFinalizeResulting(Either.Right((ACME_RESPONSE to RANDOM_NONCE))) arrangement.withCertificateRequestResulting(Either.Right(ACME_RESPONSE)) + arrangement.withRotateKeysAndMigrateConversations(Either.Right(Unit)) - //when + // when val result = enrollE2EICertificateUseCase.invoke(RANDOM_ID_TOKEN) - //then + // then result.shouldSucceed() assertTrue(result is Either.Right) @@ -1281,9 +1370,9 @@ class EnrollE2EICertificateUseCaseTest { .wasInvoked(exactly = once) verify(arrangement.e2EIRepository) - .function(arrangement.e2EIRepository::initMLSClientWithCertificate) + .function(arrangement.e2EIRepository::rotateKeysAndMigrateConversations) .with(any()) - .wasNotInvoked() + .wasInvoked(exactly = once) } @@ -1376,6 +1465,13 @@ class EnrollE2EICertificateUseCaseTest { .thenReturn(result) } + fun withRotateKeysAndMigrateConversations(result: Either) = apply { + given(e2EIRepository) + .suspendFunction(e2EIRepository::rotateKeysAndMigrateConversations) + .whenInvokedWith(any()) + .thenReturn(result) + } + fun withCertificateRequestResulting(result: Either) = apply { given(e2EIRepository) .suspendFunction(e2EIRepository::certificateRequest)