diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepository.kt index dd0f47b5996..272b6c85344 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepository.kt @@ -253,6 +253,8 @@ internal interface MessageRepository { messageId: String, conversationId: ConversationId ): Either + + suspend fun getSenderNameByMessageId(conversationId: ConversationId, messageId: String): Either } // TODO: suppress TooManyFunctions for now, something we need to fix in the future @@ -706,4 +708,7 @@ internal class MessageDataSource internal constructor( ): Either = wrapStorageRequest { messageDAO.getMessageAssetTransferStatus(messageId, conversationId.toDao()).toModel() } + + override suspend fun getSenderNameByMessageId(conversationId: ConversationId, messageId: String): Either = + wrapStorageRequest { messageDAO.getSenderNameById(messageId, conversationId.toDao()) } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/GetSenderNameByMessageIdUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/GetSenderNameByMessageIdUseCase.kt new file mode 100644 index 00000000000..276798ea6d3 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/GetSenderNameByMessageIdUseCase.kt @@ -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 + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt index bb49c7fda27..29034c28564 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt @@ -453,4 +453,7 @@ class MessageScope internal constructor( val removeMessageDraftUseCase: RemoveMessageDraftUseCase get() = RemoveMessageDraftUseCaseImpl(messageDraftRepository) + + val getSenderNameByMessageId: GetSenderNameByMessageIdUseCase + get() = GetSenderNameByMessageIdUseCase(messageRepository) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/GetSenderNameByMessageIdUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/GetSenderNameByMessageIdUseCaseTest.kt new file mode 100644 index 00000000000..137dd1839f9 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/GetSenderNameByMessageIdUseCaseTest.kt @@ -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(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(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 + ) = 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 + } +} diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Users.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Users.sq index 385d13f2602..1a36f2801fa 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Users.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Users.sq @@ -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); diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAO.kt index e37edbceea0..2d78d8d85e2 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAO.kt @@ -161,4 +161,5 @@ interface MessageDAO { suspend fun observeAssetStatuses(conversationId: QualifiedIDEntity): Flow> suspend fun getMessageAssetTransferStatus(messageId: String, conversationId: QualifiedIDEntity): AssetTransferStatusEntity + suspend fun getSenderNameById(id: String, conversationId: QualifiedIDEntity): String? } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOImpl.kt index 06d1f66ba3d..1834fa2b142 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOImpl.kt @@ -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 @@ -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 @@ -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) } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt index fdf7c7bac9e..76f9ab33e17 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt @@ -264,6 +264,7 @@ class UserDatabaseBuilder internal constructor( database.messagePreviewQueries, userId, database.reactionsQueries, + database.usersQueries, queriesContext, database.messageAssetTransferStatusQueries, database.buttonContentQueries diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOTest.kt index da4724ba739..4c346bd2fe0 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOTest.kt @@ -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(