Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Delete local conversation use case [#WPB-14601] #3182

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ interface ConversationRepository {
): Either<CoreFailure, Unit>

suspend fun deleteConversation(conversationId: ConversationId): Either<CoreFailure, Unit>
suspend fun deleteLocalConversation(conversationId: ConversationId): Either<CoreFailure, Unit>

/**
* Deletes all conversation messages
Expand Down Expand Up @@ -884,6 +885,12 @@ internal class ConversationDataSource internal constructor(
}
}

override suspend fun deleteLocalConversation(conversationId: ConversationId): Either<CoreFailure, Unit> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking between deleteLocalConversation and deleteConversationLocally - which one would you guys prefer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also there is the deleteConversation but it invoked mls method to nuke the conversation which I think we dont want while removing it locally? Am I right?

return wrapStorageRequest {
conversationDAO.deleteConversationByQualifiedID(conversationId.toDao())
}
}

override suspend fun clearContent(conversationId: ConversationId): Either<StorageFailure, Unit> =
wrapStorageRequest {
conversationDAO.clearContent(conversationId.toDao())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ internal interface MessageRepository {
messageId: String,
conversationId: ConversationId
): Either<StorageFailure, AssetTransferStatus>

suspend fun getAllAssetIdsFromConversationId(
conversationId: ConversationId,
): Either<StorageFailure, List<String>>
}

// TODO: suppress TooManyFunctions for now, something we need to fix in the future
Expand Down Expand Up @@ -706,4 +710,12 @@ internal class MessageDataSource internal constructor(
): Either<StorageFailure, AssetTransferStatus> = wrapStorageRequest {
messageDAO.getMessageAssetTransferStatus(messageId, conversationId.toDao()).toModel()
}

override suspend fun getAllAssetIdsFromConversationId(
conversationId: ConversationId
): Either<StorageFailure, List<String>> {
return wrapStorageRequest {
messageDAO.getAllMessageAssetIdsForConversationId(conversationId = conversationId.toDao())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1806,7 +1806,9 @@ class UserSessionScope internal constructor(
this,
userScopedLogger,
refreshUsersWithoutMetadata,
sessionManager.getServerConfig().links
sessionManager.getServerConfig().links,
messages.messageRepository,
assetRepository
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.feature.conversation

import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.data.asset.AssetRepository
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.message.MessageRepository
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap

interface ClearLocalConversationAssetsUseCase {
/**
* Clear all conversation assets from local storage
*
* @param conversationId - id of conversation in which assets should be cleared
*/
suspend operator fun invoke(conversationId: ConversationId): Either<CoreFailure, Unit>
}

