From ee75ec7fbe85587b368f550dec3bdbbda1ac7f5c Mon Sep 17 00:00:00 2001 From: Vitor Hugo Schwaab Date: Fri, 26 Jan 2024 13:22:18 +0100 Subject: [PATCH] feat: fetch other user's protocols from remote when resolving 1:1 conversations (#2405) --- .../kalium/logic/data/user/UserRepository.kt | 1 - .../kalium/logic/feature/UserSessionScope.kt | 8 +- .../AcceptConnectionRequestUseCase.kt | 3 +- .../GetOrCreateOneToOneConversationUseCase.kt | 5 +- .../NotifyConversationIsOpenUseCase.kt | 5 +- .../conversation/mls/OneOnOneResolver.kt | 100 ++++++++---- ...pdateSelfUserSupportedProtocolsUseCase.kt} | 6 +- ...rtedProtocolsAndResolveOneOnOnesUseCase.kt | 2 +- .../kalium/logic/feature/user/UserScope.kt | 4 +- .../conversation/MLSWelcomeEventHandler.kt | 5 +- .../NewConversationEventHandler.kt | 5 +- .../kalium/logic/sync/slow/SlowSyncWorker.kt | 4 +- .../AcceptConnectionRequestUseCaseTest.kt | 2 +- .../conversation/mls/OneOnOneResolverTest.kt | 144 +++++++++++++++++- .../UpdateSupportedProtocolsUseCaseTest.kt | 2 +- .../NewConversationEventHandlerTest.kt | 6 +- .../logic/sync/slow/SlowSyncWorkerTest.kt | 4 +- .../mls/OneOnOneResolverArrangement.kt | 3 +- 18 files changed, 252 insertions(+), 57 deletions(-) rename logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/{UpdateSupportedProtocolsUseCase.kt => UpdateSelfUserSupportedProtocolsUseCase.kt} (97%) 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 f12e403fdec..9fafd6e60b6 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 @@ -228,7 +228,6 @@ internal class UserDataSource internal constructor( override suspend fun fetchAllOtherUsers(): Either { val ids = userDAO.allOtherUsersId().map(UserIDEntity::toModel).toSet() - return fetchUsersByIds(ids) } 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 bcf4ae7e67a..4ee79a5e0af 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 @@ -309,8 +309,8 @@ import com.wire.kalium.logic.feature.user.SyncSelfUserUseCase import com.wire.kalium.logic.feature.user.SyncSelfUserUseCaseImpl import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsAndResolveOneOnOnesUseCase import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsAndResolveOneOnOnesUseCaseImpl -import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCase -import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCaseImpl +import com.wire.kalium.logic.feature.user.UpdateSelfUserSupportedProtocolsUseCase +import com.wire.kalium.logic.feature.user.UpdateSelfUserSupportedProtocolsUseCaseImpl import com.wire.kalium.logic.feature.user.UserScope import com.wire.kalium.logic.feature.user.e2ei.MarkNotifyForRevokedCertificateAsNotifiedUseCase import com.wire.kalium.logic.feature.user.e2ei.MarkNotifyForRevokedCertificateAsNotifiedUseCaseImpl @@ -967,8 +967,8 @@ class UserSessionScope internal constructor( incrementalSyncRepository ) - private val updateSupportedProtocols: UpdateSupportedProtocolsUseCase - get() = UpdateSupportedProtocolsUseCaseImpl( + private val updateSupportedProtocols: UpdateSelfUserSupportedProtocolsUseCase + get() = UpdateSelfUserSupportedProtocolsUseCaseImpl( clientRepository, userRepository, userConfigRepository, diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/connection/AcceptConnectionRequestUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/connection/AcceptConnectionRequestUseCase.kt index dd2a3ff1e97..99b2ad6a903 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/connection/AcceptConnectionRequestUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/connection/AcceptConnectionRequestUseCase.kt @@ -62,7 +62,8 @@ internal class AcceptConnectionRequestUseCaseImpl( ) }.flatMap { oneOnOneResolver.resolveOneOnOneConversationWithUserId( - connection.qualifiedToId + userId = connection.qualifiedToId, + invalidateCurrentKnownProtocols = true ).map { } }.flatMap { newGroupConversationSystemMessagesCreator.conversationStartedUnverifiedWarning( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/GetOrCreateOneToOneConversationUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/GetOrCreateOneToOneConversationUseCase.kt index 74ef29f010a..a47936c7b88 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/GetOrCreateOneToOneConversationUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/GetOrCreateOneToOneConversationUseCase.kt @@ -69,7 +69,10 @@ internal class GetOrCreateOneToOneConversationUseCaseImpl( private suspend fun resolveOneOnOneConversationWithUser(otherUserId: UserId): Either = (userRepository.getKnownUser(otherUserId).first()?.let { otherUser -> // TODO support lazily establishing mls group for team 1-1 - oneOnOneResolver.resolveOneOnOneConversationWithUser(otherUser).flatMap { + oneOnOneResolver.resolveOneOnOneConversationWithUser( + user = otherUser, + invalidateCurrentKnownProtocols = true + ).flatMap { conversationRepository.getConversationById(it)?.let { Either.Right(it) } ?: Either.Left(StorageFailure.DataNotFound) } } ?: Either.Left(StorageFailure.DataNotFound)) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/NotifyConversationIsOpenUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/NotifyConversationIsOpenUseCase.kt index ad2575e020b..5d55291afed 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/NotifyConversationIsOpenUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/NotifyConversationIsOpenUseCase.kt @@ -61,7 +61,10 @@ internal class NotifyConversationIsOpenUseCaseImpl( kaliumLogger.v( "Reevaluating protocol for 1:1 conversation with ID: ${conversationId.toLogString()}" ) - oneOnOneResolver.resolveOneOnOneConversationWithUser(conversation.otherUser) + oneOnOneResolver.resolveOneOnOneConversationWithUser( + user = conversation.otherUser, + invalidateCurrentKnownProtocols = true + ) } // Delete Ephemeral Messages that has passed the end date diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/mls/OneOnOneResolver.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/mls/OneOnOneResolver.kt index fabb1256bde..0b80a1b80f2 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/mls/OneOnOneResolver.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/mls/OneOnOneResolver.kt @@ -48,8 +48,32 @@ import kotlin.time.Duration interface OneOnOneResolver { suspend fun resolveAllOneOnOneConversations(synchronizeUsers: Boolean = false): Either suspend fun scheduleResolveOneOnOneConversationWithUserId(userId: UserId, delay: Duration = Duration.ZERO): Job - suspend fun resolveOneOnOneConversationWithUserId(userId: UserId): Either - suspend fun resolveOneOnOneConversationWithUser(user: OtherUser): Either + + /** + * Resolves a one-on-one conversation with a user based on their userId. + * + * @param userId The userId of the other user in the conversation. + * @param invalidateCurrentKnownProtocols Flag indicating whether to whether it should attempt refreshing the other user's list of + * supported protocols by fetching from remote. In case of failure, the local result will be used as a fallback. + * @return Either a [CoreFailure] if there is an error or a [ConversationId] if the resolution is successful. + */ + suspend fun resolveOneOnOneConversationWithUserId( + userId: UserId, + invalidateCurrentKnownProtocols: Boolean, + ): Either + + /** + * Resolves a one-on-one conversation with a user. + * + * @param user The other user in the conversation. + * @param invalidateCurrentKnownProtocols Flag indicating whether to whether it should attempt refreshing the other user's list of + * supported protocols by fetching from remote. In case of failure, the local result will be used as a fallback. + * @return Either a [CoreFailure] if there is an error or a [ConversationId] if the resolution is successful. + */ + suspend fun resolveOneOnOneConversationWithUser( + user: OtherUser, + invalidateCurrentKnownProtocols: Boolean, + ): Either } internal class OneOnOneResolverImpl( @@ -62,52 +86,74 @@ internal class OneOnOneResolverImpl( @OptIn(ExperimentalCoroutinesApi::class) private val dispatcher = kaliumDispatcher.default.limitedParallelism(1) + + // TODO: inherit the scope of UserSessionScope so it's cancelled if user logs out, etc. private val resolveActiveOneOnOneScope = CoroutineScope(dispatcher) override suspend fun resolveAllOneOnOneConversations(synchronizeUsers: Boolean): Either = - if (synchronizeUsers) { - userRepository.fetchAllOtherUsers() - } else { - Either.Right(Unit) - }.flatMap { + fetchAllOtherUsersIfNeeded(synchronizeUsers).flatMap { val usersWithOneOnOne = userRepository.getUsersWithOneOnOneConversation() kaliumLogger.i("Resolving one-on-one protocol for ${usersWithOneOnOne.size} user(s)") usersWithOneOnOne.foldToEitherWhileRight(Unit) { item, _ -> - resolveOneOnOneConversationWithUser(item).flatMapLeft { - when (it) { - is CoreFailure.NoKeyPackagesAvailable, - is NetworkFailure.ServerMiscommunication, - is NetworkFailure.FederatedBackendFailure, - is CoreFailure.NoCommonProtocolFound - -> { - kaliumLogger.e("Resolving one-on-one failed $it, skipping") - Either.Right(Unit) - } - - else -> { - kaliumLogger.e("Resolving one-on-one failed $it, retrying") - Either.Left(it) - } - } + resolveOneOnOneConversationWithUser( + user = item, + // Either it fetched all users on the previous step, or it's not needed + invalidateCurrentKnownProtocols = false + ).flatMapLeft { + handleBatchEntryFailure(it) }.map { } } } + private fun handleBatchEntryFailure(it: CoreFailure) = when (it) { + is CoreFailure.NoKeyPackagesAvailable, + is NetworkFailure.ServerMiscommunication, + is NetworkFailure.FederatedBackendFailure, + is CoreFailure.NoCommonProtocolFound + -> { + kaliumLogger.e("Resolving one-on-one failed $it, skipping") + Either.Right(Unit) + } + + else -> { + kaliumLogger.e("Resolving one-on-one failed $it, retrying") + Either.Left(it) + } + } + + private suspend fun fetchAllOtherUsersIfNeeded(synchronizeUsers: Boolean) = if (synchronizeUsers) { + userRepository.fetchAllOtherUsers() + } else { + Either.Right(Unit) + } + override suspend fun scheduleResolveOneOnOneConversationWithUserId(userId: UserId, delay: Duration) = resolveActiveOneOnOneScope.launch { kaliumLogger.d("Schedule resolving active one-on-one") incrementalSyncRepository.incrementalSyncState.first { it is IncrementalSyncStatus.Live } delay(delay) - resolveOneOnOneConversationWithUserId(userId) + resolveOneOnOneConversationWithUserId( + userId = userId, + invalidateCurrentKnownProtocols = true + ) } - override suspend fun resolveOneOnOneConversationWithUserId(userId: UserId): Either = + override suspend fun resolveOneOnOneConversationWithUserId( + userId: UserId, + invalidateCurrentKnownProtocols: Boolean + ): Either = userRepository.getKnownUser(userId).firstOrNull()?.let { - resolveOneOnOneConversationWithUser(it) + resolveOneOnOneConversationWithUser(it, invalidateCurrentKnownProtocols) } ?: Either.Left(StorageFailure.DataNotFound) - override suspend fun resolveOneOnOneConversationWithUser(user: OtherUser): Either { + override suspend fun resolveOneOnOneConversationWithUser( + user: OtherUser, + invalidateCurrentKnownProtocols: Boolean, + ): Either { kaliumLogger.i("Resolving one-on-one protocol for ${user.id.toLogString()}") + if (invalidateCurrentKnownProtocols) { + userRepository.fetchUsersByIds(setOf(user.id)) + } return oneOnOneProtocolSelector.getProtocolForUser(user.id).flatMap { supportedProtocol -> when (supportedProtocol) { SupportedProtocol.PROTEUS -> oneOnOneMigrator.migrateToProteus(user) 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/UpdateSelfUserSupportedProtocolsUseCase.kt similarity index 97% rename from logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsUseCase.kt rename to logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSelfUserSupportedProtocolsUseCase.kt index dc6e63f10c2..874d90d8bff 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/UpdateSelfUserSupportedProtocolsUseCase.kt @@ -39,16 +39,16 @@ import kotlinx.datetime.Instant /** * Updates the supported protocols of the current user. */ -interface UpdateSupportedProtocolsUseCase { +interface UpdateSelfUserSupportedProtocolsUseCase { suspend operator fun invoke(): Either } -internal class UpdateSupportedProtocolsUseCaseImpl( +internal class UpdateSelfUserSupportedProtocolsUseCaseImpl( private val clientsRepository: ClientRepository, private val userRepository: UserRepository, private val userConfigRepository: UserConfigRepository, private val featureSupport: FeatureSupport -) : UpdateSupportedProtocolsUseCase { +) : UpdateSelfUserSupportedProtocolsUseCase { override suspend operator fun invoke(): Either { return if (!featureSupport.isMLSSupported) { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsAndResolveOneOnOnesUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsAndResolveOneOnOnesUseCase.kt index 4de00e662e7..991ea95268a 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsAndResolveOneOnOnesUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsAndResolveOneOnOnesUseCase.kt @@ -37,7 +37,7 @@ interface UpdateSupportedProtocolsAndResolveOneOnOnesUseCase { } internal class UpdateSupportedProtocolsAndResolveOneOnOnesUseCaseImpl( - private val updateSupportedProtocols: UpdateSupportedProtocolsUseCase, + private val updateSupportedProtocols: UpdateSelfUserSupportedProtocolsUseCase, private val oneOnOneResolver: OneOnOneResolver ) : UpdateSupportedProtocolsAndResolveOneOnOnesUseCase { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt index 71f5add0843..cadfd3ce24b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt @@ -96,7 +96,7 @@ class UserScope internal constructor( private val e2EIRepository: E2EIRepository, private val mlsConversationRepository: MLSConversationRepository, private val isSelfATeamMember: IsSelfATeamMemberUseCase, - private val updateSupportedProtocolsUseCase: UpdateSupportedProtocolsUseCase, + private val updateSelfUserSupportedProtocolsUseCase: UpdateSelfUserSupportedProtocolsUseCase, ) { private val validateUserHandleUseCase: ValidateUserHandleUseCase get() = ValidateUserHandleUseCaseImpl() val getSelfUser: GetSelfUserUseCase get() = GetSelfUserUseCaseImpl(userRepository) @@ -179,7 +179,7 @@ class UserScope internal constructor( val deleteAccount: DeleteAccountUseCase get() = DeleteAccountUseCase(accountRepository) - val updateSupportedProtocols: UpdateSupportedProtocolsUseCase get() = updateSupportedProtocolsUseCase + val updateSupportedProtocols: UpdateSelfUserSupportedProtocolsUseCase get() = updateSelfUserSupportedProtocolsUseCase val observeCertificateRevocationForSelfClient: ObserveCertificateRevocationForSelfClientUseCase get() = ObserveCertificateRevocationForSelfClientUseCaseImpl( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/MLSWelcomeEventHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/MLSWelcomeEventHandler.kt index cd5c986c4a0..ca564dcd1da 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/MLSWelcomeEventHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/MLSWelcomeEventHandler.kt @@ -101,7 +101,10 @@ internal class MLSWelcomeEventHandlerImpl( .first() .flatMap { if (it is ConversationDetails.OneOne) { - oneOnOneResolver.resolveOneOnOneConversationWithUser(it.otherUser).map { Unit } + oneOnOneResolver.resolveOneOnOneConversationWithUser( + user = it.otherUser, + invalidateCurrentKnownProtocols = true + ).map { Unit } } else { Either.Right(Unit) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/NewConversationEventHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/NewConversationEventHandler.kt index 36925ffe138..364dff6e2f8 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/NewConversationEventHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/NewConversationEventHandler.kt @@ -76,7 +76,10 @@ internal class NewConversationEventHandlerImpl( private suspend fun resolveConversationIfOneOnOne(selfUserTeamId: TeamId?, event: Event.Conversation.NewConversation) = if (event.conversation.toConversationType(selfUserTeamId) == ConversationEntity.Type.ONE_ON_ONE) { val otherUserId = event.conversation.members.otherMembers.first().id.toModel() - oneOnOneResolver.resolveOneOnOneConversationWithUserId(otherUserId).map { Unit } + oneOnOneResolver.resolveOneOnOneConversationWithUserId( + userId = otherUserId, + invalidateCurrentKnownProtocols = true + ).map { Unit } } else Either.Right(Unit) /** 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 ca306ca084d..d185ec4ae8a 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 @@ -31,7 +31,7 @@ import com.wire.kalium.logic.feature.legalhold.FetchLegalHoldForSelfUserFromRemo import com.wire.kalium.logic.feature.team.SyncSelfTeamUseCase import com.wire.kalium.logic.feature.user.SyncContactsUseCase import com.wire.kalium.logic.feature.user.SyncSelfUserUseCase -import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCase +import com.wire.kalium.logic.feature.user.UpdateSelfUserSupportedProtocolsUseCase import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.foldToEitherWhileRight @@ -62,7 +62,7 @@ internal class SlowSyncWorkerImpl( private val eventRepository: EventRepository, private val syncSelfUser: SyncSelfUserUseCase, private val syncFeatureConfigs: SyncFeatureConfigsUseCase, - private val updateSupportedProtocols: UpdateSupportedProtocolsUseCase, + private val updateSupportedProtocols: UpdateSelfUserSupportedProtocolsUseCase, private val syncConversations: SyncConversationsUseCase, private val syncConnections: SyncConnectionsUseCase, private val syncSelfTeam: SyncSelfTeamUseCase, diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/connection/AcceptConnectionRequestUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/connection/AcceptConnectionRequestUseCaseTest.kt index 33b29228268..2b56e6167ff 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/connection/AcceptConnectionRequestUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/connection/AcceptConnectionRequestUseCaseTest.kt @@ -105,7 +105,7 @@ class AcceptConnectionRequestUseCaseTest { assertEquals(AcceptConnectionRequestUseCaseResult.Success, result) verify(arrangement.oneOnOneResolver) .suspendFunction(arrangement.oneOnOneResolver::resolveOneOnOneConversationWithUserId) - .with(eq(CONNECTION.qualifiedToId)) + .with(eq(CONNECTION.qualifiedToId), eq(true)) .wasInvoked(once) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/mls/OneOnOneResolverTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/mls/OneOnOneResolverTest.kt index 9b48e9db758..75487a96df9 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/mls/OneOnOneResolverTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/mls/OneOnOneResolverTest.kt @@ -32,12 +32,14 @@ import com.wire.kalium.logic.util.arrangement.repository.UserRepositoryArrangeme import com.wire.kalium.logic.util.arrangement.repository.UserRepositoryArrangementImpl import com.wire.kalium.logic.util.shouldFail import com.wire.kalium.logic.util.shouldSucceed +import io.mockative.any import io.mockative.eq import io.mockative.given import io.mockative.matchers.OneOfMatcher import io.mockative.once import io.mockative.twice import io.mockative.verify +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -63,6 +65,141 @@ class OneOnOneResolverTest { .wasInvoked(exactly = twice) } + @Test + fun givenListOneOnOneUsersAndSynchronizeUsers_whenResolveAllOneOnOneConversations_thenShouldFetchAllUserDetailsAtOnce() = runTest { + // given + val oneOnOneUsers = listOf( + TestUser.OTHER.copy(id = TestUser.OTHER_USER_ID), + TestUser.OTHER.copy(id = TestUser.OTHER_USER_ID_2) + ) + val (arrangement, resolver) = arrange { + withFetchAllOtherUsersReturning(Either.Right(Unit)) + withGetUsersWithOneOnOneConversationReturning(oneOnOneUsers) + withGetProtocolForUser(Either.Right(SupportedProtocol.MLS)) + withMigrateToMLSReturns(Either.Right(TestConversation.ID)) + } + + // when + resolver.resolveAllOneOnOneConversations(true).shouldSucceed() + + // then + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchAllOtherUsers) + .wasInvoked(exactly = once) + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchUserInfo) + .with(any()) + .wasNotInvoked() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchUsersByIds) + .with(any()) + .wasNotInvoked() + } + + @Test + fun givenSingleOneOnOneUserIdAndSynchronizeUser_whenResolveAllOneOnOneConversations_thenShouldFetchUserDetailsOnce() = runTest { + // given + val oneOnOneUser = TestUser.OTHER.copy(id = TestUser.OTHER_USER_ID) + val (arrangement, resolver) = arrange { + withFetchUsersByIdReturning(Either.Right(Unit)) + withGetProtocolForUser(Either.Right(SupportedProtocol.MLS)) + withMigrateToMLSReturns(Either.Right(TestConversation.ID)) + } + + // when + resolver.resolveOneOnOneConversationWithUser(oneOnOneUser, true).shouldSucceed() + + // then + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchAllOtherUsers) + .wasNotInvoked() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchUserInfo) + .with(any()) + .wasNotInvoked() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchUsersByIds) + .with(eq(setOf(oneOnOneUser.id))) + .wasInvoked(exactly = once) + } + + @Test + fun givenSingleOneOnOneUserIdAndSynchronizeUserFails_whenResolveAllOneOnOneConversations_thenShouldNotPropagateFailure() = runTest { + // given + val oneOnOneUser = TestUser.OTHER.copy(id = TestUser.OTHER_USER_ID) + val (arrangement, resolver) = arrange { + withFetchUsersByIdReturning(Either.Right(Unit)) + withGetProtocolForUser(Either.Right(SupportedProtocol.MLS)) + withMigrateToMLSReturns(Either.Right(TestConversation.ID)) + } + + // when + resolver.resolveOneOnOneConversationWithUser(oneOnOneUser, true) + // then + .shouldSucceed() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchUsersByIds) + .with(eq(setOf(oneOnOneUser.id))) + .wasInvoked(exactly = once) + } + + @Test + fun givenSingleOneOnOneUserAndSynchronizeUsers_whenResolveAllOneOnOneConversations_thenShouldFetchUserDetailsOnce() = runTest { + // given + val oneOnOneUser = TestUser.OTHER.copy(id = TestUser.OTHER_USER_ID) + val (arrangement, resolver) = arrange { + withGetKnownUserReturning(flowOf(oneOnOneUser)) + withFetchUsersByIdReturning(Either.Right(Unit)) + withGetProtocolForUser(Either.Right(SupportedProtocol.MLS)) + withMigrateToMLSReturns(Either.Right(TestConversation.ID)) + } + + // when + resolver.resolveOneOnOneConversationWithUserId(oneOnOneUser.id, true).shouldSucceed() + + // then + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchAllOtherUsers) + .wasNotInvoked() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchUserInfo) + .with(any()) + .wasNotInvoked() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchUsersByIds) + .with(eq(setOf(oneOnOneUser.id))) + .wasInvoked(exactly = once) + } + + @Test + fun givenSingleOneOnOneUserAndSynchronizeUserFails_whenResolveAllOneOnOneConversations_thenShouldNotPropagateFailure() = runTest { + // given + val oneOnOneUser = TestUser.OTHER.copy(id = TestUser.OTHER_USER_ID) + val (arrangement, resolver) = arrange { + withFetchUsersByIdReturning(Either.Left(CoreFailure.Unknown(null))) + withGetKnownUserReturning(flowOf(oneOnOneUser)) + withGetProtocolForUser(Either.Right(SupportedProtocol.MLS)) + withMigrateToMLSReturns(Either.Right(TestConversation.ID)) + } + + // when + resolver.resolveOneOnOneConversationWithUserId(oneOnOneUser.id, true) + // then + .shouldSucceed() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::fetchUsersByIds) + .with(eq(setOf(oneOnOneUser.id))) + .wasInvoked(exactly = once) + } + @Test fun givenResolvingOneConversationFails_whenResolveAllOneOnOneConversations_thenTheWholeOperationFails() = runTest { // given @@ -91,7 +228,7 @@ class OneOnOneResolverTest { } // when - resolver.resolveOneOnOneConversationWithUser(OTHER_USER).shouldSucceed() + resolver.resolveOneOnOneConversationWithUser(OTHER_USER, false).shouldSucceed() // then verify(arrangement.oneOnOneMigrator) @@ -109,7 +246,7 @@ class OneOnOneResolverTest { } // when - resolver.resolveOneOnOneConversationWithUser(OTHER_USER).shouldSucceed() + resolver.resolveOneOnOneConversationWithUser(OTHER_USER, false).shouldSucceed() // then verify(arrangement.oneOnOneMigrator) @@ -122,8 +259,7 @@ class OneOnOneResolverTest { UserRepositoryArrangement by UserRepositoryArrangementImpl(), OneOnOneProtocolSelectorArrangement by OneOnOneProtocolSelectorArrangementImpl(), OneOnOneMigratorArrangement by OneOnOneMigratorArrangementImpl(), - IncrementalSyncRepositoryArrangement by IncrementalSyncRepositoryArrangementImpl() - { + IncrementalSyncRepositoryArrangement by IncrementalSyncRepositoryArrangementImpl() { fun arrange() = run { block() this@Arrangement to OneOnOneResolverImpl( diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsUseCaseTest.kt index f4dbd12d406..6bbd901e343 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/UpdateSupportedProtocolsUseCaseTest.kt @@ -353,7 +353,7 @@ class UpdateSupportedProtocolsUseCaseTest { .thenReturn(Either.Right(clients)) } - fun arrange() = this to UpdateSupportedProtocolsUseCaseImpl( + fun arrange() = this to UpdateSelfUserSupportedProtocolsUseCaseImpl( clientRepository, userRepository, userConfigRepository, diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/NewConversationEventHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/NewConversationEventHandlerTest.kt index 947df9c1f8c..7eb5a3c0e12 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/NewConversationEventHandlerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/NewConversationEventHandlerTest.kt @@ -264,7 +264,7 @@ class NewConversationEventHandlerTest { // then verify(arrangement.oneOnOneResolver) .suspendFunction(arrangement.oneOnOneResolver::resolveOneOnOneConversationWithUserId) - .with(any()) + .with(any(), eq(true)) .wasNotInvoked() verify(arrangement.oneOnOneResolver) .suspendFunction(arrangement.oneOnOneResolver::resolveOneOnOneConversationWithUser) @@ -305,7 +305,7 @@ class NewConversationEventHandlerTest { // then verify(arrangement.oneOnOneResolver) .suspendFunction(arrangement.oneOnOneResolver::resolveOneOnOneConversationWithUserId) - .with(eq(otherUserId)) + .with(eq(otherUserId), eq(true)) .wasInvoked(exactly = once) } @@ -408,7 +408,7 @@ class NewConversationEventHandlerTest { fun withResolveOneOnOneConversationWithUserId(result: Either) = apply { given(oneOnOneResolver) .suspendFunction(oneOnOneResolver::resolveOneOnOneConversationWithUserId) - .whenInvokedWith(any()) + .whenInvokedWith(any(), eq(true)) .thenReturn(result) } 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 cd0cb8cd7b7..a4949f7e38a 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 @@ -30,7 +30,7 @@ import com.wire.kalium.logic.feature.legalhold.FetchLegalHoldForSelfUserFromRemo import com.wire.kalium.logic.feature.team.SyncSelfTeamUseCase import com.wire.kalium.logic.feature.user.SyncContactsUseCase import com.wire.kalium.logic.feature.user.SyncSelfUserUseCase -import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCase +import com.wire.kalium.logic.feature.user.UpdateSelfUserSupportedProtocolsUseCase import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.sync.KaliumSyncException import com.wire.kalium.logic.sync.slow.migration.steps.SyncMigrationStep @@ -507,7 +507,7 @@ class SlowSyncWorkerTest { val joinMLSConversations: JoinExistingMLSConversationsUseCase = mock(JoinExistingMLSConversationsUseCase::class) @Mock - val updateSupportedProtocols: UpdateSupportedProtocolsUseCase = mock(UpdateSupportedProtocolsUseCase::class) + val updateSupportedProtocols: UpdateSelfUserSupportedProtocolsUseCase = mock(UpdateSelfUserSupportedProtocolsUseCase::class) @Mock val oneOnOneResolver: OneOnOneResolver = mock(OneOnOneResolver::class) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/OneOnOneResolverArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/OneOnOneResolverArrangement.kt index e886d7d35f7..db3df867de3 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/OneOnOneResolverArrangement.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/OneOnOneResolverArrangement.kt @@ -23,6 +23,7 @@ import com.wire.kalium.logic.feature.conversation.mls.OneOnOneResolver import com.wire.kalium.logic.functional.Either import io.mockative.Mock import io.mockative.any +import io.mockative.eq import io.mockative.given import io.mockative.mock import kotlinx.coroutines.Job @@ -52,7 +53,7 @@ class OneOnOneResolverArrangementImpl : OneOnOneResolverArrangement { override fun withResolveOneOnOneConversationWithUserIdReturning(result: Either) { given(oneOnOneResolver) .suspendFunction(oneOnOneResolver::resolveOneOnOneConversationWithUserId) - .whenInvokedWith(any()) + .whenInvokedWith(any(), eq(true)) .thenReturn(result) }