Skip to content

Commit

Permalink
feat: discover legal hold when sending message [WPB-5999] (#2558)
Browse files Browse the repository at this point in the history
  • Loading branch information
saleniuk authored Dec 28, 2023
1 parent d141853 commit 21bbb68
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ import com.wire.kalium.logic.feature.client.ClientFingerprintUseCase
import com.wire.kalium.logic.feature.client.ClientScope
import com.wire.kalium.logic.feature.client.DeleteClientUseCase
import com.wire.kalium.logic.feature.client.FetchSelfClientsFromRemoteUseCase
import com.wire.kalium.logic.feature.client.FetchUsersClientsFromRemoteUseCase
import com.wire.kalium.logic.feature.client.GetOrRegisterClientUseCase
import com.wire.kalium.logic.feature.client.NeedsToRegisterClientUseCase
import com.wire.kalium.logic.feature.client.ObserveClientDetailsUseCase
import com.wire.kalium.logic.feature.client.ObserveClientsByUserIdUseCase
import com.wire.kalium.logic.feature.client.ObserveCurrentClientIdUseCase
import com.wire.kalium.logic.feature.client.PersistOtherUserClientsUseCase
import com.wire.kalium.logic.feature.client.UpdateClientVerificationStatusUseCase
import com.wire.kalium.logic.feature.keypackage.MLSKeyPackageCountUseCase
import com.wire.kalium.logic.sync.slow.RestartSlowSyncProcessForRecoveryUseCase
Expand Down Expand Up @@ -72,8 +72,8 @@ class ClientModule {

@ViewModelScoped
@Provides
fun providePersistOtherUsersClients(clientScope: ClientScope): PersistOtherUserClientsUseCase =
clientScope.persistOtherUserClients
fun provideFetchUsersClientsFromRemoteUseCase(clientScope: ClientScope): FetchUsersClientsFromRemoteUseCase =
clientScope.fetchUsersClients

@ViewModelScoped
@Provides
Expand All @@ -82,8 +82,8 @@ class ClientModule {

@ViewModelScoped
@Provides
fun provideSelfClientsUseCase(clientScope: ClientScope): FetchSelfClientsFromRemoteUseCase =
clientScope.selfClients
fun provideFetchSelfClientsFromRemoteUseCase(clientScope: ClientScope): FetchSelfClientsFromRemoteUseCase =
clientScope.fetchSelfClients

@ViewModelScoped
@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,17 @@ import com.wire.android.util.FileManager
import com.wire.android.util.ImageUtil
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.android.util.getAudioLengthInMs
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.configuration.FileSharingStatus
import com.wire.kalium.logic.data.asset.AttachmentType
import com.wire.kalium.logic.data.asset.KaliumFileSystem
import com.wire.kalium.logic.data.conversation.Conversation.TypingIndicatorMode
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.message.SelfDeletionTimer
import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.failure.LegalHoldEnabledForConversationFailure
import com.wire.kalium.logic.feature.asset.GetAssetSizeLimitUseCase
import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageResult
import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageUseCase
import com.wire.kalium.logic.feature.conversation.InteractionAvailability
import com.wire.kalium.logic.feature.conversation.IsInteractionAvailableResult
Expand All @@ -75,6 +78,7 @@ import com.wire.kalium.logic.feature.message.ephemeral.EnqueueMessageSelfDeletio
import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveSelfDeletionTimerSettingsForConversationUseCase
import com.wire.kalium.logic.feature.selfDeletingMessages.PersistNewSelfDeletionTimerUseCase
import com.wire.kalium.logic.feature.user.IsFileSharingEnabledUseCase
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.onFailure
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
Expand Down Expand Up @@ -216,7 +220,8 @@ class MessageComposerViewModel @Inject constructor(
shouldInformAboutDegradedBeforeSendingMessage() ->
sureAboutMessagingDialogState = SureAboutMessagingDialogState.Visible.ConversationVerificationDegraded(messageBundle)
shouldInformAboutUnderLegalHoldBeforeSendingMessage() ->
sureAboutMessagingDialogState = SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold(messageBundle)
sureAboutMessagingDialogState =
SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.BeforeSending(messageBundle)
else -> sendMessage(messageBundle)
}
}
Expand All @@ -231,7 +236,7 @@ class MessageComposerViewModel @Inject constructor(
originalMessageId = originalMessageId,
text = newContent,
mentions = newMentions.map { it.intoMessageMention() },
)
).handleLegalHoldFailureAfterSendingMessage()
}
sendTypingEvent(conversationId, TypingIndicatorMode.STOPPED)
}
Expand All @@ -256,7 +261,7 @@ class MessageComposerViewModel @Inject constructor(
text = message,
mentions = mentions.map { it.intoMessageMention() },
quotedMessageId = quotedMessageId
)
).handleLegalHoldFailureAfterSendingMessage()
}
sendTypingEvent(conversationId, TypingIndicatorMode.STOPPED)
}
Expand Down Expand Up @@ -335,7 +340,7 @@ class MessageComposerViewModel @Inject constructor(
assetDataSize = dataSize,
assetMimeType = mimeType,
audioLengthInMs = 0L
)
).handleLegalHoldFailureAfterSendingMessage()
}