internal class ClearLocalConversationAssetsUseCaseImpl(
private val messageRepository: MessageRepository,
private val assetRepository: AssetRepository
) : ClearLocalConversationAssetsUseCase {
override suspend fun invoke(conversationId: ConversationId): Either<CoreFailure, Unit> {
return messageRepository.getAllAssetIdsFromConversationId(conversationId)
.flatMap { ids ->
if (ids.isEmpty()) return Either.Right(Unit)

ids.map { id -> assetRepository.deleteAssetLocally(id) }
.reduce { acc, either ->
acc.flatMap { either }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.wire.kalium.logger.KaliumLogger
import com.wire.kalium.logic.cache.SelfConversationIdProvider
import com.wire.kalium.logic.configuration.server.ServerConfig
import com.wire.kalium.logic.configuration.server.ServerConfigRepository
import com.wire.kalium.logic.data.asset.AssetRepository
import com.wire.kalium.logic.data.connection.ConnectionRepository
import com.wire.kalium.logic.data.conversation.ConversationGroupRepository
import com.wire.kalium.logic.data.conversation.ConversationRepository
Expand All @@ -38,6 +39,7 @@ import com.wire.kalium.logic.data.conversation.folders.ConversationFolderReposit
import com.wire.kalium.logic.data.id.CurrentClientIdProvider
import com.wire.kalium.logic.data.id.QualifiedIdMapper
import com.wire.kalium.logic.data.id.SelfTeamIdProvider
import com.wire.kalium.logic.data.message.MessageRepository
import com.wire.kalium.logic.data.message.PersistMessageUseCase
import com.wire.kalium.logic.data.properties.UserPropertyRepository
import com.wire.kalium.logic.data.team.TeamRepository
Expand Down Expand Up @@ -115,6 +117,8 @@ class ConversationScope internal constructor(
private val kaliumLogger: KaliumLogger,
private val refreshUsersWithoutMetadata: RefreshUsersWithoutMetadataUseCase,
private val serverConfigLinks: ServerConfig.Links,
internal val messageRepository: MessageRepository,
internal val assetRepository: AssetRepository,
internal val dispatcher: KaliumDispatcher = KaliumDispatcherImpl,
) {

Expand Down Expand Up @@ -266,6 +270,18 @@ class ConversationScope internal constructor(
selfConversationIdProvider
)

val clearConversationAssetsLocally: ClearLocalConversationAssetsUseCase
get() = ClearLocalConversationAssetsUseCaseImpl(
messageRepository,
assetRepository
)

val deleteLocalConversationUseCase: DeleteLocalConversationUseCase
get() = DeleteLocalConversationUseCaseImpl(
conversationRepository,
clearConversationAssetsLocally
)

val joinConversationViaCode: JoinConversationViaCodeUseCase
get() = JoinConversationViaCodeUseCase(conversationGroupRepository, selfUserId)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.feature.conversation

import com.wire.kalium.logic.CoreFailure
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.flatMap

interface DeleteLocalConversationUseCase {
/**
* Delete local conversation which:
* - Clear all local assets
* - Clear content
* - Remove conversation
*
* @param conversationId - id of conversation to delete
*/
suspend operator fun invoke(conversationId: ConversationId): Either<CoreFailure, Unit>
}

internal class DeleteLocalConversationUseCaseImpl(
private val conversationRepository: ConversationRepository,
private val clearLocalConversationAssets: ClearLocalConversationAssetsUseCase
) : DeleteLocalConversationUseCase {

override suspend fun invoke(conversationId: ConversationId): Either<CoreFailure, Unit> {
return clearLocalConversationAssets(conversationId)
.flatMap { conversationRepository.clearContent(conversationId) }
.flatMap { conversationRepository.deleteLocalConversation(conversationId) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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.feature.conversation

import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.data.asset.AssetRepository
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.message.MessageRepository
import com.wire.kalium.logic.functional.Either
import io.mockative.Mock
import io.mockative.any
import io.mockative.coEvery
import io.mockative.coVerify
import io.mockative.mock
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertIs

class ClearLocalConversationAssetsUseCaseTest {

@Test
fun givenConversationAssetIds_whenAllDeletionsAreSuccess_thenSuccessResultIsPropagated() = runTest {
// given
val ids = listOf("id_1", "id_2")
val (arrangement, useCase) = Arrangement()
.withAssetIdsResponse(ids)
.withAssetClearSuccess("id_1")
.withAssetClearSuccess("id_2")
.arrange()

// when
val result = useCase(ConversationId("someValue", "someDomain"))

// then
assertIs<Either.Right<Unit>>(result)
coVerify { arrangement.assetRepository.deleteAssetLocally(any()) }.wasInvoked(exactly = 2)
}

@Test
fun givenConversationAssetIds_whenOneDeletionFailed_thenFailureResultIsPropagated() = runTest {
// given
val ids = listOf("id_1", "id_2")
val (arrangement, useCase) = Arrangement()
.withAssetIdsResponse(ids)
.withAssetClearSuccess("id_1")
.withAssetClearError("id_2")
.arrange()

// when
val result = useCase(ConversationId("someValue", "someDomain"))

// then
assertIs<Either.Left<Unit>>(result)
coVerify { arrangement.assetRepository.deleteAssetLocally(any()) }.wasInvoked(exactly = 2)
}

@Test
fun givenEmptyConversationAssetIds_whenInvoked_thenDeletionsAreNotInvoked() = runTest {
// given
val (arrangement, useCase) = Arrangement()
.withAssetIdsResponse(emptyList())
.arrange()

// when
val result = useCase(ConversationId("someValue", "someDomain"))

// then
assertIs<Either.Right<Unit>>(result)
coVerify { arrangement.assetRepository.deleteAssetLocally(any()) }.wasNotInvoked()
}

private class Arrangement {
@Mock
val messageRepository = mock(MessageRepository::class)

@Mock
val assetRepository = mock(AssetRepository::class)

suspend fun withAssetClearSuccess(id: String) = apply {
coEvery { assetRepository.deleteAssetLocally(id) }.returns(Either.Right(Unit))
}

suspend fun withAssetClearError(id: String) = apply {
coEvery { assetRepository.deleteAssetLocally(id) }.returns(Either.Left(CoreFailure.Unknown(null)))
}

suspend fun withAssetIdsResponse(ids: List<String>) = apply {
coEvery { messageRepository.getAllAssetIdsFromConversationId(any()) }.returns(Either.Right(ids))
}

fun arrange() = this to ClearLocalConversationAssetsUseCaseImpl(
messageRepository = messageRepository,
assetRepository = assetRepository
)
}
}
Loading
Loading