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 732e116824a..1fc0dce8fb1 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 @@ -85,6 +85,11 @@ interface ConversationMapper { fun legalHoldStatusFromEntity(legalHoldStatus: ConversationEntity.LegalHoldStatus): Conversation.LegalHoldStatus fun fromConversationEntityType(type: ConversationEntity.Type): Conversation.Type + + fun fromModelToDAOAccess(accessList: Set): List + fun fromModelToDAOAccessRole(accessRoleList: Set): List + fun fromApiModelToAccessModel(accessList: Set): Set + fun fromApiModelToAccessRoleModel(accessRoleList: Set): Set } @Suppress("TooManyFunctions", "LongParameterList") @@ -461,6 +466,18 @@ internal class ConversationMapperImpl( override fun fromConversationEntityType(type: ConversationEntity.Type): Conversation.Type { return type.fromDaoModelToType() } + + override fun fromModelToDAOAccess(accessList: Set): List = + accessList.map { it.toDAO() } + + override fun fromModelToDAOAccessRole(accessRoleList: Set): List = + accessRoleList.map { it.toDAO() } + + override fun fromApiModelToAccessModel(accessList: Set): Set = + accessList.map { it.toModel() }.toSet() + + override fun fromApiModelToAccessRoleModel(accessRoleList: Set): Set = + accessRoleList.map { it.toModel() }.toSet() } internal fun ConversationResponse.toConversationType(selfUserTeamId: TeamId?): ConversationEntity.Type { @@ -553,6 +570,22 @@ private fun Conversation.Access.toDAO(): ConversationEntity.Access = when (this) Conversation.Access.CODE -> ConversationEntity.Access.CODE } +private fun ConversationAccessDTO.toModel(): Conversation.Access = when (this) { + ConversationAccessDTO.PRIVATE -> Conversation.Access.PRIVATE + ConversationAccessDTO.CODE -> Conversation.Access.CODE + ConversationAccessDTO.INVITE -> Conversation.Access.INVITE + ConversationAccessDTO.SELF_INVITE -> Conversation.Access.SELF_INVITE + ConversationAccessDTO.LINK -> Conversation.Access.LINK +} + +private fun ConversationAccessRoleDTO.toModel(): Conversation.AccessRole = when (this) { + ConversationAccessRoleDTO.TEAM_MEMBER -> Conversation.AccessRole.TEAM_MEMBER + ConversationAccessRoleDTO.NON_TEAM_MEMBER -> Conversation.AccessRole.NON_TEAM_MEMBER + ConversationAccessRoleDTO.GUEST -> Conversation.AccessRole.GUEST + ConversationAccessRoleDTO.SERVICE -> Conversation.AccessRole.SERVICE + ConversationAccessRoleDTO.EXTERNAL -> Conversation.AccessRole.EXTERNAL +} + internal fun Conversation.Protocol.toApi(): ConvProtocol = when (this) { Conversation.Protocol.PROTEUS -> ConvProtocol.PROTEUS Conversation.Protocol.MIXED -> ConvProtocol.MIXED diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/Event.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/Event.kt index ea9fdebba3a..ab0f10ef26a 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/Event.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/Event.kt @@ -23,6 +23,8 @@ import com.wire.kalium.logger.obfuscateDomain import com.wire.kalium.logger.obfuscateId import com.wire.kalium.logic.data.client.Client import com.wire.kalium.logic.data.conversation.ClientId +import com.wire.kalium.logic.data.conversation.Conversation.Access +import com.wire.kalium.logic.data.conversation.Conversation.AccessRole import com.wire.kalium.logic.data.conversation.Conversation.Member import com.wire.kalium.logic.data.conversation.Conversation.Protocol import com.wire.kalium.logic.data.conversation.Conversation.ReceiptMode @@ -124,7 +126,8 @@ sealed class Event(open val id: String) { data class AccessUpdate( override val id: String, override val conversationId: ConversationId, - val data: ConversationResponse, + val access: Set, + val accessRole: Set, val qualifiedFrom: UserId, ) : Conversation(id, conversationId) { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/EventMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/EventMapper.kt index d907751c978..ba1fb539fd8 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/EventMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/EventMapper.kt @@ -23,6 +23,7 @@ import com.wire.kalium.logic.data.client.ClientMapper import com.wire.kalium.logic.data.connection.ConnectionMapper import com.wire.kalium.logic.data.conversation.ClientId import com.wire.kalium.logic.data.conversation.Conversation +import com.wire.kalium.logic.data.conversation.ConversationMapper import com.wire.kalium.logic.data.conversation.ConversationRoleMapper import com.wire.kalium.logic.data.conversation.MemberMapper import com.wire.kalium.logic.data.conversation.MutedConversationStatus @@ -64,7 +65,8 @@ class EventMapper( private val selfUserId: UserId, private val receiptModeMapper: ReceiptModeMapper = MapperProvider.receiptModeMapper(), private val clientMapper: ClientMapper = MapperProvider.clientMapper(), - private val qualifiedIdMapper: QualifiedIdMapper = MapperProvider.qualifiedIdMapper(selfUserId) + private val qualifiedIdMapper: QualifiedIdMapper = MapperProvider.qualifiedIdMapper(selfUserId), + private val conversationMapper: ConversationMapper = MapperProvider.conversationMapper(selfUserId) ) { fun fromDTO(eventResponse: EventResponse, isLive: Boolean): List { // TODO(edge-case): Multiple payloads in the same event have the same ID, is this an issue when marking lastProcessedEventId? @@ -94,7 +96,7 @@ class EventMapper( is EventContentDTO.User.LegalHoldDisabledDTO -> legalHoldDisabled(id, eventContentDTO) is EventContentDTO.FeatureConfig.FeatureConfigUpdatedDTO -> featureConfig(id, eventContentDTO) is EventContentDTO.Unknown -> unknown(id, eventContentDTO) - is EventContentDTO.Conversation.AccessUpdate -> unknown(id, eventContentDTO) + is EventContentDTO.Conversation.AccessUpdate -> conversationAccessUpdate(id, eventContentDTO) is EventContentDTO.Conversation.DeletedConversationDTO -> conversationDeleted(id, eventContentDTO) is EventContentDTO.Conversation.ConversationRenameDTO -> conversationRenamed(id, eventContentDTO) is EventContentDTO.Team.MemberLeave -> teamMemberLeft(id, eventContentDTO) @@ -194,6 +196,17 @@ class EventMapper( dateTime = eventContentDTO.time ) + private fun conversationAccessUpdate( + id: String, + eventContentDTO: EventContentDTO.Conversation.AccessUpdate + ): Event = Event.Conversation.AccessUpdate( + id = id, + conversationId = eventContentDTO.qualifiedConversation.toModel(), + access = conversationMapper.fromApiModelToAccessModel(eventContentDTO.data.access), + accessRole = conversationMapper.fromApiModelToAccessRoleModel(eventContentDTO.data.accessRole), + qualifiedFrom = eventContentDTO.qualifiedFrom.toModel() + ) + private fun conversationReceiptModeUpdate( id: String, eventContentDTO: EventContentDTO.Conversation.ReceiptModeUpdate, 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 0f0655d59b7..4c0c5e9ed49 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 @@ -381,6 +381,7 @@ import com.wire.kalium.logic.sync.receiver.UserPropertiesEventReceiver import com.wire.kalium.logic.sync.receiver.UserPropertiesEventReceiverImpl import com.wire.kalium.logic.sync.receiver.asset.AssetMessageHandler import com.wire.kalium.logic.sync.receiver.asset.AssetMessageHandlerImpl +import com.wire.kalium.logic.sync.receiver.conversation.AccessUpdateEventHandler import com.wire.kalium.logic.sync.receiver.conversation.ConversationMessageTimerEventHandler import com.wire.kalium.logic.sync.receiver.conversation.ConversationMessageTimerEventHandlerImpl import com.wire.kalium.logic.sync.receiver.conversation.DeletedConversationEventHandler @@ -1466,6 +1467,12 @@ class UserSessionScope internal constructor( callRepository = callRepository ) + private val conversationAccessUpdateEventHandler: AccessUpdateEventHandler + get() = AccessUpdateEventHandler( + conversationDAO = userStorage.database.conversationDAO, + selfUserId = userId + ) + private val conversationEventReceiver: ConversationEventReceiver by lazy { ConversationEventReceiverImpl( newMessageHandler, @@ -1481,7 +1488,8 @@ class UserSessionScope internal constructor( conversationCodeUpdateHandler, conversationCodeDeletedHandler, typingIndicatorHandler, - protocolUpdateEventHandler + protocolUpdateEventHandler, + conversationAccessUpdateEventHandler ) } override val coroutineContext: CoroutineContext = SupervisorJob() diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/ConversationEventReceiver.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/ConversationEventReceiver.kt index 6381ff6c8ca..d6e5ae16ed6 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/ConversationEventReceiver.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/ConversationEventReceiver.kt @@ -22,6 +22,7 @@ import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.event.EventDeliveryInfo import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.sync.receiver.conversation.AccessUpdateEventHandler import com.wire.kalium.logic.sync.receiver.conversation.ConversationMessageTimerEventHandler import com.wire.kalium.logic.sync.receiver.conversation.DeletedConversationEventHandler import com.wire.kalium.logic.sync.receiver.conversation.MLSWelcomeEventHandler @@ -56,7 +57,8 @@ internal class ConversationEventReceiverImpl( private val codeUpdatedHandler: CodeUpdatedHandler, private val codeDeletedHandler: CodeDeletedHandler, private val typingIndicatorHandler: TypingIndicatorHandler, - private val protocolUpdateEventHandler: ProtocolUpdateEventHandler + private val protocolUpdateEventHandler: ProtocolUpdateEventHandler, + private val accessUpdateEventHandler: AccessUpdateEventHandler ) : ConversationEventReceiver { override suspend fun onEvent(event: Event.Conversation, deliveryInfo: EventDeliveryInfo): Either { // TODO: Make sure errors are accounted for by each handler. @@ -108,11 +110,7 @@ internal class ConversationEventReceiverImpl( Either.Right(Unit) } - is Event.Conversation.AccessUpdate -> { - /* no-op */ - Either.Right(Unit) - } - + is Event.Conversation.AccessUpdate -> accessUpdateEventHandler.handle(event) is Event.Conversation.ConversationMessageTimer -> conversationMessageTimerEventHandler.handle(event) is Event.Conversation.CodeDeleted -> codeDeletedHandler.handle(event) is Event.Conversation.CodeUpdated -> codeUpdatedHandler.handle(event) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/AccessUpdateEventHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/AccessUpdateEventHandler.kt new file mode 100644 index 00000000000..8671cdd280c --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/AccessUpdateEventHandler.kt @@ -0,0 +1,49 @@ +/* + * Wire + * Copyright (C) 2024 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.sync.receiver.conversation + +import com.wire.kalium.logic.StorageFailure +import com.wire.kalium.logic.data.conversation.ConversationMapper +import com.wire.kalium.logic.data.event.Event +import com.wire.kalium.logic.data.id.toDao +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.di.MapperProvider +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.wrapStorageRequest +import com.wire.kalium.persistence.dao.conversation.ConversationDAO + +interface AccessUpdateEventHandler { + suspend fun handle(event: Event.Conversation.AccessUpdate): Either +} + +@Suppress("FunctionNaming") +fun AccessUpdateEventHandler( + selfUserId: UserId, + conversationDAO: ConversationDAO, + conversationMapper: ConversationMapper = MapperProvider.conversationMapper(selfUserId) +) = object : AccessUpdateEventHandler { + + override suspend fun handle(event: Event.Conversation.AccessUpdate): Either = + wrapStorageRequest { + conversationDAO.updateAccess( + conversationID = event.conversationId.toDao(), + accessList = conversationMapper.fromModelToDAOAccess(event.access), + accessRoleList = conversationMapper.fromModelToDAOAccessRole(event.accessRole) + ) + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapperTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapperTest.kt index 335b64421fd..4861d10b8c0 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapperTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapperTest.kt @@ -234,6 +234,110 @@ class ConversationMapperTest { assertEquals(ConversationEntity.Type.GROUP, result) } + @Test + fun givenAccessList_whenMappingFromModelToDAOAccess_thenCorrectValuesShouldBeReturned() { + // given + val accessList = setOf( + Conversation.Access.PRIVATE, + Conversation.Access.CODE, + Conversation.Access.INVITE, + Conversation.Access.LINK, + Conversation.Access.SELF_INVITE + ) + + val expected = listOf( + ConversationEntity.Access.PRIVATE, + ConversationEntity.Access.CODE, + ConversationEntity.Access.INVITE, + ConversationEntity.Access.LINK, + ConversationEntity.Access.SELF_INVITE + ) + + // when + val result = conversationMapper.fromModelToDAOAccess(accessList) + + // then + assertEquals(expected, result) + } + + @Test + fun givenAccessRoleList_whenMappingFromModelToDAOAccessRole_thenCorrectValuesShouldBeReturned() { + // given + val accessRoleList = setOf( + Conversation.AccessRole.SERVICE, + Conversation.AccessRole.GUEST, + Conversation.AccessRole.TEAM_MEMBER, + Conversation.AccessRole.NON_TEAM_MEMBER, + Conversation.AccessRole.EXTERNAL + ) + + val expected = listOf( + ConversationEntity.AccessRole.SERVICE, + ConversationEntity.AccessRole.GUEST, + ConversationEntity.AccessRole.TEAM_MEMBER, + ConversationEntity.AccessRole.NON_TEAM_MEMBER, + ConversationEntity.AccessRole.EXTERNAL + ) + + // when + val result = conversationMapper.fromModelToDAOAccessRole(accessRoleList) + + // then + assertEquals(expected, result) + } + + @Test + fun givenAccessList_whenMappingFromApiModelToAccessModel_thenCorrectValuesShouldBeReturned() { + // given + val accessList = setOf( + ConversationAccessDTO.PRIVATE, + ConversationAccessDTO.CODE, + ConversationAccessDTO.INVITE, + ConversationAccessDTO.LINK, + ConversationAccessDTO.SELF_INVITE + ) + + val expected = setOf( + Conversation.Access.PRIVATE, + Conversation.Access.CODE, + Conversation.Access.INVITE, + Conversation.Access.LINK, + Conversation.Access.SELF_INVITE + ) + + // when + val result = conversationMapper.fromApiModelToAccessModel(accessList) + + // then + assertEquals(expected, result) + } + + @Test + fun givenAccessRoleList_whenMappingFromApiModelToAccessModel_thenCorrectValuesShouldBeReturned() { + // given + val accessRoleList = setOf( + ConversationAccessRoleDTO.SERVICE, + ConversationAccessRoleDTO.GUEST, + ConversationAccessRoleDTO.TEAM_MEMBER, + ConversationAccessRoleDTO.NON_TEAM_MEMBER, + ConversationAccessRoleDTO.EXTERNAL + ) + + val expected = setOf( + Conversation.AccessRole.SERVICE, + Conversation.AccessRole.GUEST, + Conversation.AccessRole.TEAM_MEMBER, + Conversation.AccessRole.NON_TEAM_MEMBER, + Conversation.AccessRole.EXTERNAL + ) + + // when + val result = conversationMapper.fromApiModelToAccessRoleModel(accessRoleList) + + // then + assertEquals(expected, result) + } + private companion object { val ORIGINAL_CONVERSATION_ID = ConversationId("original", "oDomain") val SELF_USER_TEAM_ID = TeamId("teamID") diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestEvent.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestEvent.kt index 2ffcb8c3a99..48cde9de04f 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestEvent.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestEvent.kt @@ -139,6 +139,14 @@ object TestEvent { senderUserId = TestUser.USER_ID ) + fun accessUpdate(eventId: String = "eventId") = Event.Conversation.AccessUpdate( + id = eventId, + conversationId = TestConversation.ID, + access = setOf(Conversation.Access.PRIVATE), + accessRole = setOf(Conversation.AccessRole.TEAM_MEMBER, Conversation.AccessRole.SERVICE), + qualifiedFrom = TestUser.USER_ID + ) + fun teamMemberLeave(eventId: String = "eventId") = Event.Team.MemberLeave( eventId, teamId = "teamId", @@ -200,13 +208,6 @@ object TestEvent { timestampIso = "2022-03-30T15:36:00.000Z" ) - fun newAccessUpdateEvent() = Event.Conversation.AccessUpdate( - id = "eventId", - conversationId = TestConversation.ID, - data = TestConversation.CONVERSATION_RESPONSE, - qualifiedFrom = TestUser.USER_ID, - ) - fun codeUpdated() = Event.Conversation.CodeUpdated( id = "eventId", conversationId = TestConversation.ID, diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/ConversationEventReceiverTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/ConversationEventReceiverTest.kt index ed9df56c936..f106495aa46 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/ConversationEventReceiverTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/ConversationEventReceiverTest.kt @@ -23,6 +23,7 @@ import com.wire.kalium.logic.data.conversation.Conversation 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.receiver.conversation.AccessUpdateEventHandler import com.wire.kalium.logic.sync.receiver.conversation.ConversationMessageTimerEventHandler import com.wire.kalium.logic.sync.receiver.conversation.DeletedConversationEventHandler import com.wire.kalium.logic.sync.receiver.conversation.MLSWelcomeEventHandler @@ -208,17 +209,6 @@ class ConversationEventReceiverTest { result.shouldSucceed() } - @Test - fun givenAccessUpdateEvent_whenOnEventInvoked_thenReturnSuccess() = runTest { - val accessUpdateEvent = TestEvent.newAccessUpdateEvent() - - val (_, featureConfigEventReceiver) = Arrangement().arrange() - - val result = featureConfigEventReceiver.onEvent(accessUpdateEvent, TestEvent.liveDeliveryInfo) - - result.shouldSucceed() - } - @Test fun givenConversationMessageTimerEvent_whenOnEventInvoked_thenPropagateConversationMessageTimerEventHandlerResult() = runTest { @@ -339,6 +329,48 @@ class ConversationEventReceiverTest { result.shouldFail() } + @Test + fun givenAccessUpdateEventAndHandlingSucceeds_whenOnEventInvoked_thenSuccessHandlerResult() = runTest { + // given + val accessUpdateEvent = TestEvent.accessUpdate() + val (arrangement, handler) = Arrangement() + .withConversationAccessUpdateEventSucceeded(Either.Right(Unit)) + .arrange() + + // when + val result = handler.onEvent( + event = accessUpdateEvent, + deliveryInfo = TestEvent.liveDeliveryInfo + ) + + // then + result.shouldSucceed() + coVerify { + arrangement.accessUpdateEventHandler.handle(eq(accessUpdateEvent)) + }.wasInvoked(once) + } + + @Test + fun givenAccessUpdateEventAndHandlingFails_whenOnEventInvoked_thenHandlerPropagateFails() = runTest { + // given + val accessUpdateEvent = TestEvent.accessUpdate() + val (arrangement, handler) = Arrangement() + .withConversationAccessUpdateEventSucceeded(Either.Left(StorageFailure.Generic(RuntimeException("some error")))) + .arrange() + + // when + val result = handler.onEvent( + event = accessUpdateEvent, + deliveryInfo = TestEvent.liveDeliveryInfo + ) + + // then + result.shouldFail() + coVerify { + arrangement.accessUpdateEventHandler.handle(eq(accessUpdateEvent)) + }.wasInvoked(once) + } + private class Arrangement : CodeUpdatedHandlerArrangement by CodeUpdatedHandlerArrangementImpl(), CodeDeletedHandlerArrangement by CodeDeletedHandlerArrangementImpl() { @@ -379,6 +411,9 @@ class ConversationEventReceiverTest { @Mock val protocolUpdateEventHandler = mock(ProtocolUpdateEventHandler::class) + @Mock + val accessUpdateEventHandler = mock(AccessUpdateEventHandler::class) + private val conversationEventReceiver: ConversationEventReceiver = ConversationEventReceiverImpl( newMessageHandler = newMessageEventHandler, newConversationHandler = newConversationEventHandler, @@ -393,7 +428,8 @@ class ConversationEventReceiverTest { codeUpdatedHandler = codeUpdatedHandler, codeDeletedHandler = codeDeletedHandler, typingIndicatorHandler = typingIndicatorHandler, - protocolUpdateEventHandler = protocolUpdateEventHandler + protocolUpdateEventHandler = protocolUpdateEventHandler, + accessUpdateEventHandler = accessUpdateEventHandler ) fun arrange(block: suspend Arrangement.() -> Unit = {}) = run { @@ -425,6 +461,12 @@ class ConversationEventReceiverTest { }.returns(result) } + suspend fun withConversationAccessUpdateEventSucceeded(result: Either) = apply { + coEvery { + accessUpdateEventHandler.handle(any()) + }.returns(result) + } + suspend fun withMLSWelcomeEventSucceeded() = apply { coEvery { mlsWelcomeEventHandler.handle(any()) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/AccessUpdateHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/AccessUpdateHandlerTest.kt new file mode 100644 index 00000000000..2b99f10d69b --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/AccessUpdateHandlerTest.kt @@ -0,0 +1,118 @@ +/* + * Wire + * Copyright (C) 2024 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.sync.receiver.conversation + +import com.wire.kalium.logic.StorageFailure +import com.wire.kalium.logic.data.conversation.Conversation.Access +import com.wire.kalium.logic.data.conversation.Conversation.AccessRole +import com.wire.kalium.logic.data.conversation.ConversationMapper +import com.wire.kalium.logic.data.id.PersistenceQualifiedId +import com.wire.kalium.logic.data.id.toDao +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.persistence.dao.conversation.ConversationDAO +import com.wire.kalium.persistence.dao.conversation.ConversationEntity +import io.mockative.Mock +import io.mockative.any +import io.mockative.coEvery +import io.mockative.coVerify +import io.mockative.every +import io.mockative.matches +import io.mockative.mock +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class AccessUpdateHandlerTest { + + @Test + fun givenConversationAccessUpdateEvent_whenHandlingIt_thenShouldCallUpdateDatabase() = runTest { + // given + val event = TestEvent.accessUpdate() + + val (arrangement, eventHandler) = Arrangement() + .withMappingModelToDAOAccess( + event.access, + listOf(ConversationEntity.Access.PRIVATE) + ) + .withMappingModelToDAOAccessRole( + event.accessRole, + listOf(ConversationEntity.AccessRole.TEAM_MEMBER, ConversationEntity.AccessRole.SERVICE) + ) + .arrange() + + // when + eventHandler.handle(event) + + // then + coVerify { + arrangement.conversationDAO.updateAccess( + matches { + it == PersistenceQualifiedId( + value = TestConversation.ID.value, + domain = TestConversation.ID.domain + ) + }, + matches { + it.contains(ConversationEntity.Access.PRIVATE) + }, + matches { + it.contains(ConversationEntity.AccessRole.TEAM_MEMBER) && + it.contains(ConversationEntity.AccessRole.SERVICE) + } + ) + } + } + + private class Arrangement { + + @Mock + val conversationDAO = mock(ConversationDAO::class) + + @Mock + val conversationMapper = mock(ConversationMapper::class) + + init { + runBlocking { + coEvery { conversationDAO.updateAccess(any(), any(), any()) }.returns(Unit) + } + } + + private val accessUpdateEventHandler: AccessUpdateEventHandler = AccessUpdateEventHandler( + selfUserId = TestUser.USER_ID, + conversationDAO = conversationDAO, + conversationMapper = conversationMapper + ) + + fun withMappingModelToDAOAccess(param: Set, result: List) = apply { + every { + conversationMapper.fromModelToDAOAccess(param) + }.returns(result) + } + + fun withMappingModelToDAOAccessRole(param: Set, result: List) = apply { + every { + conversationMapper.fromModelToDAOAccessRole(param) + }.returns(result) + } + + fun arrange() = this to accessUpdateEventHandler + } +}