Skip to content

Commit

Permalink
feat: Add getSenderNameByMessageId useCase WPB-11723 (#3186)
Browse files Browse the repository at this point in the history
* feat/add_get_sender_name_by_message_id_use_case

* Added unit test for new DB query

* Fixed test
  • Loading branch information
borichellow authored Dec 23, 2024
1 parent 3c69569 commit 3c7855a
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 0 deletions.
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 @@ -463,4 +463,7 @@ class MessageScope internal constructor(
dispatchers = dispatcher,
scope = scope,
)

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
}

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

0 comments on commit 3c7855a

Please sign in to comment.