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: Add getSenderNameByMessageId useCase WPB-11723 #3186

Open
wants to merge 4 commits 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 @@ -253,6 +253,8 @@ internal interface MessageRepository {
messageId: String,
conversationId: ConversationId
): Either<StorageFailure, AssetTransferStatus>

suspend fun getSenderNameByMessageId(conversationId: ConversationId, messageId: String): Either<CoreFailure, String>
}

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

override suspend fun getSenderNameByMessageId(conversationId: ConversationId, messageId: String): Either<CoreFailure, String> =
wrapStorageRequest { messageDAO.getSenderNameById(messageId, conversationId.toDao()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.message

import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.message.MessageRepository
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.util.KaliumDispatcher
import com.wire.kalium.util.KaliumDispatcherImpl
import kotlinx.coroutines.withContext

/**
* Provides a way to get a name of user that sent a message
* using its [ConversationId] and message ID coordinates.
*/
class GetSenderNameByMessageIdUseCase internal constructor(
private val messageRepository: MessageRepository,
private val dispatchers: KaliumDispatcher = KaliumDispatcherImpl
) {
suspend operator fun invoke(
conversationId: ConversationId,
messageId: String
): Result = withContext(dispatchers.io) {
messageRepository.getSenderNameByMessageId(conversationId, messageId).fold({
Result.Failure(it)
}, {
Result.Success(it)
})
}

sealed interface Result {

data class Success(val name: String) : Result

/**
* [StorageFailure.DataNotFound] or some other generic error.
*/
data class Failure(val cause: CoreFailure) : Result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -453,4 +453,7 @@ class MessageScope internal constructor(

val removeMessageDraftUseCase: RemoveMessageDraftUseCase
get() = RemoveMessageDraftUseCaseImpl(messageDraftRepository)

val getSenderNameByMessageId: GetSenderNameByMessageIdUseCase
get() = GetSenderNameByMessageIdUseCase(messageRepository)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.message

import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.message.MessageRepository
import com.wire.kalium.logic.framework.TestConversation
import com.wire.kalium.logic.framework.TestMessage
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.test_util.TestKaliumDispatcher
import com.wire.kalium.util.KaliumDispatcher
import io.mockative.Mock
import io.mockative.coEvery
import io.mockative.coVerify
import io.mockative.mock
import io.mockative.once
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs

class GetSenderNameByMessageIdUseCaseTest {

private val testDispatchers: KaliumDispatcher = TestKaliumDispatcher

@Test
fun givenMessageAndConversationId_whenInvokingUseCase_thenShouldCallMessageRepository() = runTest(testDispatchers.io) {
val (arrangement, getSenderNameByMessageId) = Arrangement()
.withRepositorySenderNameByMessageIdReturning(CONVERSATION_ID, MESSAGE_ID, Either.Left(StorageFailure.DataNotFound))
.arrange()

getSenderNameByMessageId(CONVERSATION_ID, MESSAGE_ID)

coVerify {
arrangement.messageRepository.getSenderNameByMessageId(CONVERSATION_ID, MESSAGE_ID)
}.wasInvoked(exactly = once)
}

@Test
fun givenRepositoryFails_whenInvokingUseCase_thenShouldPropagateTheFailure() = runTest(testDispatchers.io) {
val cause = StorageFailure.DataNotFound
val (_, getSenderNameByMessageId) = Arrangement()
.withRepositorySenderNameByMessageIdReturning(CONVERSATION_ID, MESSAGE_ID, Either.Left(cause))
.arrange()

val result = getSenderNameByMessageId(CONVERSATION_ID, MESSAGE_ID)

assertIs<GetSenderNameByMessageIdUseCase.Result.Failure>(result)
assertEquals(cause, result.cause)
}

@Test
fun givenRepositorySucceeds_whenInvokingUseCase_thenShouldPropagateTheSuccess() = runTest(testDispatchers.io) {
val (_, getSenderNameByMessageId) = Arrangement()
.withRepositorySenderNameByMessageIdReturning(CONVERSATION_ID, MESSAGE_ID, Either.Right(NAME))
.arrange()

val result = getSenderNameByMessageId(CONVERSATION_ID, MESSAGE_ID)

assertIs<GetSenderNameByMessageIdUseCase.Result.Success>(result)
assertEquals(NAME, result.name)
}

private inner class Arrangement {

@Mock
val messageRepository: MessageRepository = mock(MessageRepository::class)

private val getSenderNameByMessageId by lazy {
GetSenderNameByMessageIdUseCase(messageRepository, testDispatchers)
}

suspend fun withRepositorySenderNameByMessageIdReturning(
conversationId: ConversationId,
messageId: String,
response: Either<StorageFailure, String>
) = apply {
coEvery {
messageRepository.getSenderNameByMessageId(conversationId, messageId)
}.returns(response)
}

fun arrange() = this to getSenderNameByMessageId
}

private companion object {
const val MESSAGE_ID = TestMessage.TEST_MESSAGE_ID
const val NAME = "Test User"
val CONVERSATION_ID = TestConversation.ID
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,7 @@ SELECT name, handle FROM User WHERE qualified_id = :userId;

updateTeamId:
UPDATE User SET team = ? WHERE qualified_id = ?;

selectNameByMessageId:
SELECT name FROM User
WHERE qualified_id = (SELECT Message.sender_user_id FROM Message WHERE Message.id = :messageId AND Message.conversation_id = :conversationId);
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,5 @@ interface MessageDAO {

suspend fun observeAssetStatuses(conversationId: QualifiedIDEntity): Flow<List<MessageAssetStatusEntity>>
suspend fun getMessageAssetTransferStatus(messageId: String, conversationId: QualifiedIDEntity): AssetTransferStatusEntity
suspend fun getSenderNameById(id: String, conversationId: QualifiedIDEntity): String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.wire.kalium.persistence.MessagesQueries
import com.wire.kalium.persistence.NotificationQueries
import com.wire.kalium.persistence.ReactionsQueries
import com.wire.kalium.persistence.UnreadEventsQueries
import com.wire.kalium.persistence.UsersQueries
import com.wire.kalium.persistence.content.ButtonContentQueries
import com.wire.kalium.persistence.dao.ConversationIDEntity
import com.wire.kalium.persistence.dao.QualifiedIDEntity
Expand Down Expand Up @@ -59,6 +60,7 @@ internal class MessageDAOImpl internal constructor(
private val messagePreviewQueries: MessagePreviewQueries,
private val selfUserId: UserIDEntity,
private val reactionsQueries: ReactionsQueries,
private val userQueries: UsersQueries,
private val coroutineContext: CoroutineContext,
private val assetStatusQueries: MessageAssetTransferStatusQueries,
buttonContentQueries: ButtonContentQueries
Expand Down Expand Up @@ -505,6 +507,10 @@ internal class MessageDAOImpl internal constructor(
.executeAsOne()
}

override suspend fun getSenderNameById(id: String, conversationId: QualifiedIDEntity): String? = withContext(coroutineContext) {
userQueries.selectNameByMessageId(id, conversationId).executeAsOneOrNull()?.name
}
borichellow marked this conversation as resolved.
Show resolved Hide resolved

override val platformExtensions: MessageExtensions = MessageExtensionsImpl(queries, assetViewQueries, mapper, coroutineContext)

}
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ class UserDatabaseBuilder internal constructor(
database.messagePreviewQueries,
userId,
database.reactionsQueries,
database.usersQueries,
queriesContext,
database.messageAssetTransferStatusQueries,
database.buttonContentQueries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2344,6 +2344,96 @@ class MessageDAOTest : BaseDatabaseTest() {
assertEquals(messages.size, assetStatuses.size)
}

@Test
fun givenMessagesAndUsersAreInserted_whenGettingSenderNameByMessageId_thenOnlyRelevantNameReturned() = runTest {
insertInitialData()

val userInQuestion = userDetailsEntity1
val otherUser = userDetailsEntity2

val expectedMessages = listOf(
newRegularMessageEntity(
"1",
conversationId = conversationEntity1.id,
senderUserId = userInQuestion.id,
status = MessageEntity.Status.PENDING,
senderName = userInQuestion.name!!,
sender = userInQuestion
),
newRegularMessageEntity(
"2",
conversationId = conversationEntity1.id,
senderUserId = otherUser.id,
status = MessageEntity.Status.PENDING,
senderName = otherUser.name!!,
sender = otherUser
)
)
messageDAO.insertOrIgnoreMessages(expectedMessages)

val result = messageDAO.getSenderNameById("1", conversationEntity1.id)

assertEquals(userDetailsEntity1.name, result)
}

@Test
fun givenMessagesAreInserted_whenGettingSenderNameByMessageId_thenOnlyRelevantNameReturned() = runTest {
insertInitialData()

val expectedMessages = listOf(
newRegularMessageEntity(
"1",
conversationId = conversationEntity1.id,
senderUserId = userDetailsEntity1.id,
status = MessageEntity.Status.PENDING,
senderName = userDetailsEntity1.name!!,
sender = userDetailsEntity1
),
newRegularMessageEntity(
"2",
conversationId = conversationEntity1.id,
senderUserId = userDetailsEntity2.id,
status = MessageEntity.Status.PENDING,
senderName = userDetailsEntity2.name!!,
sender = userDetailsEntity2
)
)
messageDAO.insertOrIgnoreMessages(expectedMessages)

val result = messageDAO.getSenderNameById("1", conversationEntity1.id)

assertEquals(userDetailsEntity1.name, result)
}

@Test
fun givenMessagesAreButNoUserInserted_whenGettingSenderNameByMessageId_thenNullNameReturned() = runTest {
insertInitialData()

val expectedMessages = listOf(
newRegularMessageEntity(
"1",
conversationId = conversationEntity1.id,
senderUserId = userDetailsEntity1.id.copy(value = "absolutely_another_value"),
status = MessageEntity.Status.PENDING,
senderName = "s",
sender = userDetailsEntity1.copy(name = "s", id = userDetailsEntity1.id.copy(value = "absolutely_another_value"))
),
newRegularMessageEntity(
"2",
conversationId = conversationEntity1.id,
senderUserId = userDetailsEntity2.id,
status = MessageEntity.Status.PENDING,
senderName = userDetailsEntity2.name!!,
sender = userDetailsEntity2
)
)
messageDAO.insertOrIgnoreMessages(expectedMessages)

val result = messageDAO.getSenderNameById("1", conversationEntity1.id)

assertEquals(null, result)
}

private suspend fun insertInitialData() {
userDAO.upsertUsers(listOf(userEntity1, userEntity2))
conversationDAO.insertConversation(
Expand Down
Loading