Skip to content

Commit

Permalink
feat: Delete local conversation use case [#WPB-14601]
Browse files Browse the repository at this point in the history
  • Loading branch information
m-zagorski committed Dec 17, 2024
1 parent 8efbb07 commit 523b89f
Show file tree
Hide file tree
Showing 11 changed files with 408 additions and 1 deletion.
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> {
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

0 comments on commit 523b89f

Please sign in to comment.