diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepository.kt index a1f16094bad..3ba8be244dd 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepository.kt @@ -156,6 +156,12 @@ internal class ConversationGroupRepositoryImpl( ) } } + }.flatMap { + wrapStorageRequest { + newGroupConversationSystemMessagesCreator.value.conversationStartedUnverifiedWarning( + conversationEntity.id.toModel() + ) + } }.flatMap { wrapStorageRequest { conversationDAO.getConversationByQualifiedID(conversationEntity.id)?.let { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/NewGroupConversationSystemMessagesCreator.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/NewGroupConversationSystemMessagesCreator.kt index d20972517d0..9c4bade1074 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/NewGroupConversationSystemMessagesCreator.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/NewGroupConversationSystemMessagesCreator.kt @@ -54,6 +54,8 @@ internal interface NewGroupConversationSystemMessagesCreator { conversationId: ConversationId, userIdList: Set ): Either + + suspend fun conversationStartedUnverifiedWarning(conversationId: ConversationId): Either } internal class NewGroupConversationSystemMessagesCreatorImpl( @@ -196,4 +198,18 @@ internal class NewGroupConversationSystemMessagesCreatorImpl( } } + + override suspend fun conversationStartedUnverifiedWarning(conversationId: ConversationId): Either = + persistMessage( + Message.System( + uuid4().toString(), + MessageContent.ConversationStartedUnverifiedWarning, + conversationId, + DateTimeUtil.currentIsoDateTimeString(), + selfUserId, + Message.Status.Sent, + Message.Visibility.VISIBLE, + expirationData = null + ) + ) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt index 4b89a83188a..ba31731992f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt @@ -348,6 +348,10 @@ sealed interface Message { is MessageContent.ConversationProtocolChanged -> mutableMapOf( typeKey to "conversationProtocolChanged" ) + + is MessageContent.ConversationStartedUnverifiedWarning -> mutableMapOf( + typeKey to "conversationStartedUnverifiedWarning" + ) } val standardProperties = mapOf( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt index d23d616c4ca..f30763d488d 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt @@ -298,6 +298,7 @@ sealed class MessageContent { data object HistoryLost : System() data object ConversationCreated : System() + data object ConversationStartedUnverifiedWarning : System() data object ConversationDegradedMLS : System() data object ConversationVerifiedMLS : System() data object ConversationDegradedProteus : System() @@ -357,6 +358,7 @@ fun MessageContent?.getType() = when (this) { is MessageContent.Unknown -> "Unknown" MessageContent.ConversationVerifiedMLS -> "ConversationVerification.Verified.MLS" MessageContent.ConversationVerifiedProteus -> "ConversationVerification.Verified.Proteus" + is MessageContent.ConversationStartedUnverifiedWarning -> "ConversationStartedUnverifiedWarning" null -> "null" } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt index 24b4fdad4a0..011538662a8 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt @@ -264,6 +264,7 @@ class MessageMapperImpl( MessageEntity.ContentType.CONVERSATION_VERIFIED_MLS -> null MessageEntity.ContentType.CONVERSATION_VERIFIED_PREOTEUS -> null MessageEntity.ContentType.CONVERSATION_PROTOCOL_CHANGED -> null + MessageEntity.ContentType.CONVERSATION_STARTED_UNVERIFIED_WARNING -> null } } @@ -378,6 +379,7 @@ class MessageMapperImpl( MessageEntity.FederationType.CONNECTION_REMOVED -> MessageContent.FederationStopped.ConnectionRemoved(domainList) } is MessageEntityContent.ConversationProtocolChanged -> MessageContent.ConversationProtocolChanged(protocol.toModel()) + is MessageEntityContent.ConversationStartedUnverifiedWarning -> MessageContent.ConversationStartedUnverifiedWarning } } @@ -592,4 +594,5 @@ fun MessageContent.System.toMessageEntityContent(): MessageEntityContent.System MessageContent.ConversationVerifiedProteus -> MessageEntityContent.ConversationVerifiedProteus is MessageContent.ConversationProtocolChanged -> MessageEntityContent.ConversationProtocolChanged(protocol.toDao()) MessageContent.HistoryLostProtocolChanged -> MessageEntityContent.HistoryLostProtocolChanged + is MessageContent.ConversationStartedUnverifiedWarning -> MessageEntityContent.ConversationStartedUnverifiedWarning } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt index 823f05358e0..c54c562beaa 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt @@ -109,5 +109,6 @@ internal class PersistMessageUseCaseImpl( is MessageContent.FederationStopped.ConnectionRemoved -> false is MessageContent.FederationStopped.Removed -> false is MessageContent.ConversationProtocolChanged -> false + is MessageContent.ConversationStartedUnverifiedWarning -> false } } 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 e004937d32b..001045b875f 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 @@ -1296,7 +1296,8 @@ class UserSessionScope internal constructor( logout, oneOnOneResolver, userId, - clientIdProvider + clientIdProvider, + lazy { conversations.newGroupConversationSystemMessagesCreator } ) private val userPropertiesEventReceiver: UserPropertiesEventReceiver @@ -1647,7 +1648,8 @@ class UserSessionScope internal constructor( connectionRepository, conversationRepository, userRepository, - oneOnOneResolver + oneOnOneResolver, + conversations.newGroupConversationSystemMessagesCreator ) val observeSecurityClassificationLabel: ObserveSecurityClassificationLabelUseCase 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 7b332ac47f3..5a5477e6bae 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 @@ -21,6 +21,7 @@ package com.wire.kalium.logic.feature.connection import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.data.connection.ConnectionRepository import com.wire.kalium.logic.data.conversation.ConversationRepository +import com.wire.kalium.logic.data.conversation.NewGroupConversationSystemMessagesCreator import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.conversation.mls.OneOnOneResolver @@ -33,7 +34,7 @@ import com.wire.kalium.util.DateTimeUtil /** * Use Case that allows a user accept a connection request to connect with another User */ -fun interface AcceptConnectionRequestUseCase { +interface AcceptConnectionRequestUseCase { /** * Use case [AcceptConnectionRequestUseCase] operation * @@ -46,7 +47,8 @@ fun interface AcceptConnectionRequestUseCase { internal class AcceptConnectionRequestUseCaseImpl( private val connectionRepository: ConnectionRepository, private val conversationRepository: ConversationRepository, - private val oneOnOneResolver: OneOnOneResolver + private val oneOnOneResolver: OneOnOneResolver, + private val newGroupConversationSystemMessagesCreator: NewGroupConversationSystemMessagesCreator ) : AcceptConnectionRequestUseCase { override suspend fun invoke(userId: UserId): AcceptConnectionRequestUseCaseResult { @@ -62,6 +64,10 @@ internal class AcceptConnectionRequestUseCaseImpl( oneOnOneResolver.resolveOneOnOneConversationWithUserId( connection.qualifiedToId ).map { } + }.flatMap { + newGroupConversationSystemMessagesCreator.conversationStartedUnverifiedWarning( + connection.qualifiedConversationId + ) } } .fold({ diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/connection/ConnectionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/connection/ConnectionScope.kt index 70b1908beed..d1db9c9fa7f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/connection/ConnectionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/connection/ConnectionScope.kt @@ -20,6 +20,7 @@ package com.wire.kalium.logic.feature.connection import com.wire.kalium.logic.data.connection.ConnectionRepository import com.wire.kalium.logic.data.conversation.ConversationRepository +import com.wire.kalium.logic.data.conversation.NewGroupConversationSystemMessagesCreator import com.wire.kalium.logic.data.user.UserRepository import com.wire.kalium.logic.feature.conversation.mls.OneOnOneResolver @@ -27,7 +28,8 @@ class ConnectionScope internal constructor( private val connectionRepository: ConnectionRepository, private val conversationRepository: ConversationRepository, private val userRepository: UserRepository, - private val oneOnOneResolver: OneOnOneResolver + private val oneOnOneResolver: OneOnOneResolver, + private val newGroupConversationSystemMessagesCreator: NewGroupConversationSystemMessagesCreator ) { val sendConnectionRequest: SendConnectionRequestUseCase get() = SendConnectionRequestUseCaseImpl(connectionRepository, userRepository) @@ -35,7 +37,8 @@ class ConnectionScope internal constructor( get() = AcceptConnectionRequestUseCaseImpl( connectionRepository, conversationRepository, - oneOnOneResolver + oneOnOneResolver, + newGroupConversationSystemMessagesCreator ) val cancelConnectionRequest: CancelConnectionRequestUseCase get() = CancelConnectionRequestUseCaseImpl(connectionRepository) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCase.kt index 12645b19a13..4cbfae78d84 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCase.kt @@ -21,9 +21,11 @@ package com.wire.kalium.logic.feature.conversation import com.wire.kalium.logic.data.conversation.ConversationRepository import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.fold import com.wire.kalium.logic.functional.onFailure +import com.wire.kalium.logic.functional.onSuccess import com.wire.kalium.logic.kaliumLogger import com.wire.kalium.util.DateTimeUtil @@ -33,12 +35,14 @@ interface UpdateConversationArchivedStatusUseCase { * * @param conversationId the id of the conversation where status wants to be changed * @param shouldArchiveConversation new archived status to be updated on the given conversation + * @param onlyLocally controls if archived status should only be updated locally * @param archivedStatusTimestamp the timestamp when the archiving event occurred * @return an [ConversationUpdateStatusResult] containing Success or Failure cases */ suspend operator fun invoke( conversationId: ConversationId, shouldArchiveConversation: Boolean, + onlyLocally: Boolean, archivedStatusTimestamp: Long = DateTimeUtil.currentInstant().toEpochMilliseconds() ): ArchiveStatusUpdateResult } @@ -50,43 +54,92 @@ internal class UpdateConversationArchivedStatusUseCaseImpl( override suspend operator fun invoke( conversationId: ConversationId, shouldArchiveConversation: Boolean, + onlyLocally: Boolean, archivedStatusTimestamp: Long ): ArchiveStatusUpdateResult = - conversationRepository.updateArchivedStatusRemotely(conversationId, shouldArchiveConversation, archivedStatusTimestamp).onFailure { - kaliumLogger.e("Something went wrong when updating remotely convId (${conversationId.toLogString()}) archiving " + - "status to archived = ($shouldArchiveConversation)") - }.flatMap { - conversationRepository.updateArchivedStatusLocally(conversationId, shouldArchiveConversation, archivedStatusTimestamp) - }.fold({ - kaliumLogger.e("Something went wrong when updating locally convId (${conversationId.toLogString()}) archiving " + - "status to archived = ($shouldArchiveConversation)") - ArchiveStatusUpdateResult.Failure - }, { - kaliumLogger.d("Successfully updated remotely and locally convId (${conversationId.toLogString()}) archiving " + - "status to archived = ($shouldArchiveConversation)") + if (!onlyLocally) { + archiveRemotely(conversationId, shouldArchiveConversation, archivedStatusTimestamp) + } else { + Either.Right(Unit) + } + .flatMap { + conversationRepository.updateArchivedStatusLocally(conversationId, shouldArchiveConversation, archivedStatusTimestamp) + }.fold({ + kaliumLogger.e( + "Something went wrong when updating locally convId (${conversationId.toLogString()}) archiving " + + "status to archived = ($shouldArchiveConversation)" + ) + ArchiveStatusUpdateResult.Failure + }, { + kaliumLogger.d( + "Successfully updated locally convId (${conversationId.toLogString()}) archiving " + + "status to archived = ($shouldArchiveConversation)" + ) - // Now we should make sure the conversation gets muted if it's archived or un-muted if it's unarchived - val updatedMutedStatus = if (shouldArchiveConversation) { - MutedConversationStatus.AllMuted - } else { - MutedConversationStatus.AllAllowed - } - conversationRepository.updateMutedStatusRemotely(conversationId, updatedMutedStatus, archivedStatusTimestamp) - .flatMap { - conversationRepository.updateMutedStatusLocally(conversationId, updatedMutedStatus, archivedStatusTimestamp) - }.fold({ - kaliumLogger.e( - "Something went wrong when updating the muting status of the convId: " + - "(${conversationId.toLogString()}) to (${updatedMutedStatus.status}" - ) - }, { - kaliumLogger.d( - "Successfully updated remotely and locally the muting status of the convId: " + - "(${conversationId.toLogString()}) to (${updatedMutedStatus.status}") - }) - // Even if the muting status update fails, we should still return success as the archived status update was successful - ArchiveStatusUpdateResult.Success - }) + // Now we should make sure the conversation gets muted if it's archived or un-muted if it's unarchived + val updatedMutedStatus = if (shouldArchiveConversation) { + MutedConversationStatus.AllMuted + } else { + MutedConversationStatus.AllAllowed + } + + if (!onlyLocally) { + updateMutedStatusRemotely(conversationId, updatedMutedStatus, archivedStatusTimestamp) + } else { + Either.Right(Unit) + } + .flatMap { + conversationRepository.updateMutedStatusLocally(conversationId, updatedMutedStatus, archivedStatusTimestamp) + }.fold({ + kaliumLogger.e( + "Something went wrong when updating locally the muting status of the convId: " + + "(${conversationId.toLogString()}) to (${updatedMutedStatus.status}" + ) + }, { + kaliumLogger.d( + "Successfully updated locally the muting status of the convId: " + + "(${conversationId.toLogString()}) to (${updatedMutedStatus.status}" + ) + }) + // Even if the muting status update fails, we should still return success as the archived status update was successful + ArchiveStatusUpdateResult.Success + }) + + private suspend fun archiveRemotely( + conversationId: ConversationId, + shouldArchiveConversation: Boolean, + archivedStatusTimestamp: Long + ) = conversationRepository.updateArchivedStatusRemotely(conversationId, shouldArchiveConversation, archivedStatusTimestamp) + .onFailure { + kaliumLogger.e( + "Something went wrong when updating remotely convId (${conversationId.toLogString()}) archiving " + + "status to archived = ($shouldArchiveConversation)" + ) + } + .onSuccess { + kaliumLogger.d( + "Successfully updated remotely convId (${conversationId.toLogString()}) archiving " + + "status to archived = ($shouldArchiveConversation)" + ) + } + + private suspend fun updateMutedStatusRemotely( + conversationId: ConversationId, + updatedMutedStatus: MutedConversationStatus, + archivedStatusTimestamp: Long + ) = conversationRepository.updateMutedStatusRemotely(conversationId, updatedMutedStatus, archivedStatusTimestamp) + .onFailure { + kaliumLogger.e( + "Something went wrong when updating remotely the muting status of the convId: " + + "(${conversationId.toLogString()}) to (${updatedMutedStatus.status}" + ) + } + .onSuccess { + kaliumLogger.d( + "Successfully updated remotely the muting status of the convId: " + + "(${conversationId.toLogString()}) to (${updatedMutedStatus.status}" + ) + } } sealed class ArchiveStatusUpdateResult { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/network/SessionManagerImpl.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/network/SessionManagerImpl.kt index 63695c34e60..89c6f01ccc4 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/network/SessionManagerImpl.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/network/SessionManagerImpl.kt @@ -64,23 +64,23 @@ class SessionManagerImpl internal constructor( private val serverConfigMapper: ServerConfigMapper = MapperProvider.serverConfigMapper() ) : SessionManager { - private var session: SessionDTO? = null private var serverConfig: ServerConfigDTO? = null - override suspend fun session(): SessionDTO? = withContext(coroutineContext) { - session ?: run { - wrapStorageRequest { tokenStorage.getToken(userId.toDao()) } - .map { sessionMapper.fromEntityToSessionDTO(it) } - .onSuccess { session = it } - .onFailure { - kaliumLogger.e( + override suspend fun session(): SessionDTO = withContext(coroutineContext) { + wrapStorageRequest { tokenStorage.getToken(userId.toDao()) } + .map { sessionMapper.fromEntityToSessionDTO(it) } + .fold( + { + error( """SESSION MANAGER: |"error": "missing user session", |"cause": "$it" """.trimMargin() ) + }, { session -> + kaliumLogger.i("_TOKEN_ FOUND SESSION = $session") + session } - session - } + ) } override fun serverConfig(): ServerConfigDTO = serverConfig ?: run { @@ -118,9 +118,7 @@ class SessionManagerImpl internal constructor( throw FailureToRefreshTokenException(message) }, { it - }).also { - session = it - } + }) } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/UserEventReceiver.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/UserEventReceiver.kt index ae2b70d341b..e9d6dfa7f20 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/UserEventReceiver.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/UserEventReceiver.kt @@ -23,6 +23,7 @@ import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.client.ClientRepository import com.wire.kalium.logic.data.connection.ConnectionRepository import com.wire.kalium.logic.data.conversation.ConversationRepository +import com.wire.kalium.logic.data.conversation.NewGroupConversationSystemMessagesCreator import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.event.EventLoggingStatus import com.wire.kalium.logic.data.event.logEventProcessing @@ -34,8 +35,8 @@ import com.wire.kalium.logic.feature.CurrentClientIdProvider import com.wire.kalium.logic.feature.auth.LogoutUseCase import com.wire.kalium.logic.feature.conversation.mls.OneOnOneResolver import com.wire.kalium.logic.functional.Either -import com.wire.kalium.logic.functional.flatMapLeft import com.wire.kalium.logic.functional.flatMap +import com.wire.kalium.logic.functional.flatMapLeft import com.wire.kalium.logic.functional.map import com.wire.kalium.logic.functional.onFailure import com.wire.kalium.logic.functional.onSuccess @@ -54,7 +55,8 @@ internal class UserEventReceiverImpl internal constructor( private val logout: LogoutUseCase, private val oneOnOneResolver: OneOnOneResolver, private val selfUserId: UserId, - private val currentClientIdProvider: CurrentClientIdProvider + private val currentClientIdProvider: CurrentClientIdProvider, + private val newGroupConversationSystemMessagesCreator: Lazy ) : UserEventReceiver { override suspend fun onEvent(event: Event.User): Either { @@ -108,6 +110,10 @@ internal class UserEventReceiverImpl internal constructor( delay = if (event.live) 3.seconds else ZERO ) Either.Right(Unit) + }.flatMap { + newGroupConversationSystemMessagesCreator.value.conversationStartedUnverifiedWarning( + event.connection.qualifiedConversationId + ) } } .onSuccess { 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 367b7916cec..8dc1fbf0f14 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 @@ -81,6 +81,7 @@ internal class NewConversationEventHandlerImpl( event.conversation ) newGroupConversationSystemMessagesCreator.conversationReadReceiptStatus(event.conversation) + newGroupConversationSystemMessagesCreator.conversationStartedUnverifiedWarning(event.conversation.id.toModel()) } } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepositoryTest.kt index 17c9a081617..49259cfa470 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepositoryTest.kt @@ -98,6 +98,7 @@ class ConversationGroupRepositoryTest { .withConversationDetailsById(TestConversation.GROUP_VIEW_ENTITY(PROTEUS_PROTOCOL_INFO)) .withSuccessfulNewConversationGroupStartedHandled() .withSuccessfulNewConversationMemberHandled() + .withSuccessfulNewConversationGroupStartedUnverifiedWarningHandled() .arrange() val result = conversationGroupRepository.createGroupConversation( @@ -130,6 +131,7 @@ class ConversationGroupRepositoryTest { .withConversationDetailsById(TestConversation.GROUP_VIEW_ENTITY(PROTEUS_PROTOCOL_INFO)) .withSuccessfulNewConversationGroupStartedHandled() .withSuccessfulNewConversationMemberHandled() + .withSuccessfulNewConversationGroupStartedUnverifiedWarningHandled() .arrange() val result = conversationGroupRepository.createGroupConversation( @@ -167,6 +169,7 @@ class ConversationGroupRepositoryTest { .withConversationDetailsById(TestConversation.GROUP_VIEW_ENTITY(PROTEUS_PROTOCOL_INFO)) .withSuccessfulNewConversationGroupStartedHandled() .withSuccessfulNewConversationMemberHandled() + .withSuccessfulNewConversationGroupStartedUnverifiedWarningHandled() .arrange() val unreachableUserId = TestUser.USER_ID.copy(domain = "unstableDomain2.com") @@ -255,6 +258,7 @@ class ConversationGroupRepositoryTest { .withConversationDetailsById(TestConversation.GROUP_VIEW_ENTITY(PROTEUS_PROTOCOL_INFO)) .withSuccessfulNewConversationGroupStartedHandled() .withSuccessfulNewConversationMemberHandled() + .withSuccessfulNewConversationGroupStartedUnverifiedWarningHandled() .arrange() val result = conversationGroupRepository.createGroupConversation( @@ -1148,6 +1152,39 @@ class ConversationGroupRepositoryTest { .wasInvoked(once) } + @Test + fun givenAValidConversation_whenCreating_thenConversationIsCreatedAndUnverifiedWarningSystemMessagePersisted() = runTest { + val (arrangement, conversationGroupRepository) = Arrangement() + .withCreateNewConversationAPIResponses(arrayOf(NetworkResponse.Success(CONVERSATION_RESPONSE, emptyMap(), 201))) + .withSelfTeamId(Either.Right(null)) + .withInsertConversationSuccess() + .withConversationDetailsById(TestConversation.GROUP_VIEW_ENTITY(PROTEUS_PROTOCOL_INFO)) + .withSuccessfulNewConversationGroupStartedHandled() + .withSuccessfulNewConversationMemberHandled() + .withSuccessfulNewConversationGroupStartedUnverifiedWarningHandled() + .arrange() + + val result = conversationGroupRepository.createGroupConversation( + GROUP_NAME, + listOf(TestUser.USER_ID), + ConversationOptions(protocol = ConversationOptions.Protocol.PROTEUS) + ) + + result.shouldSucceed() + + with(arrangement) { + verify(conversationDAO) + .suspendFunction(conversationDAO::insertConversation) + .with(anything()) + .wasInvoked(once) + + verify(newGroupConversationSystemMessagesCreator) + .suspendFunction(newGroupConversationSystemMessagesCreator::conversationStartedUnverifiedWarning) + .with(anything()) + .wasInvoked(once) + } + } + private class Arrangement : MemberDAOArrangement by MemberDAOArrangementImpl() { @@ -1540,6 +1577,13 @@ class ConversationGroupRepositoryTest { .thenReturnSequentially(*networkResponses) } + fun withSuccessfulNewConversationGroupStartedUnverifiedWarningHandled() = apply { + given(newGroupConversationSystemMessagesCreator) + .suspendFunction(newGroupConversationSystemMessagesCreator::conversationStartedUnverifiedWarning) + .whenInvokedWith(any()) + .thenReturn(Either.Right(Unit)) + } + fun arrange() = this to conversationGroupRepository } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/NewGroupConversationSystemMessagesCreatorTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/NewGroupConversationSystemMessagesCreatorTest.kt index b733701e995..5a6f1c6c614 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/NewGroupConversationSystemMessagesCreatorTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/NewGroupConversationSystemMessagesCreatorTest.kt @@ -20,6 +20,7 @@ package com.wire.kalium.logic.data.conversation import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.data.id.toApi import com.wire.kalium.logic.data.id.toDao +import com.wire.kalium.logic.data.id.toModel import com.wire.kalium.logic.data.message.MessageContent import com.wire.kalium.logic.data.message.PersistMessageUseCase import com.wire.kalium.logic.feature.user.IsSelfATeamMemberUseCase @@ -342,6 +343,26 @@ class NewGroupConversationSystemMessagesCreatorTest { .wasInvoked(once) } + @Test + fun givenAGroupConversation_whenPersistingAndValid_ThenShouldCreateAStartedUnverifiedSystemMessage() = runTest { + val (arrangement, sysMessageCreator) = Arrangement() + .withPersistMessageSuccess() + .arrange() + + val result = sysMessageCreator.conversationStartedUnverifiedWarning( + TestConversation.ENTITY.copy(type = ConversationEntity.Type.GROUP).id.toModel() + ) + + result.shouldSucceed() + + verify(arrangement.persistMessage) + .suspendFunction(arrangement.persistMessage::invoke) + .with(matching { + (it.content is MessageContent.System && it.content is MessageContent.ConversationStartedUnverifiedWarning) + }) + .wasInvoked(once) + } + private class Arrangement { @Mock val persistMessage = mock(PersistMessageUseCase::class) 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 de9d672683b..0b7544f901a 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 @@ -25,6 +25,8 @@ import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.framework.TestConversation import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.util.arrangement.NewGroupConversationSystemMessageCreatorArrangement +import com.wire.kalium.logic.util.arrangement.NewGroupConversationSystemMessageCreatorArrangementImpl import com.wire.kalium.logic.util.arrangement.mls.OneOnOneResolverArrangement import com.wire.kalium.logic.util.arrangement.mls.OneOnOneResolverArrangementImpl import com.wire.kalium.logic.util.arrangement.repository.ConnectionRepositoryArrangement @@ -48,6 +50,7 @@ class AcceptConnectionRequestUseCaseTest { withUpdateConnectionStatus(Either.Right(CONNECTION)) withFetchConversation(Either.Right(Unit)) withUpdateConversationModifiedDate(Either.Right(Unit)) + withPersistUnverifiedWarningMessageSuccess() withResolveOneOnOneConversationWithUserIdReturning(Either.Right(TestConversation.ID)) } @@ -69,6 +72,7 @@ class AcceptConnectionRequestUseCaseTest { withUpdateConnectionStatus(Either.Right(CONNECTION)) withFetchConversation(Either.Right(Unit)) withUpdateConversationModifiedDate(Either.Right(Unit)) + withPersistUnverifiedWarningMessageSuccess() withResolveOneOnOneConversationWithUserIdReturning(Either.Right(TestConversation.ID)) } @@ -89,6 +93,7 @@ class AcceptConnectionRequestUseCaseTest { val (arrangement, acceptConnectionRequestUseCase) = arrange { withUpdateConnectionStatus(Either.Right(CONNECTION)) withFetchConversation(Either.Right(Unit)) + withPersistUnverifiedWarningMessageSuccess() withUpdateConversationModifiedDate(Either.Right(Unit)) withResolveOneOnOneConversationWithUserIdReturning(Either.Right(TestConversation.ID)) } @@ -126,14 +131,15 @@ class AcceptConnectionRequestUseCaseTest { private class Arrangement(private val block: Arrangement.() -> Unit) : ConnectionRepositoryArrangement by ConnectionRepositoryArrangementImpl(), ConversationRepositoryArrangement by ConversationRepositoryArrangementImpl(), - OneOnOneResolverArrangement by OneOnOneResolverArrangementImpl() - { + OneOnOneResolverArrangement by OneOnOneResolverArrangementImpl(), + NewGroupConversationSystemMessageCreatorArrangement by NewGroupConversationSystemMessageCreatorArrangementImpl() { fun arrange() = run { block() this@Arrangement to AcceptConnectionRequestUseCaseImpl( connectionRepository = connectionRepository, conversationRepository = conversationRepository, - oneOnOneResolver = oneOnOneResolver + oneOnOneResolver = oneOnOneResolver, + newGroupConversationSystemMessagesCreator = newGroupConversationSystemMessagesCreator ) } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCaseTest.kt index c910116e34b..660924e17e8 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCaseTest.kt @@ -42,13 +42,14 @@ class UpdateConversationArchivedStatusUseCaseTest { val conversationId = TestConversation.ID val isConversationArchived = true val archivedStatusTimestamp = 123456789L + val onlyLocally = false val (arrangement, updateConversationArchivedStatus) = Arrangement() .withSuccessfulMutingUpdates() .withUpdateArchivedStatusFullSuccess() .arrange() - val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) + val result = updateConversationArchivedStatus(conversationId, isConversationArchived, onlyLocally, archivedStatusTimestamp) assertEquals(ArchiveStatusUpdateResult.Success::class, result::class) with(arrangement) { @@ -64,18 +65,48 @@ class UpdateConversationArchivedStatusUseCaseTest { } } + @Test + fun givenAConversationId_whenInvokingAnArchivedStatusChangeAndUserIsNotMember_thenShouldArchiveOnlyLocally() = + runTest { + val conversationId = TestConversation.ID + val isConversationArchived = true + val archivedStatusTimestamp = 123456789L + val onlyLocally = true + + val (arrangement, updateConversationArchivedStatus) = Arrangement() + .withSuccessfulMutingUpdates() + .withUpdateArchivedStatusFullSuccess() + .arrange() + + val result = updateConversationArchivedStatus(conversationId, isConversationArchived, onlyLocally, archivedStatusTimestamp) + assertEquals(ArchiveStatusUpdateResult.Success::class, result::class) + + with(arrangement) { + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusRemotely) + .with(any(), any()) + .wasNotInvoked() + + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusLocally) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + } + } + @Test fun givenAConversationId_whenInvokingAnArchivedStatusChangeAndFails_thenShouldDelegateTheCallAndReturnAFailureResult() = runTest { val conversationId = TestConversation.ID val isConversationArchived = true val archivedStatusTimestamp = 123456789L + val onlyLocally = false val (arrangement, updateConversationArchivedStatus) = Arrangement() .withSuccessfulMutingUpdates() .withRemoteUpdateArchivedStatusFailure() .arrange() - val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) + val result = updateConversationArchivedStatus(conversationId, isConversationArchived, onlyLocally, archivedStatusTimestamp) assertEquals(ArchiveStatusUpdateResult.Failure::class, result::class) with(arrangement) { @@ -96,13 +127,14 @@ class UpdateConversationArchivedStatusUseCaseTest { val conversationId = TestConversation.ID val isConversationArchived = true val archivedStatusTimestamp = 123456789L + val onlyLocally = false val (arrangement, updateConversationArchivedStatus) = Arrangement() .withLocalUpdateArchivedStatusFailure() .withSuccessfulMutingUpdates() .arrange() - val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) + val result = updateConversationArchivedStatus(conversationId, isConversationArchived, onlyLocally, archivedStatusTimestamp) assertEquals(ArchiveStatusUpdateResult.Failure::class, result::class) with(arrangement) { @@ -124,13 +156,14 @@ class UpdateConversationArchivedStatusUseCaseTest { val conversationId = TestConversation.ID val isConversationArchived = true val archivedStatusTimestamp = 123456789L + val onlyLocally = false val (arrangement, updateConversationArchivedStatus) = Arrangement() .withUpdateArchivedStatusFullSuccess() .withRemoteErrorMutingUpdates() .arrange() - val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) + val result = updateConversationArchivedStatus(conversationId, isConversationArchived, onlyLocally, archivedStatusTimestamp) assertTrue(result is ArchiveStatusUpdateResult.Success) with(arrangement) { @@ -152,13 +185,14 @@ class UpdateConversationArchivedStatusUseCaseTest { val conversationId = TestConversation.ID val isConversationArchived = true val archivedStatusTimestamp = 123456789L + val onlyLocally = false val (arrangement, updateConversationArchivedStatus) = Arrangement() .withUpdateArchivedStatusFullSuccess() .withLocalErrorMutingUpdates() .arrange() - val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) + val result = updateConversationArchivedStatus(conversationId, isConversationArchived, onlyLocally, archivedStatusTimestamp) assertTrue(result is ArchiveStatusUpdateResult.Success) with(arrangement) { diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/network/SessionManagerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/network/SessionManagerTest.kt index 50e95a03f86..983abb7035e 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/network/SessionManagerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/network/SessionManagerTest.kt @@ -39,8 +39,10 @@ import com.wire.kalium.persistence.dao.UserIDEntity import io.mockative.Mock import io.mockative.any import io.mockative.anything +import io.mockative.eq import io.mockative.given import io.mockative.mock +import io.mockative.verify import kotlinx.coroutines.test.runTest import kotlin.coroutines.EmptyCoroutineContext import kotlin.test.Test @@ -86,42 +88,58 @@ class SessionManagerTest { } @Test - fun givenInitialSessionIsUpdated_whenFetchingSession_thenSessionShouldBeUpdatedProperly() = runTest { - val expectedData = TEST_ACCOUNT_TOKENS + fun givenSuccess_whenUpdatingToken_thenItShouldCallTokenRefresherCorrectly() = runTest { val (arrangement, sessionManager) = arrange { - withCurrentTokenResult( - AuthTokenEntity( - userId = UserIDEntity("potato", "potahto"), - accessToken = "aToken", - refreshToken = "rToken", - tokenType = "tType", - cookieLabel = "cLabel" - ) - ) - withTokenRefresherResult(Either.Right(expectedData)) + withTokenRefresherResult(Either.Right(TEST_ACCOUNT_TOKENS)) } - sessionManager.session() - sessionManager.updateToken(arrangement.accessTokenApi, "egal", "egal") - val result = sessionManager.session() - assertNotNull(result) - assertEquals(expectedData.userId.value, result.userId.value) - assertEquals(expectedData.userId.domain, result.userId.domain) - assertEquals(expectedData.accessToken.value, result.accessToken) - assertEquals(expectedData.refreshToken.value, result.refreshToken) - assertEquals(expectedData.tokenType, result.tokenType) - assertEquals(expectedData.cookieLabel, result.cookieLabel) + val accessToken = "egal" + val refreshToken = "refreshToken" + sessionManager.updateToken(arrangement.accessTokenApi, accessToken, refreshToken) + + verify(arrangement.accessTokenRefresher) + .suspendFunction(arrangement.accessTokenRefresher::refreshTokenAndPersistSession) + .with(eq(accessToken), eq(refreshToken)) } @Test - fun givenTokenWasUpdated_whenGettingSession_thenItShouldBeUpdatedAsWell() = runTest { - val (arrangement, sessionManager) = arrange { - withTokenRefresherResult(Either.Right(TEST_ACCOUNT_TOKENS)) + fun givenSessionWasUpdated_whenGettingSession_thenItShouldBeUpdatedAsWell() = runTest { + var counter = 0 + val originalTokens = AuthTokenEntity( + userId = UserIDEntity("potato", "potahto"), + accessToken = "aToken", + refreshToken = "rToken", + tokenType = "tType", + cookieLabel = "cLabel" + ) + val updatedTokens = AuthTokenEntity( + userId = UserIDEntity("updated userId", "updated userDomain"), + accessToken = "a completely different token", + refreshToken = "a completely different refresh token", + tokenType = "updated tType", + cookieLabel = "updated cLabel" + ) + val (_, sessionManager) = arrange { + withCurrentTokenReturning { + counter++ + if (counter == 1) { + originalTokens + } else { + updatedTokens + } + } } - sessionManager.updateToken(arrangement.accessTokenApi, "egal", "egal") + val firstResult = sessionManager.session()!! + val secondResult = sessionManager.session()!! - assertEquals(TEST_SESSION_DTO, sessionManager.session()) + assertEquals(originalTokens.accessToken, firstResult.accessToken) + assertEquals(updatedTokens.accessToken, secondResult.accessToken) + assertEquals(updatedTokens.refreshToken, secondResult.refreshToken) + assertEquals(updatedTokens.userId.value, secondResult.userId.value) + assertEquals(updatedTokens.userId.domain, secondResult.userId.domain) + assertEquals(updatedTokens.tokenType, secondResult.tokenType) + assertEquals(updatedTokens.cookieLabel, secondResult.cookieLabel) } private class Arrangement(private val configure: Arrangement.() -> Unit) { @@ -134,7 +152,7 @@ class SessionManagerTest { val accessTokenApi = mock(AccessTokenApi::class) @Mock - private val accessTokenRefresher = mock(AccessTokenRefresher::class) + val accessTokenRefresher = mock(AccessTokenRefresher::class) private val accessTokenRefresherFactory = object : AccessTokenRefresherFactory { override fun create(accessTokenApi: AccessTokenApi): AccessTokenRefresher { return accessTokenRefresher @@ -172,10 +190,14 @@ class SessionManagerTest { } fun withCurrentTokenResult(result: AuthTokenEntity) = apply { + withCurrentTokenReturning { result } + } + + fun withCurrentTokenReturning(block: () -> AuthTokenEntity) = apply { given(tokenStorage) .function(tokenStorage::getToken) .whenInvokedWith(any()) - .thenReturn(result) + .then { block() } } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/UserEventReceiverTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/UserEventReceiverTest.kt index 6a0ee41f62c..3dec042aafe 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/UserEventReceiverTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/UserEventReceiverTest.kt @@ -24,6 +24,7 @@ import com.wire.kalium.logic.data.connection.ConnectionRepository import com.wire.kalium.logic.data.conversation.ClientId import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.conversation.ConversationRepository +import com.wire.kalium.logic.data.conversation.NewGroupConversationSystemMessagesCreator import com.wire.kalium.logic.data.logout.LogoutReason import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.UserId @@ -183,6 +184,7 @@ class UserEventReceiverTest { val (arrangement, eventReceiver) = arrange { withFetchUserInfoReturning(Either.Right(Unit)) withInsertConnectionFromEventSucceeding() + withPersistUnverifiedWarningMessageSuccess() } eventReceiver.onEvent(event) @@ -199,6 +201,7 @@ class UserEventReceiverTest { val (arrangement, eventReceiver) = arrange { withFetchUserInfoReturning(Either.Right(Unit)) withInsertConnectionFromEventSucceeding() + withPersistUnverifiedWarningMessageSuccess() } eventReceiver.onEvent(event) @@ -216,6 +219,7 @@ class UserEventReceiverTest { withFetchUserInfoReturning(Either.Right(Unit)) withInsertConnectionFromEventSucceeding() withScheduleResolveOneOnOneConversationWithUserId() + withPersistUnverifiedWarningMessageSuccess() } eventReceiver.onEvent(event) @@ -228,27 +232,28 @@ class UserEventReceiverTest { @OptIn(ExperimentalCoroutinesApi::class) @Test - fun givenLiveNewConnectionEventWithStatusAccepted_thenResolveActiveOneOnOneConversationIsScheduledWithDelay() = runTest(TestKaliumDispatcher.default) { - val event = TestEvent.newConnection(status = ConnectionState.ACCEPTED).copy(live = true) - val (arrangement, eventReceiver) = arrange { - withFetchUserInfoReturning(Either.Right(Unit)) - withInsertConnectionFromEventSucceeding() - withScheduleResolveOneOnOneConversationWithUserId() + fun givenLiveNewConnectionEventWithStatusAccepted_thenResolveActiveOneOnOneConversationIsScheduledWithDelay() = + runTest(TestKaliumDispatcher.default) { + val event = TestEvent.newConnection(status = ConnectionState.ACCEPTED).copy(live = true) + val (arrangement, eventReceiver) = arrange { + withFetchUserInfoReturning(Either.Right(Unit)) + withInsertConnectionFromEventSucceeding() + withScheduleResolveOneOnOneConversationWithUserId() + withPersistUnverifiedWarningMessageSuccess() + } + + eventReceiver.onEvent(event) + advanceUntilIdle() + + verify(arrangement.oneOnOneResolver) + .suspendFunction(arrangement.oneOnOneResolver::scheduleResolveOneOnOneConversationWithUserId) + .with(eq(event.connection.qualifiedToId), eq(3.seconds)) + .wasInvoked(exactly = once) } - eventReceiver.onEvent(event) - advanceUntilIdle() - - verify(arrangement.oneOnOneResolver) - .suspendFunction(arrangement.oneOnOneResolver::scheduleResolveOneOnOneConversationWithUserId) - .with(eq(event.connection.qualifiedToId), eq(3.seconds)) - .wasInvoked(exactly = once) - } - private class Arrangement(private val block: Arrangement.() -> Unit) : UserRepositoryArrangement by UserRepositoryArrangementImpl(), - OneOnOneResolverArrangement by OneOnOneResolverArrangementImpl() - { + OneOnOneResolverArrangement by OneOnOneResolverArrangementImpl() { @Mock val connectionRepository = mock(classOf()) @@ -264,6 +269,9 @@ class UserEventReceiverTest { @Mock val clientRepository = mock(classOf()) + @Mock + val newGroupConversationSystemMessagesCreator = mock(classOf()) + private val userEventReceiver: UserEventReceiver = UserEventReceiverImpl( clientRepository, connectionRepository, @@ -272,7 +280,8 @@ class UserEventReceiverTest { logoutUseCase, oneOnOneResolver, SELF_USER_ID, - currentClientIdProvider + currentClientIdProvider, + lazy { newGroupConversationSystemMessagesCreator } ) init { @@ -286,6 +295,13 @@ class UserEventReceiverTest { .thenReturn(Either.Right(Unit)) } + fun withPersistUnverifiedWarningMessageSuccess() = apply { + given(newGroupConversationSystemMessagesCreator) + .suspendFunction(newGroupConversationSystemMessagesCreator::conversationStartedUnverifiedWarning) + .whenInvokedWith(any()) + .then { Either.Right(Unit) } + } + fun withSaveNewClientSucceeding() = apply { given(clientRepository) .suspendFunction(clientRepository::saveNewClientEvent) 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 6360cfd7bc7..c112debd0a3 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 @@ -81,6 +81,7 @@ class NewConversationEventHandlerTest { .withFetchUsersIfUnknownIds(members) .withSelfUserTeamId(Either.Right(teamId)) .withConversationStartedSystemMessage() + .withConversationUnverifiedWarningSystemMessage() .withConversationResolvedMembersSystemMessage() .withReadReceiptsSystemMessage() .withQualifiedId(creatorQualifiedId) @@ -104,7 +105,7 @@ class NewConversationEventHandlerTest { val event = Event.Conversation.NewConversation( id = "eventId", conversationId = TestConversation.ID, - transient =false, + transient = false, live = false, timestampIso = "timestamp", conversation = TestConversation.CONVERSATION_RESPONSE, @@ -124,6 +125,7 @@ class NewConversationEventHandlerTest { .withFetchUsersIfUnknownIds(members) .withSelfUserTeamId(Either.Right(teamId)) .withConversationStartedSystemMessage() + .withConversationUnverifiedWarningSystemMessage() .withConversationResolvedMembersSystemMessage() .withReadReceiptsSystemMessage() .withQualifiedId(creatorQualifiedId) @@ -167,6 +169,7 @@ class NewConversationEventHandlerTest { .withSelfUserTeamId(Either.Right(teamId)) .withConversationStartedSystemMessage() .withConversationResolvedMembersSystemMessage() + .withConversationUnverifiedWarningSystemMessage() .withReadReceiptsSystemMessage() .withQualifiedId(creatorQualifiedId) .arrange() @@ -195,6 +198,11 @@ class NewConversationEventHandlerTest { ) .with(eq(event.conversation)) .wasInvoked(exactly = once) + + verify(arrangement.newGroupConversationSystemMessagesCreator) + .suspendFunction(arrangement.newGroupConversationSystemMessagesCreator::conversationStartedUnverifiedWarning) + .with(eq(event.conversation.id.toModel())) + .wasInvoked(exactly = once) } @Test @@ -315,6 +323,13 @@ class NewConversationEventHandlerTest { .thenReturn(Either.Right(Unit)) } + fun withConversationUnverifiedWarningSystemMessage() = apply { + given(newGroupConversationSystemMessagesCreator) + .suspendFunction(newGroupConversationSystemMessagesCreator::conversationStartedUnverifiedWarning) + .whenInvokedWith(any()) + .thenReturn(Either.Right(Unit)) + } + suspend fun withFetchUsersIfUnknownIds(members: Set) = apply { given(userRepository) .suspendFunction(userRepository::fetchUsersIfUnknownByIds) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/NewGroupConversationSystemMessageCreatorArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/NewGroupConversationSystemMessageCreatorArrangement.kt new file mode 100644 index 00000000000..541cb96c37e --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/NewGroupConversationSystemMessageCreatorArrangement.kt @@ -0,0 +1,48 @@ +/* + * Wire + * Copyright (C) 2023 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.util.arrangement + +import com.wire.kalium.logic.data.conversation.NewGroupConversationSystemMessagesCreator +import com.wire.kalium.logic.data.message.PersistMessageUseCase +import com.wire.kalium.logic.functional.Either +import io.mockative.Mock +import io.mockative.any +import io.mockative.given +import io.mockative.mock + +internal interface NewGroupConversationSystemMessageCreatorArrangement { + val newGroupConversationSystemMessagesCreator: NewGroupConversationSystemMessagesCreator + + fun withPersistUnverifiedWarningMessageSuccess(): NewGroupConversationSystemMessageCreatorArrangement +} + +internal class NewGroupConversationSystemMessageCreatorArrangementImpl : NewGroupConversationSystemMessageCreatorArrangement { + override val newGroupConversationSystemMessagesCreator: NewGroupConversationSystemMessagesCreator = + mock(NewGroupConversationSystemMessagesCreator::class) + + @Mock + val persistMessage = mock(PersistMessageUseCase::class) + + override fun withPersistUnverifiedWarningMessageSuccess() = apply { + given(newGroupConversationSystemMessagesCreator) + .suspendFunction(newGroupConversationSystemMessagesCreator::conversationStartedUnverifiedWarning) + .whenInvokedWith(any()) + .then { Either.Right(Unit) } + } + +} diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/adapter/SupportedProtocolSetAdapter.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/adapter/SupportedProtocolSetAdapter.kt index afeaf818de2..2bf23ac7f05 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/adapter/SupportedProtocolSetAdapter.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/adapter/SupportedProtocolSetAdapter.kt @@ -22,7 +22,11 @@ import com.wire.kalium.persistence.dao.SupportedProtocolEntity internal object SupportedProtocolSetAdapter : ColumnAdapter, String> { override fun decode(databaseValue: String): Set { - return databaseValue.split(SEPARATOR).map { SupportedProtocolEntity.valueOf(it) }.toSet() + return if (databaseValue.isBlank()) { + emptySet() + } else { + databaseValue.split(SEPARATOR).map { SupportedProtocolEntity.valueOf(it) }.toSet() + } } override fun encode(value: Set): String { diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt index 5adf7796f75..f45853d107d 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt @@ -193,7 +193,7 @@ sealed interface MessageEntity { NEW_CONVERSATION_RECEIPT_MODE, CONVERSATION_RECEIPT_MODE_CHANGED, HISTORY_LOST, HISTORY_LOST_PROTOCOL_CHANGED, CONVERSATION_MESSAGE_TIMER_CHANGED, CONVERSATION_CREATED, MLS_WRONG_EPOCH_WARNING, CONVERSATION_DEGRADED_MLS, CONVERSATION_DEGRADED_PREOTEUS, CONVERSATION_VERIFIED_MLS, CONVERSATION_VERIFIED_PREOTEUS, COMPOSITE, FEDERATION, - CONVERSATION_PROTOCOL_CHANGED + CONVERSATION_PROTOCOL_CHANGED, CONVERSATION_STARTED_UNVERIFIED_WARNING } enum class MemberChangeType { @@ -334,6 +334,7 @@ sealed class MessageEntityContent { data object ConversationVerifiedMLS : System() data object ConversationDegradedProteus : System() data object ConversationVerifiedProteus : System() + data object ConversationStartedUnverifiedWarning : System() data class Federation(val domainList: List, val type: MessageEntity.FederationType) : System() } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt index 5e9e897f538..081f875f1fd 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt @@ -261,6 +261,10 @@ internal class MessageInsertExtensionImpl( conversation_id = message.conversationId, protocol = content.protocol ) + + is MessageEntityContent.ConversationStartedUnverifiedWarning -> { + /* no-op */ + } } } @@ -316,6 +320,7 @@ internal class MessageInsertExtensionImpl( MessageEntityContent.ConversationVerifiedMLS, MessageEntityContent.ConversationDegradedProteus, MessageEntityContent.ConversationVerifiedProteus, + MessageEntityContent.ConversationStartedUnverifiedWarning, is MessageEntityContent.TeamMemberRemoved -> { /* no-op */ } @@ -408,5 +413,6 @@ internal class MessageInsertExtensionImpl( MessageEntityContent.ConversationVerifiedMLS -> MessageEntity.ContentType.CONVERSATION_VERIFIED_MLS MessageEntityContent.ConversationVerifiedProteus -> MessageEntity.ContentType.CONVERSATION_VERIFIED_PREOTEUS is MessageEntityContent.ConversationProtocolChanged -> MessageEntity.ContentType.CONVERSATION_PROTOCOL_CHANGED + is MessageEntityContent.ConversationStartedUnverifiedWarning -> MessageEntity.ContentType.CONVERSATION_STARTED_UNVERIFIED_WARNING } } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt index 0a89eb13b5a..a7075f48bbb 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt @@ -206,6 +206,7 @@ object MessageMapper { MessageEntity.ContentType.CONVERSATION_VERIFIED_MLS -> MessagePreviewEntityContent.Unknown MessageEntity.ContentType.CONVERSATION_VERIFIED_PREOTEUS -> MessagePreviewEntityContent.Unknown MessageEntity.ContentType.CONVERSATION_PROTOCOL_CHANGED -> MessagePreviewEntityContent.Unknown + MessageEntity.ContentType.CONVERSATION_STARTED_UNVERIFIED_WARNING -> MessagePreviewEntityContent.Unknown } } @@ -577,6 +578,8 @@ object MessageMapper { MessageEntity.ContentType.CONVERSATION_PROTOCOL_CHANGED -> MessageEntityContent.ConversationProtocolChanged( protocol = conversationProtocolChanged ?: ConversationEntity.Protocol.PROTEUS ) + + MessageEntity.ContentType.CONVERSATION_STARTED_UNVERIFIED_WARNING -> MessageEntityContent.ConversationStartedUnverifiedWarning } return createMessageEntity( diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/adapter/SupportedProtocolSetAdapterTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/adapter/SupportedProtocolSetAdapterTest.kt new file mode 100644 index 00000000000..3a1f12b0822 --- /dev/null +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/adapter/SupportedProtocolSetAdapterTest.kt @@ -0,0 +1,64 @@ +/* + * Wire + * Copyright (C) 2023 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.adapter + +import com.wire.kalium.persistence.dao.SupportedProtocolEntity +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SupportedProtocolSetAdapterTest { + + private val adapter = SupportedProtocolSetAdapter + + @Test + fun givenEmptySet_whenEncodingAndDecoding_thenShouldReturnEmptySet() { + val encoded = adapter.encode(emptySet()) + val decoded = adapter.decode(encoded) + + assertTrue(decoded.isEmpty()) + } + + @Test + fun givenEmptyString_whenDecodingAndEncoding_thenShouldReturnEmptyString() { + val decoded = adapter.decode("") + val encoded = adapter.encode(decoded) + + assertTrue(encoded.isEmpty()) + } + + @Test + fun givenProteus_whenEncodingAndDecoding_thenShouldReturnProteus() { + val encoded = adapter.encode(setOf(SupportedProtocolEntity.PROTEUS)) + val decoded = adapter.decode(encoded) + + assertEquals(1, decoded.size) + assertContains(decoded, SupportedProtocolEntity.PROTEUS) + } + + @Test + fun givenMLSAndProteus_whenEncodingAndDecoding_thenShouldReturnMLSAndProteus() { + val encoded = adapter.encode(setOf(SupportedProtocolEntity.MLS, SupportedProtocolEntity.PROTEUS)) + val decoded = adapter.decode(encoded) + + assertEquals(2, decoded.size) + assertContains(decoded, SupportedProtocolEntity.MLS) + assertContains(decoded, SupportedProtocolEntity.PROTEUS) + } +}