From cad4139e92cdd24b80d4efa78fd4c05ba48a101d Mon Sep 17 00:00:00 2001 From: Jacob Persson <7156+typfel@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:04:14 +0200 Subject: [PATCH] feat: update supported protocols after deleting a client (#2083) --- .../kalium/logic/feature/UserSessionScope.kt | 4 +- .../logic/feature/client/ClientScope.kt | 14 +- .../feature/client/DeleteClientUseCase.kt | 24 +++- .../user/UpdateSupportedProtocolsUseCase.kt | 10 +- .../kalium/logic/sync/slow/SlowSyncWorker.kt | 3 +- .../feature/client/DeleteClientUseCaseTest.kt | 126 +++++++++++++----- .../logic/sync/slow/SlowSyncWorkerTest.kt | 2 +- .../arrangement/UserRepositoryArrangement.kt | 10 ++ 8 files changed, 148 insertions(+), 45 deletions(-) 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 c683392c3eb..80874b02f0d 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 @@ -1388,7 +1388,9 @@ class UserSessionScope internal constructor( userRepository, authenticationScope.secondFactorVerificationRepository, slowSyncRepository, - cachedClientIdClearer + cachedClientIdClearer, + users.updateSupportedProtocols, + oneOnOneResolver ) val conversations: ConversationScope by lazy { ConversationScope( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/ClientScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/ClientScope.kt index f7af6ae4941..ea19fb70f63 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/ClientScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/ClientScope.kt @@ -35,6 +35,7 @@ import com.wire.kalium.logic.data.user.UserRepository import com.wire.kalium.logic.feature.CachedClientIdClearer import com.wire.kalium.logic.feature.CurrentClientIdProvider import com.wire.kalium.logic.feature.ProteusClientProvider +import com.wire.kalium.logic.feature.conversation.mls.OneOnOneResolver import com.wire.kalium.logic.feature.keypackage.MLSKeyPackageCountUseCase import com.wire.kalium.logic.feature.keypackage.MLSKeyPackageCountUseCaseImpl import com.wire.kalium.logic.feature.keypackage.RefillKeyPackagesUseCase @@ -42,6 +43,7 @@ import com.wire.kalium.logic.feature.keypackage.RefillKeyPackagesUseCaseImpl import com.wire.kalium.logic.feature.session.DeregisterTokenUseCase import com.wire.kalium.logic.feature.session.DeregisterTokenUseCaseImpl import com.wire.kalium.logic.feature.session.UpgradeCurrentSessionUseCase +import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCase import com.wire.kalium.logic.sync.slow.RestartSlowSyncProcessForRecoveryUseCase import com.wire.kalium.logic.sync.slow.RestartSlowSyncProcessForRecoveryUseCaseImpl import com.wire.kalium.util.DelicateKaliumApi @@ -66,7 +68,9 @@ class ClientScope @OptIn(DelicateKaliumApi::class) internal constructor( private val userRepository: UserRepository, private val secondFactorVerificationRepository: SecondFactorVerificationRepository, private val slowSyncRepository: SlowSyncRepository, - private val cachedClientIdClearer: CachedClientIdClearer + private val cachedClientIdClearer: CachedClientIdClearer, + private val updateSupportedProtocols: UpdateSupportedProtocolsUseCase, + private val oneOnOneResolver: OneOnOneResolver ) { @OptIn(DelicateKaliumApi::class) val register: RegisterClientUseCase @@ -85,7 +89,13 @@ class ClientScope @OptIn(DelicateKaliumApi::class) internal constructor( val selfClients: FetchSelfClientsFromRemoteUseCase get() = FetchSelfClientsFromRemoteUseCaseImpl(clientRepository, clientIdProvider) val observeClientDetailsUseCase: ObserveClientDetailsUseCase get() = ObserveClientDetailsUseCaseImpl(clientRepository, clientIdProvider) - val deleteClient: DeleteClientUseCase get() = DeleteClientUseCaseImpl(clientRepository) + val deleteClient: DeleteClientUseCase + get() = DeleteClientUseCaseImpl( + clientRepository, + updateSupportedProtocols, + userRepository, + oneOnOneResolver + ) val needsToRegisterClient: NeedsToRegisterClientUseCase get() = NeedsToRegisterClientUseCaseImpl(clientIdProvider, sessionRepository, proteusClientProvider, selfUserId) val deregisterNativePushToken: DeregisterTokenUseCase diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/DeleteClientUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/DeleteClientUseCase.kt index 53408f5fc4a..9379b5bce1e 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/DeleteClientUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/DeleteClientUseCase.kt @@ -22,7 +22,12 @@ import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.NetworkFailure import com.wire.kalium.logic.data.client.ClientRepository import com.wire.kalium.logic.data.client.DeleteClientParam +import com.wire.kalium.logic.data.user.UserRepository +import com.wire.kalium.logic.feature.conversation.mls.OneOnOneResolver +import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCase +import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.fold +import com.wire.kalium.logic.functional.onSuccess import com.wire.kalium.network.exceptions.KaliumException import com.wire.kalium.network.exceptions.isBadRequest import com.wire.kalium.network.exceptions.isInvalidCredentials @@ -36,9 +41,24 @@ interface DeleteClientUseCase { suspend operator fun invoke(param: DeleteClientParam): DeleteClientResult } -class DeleteClientUseCaseImpl(private val clientRepository: ClientRepository) : DeleteClientUseCase { +internal class DeleteClientUseCaseImpl( + private val clientRepository: ClientRepository, + private val updateSupportedProtocols: UpdateSupportedProtocolsUseCase, + private val userRepository: UserRepository, + private val oneOnOneResolver: OneOnOneResolver +) : DeleteClientUseCase { override suspend operator fun invoke(param: DeleteClientParam): DeleteClientResult = - clientRepository.deleteClient(param).fold( + clientRepository.deleteClient(param) + .onSuccess { + updateSupportedProtocols().onSuccess { updated -> + if (updated) { + userRepository.fetchAllOtherUsers().flatMap { + oneOnOneResolver.resolveAllOneOnOneConversations() + } + } + } + } + .fold( { handleError(it) }, { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsUseCase.kt index 228343bc652..00f86da5cc3 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsUseCase.kt @@ -41,7 +41,7 @@ import kotlinx.datetime.Instant * Updates the supported protocols of the current user. */ interface UpdateSupportedProtocolsUseCase { - suspend operator fun invoke(): Either + suspend operator fun invoke(): Either } internal class UpdateSupportedProtocolsUseCaseImpl( @@ -50,7 +50,7 @@ internal class UpdateSupportedProtocolsUseCaseImpl( private val featureConfigRepository: FeatureConfigRepository ) : UpdateSupportedProtocolsUseCase { - override suspend operator fun invoke(): Either { + override suspend operator fun invoke(): Either { kaliumLogger.d("Updating supported protocols") return (userRepository.getSelfUser()?.let { selfUser -> @@ -59,16 +59,16 @@ internal class UpdateSupportedProtocolsUseCaseImpl( "Updating supported protocols = $newSupportedProtocols previously = ${selfUser.supportedProtocols}" ) if (newSupportedProtocols != selfUser.supportedProtocols) { - userRepository.updateSupportedProtocols(newSupportedProtocols) + userRepository.updateSupportedProtocols(newSupportedProtocols).map { true } } else { - Either.Right(Unit) + Either.Right(false) } }.flatMapLeft { if (it is NetworkFailure.FeatureNotSupported) { kaliumLogger.w( "Skip updating supported protocols since it's not supported by the backend API" ) - Either.Right(Unit) + Either.Right(false) } else { Either.Left(it) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorker.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorker.kt index 298e0d90cf9..279f3a2a56f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorker.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorker.kt @@ -34,6 +34,7 @@ import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCase import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.isRight +import com.wire.kalium.logic.functional.map import com.wire.kalium.logic.functional.nullableFold import com.wire.kalium.logic.functional.onFailure import com.wire.kalium.logic.kaliumLogger @@ -82,7 +83,7 @@ internal class SlowSyncWorkerImpl( performStep(SlowSyncStep.SELF_USER, syncSelfUser::invoke) .continueWithStep(SlowSyncStep.FEATURE_FLAGS, syncFeatureConfigs::invoke) - .continueWithStep(SlowSyncStep.UPDATE_SUPPORTED_PROTOCOLS, updateSupportedProtocols::invoke) + .continueWithStep(SlowSyncStep.UPDATE_SUPPORTED_PROTOCOLS) { updateSupportedProtocols.invoke().map { } } .continueWithStep(SlowSyncStep.CONVERSATIONS, syncConversations::invoke) .continueWithStep(SlowSyncStep.CONNECTIONS, syncConnections::invoke) .continueWithStep(SlowSyncStep.SELF_TEAM, syncSelfTeam::invoke) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/DeleteClientUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/DeleteClientUseCaseTest.kt index 85ff85e434d..4a96452d408 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/DeleteClientUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/DeleteClientUseCaseTest.kt @@ -18,12 +18,18 @@ package com.wire.kalium.logic.feature.client +import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.NetworkFailure import com.wire.kalium.logic.data.client.ClientRepository import com.wire.kalium.logic.data.client.DeleteClientParam +import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCase import com.wire.kalium.logic.framework.TestClient import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.test_util.TestNetworkException +import com.wire.kalium.logic.util.arrangement.UserRepositoryArrangement +import com.wire.kalium.logic.util.arrangement.UserRepositoryArrangementImpl +import com.wire.kalium.logic.util.arrangement.mls.OneOnOneResolverArrangement +import com.wire.kalium.logic.util.arrangement.mls.OneOnOneResolverArrangementImpl import com.wire.kalium.network.exceptions.KaliumException import io.ktor.utils.io.errors.IOException import io.mockative.Mock @@ -35,35 +41,24 @@ import io.mockative.mock import io.mockative.once import io.mockative.verify import kotlinx.coroutines.test.runTest -import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertIs import kotlin.test.assertSame class DeleteClientUseCaseTest { - @Mock - private val clientRepository = mock(classOf()) - - private lateinit var deleteClient: DeleteClientUseCase - - @BeforeTest - fun setup() { - deleteClient = DeleteClientUseCaseImpl(clientRepository) - } - @Test fun givenDeleteClientParams_whenDeleting_thenTheRepositoryShouldBeCalledWithCorrectParameters() = runTest { val params = DELETE_CLIENT_PARAMETERS - given(clientRepository) - .suspendFunction(clientRepository::deleteClient) - .whenInvokedWith(anything()) - .then { Either.Left(TEST_FAILURE) } + + val (arrangement, deleteClient) = arrange { + withDeleteClient(Either.Left(TEST_FAILURE)) + } deleteClient(params) - verify(clientRepository) - .suspendFunction(clientRepository::deleteClient) + verify(arrangement.clientRepository) + .suspendFunction(arrangement.clientRepository::deleteClient) .with(eq(params)) .wasInvoked(once) } @@ -71,10 +66,9 @@ class DeleteClientUseCaseTest { @Test fun givenRepositoryDeleteClientFailsDueToGenericError_whenDeleting_thenGenericErrorShouldBeReturned() = runTest { val genericFailure = TEST_FAILURE - given(clientRepository) - .suspendFunction(clientRepository::deleteClient) - .whenInvokedWith(anything()) - .then { Either.Left(genericFailure) } + val (_, deleteClient) = arrange { + withDeleteClient(Either.Left(genericFailure)) + } val result = deleteClient(DELETE_CLIENT_PARAMETERS) @@ -85,10 +79,9 @@ class DeleteClientUseCaseTest { @Test fun givenRepositoryDeleteClientFailsDueToWrongPassword_whenDeleting_thenInvalidCredentialsErrorShouldBeReturned() = runTest { val wrongPasswordFailure = NetworkFailure.ServerMiscommunication(TestNetworkException.invalidCredentials) - given(clientRepository) - .suspendFunction(clientRepository::deleteClient) - .whenInvokedWith(anything()) - .then { Either.Left(wrongPasswordFailure) } + val (_, deleteClient) = arrange { + withDeleteClient(Either.Left(wrongPasswordFailure)) + } val result = deleteClient(DELETE_CLIENT_PARAMETERS) @@ -98,10 +91,9 @@ class DeleteClientUseCaseTest { @Test fun givenRepositoryDeleteClientFailsDueToMissingPassword_whenDeleting_thenPasswordAuthRequiredErrorShouldBeReturned() = runTest { val missingPasswordFailure = NetworkFailure.ServerMiscommunication(TestNetworkException.missingAuth) - given(clientRepository) - .suspendFunction(clientRepository::deleteClient) - .whenInvokedWith(anything()) - .then { Either.Left(missingPasswordFailure) } + val (_, deleteClient) = arrange { + withDeleteClient(Either.Left(missingPasswordFailure)) + } val result = deleteClient(DELETE_CLIENT_PARAMETERS) @@ -111,17 +103,85 @@ class DeleteClientUseCaseTest { @Test fun givenRepositoryDeleteClientFailsDueToBadRequest_whenDeleting_thenInvalidCredentialsErrorShouldBeReturned() = runTest { val badRequest = NetworkFailure.ServerMiscommunication(TestNetworkException.badRequest) - given(clientRepository) - .suspendFunction(clientRepository::deleteClient) - .whenInvokedWith(anything()) - .then { Either.Left(badRequest) } + val (_, deleteClient) = arrange { + withDeleteClient(Either.Left(badRequest)) + } val result = deleteClient(DELETE_CLIENT_PARAMETERS) assertIs(result) } + @Test + fun givenRepositoryDeleteClientSucceeds_whenDeleting_thenUpdateSupportedProtocols() = runTest { + val (arrangement, deleteClient) = arrange { + withDeleteClient(Either.Right(Unit)) + withUpdateSupportedProtocols(Either.Right(false)) + } + + val result = deleteClient(DELETE_CLIENT_PARAMETERS) + + assertIs(result) + verify(arrangement.updateSupportedProtocols) + .suspendFunction(arrangement.updateSupportedProtocols::invoke) + .wasInvoked(exactly = once) + } + + @Test + fun givenSupportedProtocolsAreUpdated_whenDeleting_thenResolveActiveOneOnOneConversations() = runTest { + val (arrangement, deleteClient) = arrange { + withDeleteClient(Either.Right(Unit)) + withUpdateSupportedProtocols(Either.Right(true)) + withFetchAllOtherUsersReturning(Either.Right(Unit)) + withResolveAllOneOnOneConversationsReturning(Either.Right(Unit)) + } + + val result = deleteClient(DELETE_CLIENT_PARAMETERS) + + assertIs(result) + verify(arrangement.updateSupportedProtocols) + .suspendFunction(arrangement.updateSupportedProtocols::invoke) + .wasInvoked(exactly = once) + } + + private class Arrangement(private val block: Arrangement.() -> Unit) : + UserRepositoryArrangement by UserRepositoryArrangementImpl(), + OneOnOneResolverArrangement by OneOnOneResolverArrangementImpl() + { + @Mock + val clientRepository = mock(classOf()) + + @Mock + val updateSupportedProtocols = mock(classOf()) + + fun withDeleteClient(result: Either) { + given(clientRepository) + .suspendFunction(clientRepository::deleteClient) + .whenInvokedWith(anything()) + .then { result } + } + + fun withUpdateSupportedProtocols(result: Either) { + given(updateSupportedProtocols) + .suspendFunction(updateSupportedProtocols::invoke) + .whenInvoked() + .thenReturn(result) + } + + fun arrange() = run { + block() + this@Arrangement to DeleteClientUseCaseImpl( + clientRepository = clientRepository, + updateSupportedProtocols = updateSupportedProtocols, + userRepository = userRepository, + oneOnOneResolver = oneOnOneResolver, + ) + } + } + private companion object { + fun arrange(configuration: Arrangement.() -> Unit) = Arrangement(configuration).arrange() + val CLIENT = TestClient.CLIENT val DELETE_CLIENT_PARAMETERS = DeleteClientParam("pass", CLIENT.id) val TEST_FAILURE = NetworkFailure.ServerMiscommunication(KaliumException.GenericError(IOException("no internet"))) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorkerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorkerTest.kt index 5f20712e57a..c3f96d13558 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorkerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorkerTest.kt @@ -594,7 +594,7 @@ class SlowSyncWorkerTest { given(updateSupportedProtocols) .suspendFunction(updateSupportedProtocols::invoke) .whenInvoked() - .thenReturn(success) + .thenReturn(Either.Right(true)) } fun withUpdateSupportedProtocolsFailure() = apply { diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/UserRepositoryArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/UserRepositoryArrangement.kt index f317e5dc87f..0984075a4cc 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/UserRepositoryArrangement.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/UserRepositoryArrangement.kt @@ -46,6 +46,8 @@ internal interface UserRepositoryArrangement { fun withGetKnownUserReturning(result: Flow) fun withGetUsersWithOneOnOneConversationReturning(result: List) + + fun withFetchAllOtherUsersReturning(result: Either) } internal class UserRepositoryArrangementImpl: UserRepositoryArrangement { @@ -102,4 +104,12 @@ internal class UserRepositoryArrangementImpl: UserRepositoryArrangement { .whenInvoked() .thenReturn(result) } + + override fun withFetchAllOtherUsersReturning(result: Either) { + given(userRepository) + .suspendFunction(userRepository::fetchAllOtherUsers) + .whenInvoked() + .thenReturn(result) + } + }