AttachmentType.VIDEO,
Expand All @@ -354,7 +359,7 @@ class MessageComposerViewModel @Inject constructor(
dataPath = dataPath,
mimeType = mimeType
)
)
).handleLegalHoldFailureAfterSendingMessage()
} catch (e: OutOfMemoryError) {
appLogger.e("There was an OutOfMemory error while uploading the asset")
onSnackbarMessage(ConversationSnackbarMessages.ErrorSendingAsset)
Expand All @@ -366,6 +371,19 @@ class MessageComposerViewModel @Inject constructor(
}
}

private fun CoreFailure.handleLegalHoldFailureAfterSendingMessage() = also {
if (this is LegalHoldEnabledForConversationFailure) {
sureAboutMessagingDialogState = SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.AfterSending(this.messageId)
}
}
private fun Either<CoreFailure, Unit>.handleLegalHoldFailureAfterSendingMessage() =
onFailure { it.handleLegalHoldFailureAfterSendingMessage() }
private fun ScheduleNewAssetMessageResult.handleLegalHoldFailureAfterSendingMessage() = also {
if (it is ScheduleNewAssetMessageResult.Failure) {
it.coreFailure.handleLegalHoldFailureAfterSendingMessage()
}
}

fun retrySendingMessage(messageId: String) {
viewModelScope.launch {
retryFailedMessage(messageId = messageId, conversationId = conversationId)
Expand Down Expand Up @@ -481,7 +499,14 @@ class MessageComposerViewModel @Inject constructor(
(sureAboutMessagingDialogState as? SureAboutMessagingDialogState.Visible)?.let {
viewModelScope.launch {
it.markAsNotified()
trySendMessage(it.messageBundleToSend)
when (it) {
is SureAboutMessagingDialogState.Visible.ConversationVerificationDegraded ->
trySendMessage(it.messageBundleToSend)
is SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.BeforeSending ->
trySendMessage(it.messageBundleToSend)
is SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.AfterSending ->
retrySendingMessage(it.messageId)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ package com.wire.android.ui.home.conversations
import com.wire.android.ui.home.messagecomposer.state.MessageBundle
import com.wire.android.ui.home.newconversation.model.Contact
import com.wire.kalium.logic.data.asset.AttachmentType
import com.wire.kalium.logic.data.id.MessageId
import com.wire.kalium.logic.data.message.SelfDeletionTimer
import com.wire.kalium.logic.feature.conversation.InteractionAvailability
import kotlin.time.Duration.Companion.ZERO
Expand Down Expand Up @@ -51,8 +52,11 @@ sealed class InvalidLinkDialogState {

sealed class SureAboutMessagingDialogState {
data object Hidden : SureAboutMessagingDialogState()
sealed class Visible(open val messageBundleToSend: MessageBundle) : SureAboutMessagingDialogState() {
data class ConversationVerificationDegraded(override val messageBundleToSend: MessageBundle) : Visible(messageBundleToSend)
data class ConversationUnderLegalHold(override val messageBundleToSend: MessageBundle) : Visible(messageBundleToSend)
sealed class Visible : SureAboutMessagingDialogState() {
data class ConversationVerificationDegraded(val messageBundleToSend: MessageBundle) : Visible()
sealed class ConversationUnderLegalHold : Visible() {
data class BeforeSending(val messageBundleToSend: MessageBundle) : ConversationUnderLegalHold()
data class AfterSending(val messageId: MessageId) : ConversationUnderLegalHold()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ import com.wire.kalium.logic.data.conversation.MutedConversationStatus
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.client.FetchUsersClientsFromRemoteUseCase
import com.wire.kalium.logic.feature.client.ObserveClientsByUserIdUseCase
import com.wire.kalium.logic.feature.client.PersistOtherUserClientsUseCase
import com.wire.kalium.logic.feature.connection.BlockUserResult
import com.wire.kalium.logic.feature.connection.BlockUserUseCase
import com.wire.kalium.logic.feature.connection.UnblockUserResult
Expand Down Expand Up @@ -100,7 +100,7 @@ class OtherUserProfileScreenViewModel @Inject constructor(
private val removeMemberFromConversation: RemoveMemberFromConversationUseCase,
private val updateMemberRole: UpdateConversationMemberRoleUseCase,
private val observeClientList: ObserveClientsByUserIdUseCase,
private val persistOtherUserClients: PersistOtherUserClientsUseCase,
private val fetchUsersClients: FetchUsersClientsFromRemoteUseCase,
private val clearConversationContentUseCase: ClearConversationContentUseCase,
private val updateConversationArchivedStatus: UpdateConversationArchivedStatusUseCase,
savedStateHandle: SavedStateHandle
Expand Down Expand Up @@ -152,7 +152,7 @@ class OtherUserProfileScreenViewModel @Inject constructor(

private fun persistClients() {
viewModelScope.launch(dispatchers.io()) {
persistOtherUserClients(userId)
fetchUsersClients(listOf(userId))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ internal class MessageComposerViewModelArrangement {
)
} returns Either.Right(Unit)
}
fun withFailedSendTextMessage(failure: CoreFailure) = apply {
coEvery {
sendTextMessage(
any(),
any(),
any(),
any()
)
} returns Either.Left(failure)
}

fun withSuccessfulSendEditTextMessage() = apply {
coEvery {
Expand Down Expand Up @@ -334,6 +344,10 @@ internal class MessageComposerViewModelArrangement {
coEvery { observeConversationUnderLegalHoldNotified(any()) } returns flowOf(flag)
}

fun withSuccessfulRetryFailedMessage() = apply {
coEvery { retryFailedMessageUseCase(any(), any()) } returns Either.Right(Unit)
}

fun arrange() = this to viewModel
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import com.wire.android.ui.home.messagecomposer.state.Ping
import com.wire.kalium.logic.data.asset.AttachmentType
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.message.SelfDeletionTimer
import com.wire.kalium.logic.failure.LegalHoldEnabledForConversationFailure
import com.wire.kalium.logic.feature.asset.GetAssetSizeLimitUseCaseImpl.Companion.ASSET_SIZE_DEFAULT_LIMIT_BYTES
import io.mockk.coVerify
import io.mockk.verify
Expand Down Expand Up @@ -680,7 +681,7 @@ class MessageComposerViewModelTest {
}

@Test
fun `given that user needs to be informed about enabled legal hold, when invoked sending, then message is not sent and dialog shown`() =
fun `given that user needs to be informed about enabled legal hold when sending, then message is not sent and dialog shown`() =
runTest {
// given
val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList())
Expand All @@ -693,13 +694,13 @@ class MessageComposerViewModelTest {
// then
coVerify(exactly = 0) { arrangement.sendTextMessage.invoke(any(), any(), any(), any()) }
assertEquals(
SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold(messageBundle),
SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.BeforeSending(messageBundle),
viewModel.sureAboutMessagingDialogState
)
}

@Test
fun `given that user chose to dismiss when enabled legal hold, when invoked sending, then message is not sent and dialog hidden`() =
fun `given that user chose to dismiss when enabled legal hold before sending, then message is not sent and dialog hidden`() =
runTest {
// given
val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList())
Expand All @@ -718,7 +719,7 @@ class MessageComposerViewModelTest {
}

@Test
fun `given that user chose to send anyway when enabled legal hold, when invoked sending, then message is sent and dialog hidden`() =
fun `given that user chose to send anyway when enabled legal hold before sending, then message is sent and dialog hidden`() =
runTest {
// given
val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList())
Expand All @@ -736,4 +737,64 @@ class MessageComposerViewModelTest {
coVerify(exactly = 1) { arrangement.sendTextMessage.invoke(any(), any(), any(), any()) }
assertEquals(SureAboutMessagingDialogState.Hidden, viewModel.sureAboutMessagingDialogState)
}

@Test
fun `given that user needs to be informed about enabled legal hold when sending fails, then message is not resent and dialog shown`() =
runTest {
// given
val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList())
val messageId = "messageId"
val (arrangement, viewModel) = MessageComposerViewModelArrangement()
.withSuccessfulViewModelInit()
.withFailedSendTextMessage(LegalHoldEnabledForConversationFailure(messageId))
.arrange()
// when
viewModel.trySendMessage(messageBundle)
// then
coVerify(exactly = 0) { arrangement.retryFailedMessageUseCase.invoke(eq(messageId), any()) }
assertEquals(
SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.AfterSending(messageId),
viewModel.sureAboutMessagingDialogState
)
}

@Test
fun `given that user chose to dismiss when enabled legal hold when sending fails, then message is not resent and dialog hidden`() =
runTest {
// given
val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList())
val messageId = "messageId"
val (arrangement, viewModel) = MessageComposerViewModelArrangement()
.withSuccessfulViewModelInit()
.withObserveConversationUnderLegalHoldNotified(true)
.withFailedSendTextMessage(LegalHoldEnabledForConversationFailure(messageId))
.arrange()
viewModel.trySendMessage(messageBundle)
// when
viewModel.dismissSureAboutSendingMessage()
advanceUntilIdle()
// then
coVerify(exactly = 0) { arrangement.retryFailedMessageUseCase.invoke(any(), any()) }
assertEquals(SureAboutMessagingDialogState.Hidden, viewModel.sureAboutMessagingDialogState)
}

@Test
fun `given that user chose to send anyway when enabled legal hold when sending fails, then message is resent and dialog hidden`() =
runTest {
// given
val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList())
val messageId = "messageId"
val (arrangement, viewModel) = MessageComposerViewModelArrangement()
.withSuccessfulViewModelInit()
.withFailedSendTextMessage(LegalHoldEnabledForConversationFailure(messageId))
.withSuccessfulRetryFailedMessage()
.arrange()
viewModel.trySendMessage(messageBundle)
// when
viewModel.acceptSureAboutSendingMessage()
advanceUntilIdle()
// then
coVerify(exactly = 1) { arrangement.retryFailedMessageUseCase.invoke(eq(messageId), any()) }
assertEquals(SureAboutMessagingDialogState.Hidden, viewModel.sureAboutMessagingDialogState)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import com.wire.android.ui.userprofile.other.OtherUserProfileScreenViewModelTest
import com.wire.android.ui.userprofile.other.OtherUserProfileScreenViewModelTest.Companion.USER_ID
import com.wire.android.util.ui.WireSessionImageLoader
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.feature.client.FetchUsersClientsFromRemoteUseCase
import com.wire.kalium.logic.feature.client.ObserveClientsByUserIdUseCase
import com.wire.kalium.logic.feature.client.PersistOtherUserClientsUseCase
import com.wire.kalium.logic.feature.connection.BlockUserResult
import com.wire.kalium.logic.feature.connection.BlockUserUseCase
import com.wire.kalium.logic.feature.connection.UnblockUserUseCase
Expand Down Expand Up @@ -96,7 +96,7 @@ internal class OtherUserProfileViewModelArrangement {
lateinit var observeClientList: ObserveClientsByUserIdUseCase

@MockK
lateinit var persistOtherUserClientsUseCase: PersistOtherUserClientsUseCase
lateinit var fetchUsersClientsFromRemote: FetchUsersClientsFromRemoteUseCase

@MockK
lateinit var clearConversationContent: ClearConversationContentUseCase
Expand All @@ -118,7 +118,7 @@ internal class OtherUserProfileViewModelArrangement {
removeMemberFromConversationUseCase,
updateConversationMemberRoleUseCase,
observeClientList,
persistOtherUserClientsUseCase,
fetchUsersClientsFromRemote,
clearConversationContent,
updateConversationArchivedStatus,
savedStateHandle
Expand Down
2 changes: 1 addition & 1 deletion kalium
Submodule kalium updated 16 files
+1 βˆ’1 cli/src/commonMain/kotlin/com/wire/kalium/cli/commands/DeleteClientCommand.kt
+24 βˆ’0 logic/src/commonMain/kotlin/com/wire/kalium/logic/failure/LegalHoldEnabledForConversationFailure.kt
+9 βˆ’5 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
+5 βˆ’10 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/ClientScope.kt
+6 βˆ’6 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/FetchUsersClientsFromRemoteUseCase.kt
+8 βˆ’2 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/DebugScope.kt
+6 βˆ’0 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt
+16 βˆ’4 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageSendFailureHandler.kt
+44 βˆ’16 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageSender.kt
+39 βˆ’8 logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/legalhold/LegalHoldHandler.kt
+76 βˆ’16 logic/src/commonTest/kotlin/com/wire/kalium/logic/data/prekey/MessageSendFailureHandlerTest.kt
+10 βˆ’10 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/FetchUsersClientsFromRemoteUseCaseTest.kt
+145 βˆ’0 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MessageSenderTest.kt
+160 βˆ’9 logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/legalhold/LegalHoldHandlerTest.kt
+15 βˆ’0 logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/ClientRepositoryArrangement.kt
+1 βˆ’1 tango-tests/src/integrationTest/kotlin/PocIntegrationTest.kt

0 comments on commit 21bbb68

Please sign in to comment.