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 fde8118e314..63e330efcdf 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 @@ -288,9 +288,13 @@ interface ConversationRepository { suspend fun updateLegalHoldStatus( conversationId: ConversationId, legalHoldStatus: Conversation.LegalHoldStatus - ): Either + ): Either + + suspend fun setLegalHoldStatusChangeNotified(conversationId: ConversationId): Either + + suspend fun observeLegalHoldStatus(conversationId: ConversationId): Flow> - suspend fun observeLegalHoldForConversation(conversationId: ConversationId): Flow> + suspend fun observeLegalHoldStatusChangeNotified(conversationId: ConversationId): Flow> } @Suppress("LongParameterList", "TooManyFunctions", "LargeClass") @@ -1077,20 +1081,36 @@ internal class ConversationDataSource internal constructor( override suspend fun updateLegalHoldStatus( conversationId: ConversationId, legalHoldStatus: Conversation.LegalHoldStatus - ): Either { + ): Either { val legalHoldStatusEntity = conversationMapper.legalHoldStatusToEntity(legalHoldStatus) return wrapStorageRequest { - conversationDAO.updateLegalHoldStatus( - conversationId = conversationId.toDao(), - legalHoldStatus = legalHoldStatusEntity - ) + conversationId.toDao().let { conversationIdEntity -> + conversationDAO.updateLegalHoldStatus( + conversationId = conversationIdEntity, + legalHoldStatus = legalHoldStatusEntity + ).also { legalHoldUpdated -> + if (legalHoldUpdated) { + conversationDAO.updateLegalHoldStatusChangeNotified(conversationId = conversationIdEntity, notified = false) + } + } + } } } + override suspend fun setLegalHoldStatusChangeNotified(conversationId: ConversationId): Either = + wrapStorageRequest { + conversationDAO.updateLegalHoldStatusChangeNotified(conversationId = conversationId.toDao(), notified = true) + } - override suspend fun observeLegalHoldForConversation(conversationId: ConversationId) = - conversationDAO.observeLegalHoldForConversation(conversationId.toDao()) + override suspend fun observeLegalHoldStatus(conversationId: ConversationId) = + conversationDAO.observeLegalHoldStatus(conversationId.toDao()) .map { conversationMapper.legalHoldStatusFromEntity(it) } .wrapStorageRequest() + .distinctUntilChanged() + + override suspend fun observeLegalHoldStatusChangeNotified(conversationId: ConversationId): Flow> = + conversationDAO.observeLegalHoldStatusChangeNotified(conversationId.toDao()) + .wrapStorageRequest() + .distinctUntilChanged() companion object { const val DEFAULT_MEMBER_ROLE = "wire_member" diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt index ef067beeed5..e5f11319796 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt @@ -318,5 +318,9 @@ class ConversationScope internal constructor( get() = SetUserInformedAboutVerificationUseCaseImpl(conversationRepository) val observeInformAboutVerificationBeforeMessagingFlagUseCase: ObserveDegradedConversationNotifiedUseCase get() = ObserveDegradedConversationNotifiedUseCaseImpl(conversationRepository) + val setNotifiedAboutConversationUnderLegalHold: SetNotifiedAboutConversationUnderLegalHoldUseCase + get() = SetNotifiedAboutConversationUnderLegalHoldUseCaseImpl(conversationRepository) + val observeConversationUnderLegalHoldNotified: ObserveConversationUnderLegalHoldNotifiedUseCase + get() = ObserveConversationUnderLegalHoldNotifiedUseCaseImpl(conversationRepository) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationUnderLegalHoldNotifiedUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationUnderLegalHoldNotifiedUseCase.kt new file mode 100644 index 00000000000..c9b84d9f253 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationUnderLegalHoldNotifiedUseCase.kt @@ -0,0 +1,53 @@ +/* + * 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.conversation.ConversationRepository +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.functional.flatMapRightWithEither +import com.wire.kalium.logic.functional.mapRight +import com.wire.kalium.logic.functional.mapToRightOr +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged + +/** + * UseCase for observing if User was notified about conversation being subject of legal hold + */ +interface ObserveConversationUnderLegalHoldNotifiedUseCase { + suspend operator fun invoke(conversationId: ConversationId): Flow +} + +internal class ObserveConversationUnderLegalHoldNotifiedUseCaseImpl internal constructor( + private val conversationRepository: ConversationRepository +) : ObserveConversationUnderLegalHoldNotifiedUseCase { + + override suspend fun invoke(conversationId: ConversationId): Flow = + conversationRepository.observeLegalHoldStatus(conversationId) + .flatMapRightWithEither { legalHoldStatus -> + conversationRepository.observeLegalHoldStatusChangeNotified(conversationId) + .mapRight { isUserNotifiedAboutStatusChange -> + when (legalHoldStatus) { + Conversation.LegalHoldStatus.ENABLED -> isUserNotifiedAboutStatusChange + else -> true // we only need to notify if legal hold was enabled + } + } + } + .mapToRightOr(true) + .distinctUntilChanged() +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/SetNotifiedAboutConversationUnderLegalHoldUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/SetNotifiedAboutConversationUnderLegalHoldUseCase.kt new file mode 100644 index 00000000000..42d5618d86f --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/SetNotifiedAboutConversationUnderLegalHoldUseCase.kt @@ -0,0 +1,37 @@ +/* + * 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.ConversationRepository +import com.wire.kalium.logic.data.id.ConversationId + +/** + * UseCase for setting legal_hold_change_notified flag to true, + * it means that User was notified about the recent change in legal hold status. + */ +interface SetNotifiedAboutConversationUnderLegalHoldUseCase { + suspend operator fun invoke(conversationId: ConversationId) +} + +internal class SetNotifiedAboutConversationUnderLegalHoldUseCaseImpl internal constructor( + private val conversationRepository: ConversationRepository +) : SetNotifiedAboutConversationUnderLegalHoldUseCase { + override suspend fun invoke(conversationId: ConversationId) { + conversationRepository.setLegalHoldStatusChangeNotified(conversationId) + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MLSMessageCreator.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MLSMessageCreator.kt index f91ca343fa2..7d9e78090fd 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MLSMessageCreator.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MLSMessageCreator.kt @@ -64,7 +64,7 @@ class MLSMessageCreatorImpl( else -> false } - val legalHoldStatus = conversationRepository.observeLegalHoldForConversation( + val legalHoldStatus = conversationRepository.observeLegalHoldStatus( message.conversationId ).first().let { legalHoldStatusMapper.mapLegalHoldConversationStatus(it, message) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreator.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreator.kt index 9fdb83c93b9..684ca60b122 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreator.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreator.kt @@ -86,7 +86,7 @@ class MessageEnvelopeCreatorImpl( else -> false } - val legalHoldStatus = conversationRepository.observeLegalHoldForConversation( + val legalHoldStatus = conversationRepository.observeLegalHoldStatus( message.conversationId ).first().let { legalHoldStatusMapper.mapLegalHoldConversationStatus(it, message) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryTest.kt index eeffee2c073..d60b0fbe93c 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryTest.kt @@ -1321,12 +1321,15 @@ class ConversationRepositoryTest { } @Test - fun givenLegalHoldStatus_whenUpdateIsCalled_thenInvokeUpdateLegalHoldStatusFromOnce() = runTest { + fun givenLegalHoldStatus_whenUpdateIsCalled_thenInvokeUpdateLegalHoldStatusOnce() = runTest { + // given val (arrange, conversationRepository) = Arrangement() + .withUpdateLegalHoldStatus(true) + .withUpdateLegalHoldStatusChangeNotified(true) .arrange() - + // when conversationRepository.updateLegalHoldStatus(CONVERSATION_ID, Conversation.LegalHoldStatus.ENABLED) - + // then verify(arrange.conversationDAO) .suspendFunction(arrange.conversationDAO::updateLegalHoldStatus) .with(eq(CONVERSATION_ID.toDao()), any()) @@ -1334,15 +1337,61 @@ class ConversationRepositoryTest { } @Test - fun givenConversationId_whenObservingLegalHoldStatus_thenInvokeObserveLegalHoldStatusFromOnce() = runTest { + fun givenLegalHoldStatusUpdated_whenUpdateChangeNotifiedIsCalled_thenInvokeUpdateLegalHoldStatusChangeNotifiedOnce() = runTest { + // given + val (arrange, conversationRepository) = Arrangement() + .withUpdateLegalHoldStatus(true) + .withUpdateLegalHoldStatusChangeNotified(true) + .arrange() + // when + conversationRepository.updateLegalHoldStatus(CONVERSATION_ID, Conversation.LegalHoldStatus.ENABLED) + // then + verify(arrange.conversationDAO) + .suspendFunction(arrange.conversationDAO::updateLegalHoldStatusChangeNotified) + .with(eq(CONVERSATION_ID.toDao()), any()) + .wasInvoked(exactly = once) + } + + @Test + fun givenLegalHoldStatusNotUpdated_whenUpdateChangeNotifiedIsCalled_thenDoNotInvokeUpdateLegalHoldStatusChangeNotified() = runTest { + // given + val (arrange, conversationRepository) = Arrangement() + .withUpdateLegalHoldStatus(false) + .withUpdateLegalHoldStatusChangeNotified(false) + .arrange() + // when + conversationRepository.updateLegalHoldStatus(CONVERSATION_ID, Conversation.LegalHoldStatus.ENABLED) + // then + verify(arrange.conversationDAO) + .suspendFunction(arrange.conversationDAO::updateLegalHoldStatusChangeNotified) + .with(eq(CONVERSATION_ID.toDao()), any()) + .wasNotInvoked() + } + + @Test + fun givenConversationId_whenObservingLegalHoldStatus_thenInvokeObserveLegalHoldStatusOnce() = runTest { val (arrange, conversationRepository) = Arrangement() .withObserveLegalHoldStatus() .arrange() - conversationRepository.observeLegalHoldForConversation(CONVERSATION_ID) + conversationRepository.observeLegalHoldStatus(CONVERSATION_ID) + + verify(arrange.conversationDAO) + .suspendFunction(arrange.conversationDAO::observeLegalHoldStatus) + .with(eq(CONVERSATION_ID.toDao())) + .wasInvoked(exactly = once) + } + + @Test + fun givenConversationId_whenObservingLegalHoldStatusChangeNotified_thenInvokeObserveLegalHoldStatusChangeNotifiedOnce() = runTest { + val (arrange, conversationRepository) = Arrangement() + .withObserveLegalHoldStatusChangeNotified() + .arrange() + + conversationRepository.observeLegalHoldStatusChangeNotified(CONVERSATION_ID) verify(arrange.conversationDAO) - .suspendFunction(arrange.conversationDAO::observeLegalHoldForConversation) + .suspendFunction(arrange.conversationDAO::observeLegalHoldStatusChangeNotified) .with(eq(CONVERSATION_ID.toDao())) .wasInvoked(exactly = once) } @@ -1713,11 +1762,32 @@ class ConversationRepositoryTest { fun withObserveLegalHoldStatus() = apply { given(conversationDAO) - .suspendFunction(conversationDAO::observeLegalHoldForConversation) + .suspendFunction(conversationDAO::observeLegalHoldStatus) .whenInvokedWith(any()) .thenReturn(flowOf(ConversationEntity.LegalHoldStatus.ENABLED)) } + fun withObserveLegalHoldStatusChangeNotified() = apply { + given(conversationDAO) + .suspendFunction(conversationDAO::observeLegalHoldStatusChangeNotified) + .whenInvokedWith(any()) + .thenReturn(flowOf(true)) + } + + fun withUpdateLegalHoldStatus(updated: Boolean) = apply { + given(conversationDAO) + .suspendFunction(conversationDAO::updateLegalHoldStatus) + .whenInvokedWith(any(), any()) + .thenReturn(updated) + } + + fun withUpdateLegalHoldStatusChangeNotified(updated: Boolean) = apply { + given(conversationDAO) + .suspendFunction(conversationDAO::updateLegalHoldStatusChangeNotified) + .whenInvokedWith(any(), any()) + .thenReturn(updated) + } + fun arrange() = this to conversationRepository } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationUnderLegalHoldNotifiedUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationUnderLegalHoldNotifiedUseCaseTest.kt new file mode 100644 index 00000000000..eb0d785ddd0 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationUnderLegalHoldNotifiedUseCaseTest.kt @@ -0,0 +1,96 @@ +/* + * 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.Conversation +import com.wire.kalium.logic.data.conversation.ConversationRepository +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.functional.map +import io.mockative.Mock +import io.mockative.any +import io.mockative.given +import io.mockative.mock +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class ObserveConversationUnderLegalHoldNotifiedUseCaseTest { + + private fun testObserving( + given: Either>, + expected: Boolean + ) = runTest { + // given + val conversationId = ConversationId("conversationId", "domain") + val (_, useCase) = Arrangement() + .withObserveLegalHoldStatusForConversation(given.map { it.first }) + .withObserveLegalHoldStatusChangeNotifiedForConversation(given.map { it.second }) + .arrange() + // when + val result = useCase.invoke(conversationId) + // then + assertEquals(expected, result.first()) + } + + @Test + fun givenFailure_whenObserving_thenReturnTrue() = + testObserving(Either.Left(StorageFailure.DataNotFound), true) + @Test + fun givenLegalHoldEnabledAndNotNotified_whenObserving_thenReturnFalse() = + testObserving(Either.Right(Conversation.LegalHoldStatus.ENABLED to false), false) + @Test + fun givenLegalHoldEnabledAndNotified_whenObserving_thenReturnTrue() = + testObserving(Either.Right(Conversation.LegalHoldStatus.ENABLED to true), true) + @Test + fun givenLegalHoldDisabledAndNotNotified_whenObserving_thenReturnFalse() = + testObserving(Either.Right(Conversation.LegalHoldStatus.DISABLED to false), true) + @Test + fun givenLegalHoldDisabledAndNotified_whenObserving_thenReturnTrue() = + testObserving(Either.Right(Conversation.LegalHoldStatus.DISABLED to true), true) + + private class Arrangement() { + @Mock + val conversationRepository = mock(ConversationRepository::class) + + private val useCase: ObserveConversationUnderLegalHoldNotifiedUseCase by lazy { + ObserveConversationUnderLegalHoldNotifiedUseCaseImpl(conversationRepository) + } + + fun arrange() = this to useCase + fun withObserveLegalHoldStatusForConversation( + result: Either + ) = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::observeLegalHoldStatus) + .whenInvokedWith(any()) + .thenReturn(flowOf(result)) + } + fun withObserveLegalHoldStatusChangeNotifiedForConversation( + result: Either + ) = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::observeLegalHoldStatusChangeNotified) + .whenInvokedWith(any()) + .thenReturn(flowOf(result)) + } + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/SetNotifiedAboutConversationUnderLegalHoldUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/SetNotifiedAboutConversationUnderLegalHoldUseCaseTest.kt new file mode 100644 index 00000000000..e9616149f48 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/SetNotifiedAboutConversationUnderLegalHoldUseCaseTest.kt @@ -0,0 +1,66 @@ +/* + * 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.ConversationRepository +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.functional.Either +import io.mockative.Mock +import io.mockative.any +import io.mockative.eq +import io.mockative.given +import io.mockative.mock +import io.mockative.once +import io.mockative.verify +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class SetNotifiedAboutConversationUnderLegalHoldUseCaseTest { + + @Test + fun givenConversationId_whenInvoke_thenRepositoryIsCalledCorrectly() = runTest { + // given + val conversationId = ConversationId("conversationId", "domain") + val (arrangement, useCase) = Arrangement() + .withSetLegalHoldStatusChangeNotifiedSuccessful() + .arrange() + // when + useCase.invoke(conversationId) + // then + verify(arrangement.conversationRepository) + .suspendFunction(arrangement.conversationRepository::setLegalHoldStatusChangeNotified) + .with(eq(conversationId)) + .wasInvoked(exactly = once) + } + + private class Arrangement() { + @Mock + val conversationRepository = mock(ConversationRepository::class) + + private val useCase: SetNotifiedAboutConversationUnderLegalHoldUseCase by lazy { + SetNotifiedAboutConversationUnderLegalHoldUseCaseImpl(conversationRepository) + } + fun arrange() = this to useCase + fun withSetLegalHoldStatusChangeNotifiedSuccessful() = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::setLegalHoldStatusChangeNotified) + .whenInvokedWith(any()) + .thenReturn(Either.Right(true)) + } + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MLSMessageCreatorTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MLSMessageCreatorTest.kt index 0ec56b6d3fb..9e70ca7e0f5 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MLSMessageCreatorTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MLSMessageCreatorTest.kt @@ -39,7 +39,6 @@ import io.mockative.given import io.mockative.mock import io.mockative.once import io.mockative.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import kotlin.test.BeforeTest @@ -82,7 +81,7 @@ class MLSMessageCreatorTest { .then { Either.Right(MLS_CLIENT) } given(conversationRepository) - .suspendFunction(conversationRepository::observeLegalHoldForConversation) + .suspendFunction(conversationRepository::observeLegalHoldStatus) .whenInvokedWith(anything()) .then { flowOf(Either.Right(Conversation.LegalHoldStatus.DISABLED)) } @@ -110,7 +109,7 @@ class MLSMessageCreatorTest { .wasInvoked(once) verify(conversationRepository) - .suspendFunction(conversationRepository::observeLegalHoldForConversation) + .suspendFunction(conversationRepository::observeLegalHoldStatus) .with(anything()) .wasInvoked(once) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreatorTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreatorTest.kt index 60069467ce7..18ac3b1fce9 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreatorTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreatorTest.kt @@ -92,7 +92,7 @@ class MessageEnvelopeCreatorTest { ) given(conversationRepository) - .suspendFunction(conversationRepository::observeLegalHoldForConversation) + .suspendFunction(conversationRepository::observeLegalHoldStatus) .whenInvokedWith(anything()) .then { flowOf(Either.Right(Conversation.LegalHoldStatus.DISABLED)) } @@ -132,7 +132,7 @@ class MessageEnvelopeCreatorTest { ) verify(conversationRepository) - .suspendFunction(conversationRepository::observeLegalHoldForConversation) + .suspendFunction(conversationRepository::observeLegalHoldStatus) .with(anything()) .wasInvoked(once) @@ -187,7 +187,7 @@ class MessageEnvelopeCreatorTest { } verify(conversationRepository) - .suspendFunction(conversationRepository::observeLegalHoldForConversation) + .suspendFunction(conversationRepository::observeLegalHoldStatus) .with(anything()) .wasInvoked(once) @@ -235,7 +235,7 @@ class MessageEnvelopeCreatorTest { } verify(conversationRepository) - .suspendFunction(conversationRepository::observeLegalHoldForConversation) + .suspendFunction(conversationRepository::observeLegalHoldStatus) .with(anything()) .wasInvoked(once) @@ -288,7 +288,7 @@ class MessageEnvelopeCreatorTest { } verify(conversationRepository) - .suspendFunction(conversationRepository::observeLegalHoldForConversation) + .suspendFunction(conversationRepository::observeLegalHoldStatus) .with(anything()) .wasInvoked(once) @@ -318,7 +318,7 @@ class MessageEnvelopeCreatorTest { } verify(conversationRepository) - .suspendFunction(conversationRepository::observeLegalHoldForConversation) + .suspendFunction(conversationRepository::observeLegalHoldStatus) .with(anything()) .wasInvoked(once) @@ -348,7 +348,7 @@ class MessageEnvelopeCreatorTest { .wasInvoked(exactly = once) verify(conversationRepository) - .suspendFunction(conversationRepository::observeLegalHoldForConversation) + .suspendFunction(conversationRepository::observeLegalHoldStatus) .with(anything()) .wasInvoked(once) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandlerTest.kt index 72c05b3cd88..f2cd42bdee8 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandlerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandlerTest.kt @@ -406,7 +406,7 @@ class NewMessageEventHandlerTest { given(conversationRepository) .suspendFunction(conversationRepository::updateLegalHoldStatus) .whenInvokedWith(any(), any()) - .thenReturn(Either.Right(Unit)) + .thenReturn(Either.Right(true)) } fun withMLSUnpackerReturning(result: Either>) = diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/legalhold/LegalHoldSystemMessageHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/legalhold/LegalHoldSystemMessageHandlerTest.kt index b504ba2b346..126af68bd3c 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/legalhold/LegalHoldSystemMessageHandlerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/legalhold/LegalHoldSystemMessageHandlerTest.kt @@ -20,7 +20,6 @@ package com.wire.kalium.logic.sync.receiver.handler.legalhold import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.conversation.ConversationRepository -import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.message.Message import com.wire.kalium.logic.data.message.MessageContent @@ -377,7 +376,7 @@ class LegalHoldSystemMessagesHandlerTest { given(conversationRepository) .suspendFunction(conversationRepository::updateLegalHoldStatus) .whenInvokedWith(any(), any()) - .thenReturn(Either.Right(Unit)) + .thenReturn(Either.Right(true)) } fun withDeleteLegalHoldRequestSuccess() = apply { given(userConfigRepository) 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 7d8725bf088..29e5f1697c2 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 @@ -47,6 +47,13 @@ CREATE TABLE Conversation ( legal_hold_status TEXT AS ConversationEntity.LegalHoldStatus NOT NULL DEFAULT "DISABLED" ); +CREATE TABLE ConversationLegalHoldStatusChangeNotified ( + conversation_id TEXT AS QualifiedIDEntity NOT NULL PRIMARY KEY, + legal_hold_status_change_notified INTEGER AS Boolean NOT NULL DEFAULT 1, + + FOREIGN KEY (conversation_id) REFERENCES Conversation(qualified_id) ON DELETE CASCADE ON UPDATE CASCADE +); + -- Optimise comparisons and sorting by dates: CREATE INDEX conversation_modified_date_index ON Conversation(last_modified_date); CREATE INDEX conversation_notified_date_index ON Conversation(last_notified_date); @@ -409,15 +416,30 @@ UPDATE Conversation SET verification_status = :status WHERE qualified_id = :conversationId; -updateLegalHoldStatus: +updateLegalHoldStatus { UPDATE Conversation -SET legal_hold_status = ? -WHERE qualified_id = :qualified_id; +SET legal_hold_status = :legal_hold_status +WHERE qualified_id = :qualified_id AND legal_hold_status != :legal_hold_status; +SELECT changes(); +} + +upsertLegalHoldStatusChangeNotified { +INSERT INTO ConversationLegalHoldStatusChangeNotified(conversation_id, legal_hold_status_change_notified) +VALUES (:conversationId, :notified) +ON CONFLICT(conversation_id) DO UPDATE SET + legal_hold_status_change_notified = excluded.legal_hold_status_change_notified + WHERE legal_hold_status_change_notified != excluded.legal_hold_status_change_notified; +SELECT changes(); +} selectLegalHoldStatus: SELECT legal_hold_status FROM Conversation WHERE qualified_id = :conversationId; +selectLegalHoldStatusChangeNotified: +SELECT legal_hold_status_change_notified FROM ConversationLegalHoldStatusChangeNotified +WHERE conversation_id = :conversationId; + selectDegradedConversationNotified: SELECT degraded_conversation_notified FROM Conversation WHERE qualified_id = :conversationId; diff --git a/persistence/src/commonMain/db_user/migrations/72.sqm b/persistence/src/commonMain/db_user/migrations/72.sqm new file mode 100644 index 00000000000..5a26d22fc5f --- /dev/null +++ b/persistence/src/commonMain/db_user/migrations/72.sqm @@ -0,0 +1,9 @@ +import com.wire.kalium.persistence.dao.QualifiedIDEntity; +import kotlin.Boolean; + +CREATE TABLE ConversationLegalHoldStatusChangeNotified ( + conversation_id TEXT AS QualifiedIDEntity NOT NULL PRIMARY KEY, + legal_hold_status_change_notified INTEGER AS Boolean NOT NULL DEFAULT 1, + + FOREIGN KEY (conversation_id) REFERENCES Conversation(qualified_id) ON DELETE CASCADE ON UPDATE CASCADE +); 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 b075a5f7034..15201be46d9 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 @@ -108,6 +108,8 @@ interface ConversationDAO { suspend fun observeUnreadArchivedConversationsCount(): Flow suspend fun observeDegradedConversationNotified(conversationId: QualifiedIDEntity): Flow suspend fun updateDegradedConversationNotifiedFlag(conversationId: QualifiedIDEntity, updateFlag: Boolean) - suspend fun updateLegalHoldStatus(conversationId: QualifiedIDEntity, legalHoldStatus: ConversationEntity.LegalHoldStatus) - suspend fun observeLegalHoldForConversation(conversationId: QualifiedIDEntity): Flow + suspend fun updateLegalHoldStatus(conversationId: QualifiedIDEntity, legalHoldStatus: ConversationEntity.LegalHoldStatus): Boolean + suspend fun updateLegalHoldStatusChangeNotified(conversationId: QualifiedIDEntity, notified: Boolean): Boolean + suspend fun observeLegalHoldStatus(conversationId: QualifiedIDEntity): Flow + suspend fun observeLegalHoldStatusChangeNotified(conversationId: QualifiedIDEntity): Flow } 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 023cb538cd5..9badd4d15b1 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 @@ -399,13 +399,23 @@ internal class ConversationDAOImpl internal constructor( conversationId: QualifiedIDEntity, legalHoldStatus: ConversationEntity.LegalHoldStatus ) = withContext(coroutineContext) { - conversationQueries.updateLegalHoldStatus(legalHoldStatus, conversationId) + conversationQueries.updateLegalHoldStatus(legalHoldStatus, conversationId).executeAsOne() > 0 } - override suspend fun observeLegalHoldForConversation(conversationId: QualifiedIDEntity) = + override suspend fun updateLegalHoldStatusChangeNotified(conversationId: QualifiedIDEntity, notified: Boolean) = + withContext(coroutineContext) { + conversationQueries.upsertLegalHoldStatusChangeNotified(conversationId, notified).executeAsOne() > 0 + } + + override suspend fun observeLegalHoldStatus(conversationId: QualifiedIDEntity) = conversationQueries.selectLegalHoldStatus(conversationId) .asFlow() .mapToOneOrDefault(ConversationEntity.LegalHoldStatus.DISABLED) .flowOn(coroutineContext) + override suspend fun observeLegalHoldStatusChangeNotified(conversationId: QualifiedIDEntity) = + conversationQueries.selectLegalHoldStatusChangeNotified(conversationId) + .asFlow() + .mapToOneOrDefault(true) + .flowOn(coroutineContext) } 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 0629743b9b4..0afd21f2499 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 @@ -25,6 +25,7 @@ import com.wire.kalium.persistence.Call import com.wire.kalium.persistence.Client import com.wire.kalium.persistence.Connection import com.wire.kalium.persistence.Conversation +import com.wire.kalium.persistence.ConversationLegalHoldStatusChangeNotified import com.wire.kalium.persistence.Member import com.wire.kalium.persistence.Message import com.wire.kalium.persistence.MessageAssetContent @@ -244,4 +245,8 @@ internal object TableMapper { legal_hold_member_listAdapter = QualifiedIDListAdapter, legal_hold_typeAdapter = EnumColumnAdapter() ) + + val conversationLegalHoldStatusChangeNotifiedAdapter = ConversationLegalHoldStatusChangeNotified.Adapter( + conversation_idAdapter = QualifiedIDAdapter + ) } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt index a9fddbeb605..d09ba08de97 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt @@ -158,6 +158,7 @@ class UserDatabaseBuilder internal constructor( MessageLegalHoldContentAdapter = TableMapper.messageLegalHoldContentAdapter, MessageConversationProtocolChangedDuringACallContentAdapter = TableMapper.messageConversationProtocolChangedDuringACAllContentAdapter, + ConversationLegalHoldStatusChangeNotifiedAdapter = TableMapper.conversationLegalHoldStatusChangeNotifiedAdapter, ) init { 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 816263c7b98..8b3fda064ce 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 @@ -1589,6 +1589,73 @@ class ConversationDAOTest : BaseDatabaseTest() { ) } + @Test + fun givenNewLegalHoldStatus_whenUpdating_thenShouldReturnTrue() = runTest { + // given + val conversationId = QualifiedIDEntity("conversationId", "domain") + conversationDAO.insertConversation(conversationEntity1.copy(conversationId)) + conversationDAO.updateLegalHoldStatus(conversationId, ConversationEntity.LegalHoldStatus.DISABLED) + // when + val result = conversationDAO.updateLegalHoldStatus(conversationId, ConversationEntity.LegalHoldStatus.ENABLED) + // then + assertEquals(true, result) + } + @Test + fun givenTheSameLegalHoldStatus_whenUpdating_thenShouldReturnFalse() = runTest { + // given + val conversationId = QualifiedIDEntity("conversationId", "domain") + conversationDAO.insertConversation(conversationEntity1.copy(conversationId)) + conversationDAO.updateLegalHoldStatus(conversationId, ConversationEntity.LegalHoldStatus.DISABLED) + // when + val result = conversationDAO.updateLegalHoldStatus(conversationId, ConversationEntity.LegalHoldStatus.DISABLED) + // then + assertEquals(false, result) + } + @Test + fun givenNewLegalHoldStatusChangeNotifiedFlag_whenUpdating_thenShouldReturnTrue() = runTest { + // given + val conversationId = QualifiedIDEntity("conversationId", "domain") + conversationDAO.insertConversation(conversationEntity1.copy(conversationId)) + conversationDAO.updateLegalHoldStatusChangeNotified(conversationId, true) + // when + val result = conversationDAO.updateLegalHoldStatusChangeNotified(conversationId, false) + // then + assertEquals(true, result) + } + @Test + fun givenTheSameLegalHoldStatusChangeNotifiedFlag_whenUpdating_thenShouldReturnFalse() = runTest { + // given + val conversationId = QualifiedIDEntity("conversationId", "domain") + conversationDAO.insertConversation(conversationEntity1.copy(conversationId)) + conversationDAO.updateLegalHoldStatusChangeNotified(conversationId, true) + // when + val result = conversationDAO.updateLegalHoldStatusChangeNotified(conversationId, true) + // then + assertEquals(false, result) + } + @Test + fun givenLegalHoldStatus_whenObserving_thenShouldReturnCorrectValue() = runTest { + // given + val conversationId = QualifiedIDEntity("conversationId", "domain") + conversationDAO.insertConversation(conversationEntity1.copy(conversationId)) + conversationDAO.updateLegalHoldStatus(conversationId, ConversationEntity.LegalHoldStatus.ENABLED) + // when + val result = conversationDAO.observeLegalHoldStatus(conversationId).first() + // then + assertEquals(ConversationEntity.LegalHoldStatus.ENABLED, result) + } + @Test + fun givenLegalHoldStatusChangeNotified_whenObserving_thenShouldReturnCorrectValue() = runTest { + // given + val conversationId = QualifiedIDEntity("conversationId", "domain") + conversationDAO.insertConversation(conversationEntity1.copy(conversationId)) + conversationDAO.updateLegalHoldStatusChangeNotified(conversationId, false) + // when + val result = conversationDAO.observeLegalHoldStatusChangeNotified(conversationId).first() + // then + assertEquals(false, result) + } + private fun ConversationEntity.toViewEntity(userEntity: UserEntity? = null): ConversationViewEntity { val protocol: ConversationEntity.Protocol val mlsGroupId: String?