From 25940016b2f27eb07d9f49270d94a79d91b72e0c Mon Sep 17 00:00:00 2001 From: Boris Safonov Date: Tue, 17 Dec 2024 16:49:07 +0200 Subject: [PATCH 1/3] feat/add_get_sender_name_by_message_id_use_case --- .../logic/data/message/MessageRepository.kt | 5 + .../GetSenderNameByMessageIdUseCase.kt | 53 +++++++++ .../logic/feature/message/MessageScope.kt | 3 + .../GetSenderNameByMessageIdUseCaseTest.kt | 108 ++++++++++++++++++ .../com/wire/kalium/persistence/Users.sq | 4 + .../persistence/dao/message/MessageDAO.kt | 1 + .../persistence/dao/message/MessageDAOImpl.kt | 6 + .../persistence/db/UserDatabaseBuilder.kt | 1 + 8 files changed, 181 insertions(+) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/GetSenderNameByMessageIdUseCase.kt create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/GetSenderNameByMessageIdUseCaseTest.kt 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..33b57989039 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/GetSenderNameByMessageIdUseCase.kt @@ -0,0 +1,53 @@ +/* + * 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 + +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..37999004c0f --- /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.getMessageById(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..06b200dc684 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 LIMIT 1); 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 From b4b66eb70a59f7f1c8178d75498d14a2cc4a0db8 Mon Sep 17 00:00:00 2001 From: Boris Safonov Date: Tue, 17 Dec 2024 21:45:48 +0200 Subject: [PATCH 2/3] Added unit test for new DB query --- .../GetSenderNameByMessageIdUseCase.kt | 4 + .../com/wire/kalium/persistence/Users.sq | 2 +- .../persistence/dao/message/MessageDAOTest.kt | 90 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) 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 index 33b57989039..276798ea6d3 100644 --- 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 @@ -26,6 +26,10 @@ 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 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 06b200dc684..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 @@ -279,4 +279,4 @@ 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 LIMIT 1); +WHERE qualified_id = (SELECT Message.sender_user_id FROM Message WHERE Message.id = :messageId AND Message.conversation_id = :conversationId); 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( From c0aef5c08d32e296f36e89f196dc3906862126b3 Mon Sep 17 00:00:00 2001 From: Boris Safonov Date: Tue, 17 Dec 2024 22:26:16 +0200 Subject: [PATCH 3/3] Fixed test --- .../feature/message/GetSenderNameByMessageIdUseCaseTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 37999004c0f..137dd1839f9 100644 --- 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 @@ -49,7 +49,7 @@ class GetSenderNameByMessageIdUseCaseTest { getSenderNameByMessageId(CONVERSATION_ID, MESSAGE_ID) coVerify { - arrangement.messageRepository.getMessageById(CONVERSATION_ID, MESSAGE_ID) + arrangement.messageRepository.getSenderNameByMessageId(CONVERSATION_ID, MESSAGE_ID) }.wasInvoked(exactly = once) }