diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/connection/ConnectionRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/connection/ConnectionRepository.kt index 60b7f00b193..b3ed3785d55 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/connection/ConnectionRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/connection/ConnectionRepository.kt @@ -258,7 +258,8 @@ internal class ConnectionDataSource( messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) ) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/Conversation.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/Conversation.kt index 94f6fdcd30e..c0a79b5eab3 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/Conversation.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/Conversation.kt @@ -70,7 +70,8 @@ data class Conversation( val messageTimer: Duration?, val userMessageTimer: Duration?, val archived: Boolean, - val archivedDateTime: Instant? + val archivedDateTime: Instant?, + val verificationStatus: VerificationStatus ) { companion object { @@ -178,6 +179,8 @@ data class Conversation( enum class ReceiptMode { DISABLED, ENABLED } enum class TypingIndicatorMode { STARTED, STOPPED } + enum class VerificationStatus { VERIFIED, NOT_VERIFIED, DEGRADED } + @Suppress("MagicNumber") enum class CipherSuite(val tag: Int) { UNKNOWN(0), @@ -300,7 +303,8 @@ sealed class ConversationDetails(open val conversation: Conversation) { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) ) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt index 170a0eea7b0..44d2f55e731 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt @@ -81,6 +81,8 @@ interface ConversationMapper { fun fromMigrationModel(conversation: Conversation): ConversationEntity fun fromFailedGroupConversationToEntity(conversationId: NetworkQualifiedId): ConversationEntity + fun verificationStatusToEntity(verificationStatus: Conversation.VerificationStatus): ConversationEntity.VerificationStatus + fun verificationStatusFromEntity(verificationStatus: ConversationEntity.VerificationStatus): Conversation.VerificationStatus } @Suppress("TooManyFunctions", "LongParameterList") @@ -119,7 +121,8 @@ internal class ConversationMapperImpl( userMessageTimer = null, // user picked self deletion timer is only persisted locally hasIncompleteMetadata = false, archived = apiModel.members.self.otrArchived ?: false, - archivedInstant = apiModel.members.self.otrArchivedRef?.toInstant() + archivedInstant = apiModel.members.self.otrArchivedRef?.toInstant(), + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) override fun fromApiModelToDaoModel(apiModel: ConvProtocol): Protocol = when (apiModel) { @@ -161,7 +164,8 @@ internal class ConversationMapperImpl( messageTimer = messageTimer?.toDuration(DurationUnit.MILLISECONDS), userMessageTimer = userMessageTimer?.toDuration(DurationUnit.MILLISECONDS), archived = archived, - archivedDateTime = archivedDateTime + archivedDateTime = archivedDateTime, + verificationStatus = verificationStatusFromEntity(verificationStatus) ) } @@ -186,7 +190,8 @@ internal class ConversationMapperImpl( messageTimer = messageTimer?.toDuration(DurationUnit.MILLISECONDS), userMessageTimer = userMessageTimer?.toDuration(DurationUnit.MILLISECONDS), archived = archived, - archivedDateTime = archivedInstant + archivedDateTime = archivedInstant, + verificationStatus = verificationStatusFromEntity(verificationStatus) ) } @@ -378,7 +383,8 @@ internal class ConversationMapperImpl( messageTimer = messageTimer?.inWholeMilliseconds, userMessageTimer = userMessageTimer?.inWholeMilliseconds, archived = archived, - archivedInstant = archivedDateTime + archivedInstant = archivedDateTime, + verificationStatus = verificationStatusToEntity(verificationStatus) ) } @@ -406,7 +412,8 @@ internal class ConversationMapperImpl( userMessageTimer = null, hasIncompleteMetadata = true, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) private fun ConversationResponse.getProtocolInfo(mlsGroupState: GroupState?): ProtocolInfo { @@ -443,6 +450,12 @@ internal class ConversationMapperImpl( ConversationResponse.Type.WAIT_FOR_CONNECTION -> ConversationEntity.Type.CONNECTION_PENDING } } + + override fun verificationStatusFromEntity(verificationStatus: ConversationEntity.VerificationStatus) = + Conversation.VerificationStatus.valueOf(verificationStatus.name) + + override fun verificationStatusToEntity(verificationStatus: Conversation.VerificationStatus) = + ConversationEntity.VerificationStatus.valueOf(verificationStatus.name) } private fun ConversationEntity.Type.fromDaoModelToType(): Conversation.Type = when (this) { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt index cffe6d83b7a..3957336400a 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt @@ -227,6 +227,13 @@ interface ConversationRepository { suspend fun getOneOnOneConversationsWithFederatedMembers( domain: String ): Either + + suspend fun updateVerificationStatus( + verificationStatus: Conversation.VerificationStatus, + conversationID: ConversationId + ): Either + + suspend fun getConversationDetailsByMLSGroupId(mlsGroupId: GroupID): Either } @Suppress("LongParameterList", "TooManyFunctions") @@ -838,6 +845,21 @@ internal class ConversationDataSource internal constructor( .mapValues { it.value.toModel() } } + override suspend fun updateVerificationStatus( + verificationStatus: Conversation.VerificationStatus, + conversationID: ConversationId + ): Either = + wrapStorageRequest { + conversationDAO.updateVerificationStatus( + conversationMapper.verificationStatusToEntity(verificationStatus), + conversationID.toDao() + ) + } + + override suspend fun getConversationDetailsByMLSGroupId(mlsGroupId: GroupID): Either = + wrapStorageRequest { conversationDAO.getConversationByGroupID(mlsGroupId.value) } + .map { conversationMapper.fromDaoModelToDetails(it, null, mapOf()) } + private suspend fun persistIncompleteConversations( conversationsFailed: List ): Either { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationVerificationStatus.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationVerificationStatus.kt deleted file mode 100644 index 3f9c1a5d421..00000000000 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationVerificationStatus.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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.data.conversation - -enum class ConversationVerificationStatus { - NOT_VERIFIED, VERIFIED, DEGRADED -} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt index 6abbb564d7d..e09c00307fe 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt @@ -106,7 +106,7 @@ interface MLSConversationRepository { suspend fun setProposalTimer(timer: ProposalTimer, inMemory: Boolean = false) suspend fun observeProposalTimers(): Flow suspend fun observeEpochChanges(): Flow - suspend fun getConversationVerificationStatus(groupID: GroupID): Either + suspend fun getConversationVerificationStatus(groupID: GroupID): Either } private enum class CommitStrategy { @@ -195,7 +195,7 @@ internal class MLSConversationDataSource( kaliumLogger.i("Created conversation from welcome message (groupID = $groupID)") wrapStorageRequest { - if (conversationDAO.getConversationByGroupID(groupID).first() != null) { + if (conversationDAO.observeConversationByGroupID(groupID).first() != null) { // Welcome arrived after the conversation create event, updating existing conversation. conversationDAO.updateConversationGroupState( ConversationEntity.GroupState.ESTABLISHED, @@ -486,12 +486,12 @@ internal class MLSConversationDataSource( } } - override suspend fun getConversationVerificationStatus(groupID: GroupID): Either = + override suspend fun getConversationVerificationStatus(groupID: GroupID): Either = mlsClientProvider.getMLSClient().flatMap { mlsClient -> wrapMLSRequest { mlsClient.isGroupVerified(idMapper.toCryptoModel(groupID)) } }.map { - if (it) ConversationVerificationStatus.VERIFIED - else ConversationVerificationStatus.NOT_VERIFIED + if (it) Conversation.VerificationStatus.VERIFIED + else Conversation.VerificationStatus.NOT_VERIFIED } private suspend fun retryOnCommitFailure( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/web/WebMappers.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/web/WebMappers.kt index 8466140cca9..ad050d685fe 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/web/WebMappers.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/web/WebMappers.kt @@ -169,7 +169,8 @@ fun WebConversationContent.toConversation(selfUserId: UserId): Conversation? { messageTimer = messageTimer?.toDuration(DurationUnit.MILLISECONDS), userMessageTimer = null, archived = archivedState ?: false, - archivedDateTime = conversationArchivedTimestamp + archivedDateTime = conversationArchivedTimestamp, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) } } 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 298f15243a4..9cf7f01c66a 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 @@ -154,12 +154,10 @@ import com.wire.kalium.logic.feature.connection.ConnectionScope import com.wire.kalium.logic.feature.connection.SyncConnectionsUseCase import com.wire.kalium.logic.feature.connection.SyncConnectionsUseCaseImpl import com.wire.kalium.logic.feature.conversation.ConversationScope -import com.wire.kalium.logic.feature.conversation.ConversationVerificationStatusHandler -import com.wire.kalium.logic.feature.conversation.ConversationVerificationStatusHandlerImpl import com.wire.kalium.logic.feature.conversation.ConversationsRecoveryManager import com.wire.kalium.logic.feature.conversation.ConversationsRecoveryManagerImpl -import com.wire.kalium.logic.feature.conversation.GetConversationVerificationStatusUseCase -import com.wire.kalium.logic.feature.conversation.GetConversationVerificationStatusUseCaseImpl +import com.wire.kalium.logic.feature.conversation.ObserveOtherUserSecurityClassificationLabelUseCase +import com.wire.kalium.logic.feature.conversation.ObserveOtherUserSecurityClassificationLabelUseCaseImpl import com.wire.kalium.logic.feature.conversation.JoinExistingMLSConversationUseCase import com.wire.kalium.logic.feature.conversation.JoinExistingMLSConversationUseCaseImpl import com.wire.kalium.logic.feature.conversation.JoinExistingMLSConversationsUseCase @@ -170,8 +168,8 @@ import com.wire.kalium.logic.feature.conversation.LeaveSubconversationUseCase import com.wire.kalium.logic.feature.conversation.LeaveSubconversationUseCaseImpl import com.wire.kalium.logic.feature.conversation.MLSConversationsRecoveryManager import com.wire.kalium.logic.feature.conversation.MLSConversationsRecoveryManagerImpl -import com.wire.kalium.logic.feature.conversation.ObserveOtherUserSecurityClassificationLabelUseCase -import com.wire.kalium.logic.feature.conversation.ObserveOtherUserSecurityClassificationLabelUseCaseImpl +import com.wire.kalium.logic.feature.conversation.MLSConversationsVerificationStatusesHandler +import com.wire.kalium.logic.feature.conversation.MLSConversationsVerificationStatusesHandlerImpl import com.wire.kalium.logic.feature.conversation.ObserveSecurityClassificationLabelUseCase import com.wire.kalium.logic.feature.conversation.ObserveSecurityClassificationLabelUseCaseImpl import com.wire.kalium.logic.feature.conversation.RecoverMLSConversationsUseCase @@ -1498,20 +1496,6 @@ class UserSessionScope internal constructor( } } - private val conversationVerificationStatusHandler: ConversationVerificationStatusHandler - get() = ConversationVerificationStatusHandlerImpl( - conversationRepository, - persistMessage, - userId - ) - - val getConversationVerificationStatus: GetConversationVerificationStatusUseCase - get() = GetConversationVerificationStatusUseCaseImpl( - conversationRepository, - mlsConversationRepository, - conversationVerificationStatusHandler - ) - internal val getProxyCredentials: GetProxyCredentialsUseCase get() = GetProxyCredentialsUseCaseImpl(sessionManager) @@ -1521,6 +1505,10 @@ class UserSessionScope internal constructor( override val coroutineContext: CoroutineContext = SupervisorJob() + private val mlsConversationsVerificationStatusesHandler: MLSConversationsVerificationStatusesHandler by lazy { + MLSConversationsVerificationStatusesHandlerImpl(conversationRepository, persistMessage, mlsConversationRepository, userId) + } + init { launch { apiMigrationManager.performMigrations() @@ -1556,6 +1544,10 @@ class UserSessionScope internal constructor( launch { avsSyncStateReporter.execute() } + + launch { + mlsConversationsVerificationStatusesHandler.invoke() + } } fun onDestroy() { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationVerificationStatusHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationVerificationStatusHandler.kt deleted file mode 100644 index 8f56d32e9ac..00000000000 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationVerificationStatusHandler.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.feature.conversation - -import com.benasher44.uuid.uuid4 -import com.wire.kalium.logic.data.conversation.Conversation -import com.wire.kalium.logic.data.conversation.ConversationRepository -import com.wire.kalium.logic.data.conversation.ConversationVerificationStatus -import com.wire.kalium.logic.data.message.Message -import com.wire.kalium.logic.data.message.MessageContent -import com.wire.kalium.logic.data.message.PersistMessageUseCase -import com.wire.kalium.logic.data.user.UserId -import com.wire.kalium.logic.functional.flatMap -import com.wire.kalium.logic.functional.getOrElse -import com.wire.kalium.util.DateTimeUtil -import com.wire.kalium.util.KaliumDispatcher -import com.wire.kalium.util.KaliumDispatcherImpl -import kotlinx.coroutines.withContext - -/** - * Notify user (by adding System message in conversation) if needed about changes of Conversation Verification status. - */ -internal interface ConversationVerificationStatusHandler { - suspend operator fun invoke(conversation: Conversation, status: ConversationVerificationStatus) -} - -internal class ConversationVerificationStatusHandlerImpl( - private val conversationRepository: ConversationRepository, - private val persistMessage: PersistMessageUseCase, - private val selfUserId: UserId, - kaliumDispatcher: KaliumDispatcher = KaliumDispatcherImpl -) : ConversationVerificationStatusHandler { - private val dispatcher = kaliumDispatcher.io - - override suspend fun invoke(conversation: Conversation, status: ConversationVerificationStatus): Unit = withContext(dispatcher) { - if (shouldNotifyUser(conversation, status)) { - val content = when (conversation.protocol) { - is Conversation.ProtocolInfo.MLS -> MessageContent.ConversationDegradedMLS - Conversation.ProtocolInfo.Proteus -> MessageContent.ConversationDegradedProteus - } - val conversationDegradedMessage = Message.System( - id = uuid4().toString(), - content = content, - conversationId = conversation.id, - date = DateTimeUtil.currentIsoDateTimeString(), - senderUserId = selfUserId, - status = Message.Status.Sent, - visibility = Message.Visibility.VISIBLE, - expirationData = null - ) - - persistMessage(conversationDegradedMessage) - .flatMap { conversationRepository.setInformedAboutDegradedMLSVerificationFlag(conversation.id, true) } - } else if (status != ConversationVerificationStatus.DEGRADED) { - conversationRepository.setInformedAboutDegradedMLSVerificationFlag(conversation.id, false) - } - } - - private suspend fun shouldNotifyUser(conversation: Conversation, status: ConversationVerificationStatus): Boolean = - if (status == ConversationVerificationStatus.DEGRADED) { - if (conversation.protocol is Conversation.ProtocolInfo.MLS) { - !conversationRepository.isInformedAboutDegradedMLSVerification(conversation.id).getOrElse(true) - } else { - // TODO check flag for Proteus after implementing it. - false - } - } else { - false - } -} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/GetConversationVerificationStatusUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/GetConversationVerificationStatusUseCase.kt deleted file mode 100644 index b32840e194a..00000000000 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/GetConversationVerificationStatusUseCase.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.feature.conversation - -import com.wire.kalium.logic.CoreFailure -import com.wire.kalium.logic.data.conversation.Conversation -import com.wire.kalium.logic.data.conversation.ConversationRepository -import com.wire.kalium.logic.data.conversation.ConversationVerificationStatus -import com.wire.kalium.logic.data.conversation.MLSConversationRepository -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.getOrElse -import com.wire.kalium.logic.functional.map -import com.wire.kalium.logic.functional.onSuccess -import com.wire.kalium.util.KaliumDispatcher -import com.wire.kalium.util.KaliumDispatcherImpl -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -/** - * Operation that fetches [ConversationVerificationStatus] of the specific conversation - * - * @param conversationId - * @return [ConversationVerificationStatusResult.Failure] when error occurred. - * [ConversationVerificationStatusResult.Success] with [ConversationVerificationStatus] and [ConversationProtocol] of the conversation - * in other cases. - */ -interface GetConversationVerificationStatusUseCase { - suspend operator fun invoke(conversationId: ConversationId): ConversationVerificationStatusResult -} - -class GetConversationVerificationStatusUseCaseImpl internal constructor( - private val conversationRepository: ConversationRepository, - private val mlsConversationRepository: MLSConversationRepository, - private val verificationStatusHandler: ConversationVerificationStatusHandler, - kaliumDispatcher: KaliumDispatcher = KaliumDispatcherImpl -) : GetConversationVerificationStatusUseCase { - private val dispatcher = kaliumDispatcher.io - - override suspend fun invoke(conversationId: ConversationId): ConversationVerificationStatusResult = withContext(dispatcher) { - conversationRepository.baseInfoById(conversationId) - .flatMap { conversation -> - if (conversation.protocol is Conversation.ProtocolInfo.MLS) { - getConversationMLSVerificationStatus(conversation.protocol) - } else { - getConversationProteusVerificationStatus(conversation.id) - } - .onSuccess { - if (it is ConversationVerificationStatusResult.Success) - launch { verificationStatusHandler(conversation, it.status) } - } - } - .getOrElse { ConversationVerificationStatusResult.Failure(it) } - } - - private suspend fun getConversationMLSVerificationStatus(protocol: Conversation.ProtocolInfo.MLS) = - mlsConversationRepository.getConversationVerificationStatus(protocol.groupId) - .map { ConversationVerificationStatusResult.Success(ConversationProtocol.MLS, it) } - - private suspend fun getConversationProteusVerificationStatus( - conversationId: ConversationId - ): Either { - // TODO needs to be handled for Proteus conversation that is not implemented yet - return Either.Right( - ConversationVerificationStatusResult.Success( - ConversationProtocol.PROTEUS, - ConversationVerificationStatus.NOT_VERIFIED - ) - ) - } -} - -sealed class ConversationVerificationStatusResult { - data class Success( - val protocol: ConversationProtocol, - val status: ConversationVerificationStatus - ) : ConversationVerificationStatusResult() - - data class Failure(val storageFailure: CoreFailure) : ConversationVerificationStatusResult() -} - -enum class ConversationProtocol { - PROTEUS, MLS -} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/MLSConversationsVerificationStatusesHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/MLSConversationsVerificationStatusesHandler.kt new file mode 100644 index 00000000000..87b30e6defd --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/MLSConversationsVerificationStatusesHandler.kt @@ -0,0 +1,109 @@ +/* + * 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.feature.conversation + +import com.benasher44.uuid.uuid4 +import com.wire.kalium.logic.data.conversation.Conversation.VerificationStatus +import com.wire.kalium.logic.data.conversation.ConversationDetails +import com.wire.kalium.logic.data.conversation.ConversationRepository +import com.wire.kalium.logic.data.conversation.MLSConversationRepository +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.message.Message +import com.wire.kalium.logic.data.message.MessageContent +import com.wire.kalium.logic.data.message.PersistMessageUseCase +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.functional.map +import com.wire.kalium.logic.functional.onlyRight +import com.wire.kalium.util.DateTimeUtil +import kotlinx.coroutines.flow.mapLatest + +/** + * Observes all the MLS Conversations Verification status. + * Notify user (by adding System message in conversation) if needed about changes. + */ +internal interface MLSConversationsVerificationStatusesHandler { + suspend operator fun invoke() +} + +internal class MLSConversationsVerificationStatusesHandlerImpl( + private val conversationRepository: ConversationRepository, + private val persistMessage: PersistMessageUseCase, + private val mlsConversationRepository: MLSConversationRepository, + private val selfUserId: UserId, +) : MLSConversationsVerificationStatusesHandler { + + override suspend fun invoke() = + mlsConversationRepository.observeEpochChanges() + .mapLatest { groupId -> mlsConversationRepository.getConversationVerificationStatus(groupId).map { groupId to it } } + .onlyRight() + .collect { (groupId, newStatus) -> + conversationRepository.getConversationDetailsByMLSGroupId(groupId) + .map { conversation -> updateStatusAndNotifyUserIfNeeded(newStatus, conversation) } + } + + private suspend fun updateStatusAndNotifyUserIfNeeded(newStatusFromCC: VerificationStatus, conversation: ConversationDetails) { + val currentStatus = conversation.conversation.verificationStatus + val newStatus = getActualNewStatus(newStatusFromCC, currentStatus) + + if (newStatus == currentStatus) return + + conversationRepository.updateVerificationStatus(newStatus, conversation.conversation.id) + + if (newStatus == VerificationStatus.DEGRADED) { + notifyUserAboutStateChanges(conversation.conversation.id, VerificationStatus.DEGRADED) + } + } + + /** + * Current CoreCrypto implementation returns only a boolean flag "if conversation is verified or not". + * So we need to calculate if status was degraded on our side by comparing it to the previous status. + * @param newStatusFromCC - [VerificationStatus] that CoreCrypto returns. + * @param currentStatus - [VerificationStatus] that conversation currently has. + * @return [VerificationStatus] that should be saved to DB. + */ + private fun getActualNewStatus(newStatusFromCC: VerificationStatus, currentStatus: VerificationStatus): VerificationStatus = + if (newStatusFromCC == VerificationStatus.NOT_VERIFIED && currentStatus == VerificationStatus.VERIFIED) + VerificationStatus.DEGRADED + else if (newStatusFromCC == VerificationStatus.NOT_VERIFIED && currentStatus == VerificationStatus.DEGRADED) + VerificationStatus.DEGRADED + else + newStatusFromCC + + /** + * Add a SystemMessage into a conversation, to inform user when the conversation verification status becomes DEGRADED. + */ + private suspend fun notifyUserAboutStateChanges( + conversationId: ConversationId, + updatedStatus: VerificationStatus + ) { + // TODO notify about verified too + val content = MessageContent.ConversationDegradedMLS + val conversationDegradedMessage = Message.System( + id = uuid4().toString(), + content = content, + conversationId = conversationId, + date = DateTimeUtil.currentIsoDateTimeString(), + senderUserId = selfUserId, + status = Message.Status.Sent, + visibility = Message.Visibility.VISIBLE, + expirationData = null + ) + + persistMessage(conversationDegradedMessage) + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt index 67938f629d4..ff5e2f974e4 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt @@ -19,7 +19,6 @@ package com.wire.kalium.logic.data.conversation import com.wire.kalium.cryptography.CommitBundle -import com.wire.kalium.cryptography.DecryptedMessageBundle import com.wire.kalium.cryptography.GroupInfoBundle import com.wire.kalium.cryptography.GroupInfoEncryptionType import com.wire.kalium.cryptography.MLSClient @@ -27,7 +26,6 @@ import com.wire.kalium.cryptography.RatchetTreeType import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.data.event.Event -import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.GroupID import com.wire.kalium.logic.data.id.QualifiedClientID import com.wire.kalium.logic.data.keypackage.KeyPackageRepository @@ -37,11 +35,9 @@ import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKey import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysRepository import com.wire.kalium.logic.di.MapperProvider import com.wire.kalium.logic.framework.TestConversation -import com.wire.kalium.logic.framework.TestEvent import com.wire.kalium.logic.framework.TestUser import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.sync.SyncManager -import com.wire.kalium.logic.sync.receiver.conversation.message.MLSMessageUnpackerTest import com.wire.kalium.logic.test_util.TestKaliumDispatcher import com.wire.kalium.logic.util.shouldFail import com.wire.kalium.logic.util.shouldSucceed @@ -929,7 +925,7 @@ class MLSConversationRepositoryTest { .arrange() assertEquals( - Either.Right(ConversationVerificationStatus.VERIFIED), + Either.Right(Conversation.VerificationStatus.VERIFIED), mlsConversationRepository.getConversationVerificationStatus(Arrangement.GROUP_ID) ) @@ -947,7 +943,7 @@ class MLSConversationRepositoryTest { .arrange() assertEquals( - Either.Right(ConversationVerificationStatus.NOT_VERIFIED), + Either.Right(Conversation.VerificationStatus.NOT_VERIFIED), mlsConversationRepository.getConversationVerificationStatus(Arrangement.GROUP_ID) ) @@ -1022,14 +1018,14 @@ class MLSConversationRepositoryTest { fun withGetConversationByGroupIdSuccessful() = apply { given(conversationDAO) - .suspendFunction(conversationDAO::getConversationByGroupID) + .suspendFunction(conversationDAO::observeConversationByGroupID) .whenInvokedWith(anything()) .then { flowOf(TestConversation.VIEW_ENTITY) } } fun withGetConversationByGroupIdFailing() = apply { given(conversationDAO) - .suspendFunction(conversationDAO::getConversationByGroupID) + .suspendFunction(conversationDAO::observeConversationByGroupID) .whenInvokedWith(anything()) .then { flowOf(null) } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/EndCallOnConversationChangeUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/EndCallOnConversationChangeUseCaseTest.kt index 725efa0f393..56370caa99c 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/EndCallOnConversationChangeUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/EndCallOnConversationChangeUseCaseTest.kt @@ -18,6 +18,7 @@ import com.wire.kalium.logic.data.user.type.UserType import com.wire.kalium.logic.feature.call.Call import com.wire.kalium.logic.feature.call.CallStatus import com.wire.kalium.logic.functional.Either +import com.wire.kalium.persistence.dao.conversation.ConversationEntity import io.mockative.Mock import io.mockative.classOf import io.mockative.eq @@ -150,7 +151,8 @@ class EndCallOnConversationChangeUseCaseTest { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) val otherUser = OtherUser( diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ConversationVerificationStatusHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ConversationVerificationStatusHandlerTest.kt deleted file mode 100644 index 942da4620d1..00000000000 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ConversationVerificationStatusHandlerTest.kt +++ /dev/null @@ -1,220 +0,0 @@ -/* - * 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.feature.conversation - -import com.wire.kalium.logic.StorageFailure -import com.wire.kalium.logic.data.conversation.ConversationRepository -import com.wire.kalium.logic.data.conversation.ConversationVerificationStatus -import com.wire.kalium.logic.data.message.PersistMessageUseCase -import com.wire.kalium.logic.framework.TestConversation -import com.wire.kalium.logic.framework.TestUser -import com.wire.kalium.logic.functional.Either -import io.mockative.Mock -import io.mockative.any -import io.mockative.classOf -import io.mockative.eq -import io.mockative.given -import io.mockative.mock -import io.mockative.once -import io.mockative.verify -import kotlinx.coroutines.test.runTest -import kotlin.test.Test -import kotlin.test.assertEquals - -class ConversationVerificationStatusHandlerTest { - - @Test - fun given_whenInvokingWithVerifiedStatus_thenInformedFlagSetFalse() = runTest { - val conversation = TestConversation.MLS_CONVERSATION - val (arrangement, handler) = Arrangement() - .arrange() - - handler(conversation, ConversationVerificationStatus.VERIFIED) - - verify(arrangement.conversationRepository) - .suspendFunction(arrangement.conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .with(eq(conversation.id), eq(false)) - .wasInvoked(once) - } - - @Test - fun given_whenInvokingWithNotVerifiedStatus_thenInformedFlagSetFalse() = runTest { - val conversation = TestConversation.MLS_CONVERSATION - val (arrangement, handler) = Arrangement() - .arrange() - - handler(conversation, ConversationVerificationStatus.NOT_VERIFIED) - - verify(arrangement.conversationRepository) - .suspendFunction(arrangement.conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .with(eq(conversation.id), eq(false)) - .wasInvoked(once) - } - - @Test - fun givenProteusConversation_whenInvokingWithDegradedStatus_thenInformedFlagNotSetFalse() = runTest { - val conversation = TestConversation.CONVERSATION - val (arrangement, handler) = Arrangement() - .arrange() - - handler(conversation, ConversationVerificationStatus.DEGRADED) - - verify(arrangement.conversationRepository) - .suspendFunction(arrangement.conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .with(eq(conversation.id), eq(false)) - .wasNotInvoked() - } - - @Test - fun givenMLSConversation_whenInvokingWithDegradedStatus_thenInformedFlagNotSetFalse() = runTest { - val conversation = TestConversation.CONVERSATION - val (arrangement, handler) = Arrangement() - .withInformedAboutDegradedMLSVerification(Either.Right(true)) - .arrange() - - handler(conversation, ConversationVerificationStatus.DEGRADED) - - verify(arrangement.conversationRepository) - .suspendFunction(arrangement.conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .with(eq(conversation.id), eq(false)) - .wasNotInvoked() - } - - @Test - fun givenMLSConversationAndInformedAboutDegraded_whenInvokingWithVerifiedStatus_thenInformedFlagSetTrue() = runTest { - val conversation = TestConversation.CONVERSATION - val (arrangement, handler) = Arrangement() - .withInformedAboutDegradedMLSVerification(Either.Right(true)) - .arrange() - - handler(conversation, ConversationVerificationStatus.VERIFIED) - - verify(arrangement.conversationRepository) - .suspendFunction(arrangement.conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .with(eq(conversation.id), eq(false)) - .wasInvoked(once) - } - - @Test - fun givenUserInformedAboutDegradedStatus_whenInvokingWithDegradedStatus_thenInformedFlagSetFalse() = runTest { - val conversation = TestConversation.MLS_CONVERSATION - val (arrangement, handler) = Arrangement() - .withInformedAboutDegradedMLSVerification(Either.Right(true)) - .arrange() - - handler(conversation, ConversationVerificationStatus.DEGRADED) - - verify(arrangement.conversationRepository) - .suspendFunction(arrangement.conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .with(eq(conversation.id), eq(false)) - .wasNotInvoked() - } - - @Test - fun givenFailureWhileGettingUserInformedAboutDegradedStatus_whenInvokingWithDegradedStatus_thenInformedFlagNotSetFalse() = runTest { - val conversation = TestConversation.MLS_CONVERSATION - val (arrangement, handler) = Arrangement() - .withInformedAboutDegradedMLSVerification(Either.Left(StorageFailure.DataNotFound)) - .arrange() - - handler(conversation, ConversationVerificationStatus.DEGRADED) - - verify(arrangement.conversationRepository) - .suspendFunction(arrangement.conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .with(eq(conversation.id), eq(false)) - .wasNotInvoked() - } - - @Test - fun givenUserIsNotInformedAboutDegradedStatus_whenInvokingWithDegradedStatus_thenInformedFlagSetTrueAndAddMessage() = runTest { - val conversation = TestConversation.MLS_CONVERSATION - val (arrangement, handler) = Arrangement() - .withInformedAboutDegradedMLSVerification(Either.Right(false)) - .withPersistMessageResult(Either.Right(Unit)) - .arrange() - - handler(conversation, ConversationVerificationStatus.DEGRADED) - - verify(arrangement.conversationRepository) - .suspendFunction(arrangement.conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .with(eq(conversation.id), eq(true)) - .wasInvoked(once) - - verify(arrangement.persistMessageUseCase) - .suspendFunction(arrangement.persistMessageUseCase::invoke) - .with(any()) - .wasInvoked(once) - } - - @Test - fun givenUserIsNotInformedAboutDegradedStatus_whenFailureWhilePersistMessage_thenInformedFlagNotSetTrue() = runTest { - val conversation = TestConversation.MLS_CONVERSATION - val (arrangement, handler) = Arrangement() - .withInformedAboutDegradedMLSVerification(Either.Right(false)) - .withPersistMessageResult(Either.Left(StorageFailure.DataNotFound)) - .arrange() - - handler(conversation, ConversationVerificationStatus.DEGRADED) - - verify(arrangement.conversationRepository) - .suspendFunction(arrangement.conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .with(eq(conversation.id), eq(true)) - .wasNotInvoked() - - verify(arrangement.persistMessageUseCase) - .suspendFunction(arrangement.persistMessageUseCase::invoke) - .with(any()) - .wasInvoked(once) - } - - private class Arrangement { - - @Mock - val conversationRepository = mock(classOf()) - - @Mock - val persistMessageUseCase = mock(classOf()) - - fun arrange() = this to ConversationVerificationStatusHandlerImpl( - conversationRepository, - persistMessageUseCase, - TestUser.USER_ID - ) - - init { - given(conversationRepository) - .suspendFunction(conversationRepository::setInformedAboutDegradedMLSVerificationFlag) - .whenInvokedWith(any()) - .thenReturn(Either.Right(Unit)) - } - - fun withInformedAboutDegradedMLSVerification(isInformed: Either) = apply { - given(conversationRepository) - .suspendFunction(conversationRepository::isInformedAboutDegradedMLSVerification) - .whenInvokedWith(any()) - .thenReturn(isInformed) - } - - fun withPersistMessageResult(result: Either) = apply { - given(persistMessageUseCase) - .suspendFunction(persistMessageUseCase::invoke) - .whenInvokedWith(any()) - .thenReturn(result) - } - } -} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetConversationVerificationStatusUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetConversationVerificationStatusUseCaseTest.kt deleted file mode 100644 index 0884aa79500..00000000000 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetConversationVerificationStatusUseCaseTest.kt +++ /dev/null @@ -1,145 +0,0 @@ -package com.wire.kalium.logic.feature.conversation - -import com.wire.kalium.logic.data.conversation.Conversation -import com.wire.kalium.logic.data.conversation.ConversationRepository -import com.wire.kalium.logic.data.conversation.ConversationVerificationStatus -import com.wire.kalium.logic.data.conversation.MLSConversationRepository -import com.wire.kalium.logic.data.id.ConversationId -import com.wire.kalium.logic.data.id.GroupID -import com.wire.kalium.logic.framework.TestConversation -import com.wire.kalium.logic.functional.Either -import com.wire.kalium.util.DateTimeUtil -import io.mockative.Mock -import io.mockative.any -import io.mockative.anything -import io.mockative.classOf -import io.mockative.eq -import io.mockative.given -import io.mockative.mock -import io.mockative.once -import io.mockative.verify -import kotlinx.coroutines.test.runTest -import kotlin.test.Test -import kotlin.test.assertEquals - -class GetConversationVerificationStatusUseCaseTest { - - @Test - fun givenNoMLSClientIsRegistered_whenInvokingUseCase_thengetConversationVerificationStatusIsNotCalled() = - runTest { - val (arrangement, getConversationVerificationStatus) = Arrangement() - .withGetConversationsByIdSuccessful(Arrangement.PROTEUS_CONVERSATION1) - .arrange() - - assertEquals( - ConversationVerificationStatusResult.Success(ConversationProtocol.PROTEUS, ConversationVerificationStatus.NOT_VERIFIED), - getConversationVerificationStatus(Arrangement.PROTEUS_CONVERSATION1.id) - ) - - verify(arrangement.mlsConversationRepository) - .suspendFunction(arrangement.mlsConversationRepository::getConversationVerificationStatus) - .with(eq(Arrangement.PROTEUS_CONVERSATION1)) - .wasNotInvoked() - - verify(arrangement.verificationStatusHandler) - .suspendFunction(arrangement.verificationStatusHandler::invoke) - .with(eq(Arrangement.PROTEUS_CONVERSATION1), eq(ConversationVerificationStatus.NOT_VERIFIED)) - .wasInvoked(once) - } - - @Test - fun givenNonRecoverableFailure_whenInvokingUseCase_thenGetConversationVerificationStatusIsVerified() = runTest { - val (arrangement, getConversationVerificationStatus) = Arrangement() - .withGetConversationsByIdSuccessful() - .withMLSGroupVerificationStatus(ConversationVerificationStatus.VERIFIED) - .arrange() - - assertEquals( - ConversationVerificationStatusResult.Success(ConversationProtocol.MLS, ConversationVerificationStatus.VERIFIED), - getConversationVerificationStatus(Arrangement.MLS_CONVERSATION1.id) - ) - - verify(arrangement.verificationStatusHandler) - .suspendFunction(arrangement.verificationStatusHandler::invoke) - .with(eq(Arrangement.MLS_CONVERSATION1), eq(ConversationVerificationStatus.VERIFIED)) - .wasInvoked(once) - } - - @Test - fun givenNonRecoverableFailureAndNotVerifiedMLSStatus_whenInvokingUseCase_thenGetConversationVerificationStatusIsVerified() = - runTest { - val (arrangement, getConversationVerificationStatus) = Arrangement() - .withGetConversationsByIdSuccessful() - .withMLSGroupVerificationStatus(ConversationVerificationStatus.NOT_VERIFIED) - .arrange() - - assertEquals( - ConversationVerificationStatusResult.Success(ConversationProtocol.MLS, ConversationVerificationStatus.NOT_VERIFIED), - getConversationVerificationStatus(Arrangement.MLS_CONVERSATION1.id) - ) - - verify(arrangement.verificationStatusHandler) - .suspendFunction(arrangement.verificationStatusHandler::invoke) - .with(eq(Arrangement.MLS_CONVERSATION1), eq(ConversationVerificationStatus.NOT_VERIFIED)) - .wasInvoked(once) - } - - private class Arrangement { - - @Mock - val conversationRepository = mock(classOf()) - - @Mock - val mlsConversationRepository = mock(classOf()) - - @Mock - val verificationStatusHandler = mock(classOf()) - - fun arrange() = this to GetConversationVerificationStatusUseCaseImpl( - conversationRepository, - mlsConversationRepository, - verificationStatusHandler - ) - - init { - given(verificationStatusHandler) - .suspendFunction(verificationStatusHandler::invoke) - .whenInvokedWith(any(), any()) - .thenReturn(Unit) - } - - @Suppress("MaxLineLength") - fun withGetConversationsByIdSuccessful(conversation: Conversation = MLS_CONVERSATION1) = - apply { - given(conversationRepository) - .suspendFunction(conversationRepository::baseInfoById) - .whenInvokedWith(anything()) - .then { Either.Right(conversation) } - } - - fun withMLSGroupVerificationStatus(status: ConversationVerificationStatus) = apply { - given(mlsConversationRepository) - .suspendFunction(mlsConversationRepository::getConversationVerificationStatus) - .whenInvokedWith(anything()) - .thenReturn(Either.Right(status)) - } - - companion object { - val GROUP_ID1 = GroupID("group1") - - val MLS_CONVERSATION1 = TestConversation.GROUP( - Conversation.ProtocolInfo.MLS( - GROUP_ID1, - Conversation.ProtocolInfo.MLS.GroupState.PENDING_JOIN, - epoch = 1UL, - keyingMaterialLastUpdate = DateTimeUtil.currentInstant(), - cipherSuite = Conversation.CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 - ) - ).copy(id = ConversationId("id1", "domain")) - - val PROTEUS_CONVERSATION1 = TestConversation.GROUP( - Conversation.ProtocolInfo.Proteus - ).copy(id = ConversationId("id1", "domain")) - } - } -} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetOrCreateOneToOneConversationUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetOrCreateOneToOneConversationUseCaseTest.kt index eed2538d188..0ec4cffb143 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetOrCreateOneToOneConversationUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetOrCreateOneToOneConversationUseCaseTest.kt @@ -27,6 +27,7 @@ import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.functional.Either +import com.wire.kalium.persistence.dao.conversation.ConversationEntity import io.mockative.Mock import io.mockative.anything import io.mockative.classOf @@ -129,7 +130,8 @@ class GetOrCreateOneToOneConversationUseCaseTest { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/MLSConversationsVerificationStatusesHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/MLSConversationsVerificationStatusesHandlerTest.kt new file mode 100644 index 00000000000..1339f37fdec --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/MLSConversationsVerificationStatusesHandlerTest.kt @@ -0,0 +1,197 @@ +/* + * 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.feature.conversation + +import com.wire.kalium.logic.data.conversation.Conversation +import com.wire.kalium.logic.data.message.Message +import com.wire.kalium.logic.framework.TestConversation +import com.wire.kalium.logic.framework.TestConversationDetails +import com.wire.kalium.logic.framework.TestUser +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.util.arrangement.repository.ConversationRepositoryArrangement +import com.wire.kalium.logic.util.arrangement.repository.ConversationRepositoryArrangementImpl +import com.wire.kalium.logic.util.arrangement.repository.MLSConversationRepositoryArrangement +import com.wire.kalium.logic.util.arrangement.repository.MLSConversationRepositoryArrangementImpl +import com.wire.kalium.logic.util.arrangement.usecase.PersistMessageUseCaseArrangement +import com.wire.kalium.logic.util.arrangement.usecase.PersistMessageUseCaseArrangementImpl +import io.mockative.any +import io.mockative.anyInstanceOf +import io.mockative.eq +import io.mockative.once +import io.mockative.verify +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class MLSConversationsVerificationStatusesHandlerTest { + + @Test + fun givenVerifiedConversation_whenVerifiedStatusComes_thenNotingChanged() = runTest { + val conversationDetails = TestConversationDetails.CONVERSATION_GROUP.copy( + conversation = TestConversation.MLS_CONVERSATION.copy( + verificationStatus = Conversation.VerificationStatus.VERIFIED + ) + ) + val (arrangement, handler) = arrange { + withObserveEpochChanges(flowOf(TestConversation.GROUP_ID)) + withMLSConversationVerificationStatus(Either.Right(Conversation.VerificationStatus.VERIFIED)) + withConversationDetailsByMLSGroupId(Either.Right(conversationDetails)) + } + + handler() + advanceUntilIdle() + + verify(arrangement.conversationRepository) + .suspendFunction(arrangement.conversationRepository::updateVerificationStatus) + .with(any(), any()) + .wasNotInvoked() + + verify(arrangement.persistMessageUseCase) + .suspendFunction(arrangement.persistMessageUseCase::invoke) + .with(any()) + .wasNotInvoked() + } + + @Test + fun givenNotVerifiedConversation_whenNotVerifiedStatusComes_thenNothingChanged() = runTest { + val conversationDetails = TestConversationDetails.CONVERSATION_GROUP.copy(conversation = TestConversation.MLS_CONVERSATION) + val (arrangement, handler) = arrange { + withObserveEpochChanges(flowOf(TestConversation.GROUP_ID)) + withMLSConversationVerificationStatus(Either.Right(Conversation.VerificationStatus.NOT_VERIFIED)) + withConversationDetailsByMLSGroupId(Either.Right(conversationDetails)) + } + + handler() + advanceUntilIdle() + + verify(arrangement.conversationRepository) + .suspendFunction(arrangement.conversationRepository::updateVerificationStatus) + .with(any(), any()) + .wasNotInvoked() + + verify(arrangement.persistMessageUseCase) + .suspendFunction(arrangement.persistMessageUseCase::invoke) + .with(any()) + .wasNotInvoked() + } + + @Test + fun givenVerifiedConversation_whenNotVerifiedStatusComes_thenStatusSetToDegradedAndSystemMessageAdded() = runTest { + val conversationDetails = TestConversationDetails.CONVERSATION_GROUP.copy( + conversation = TestConversation.MLS_CONVERSATION.copy( + verificationStatus = Conversation.VerificationStatus.VERIFIED + ) + ) + val (arrangement, handler) = arrange { + withObserveEpochChanges(flowOf(TestConversation.GROUP_ID)) + withMLSConversationVerificationStatus(Either.Right(Conversation.VerificationStatus.NOT_VERIFIED)) + withConversationDetailsByMLSGroupId(Either.Right(conversationDetails)) + } + + handler() + advanceUntilIdle() + + verify(arrangement.conversationRepository) + .suspendFunction(arrangement.conversationRepository::updateVerificationStatus) + .with(eq(Conversation.VerificationStatus.DEGRADED), eq(conversationDetails.conversation.id)) + .wasInvoked(once) + + verify(arrangement.persistMessageUseCase) + .suspendFunction(arrangement.persistMessageUseCase::invoke) + .with(anyInstanceOf(Message.System::class)) + .wasInvoked(once) + } + + @Test + fun givenDegradedConversation_whenNotVerifiedStatusComes_thenNothingChanged() = runTest { + val conversationDetails = TestConversationDetails.CONVERSATION_GROUP.copy( + conversation = TestConversation.MLS_CONVERSATION + .copy(verificationStatus = Conversation.VerificationStatus.DEGRADED) + ) + val (arrangement, handler) = arrange { + withObserveEpochChanges(flowOf(TestConversation.GROUP_ID)) + withMLSConversationVerificationStatus(Either.Right(Conversation.VerificationStatus.NOT_VERIFIED)) + withConversationDetailsByMLSGroupId(Either.Right(conversationDetails)) + } + + handler() + advanceUntilIdle() + + verify(arrangement.conversationRepository) + .suspendFunction(arrangement.conversationRepository::updateVerificationStatus) + .with(any(), any()) + .wasNotInvoked() + + verify(arrangement.persistMessageUseCase) + .suspendFunction(arrangement.persistMessageUseCase::invoke) + .with(any()) + .wasNotInvoked() + } + + @Test + fun givenDegradedConversation_whenVerifiedStatusComes_thenStatusUpdated() = runTest { + val conversationDetails = TestConversationDetails.CONVERSATION_GROUP.copy( + conversation = TestConversation.MLS_CONVERSATION.copy( + verificationStatus = Conversation.VerificationStatus.DEGRADED + ) + ) + val (arrangement, handler) = arrange { + withObserveEpochChanges(flowOf(TestConversation.GROUP_ID)) + withMLSConversationVerificationStatus(Either.Right(Conversation.VerificationStatus.VERIFIED)) + withConversationDetailsByMLSGroupId(Either.Right(conversationDetails)) + } + + handler() + advanceUntilIdle() + + verify(arrangement.conversationRepository) + .suspendFunction(arrangement.conversationRepository::updateVerificationStatus) + .with(eq(Conversation.VerificationStatus.VERIFIED), eq(conversationDetails.conversation.id)) + .wasInvoked(once) + + verify(arrangement.persistMessageUseCase) + .suspendFunction(arrangement.persistMessageUseCase::invoke) + .with(anyInstanceOf(Message.System::class)) + .wasNotInvoked() + } + + private fun arrange(block: Arrangement.() -> Unit) = Arrangement(block).arrange() + + private class Arrangement( + private val block: Arrangement.() -> Unit + ) : ConversationRepositoryArrangement by ConversationRepositoryArrangementImpl(), + PersistMessageUseCaseArrangement by PersistMessageUseCaseArrangementImpl(), + MLSConversationRepositoryArrangement by MLSConversationRepositoryArrangementImpl() { + + + init { + withUpdateVerificationStatus(Either.Right(Unit)) + withPersistingMessage(Either.Right(Unit)) + } + + fun arrange() = block().run { + this@Arrangement to MLSConversationsVerificationStatusesHandlerImpl( + conversationRepository = conversationRepository, + persistMessage = persistMessageUseCase, + mlsConversationRepository = mlsConversationRepository, + selfUserId = TestUser.USER_ID + ) + } + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationDetailsUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationDetailsUseCaseTest.kt index b678b18be3b..dd48b715833 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationDetailsUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationDetailsUseCaseTest.kt @@ -27,7 +27,9 @@ import com.wire.kalium.logic.data.conversation.LegalHoldStatus import com.wire.kalium.logic.framework.TestConversation import com.wire.kalium.logic.functional.Either import io.mockative.Mock +import io.mockative.any import io.mockative.anything +import io.mockative.classOf import io.mockative.eq import io.mockative.given import io.mockative.mock diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationAccessUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationAccessUseCaseTest.kt index 94be1e52aad..4dd6f6c0ddc 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationAccessUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationAccessUseCaseTest.kt @@ -31,6 +31,7 @@ import com.wire.kalium.logic.data.id.TeamId import com.wire.kalium.logic.framework.TestConversation import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.sync.SyncManager +import com.wire.kalium.persistence.dao.conversation.ConversationEntity import io.mockative.Mock import io.mockative.any import io.mockative.classOf @@ -408,7 +409,8 @@ class UpdateConversationAccessUseCaseTest { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestConversation.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestConversation.kt index b4911825857..faca03c87fa 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestConversation.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestConversation.kt @@ -75,7 +75,8 @@ object TestConversation { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) fun SELF(protocolInfo: ProtocolInfo = ProtocolInfo.Proteus) = Conversation( @@ -96,7 +97,8 @@ object TestConversation { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) fun GROUP(protocolInfo: ProtocolInfo = ProtocolInfo.Proteus) = Conversation( @@ -117,7 +119,8 @@ object TestConversation { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) fun GROUP_VIEW_ENTITY(protocolInfo: ConversationEntity.ProtocolInfo = ConversationEntity.ProtocolInfo.Proteus) = ConversationViewEntity( @@ -159,7 +162,8 @@ object TestConversation { userMessageTimer = null, userDefederated = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) fun one_on_one(convId: ConversationId) = Conversation( @@ -180,7 +184,8 @@ object TestConversation { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) val NETWORK_ID = QualifiedID("valueConversation", "domainConversation") @@ -270,7 +275,8 @@ object TestConversation { messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val VIEW_ENTITY = ConversationViewEntity( id = ENTITY_ID, @@ -309,7 +315,8 @@ object TestConversation { userMessageTimer = null, userDefederated = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val CONVERSATION = Conversation( @@ -330,21 +337,23 @@ object TestConversation { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) + val MLS_PROTOCOL_INFO = ProtocolInfo.MLS( + GROUP_ID, + ProtocolInfo.MLS.GroupState.PENDING_JOIN, + 0UL, + Instant.parse("2021-03-30T15:36:00.000Z"), + cipherSuite = Conversation.CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 + ) val MLS_CONVERSATION = Conversation( ConversationId("conv_id", "domain"), "MLS Name", Conversation.Type.ONE_ON_ONE, TestTeam.TEAM_ID, - ProtocolInfo.MLS( - GROUP_ID, - ProtocolInfo.MLS.GroupState.PENDING_JOIN, - 0UL, - Instant.parse("2021-03-30T15:36:00.000Z"), - cipherSuite = Conversation.CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 - ), + MLS_PROTOCOL_INFO, MutedConversationStatus.AllAllowed, null, null, @@ -357,7 +366,8 @@ object TestConversation { messageTimer = null, userMessageTimer = null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED ) val CONVERSATION_CODE_INFO: ConversationCodeInfo = ConversationCodeInfo("conv_id_value", "name") diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSWrongEpochHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSWrongEpochHandlerTest.kt index 0fdb24d94f1..055f37d99b7 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSWrongEpochHandlerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSWrongEpochHandlerTest.kt @@ -37,11 +37,9 @@ import io.mockative.matching import io.mockative.mock import io.mockative.once import io.mockative.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlin.test.Test -@OptIn(ExperimentalCoroutinesApi::class) class MLSWrongEpochHandlerTest { @Test diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/ConversationRepositoryArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/ConversationRepositoryArrangement.kt index 9302f5d24ac..8830f761c9f 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/ConversationRepositoryArrangement.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/ConversationRepositoryArrangement.kt @@ -20,6 +20,7 @@ package com.wire.kalium.logic.util.arrangement.repository import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.conversation.Conversation +import com.wire.kalium.logic.data.conversation.ConversationDetails import com.wire.kalium.logic.data.conversation.ConversationRepository import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId @@ -48,6 +49,11 @@ internal interface ConversationRepositoryArrangement { fun withDeletingConversationSucceeding(conversationId: Matcher = any()) fun withDeletingConversationFailing(conversationId: Matcher = any()) fun withGetConversation(conversation: Conversation? = TestConversation.CONVERSATION) + fun withSetInformedAboutDegradedMLSVerificationFlagResult(result: Either = Either.Right(Unit)) + fun withInformedAboutDegradedMLSVerification(isInformed: Either): ConversationRepositoryArrangementImpl + fun withConversationProtocolInfo(result: Either): ConversationRepositoryArrangementImpl + fun withUpdateVerificationStatus(result: Either): ConversationRepositoryArrangementImpl + fun withConversationDetailsByMLSGroupId(result: Either): ConversationRepositoryArrangementImpl } internal open class ConversationRepositoryArrangementImpl : ConversationRepositoryArrangement { @@ -95,4 +101,39 @@ internal open class ConversationRepositoryArrangementImpl : ConversationReposito .whenInvokedWith(any()) .thenReturn(conversation) } + + override fun withSetInformedAboutDegradedMLSVerificationFlagResult(result: Either) { + given(conversationRepository) + .suspendFunction(conversationRepository::setInformedAboutDegradedMLSVerificationFlag) + .whenInvokedWith(any()) + .thenReturn(result) + } + + override fun withInformedAboutDegradedMLSVerification(isInformed: Either) = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::isInformedAboutDegradedMLSVerification) + .whenInvokedWith(any()) + .thenReturn(isInformed) + } + + override fun withConversationProtocolInfo(result: Either) = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::getConversationProtocolInfo) + .whenInvokedWith(any()) + .thenReturn(result) + } + + override fun withUpdateVerificationStatus(result: Either) = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::updateVerificationStatus) + .whenInvokedWith(any()) + .thenReturn(result) + } + + override fun withConversationDetailsByMLSGroupId(result: Either) = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::getConversationDetailsByMLSGroupId) + .whenInvokedWith(any()) + .thenReturn(result) + } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/MLSConversationRepositoryArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/MLSConversationRepositoryArrangement.kt new file mode 100644 index 00000000000..c6c60f03acf --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/MLSConversationRepositoryArrangement.kt @@ -0,0 +1,55 @@ +/* + * 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.repository + +import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.data.conversation.Conversation +import com.wire.kalium.logic.data.conversation.MLSConversationRepository +import com.wire.kalium.logic.data.id.GroupID +import com.wire.kalium.logic.functional.Either +import io.mockative.Mock +import io.mockative.any +import io.mockative.given +import io.mockative.mock +import kotlinx.coroutines.flow.Flow + +internal interface MLSConversationRepositoryArrangement { + val mlsConversationRepository: MLSConversationRepository + + fun withObserveEpochChanges(result: Flow) + fun withMLSConversationVerificationStatus(result: Either) +} + +internal open class MLSConversationRepositoryArrangementImpl : MLSConversationRepositoryArrangement { + @Mock + override val mlsConversationRepository: MLSConversationRepository = mock(MLSConversationRepository::class) + + override fun withObserveEpochChanges(result: Flow) { + given(mlsConversationRepository) + .suspendFunction(mlsConversationRepository::observeEpochChanges) + .whenInvoked() + .thenReturn(result) + } + + override fun withMLSConversationVerificationStatus(result: Either) { + given(mlsConversationRepository) + .suspendFunction(mlsConversationRepository::getConversationVerificationStatus) + .whenInvokedWith(any()) + .thenReturn(result) + } +} diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq index 32dc01d17f9..6dd7da59087 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq @@ -39,7 +39,8 @@ CREATE TABLE Conversation ( mls_degraded_notified INTEGER AS Boolean NOT NULL DEFAULT 0, is_guest_password_protected INTEGER AS Boolean DEFAULT 0 NOT NULL, archived INTEGER AS Boolean NOT NULL DEFAULT 0, - archived_date_time INTEGER AS Instant + archived_date_time INTEGER AS Instant, + verification_status TEXT AS ConversationEntity.VerificationStatus NOT NULL DEFAULT "NOT_VERIFIED" ); -- Optimise comparisons and sorting by dates: @@ -214,7 +215,8 @@ Conversation.message_timer, Conversation.user_message_timer, Conversation.incomplete_metadata, Conversation.archived, -Conversation.archived_date_time +Conversation.archived_date_time, +Conversation.verification_status FROM Conversation LEFT JOIN Member ON Conversation.qualified_id = Member.conversation AND Conversation.type IS 'ONE_ON_ONE' @@ -250,7 +252,7 @@ selectByQualifiedId: SELECT * FROM ConversationDetails WHERE qualifiedId = ?; selectConversationByQualifiedId: -SELECT qualified_id, name, type, team_id, mls_group_id, mls_group_state, mls_epoch, mls_proposal_timer, protocol, muted_status, muted_time, creator_id, last_modified_date, last_notified_date, last_read_date, access_list, access_role_list, mls_last_keying_material_update_date, mls_cipher_suite, receipt_mode, message_timer, user_message_timer, archived, archived_date_time FROM Conversation WHERE qualified_id = ?; +SELECT qualified_id, name, type, team_id, mls_group_id, mls_group_state, mls_epoch, mls_proposal_timer, protocol, muted_status, muted_time, creator_id, last_modified_date, last_notified_date, last_read_date, access_list, access_role_list, mls_last_keying_material_update_date, mls_cipher_suite, receipt_mode, message_timer, user_message_timer, archived, archived_date_time, verification_status FROM Conversation WHERE qualified_id = ?; selectProtocolInfoByQualifiedId: SELECT protocol, mls_group_id, mls_group_state, mls_epoch , @@ -343,6 +345,11 @@ SELECT qualified_id FROM Conversation WHERE type IS NOT 'SELF' AND incomplete_metadata = 1; +updateVerificationStatus: +UPDATE Conversation +SET verification_status = ? +WHERE qualified_id = ?; + clearContent { DELETE FROM Asset WHERE key IN (SELECT asset_id FROM MessageAssetContent WHERE conversation_id = :conversationId); DELETE FROM Message WHERE conversation_id = :conversationId; diff --git a/persistence/src/commonMain/db_user/migrations/58.sqm b/persistence/src/commonMain/db_user/migrations/58.sqm new file mode 100644 index 00000000000..3fc1354030e --- /dev/null +++ b/persistence/src/commonMain/db_user/migrations/58.sqm @@ -0,0 +1,98 @@ +import com.wire.kalium.persistence.dao.conversation.ConversationEntity; + +ALTER TABLE Conversation ADD COLUMN verification_status TEXT AS ConversationEntity.VerificationStatus NOT NULL DEFAULT "NOT_VERIFIED" ; + +DROP VIEW IF EXISTS ConversationDetails; + +CREATE VIEW IF NOT EXISTS ConversationDetails AS +SELECT +Conversation.qualified_id AS qualifiedId, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.name + WHEN 'CONNECTION_PENDING' THEN connection_user.name + ELSE Conversation.name +END AS name, +Conversation.type, +Call.status AS callStatus, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.preview_asset_id + WHEN 'CONNECTION_PENDING' THEN connection_user.preview_asset_id +END AS previewAssetId, +Conversation.muted_status AS mutedStatus, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.team + ELSE Conversation.team_id +END AS teamId, +CASE (Conversation.type) + WHEN 'CONNECTION_PENDING' THEN Connection.last_update_date + ELSE Conversation.last_modified_date +END AS lastModifiedDate, +Conversation.last_read_date AS lastReadDate, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.user_availability_status + WHEN 'CONNECTION_PENDING' THEN connection_user.user_availability_status +END AS userAvailabilityStatus, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.user_type + WHEN 'CONNECTION_PENDING' THEN connection_user.user_type +END AS userType, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.bot_service + WHEN 'CONNECTION_PENDING' THEN connection_user.bot_service +END AS botService, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.deleted + WHEN 'CONNECTION_PENDING' THEN connection_user.deleted +END AS userDeleted, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.defederated + WHEN 'CONNECTION_PENDING' THEN connection_user.defederated +END AS userDefederated, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.connection_status + WHEN 'CONNECTION_PENDING' THEN connection_user.connection_status +END AS connectionStatus, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.qualified_id + WHEN 'CONNECTION_PENDING' THEN connection_user.qualified_id +END AS otherUserId, +CASE + WHEN ((SELECT id FROM SelfUser LIMIT 1) LIKE (Conversation.creator_id || '@%')) THEN 1 + ELSE 0 +END AS isCreator, +Conversation.last_notified_date AS lastNotifiedMessageDate, +memberRole. role AS selfRole, +Conversation.protocol, +Conversation.mls_cipher_suite, +Conversation.mls_epoch, +Conversation.mls_group_id, +Conversation.mls_last_keying_material_update_date, +Conversation.mls_group_state, +Conversation.access_list, +Conversation.access_role_list, +Conversation.team_id, +Conversation.mls_proposal_timer, +Conversation.muted_time, +Conversation.creator_id, +Conversation.last_modified_date, +Conversation.receipt_mode, +Conversation.message_timer, +Conversation.user_message_timer, +Conversation.incomplete_metadata, +Conversation.archived, +Conversation.archived_date_time, +Conversation.verification_status +FROM Conversation +LEFT JOIN Member ON Conversation.qualified_id = Member.conversation + AND Conversation.type IS 'ONE_ON_ONE' + AND Member.user IS NOT (SELECT SelfUser.id FROM SelfUser LIMIT 1) +LEFT JOIN Member AS memberRole ON Conversation.qualified_id = memberRole.conversation + AND memberRole.user IS (SELECT SelfUser.id FROM SelfUser LIMIT 1) +LEFT JOIN User ON User.qualified_id = Member.user +LEFT JOIN Connection ON Connection.qualified_conversation = Conversation.qualified_id + AND (Connection.status = 'SENT' + OR Connection.status = 'PENDING' + OR Connection.status = 'NOT_CONNECTED' + AND Conversation.type IS 'CONNECTION_PENDING') +LEFT JOIN User AS connection_user ON Connection.qualified_to = connection_user.qualified_id +LEFT JOIN Call ON Call.id IS (SELECT id FROM Call WHERE Call.conversation_id = Conversation.qualified_id AND Call.status IS 'STILL_ONGOING' ORDER BY created_at DESC LIMIT 1); diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt index 20ac8ecb1b0..1f1886ddfb3 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt @@ -47,7 +47,7 @@ interface ConversationDAO { suspend fun getConversationByQualifiedID(qualifiedID: QualifiedIDEntity): ConversationViewEntity? suspend fun observeConversationWithOtherUser(userId: UserIDEntity): Flow suspend fun getConversationProtocolInfo(qualifiedID: QualifiedIDEntity): ConversationEntity.ProtocolInfo? - suspend fun getConversationByGroupID(groupID: String): Flow + suspend fun observeConversationByGroupID(groupID: String): Flow suspend fun getConversationIdByGroupID(groupID: String): QualifiedIDEntity? suspend fun getConversationsByGroupState(groupState: ConversationEntity.GroupState): List suspend fun deleteConversationByQualifiedID(qualifiedID: QualifiedIDEntity) @@ -92,4 +92,6 @@ interface ConversationDAO { suspend fun updateUserMessageTimer(conversationId: QualifiedIDEntity, messageTimer: Long?) suspend fun getConversationsWithoutMetadata(): List suspend fun clearContent(conversationId: QualifiedIDEntity) + suspend fun updateVerificationStatus(verificationStatus: ConversationEntity.VerificationStatus, conversationId: QualifiedIDEntity) + suspend fun getConversationByGroupID(groupID: String): ConversationViewEntity } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt index 9eabf7c69e8..3d6b5fd3b17 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt @@ -189,7 +189,7 @@ internal class ConversationDAOImpl internal constructor( conversationQueries.selectProtocolInfoByQualifiedId(qualifiedID, conversationMapper::mapProtocolInfo).executeAsOneOrNull() } - override suspend fun getConversationByGroupID(groupID: String): Flow { + override suspend fun observeConversationByGroupID(groupID: String): Flow { return conversationQueries.selectByGroupId(groupID) .asFlow() .flowOn(coroutineContext) @@ -197,6 +197,12 @@ internal class ConversationDAOImpl internal constructor( .map { it?.let { conversationMapper.toModel(it) } } } + override suspend fun getConversationByGroupID(groupID: String): ConversationViewEntity { + return conversationQueries.selectByGroupId(groupID) + .executeAsOne() + .let { it.let { conversationMapper.toModel(it) } } + } + override suspend fun getConversationIdByGroupID(groupID: String) = withContext(coroutineContext) { conversationQueries.getConversationIdByGroupId(groupID).executeAsOneOrNull() } @@ -329,4 +335,11 @@ internal class ConversationDAOImpl internal constructor( override suspend fun clearContent(conversationId: QualifiedIDEntity) = withContext(coroutineContext) { conversationQueries.clearContent(conversationId) } + + override suspend fun updateVerificationStatus( + verificationStatus: ConversationEntity.VerificationStatus, + conversationId: QualifiedIDEntity + ) = withContext(coroutineContext) { + conversationQueries.updateVerificationStatus(verificationStatus, conversationId) + } } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationEntity.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationEntity.kt index caa03d4037a..2fa67582168 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationEntity.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationEntity.kt @@ -43,7 +43,8 @@ data class ConversationEntity( val userMessageTimer: Long?, val hasIncompleteMetadata: Boolean = false, val archived: Boolean = false, - val archivedInstant: Instant? + val archivedInstant: Instant?, + val verificationStatus: VerificationStatus ) { enum class AccessRole { TEAM_MEMBER, NON_TEAM_MEMBER, GUEST, SERVICE, EXTERNAL; } @@ -55,6 +56,7 @@ data class ConversationEntity( enum class Protocol { PROTEUS, MLS } enum class ReceiptMode { DISABLED, ENABLED } + enum class VerificationStatus { VERIFIED, NOT_VERIFIED, DEGRADED } @Suppress("MagicNumber") enum class CipherSuite(val cipherSuiteTag: Int) { diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationMapper.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationMapper.kt index f65f3f539bd..66a277f8eac 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationMapper.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationMapper.kt @@ -66,7 +66,8 @@ internal class ConversationMapper { userMessageTimer = user_message_timer, userDefederated = userDefederated, archived = archived, - archivedDateTime = archived_date_time + archivedDateTime = archived_date_time, + verificationStatus = verification_status ) } @@ -95,7 +96,8 @@ internal class ConversationMapper { messageTimer: Long?, userMessageTimer: Long?, archived: Boolean, - archivedDateTime: Instant? + archivedDateTime: Instant?, + verificationStatus: ConversationEntity.VerificationStatus ) = ConversationEntity( id = qualifiedId, name = name, @@ -121,7 +123,8 @@ internal class ConversationMapper { messageTimer = messageTimer, userMessageTimer = userMessageTimer, archived = archived, - archivedInstant = archivedDateTime + archivedInstant = archivedDateTime, + verificationStatus = verificationStatus ) fun fromOneToOneToModel(conversation: SelectConversationByMember?): ConversationViewEntity? { @@ -169,7 +172,8 @@ internal class ConversationMapper { userMessageTimer = user_message_timer, userDefederated = userDefederated, archived = archived, - archivedDateTime = archived_date_time + archivedDateTime = archived_date_time, + verificationStatus = verification_status ) } } @@ -181,7 +185,7 @@ internal class ConversationMapper { mlsGroupState: ConversationEntity.GroupState, mlsEpoch: Long, mlsLastKeyingMaterialUpdate: Instant, - mlsCipherSuite: ConversationEntity.CipherSuite, + mlsCipherSuite: ConversationEntity.CipherSuite ): ConversationEntity.ProtocolInfo { return when (protocol) { ConversationEntity.Protocol.MLS -> ConversationEntity.ProtocolInfo.MLS( diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationViewEntity.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationViewEntity.kt index 1a955d4d5f0..15afb228259 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationViewEntity.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationViewEntity.kt @@ -65,7 +65,8 @@ data class ConversationViewEntity( val messageTimer: Long?, val userMessageTimer: Long?, val archived: Boolean, - val archivedDateTime: Instant? + val archivedDateTime: Instant?, + val verificationStatus: ConversationEntity.VerificationStatus ) { val isMember: Boolean get() = selfRole != null diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/TableMapper.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/TableMapper.kt index 0f4215ae42a..04fa1100167 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/TableMapper.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/TableMapper.kt @@ -95,7 +95,8 @@ internal object TableMapper { last_modified_dateAdapter = InstantTypeAdapter, last_notified_dateAdapter = InstantTypeAdapter, mls_last_keying_material_update_dateAdapter = InstantTypeAdapter, - archived_date_timeAdapter = InstantTypeAdapter + archived_date_timeAdapter = InstantTypeAdapter, + verification_statusAdapter = EnumColumnAdapter() ) val memberAdapter = Member.Adapter( userAdapter = QualifiedIDAdapter, diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/DatabaseImporterTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/DatabaseImporterTest.kt index a4c5acceaa5..bc2ae29bb0a 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/DatabaseImporterTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/DatabaseImporterTest.kt @@ -653,7 +653,8 @@ class DatabaseImporterTest : BaseDatabaseTest() { messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = verificationStatus ) } } @@ -691,7 +692,8 @@ class DatabaseImporterTest : BaseDatabaseTest() { messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) conversationAdded.add(overlappingConversation) diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/UserDatabaseDataGenerator.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/UserDatabaseDataGenerator.kt index 312bd241ea3..6515095b145 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/UserDatabaseDataGenerator.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/UserDatabaseDataGenerator.kt @@ -279,7 +279,8 @@ class UserDatabaseDataGenerator( messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) ) @@ -323,7 +324,8 @@ class UserDatabaseDataGenerator( messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) userDatabaseBuilder.conversationDAO.insertConversation(conversation) return conversation @@ -391,7 +393,8 @@ class UserDatabaseDataGenerator( messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) userDatabaseBuilder.conversationDAO.insertConversation(conversationEntity) @@ -458,7 +461,8 @@ class UserDatabaseDataGenerator( messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) ) @@ -503,7 +507,8 @@ class UserDatabaseDataGenerator( messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) ) @@ -617,7 +622,8 @@ class UserDatabaseDataGenerator( messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) ) diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt index 6bbff7247af..591b37e57e8 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt @@ -127,7 +127,7 @@ class ConversationDAOTest : BaseDatabaseTest() { conversationDAO.insertConversation(conversationEntity2) insertTeamUserAndMember(team, user2, conversationEntity2.id) val result = - conversationDAO.getConversationByGroupID((conversationEntity2.protocolInfo as ConversationEntity.ProtocolInfo.MLS).groupId) + conversationDAO.observeConversationByGroupID((conversationEntity2.protocolInfo as ConversationEntity.ProtocolInfo.MLS).groupId) .first() assertEquals(conversationEntity2.toViewEntity(user2), result) } @@ -332,7 +332,7 @@ class ConversationDAOTest : BaseDatabaseTest() { // when conversationDAO.updateKeyingMaterial(conversationProtocolInfo.groupId, newUpdate) // then - assertEquals(expected, conversationDAO.getConversationByGroupID(conversationProtocolInfo.groupId).first()?.protocolInfo) + assertEquals(expected, conversationDAO.observeConversationByGroupID(conversationProtocolInfo.groupId).first()?.protocolInfo) } @Test @@ -1044,7 +1044,8 @@ class ConversationDAOTest : BaseDatabaseTest() { userMessageTimer = null, userDefederated = if (type == ConversationEntity.Type.ONE_ON_ONE) userEntity?.defederated else null, archived = false, - archivedDateTime = null + archivedDateTime = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) } @@ -1075,7 +1076,8 @@ class ConversationDAOTest : BaseDatabaseTest() { messageTimer = messageTimer, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val conversationEntity2 = ConversationEntity( QualifiedIDEntity("2", "wire.com"), @@ -1100,7 +1102,8 @@ class ConversationDAOTest : BaseDatabaseTest() { messageTimer = messageTimer, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val conversationEntity3 = ConversationEntity( @@ -1128,7 +1131,8 @@ class ConversationDAOTest : BaseDatabaseTest() { messageTimer = messageTimer, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val conversationEntity4 = ConversationEntity( @@ -1156,7 +1160,8 @@ class ConversationDAOTest : BaseDatabaseTest() { messageTimer = messageTimer, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val member1 = MemberEntity(user1.id, MemberEntity.Role.Admin) diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationMetaDataDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationMetaDataDAOTest.kt index 972715d73c0..508e666440b 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationMetaDataDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationMetaDataDAOTest.kt @@ -84,7 +84,8 @@ class ConversationMetaDataDAOTest : BaseDatabaseTest() { messageTimer = 5000L, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) } } diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt index 8e78dafa82f..0b1149b154c 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt @@ -368,7 +368,8 @@ class ClientDAOTest : BaseDatabaseTest() { messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) } } diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/ConversationStubs.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/ConversationStubs.kt index 26ee100a4f8..8e415d6267a 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/ConversationStubs.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/ConversationStubs.kt @@ -39,7 +39,8 @@ fun newConversationEntity(id: String = "test") = ConversationEntity( messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) fun newConversationEntity( @@ -62,5 +63,6 @@ fun newConversationEntity( messageTimer = null, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/TestStubs.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/TestStubs.kt index fe7fe090e3a..06ad49d36e1 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/TestStubs.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/TestStubs.kt @@ -52,7 +52,8 @@ internal object TestStubs { messageTimer = messageTimer, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val conversationEntity2 = ConversationEntity( QualifiedIDEntity("2", "wire.com"), @@ -77,7 +78,8 @@ internal object TestStubs { messageTimer = messageTimer, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val conversationEntity3 = ConversationEntity( @@ -105,7 +107,8 @@ internal object TestStubs { messageTimer = messageTimer, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val conversationEntity4 = ConversationEntity( @@ -133,7 +136,8 @@ internal object TestStubs { messageTimer = messageTimer, userMessageTimer = null, archived = false, - archivedInstant = null + archivedInstant = null, + verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED ) val member1 = MemberEntity(user1.id, MemberEntity.Role.Admin)