From 3c2cf58e3b6b4914c166cce0d6c10e8b6f14d6e7 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 2 May 2023 22:41:19 +0200 Subject: [PATCH 01/20] feat: adjust query to hide 1:1 convos without metadata --- .../db_user/com/wire/kalium/persistence/Conversations.sq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq index 35d22d7595b..3d13705c52b 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq @@ -203,7 +203,7 @@ selectAllConversationDetails: SELECT * FROM ConversationDetails WHERE type IS NOT "SELF" AND (type IS "GROUP" AND (name IS NOT NULL OR otherUserId IS NOT NULL) --filter deleted groups after first sync -OR (type IS NOT "GROUP" AND otherUserId IS NOT NULL)) -- show other conversation- todo: problem here! if the sync wasn't succesful the the user seems to be null! +OR (type IS NOT "GROUP" AND (name IS NOT NULL AND otherUserId IS NOT NULL))) -- show 1:1 convos if they have user metadata- todo: problem here! if the sync wasn't succesful the the user seems to be null! AND (protocol IS "PROTEUS" OR (protocol IS "MLS" AND mls_group_state IS "ESTABLISHED")) ORDER BY lastModifiedDate DESC, name COLLATE NOCASE ASC; From e43eb23341a0ac732a3cccd5b493233ea0148bbf Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Fri, 5 May 2023 11:06:28 +0200 Subject: [PATCH 02/20] feat: adjustment query to consider deleted users logic as it is now --- .../db_user/com/wire/kalium/persistence/Conversations.sq | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq index 3d13705c52b..48e824726b6 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq @@ -201,9 +201,12 @@ LEFT JOIN Call ON Call.id IS (SELECT id FROM Call WHERE Call.conversation_id = C selectAllConversationDetails: SELECT * FROM ConversationDetails -WHERE type IS NOT "SELF" AND -(type IS "GROUP" AND (name IS NOT NULL OR otherUserId IS NOT NULL) --filter deleted groups after first sync -OR (type IS NOT "GROUP" AND (name IS NOT NULL AND otherUserId IS NOT NULL))) -- show 1:1 convos if they have user metadata- todo: problem here! if the sync wasn't succesful the the user seems to be null! +WHERE type IS NOT "SELF" +AND ( + type IS "GROUP" AND (name IS NOT NULL OR otherUserId IS NOT NULL) --filter deleted groups after first sync + OR (type IS "ONE_ON_ONE" AND (name IS NOT NULL AND otherUserId IS NOT NULL)) -- show 1:1 convos if they have user metadata + OR (type IS "ONE_ON_ONE" AND userDeleted = 1) -- show deleted 1:1 convos, to maintain prev, logic +) AND (protocol IS "PROTEUS" OR (protocol IS "MLS" AND mls_group_state IS "ESTABLISHED")) ORDER BY lastModifiedDate DESC, name COLLATE NOCASE ASC; From adace808f932cb7995d2d701f029b30fd9a7860b Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Fri, 5 May 2023 14:43:42 +0200 Subject: [PATCH 03/20] feat: tests for query conversations details --- .../persistence/dao/ConversationDAOTest.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt index 278ecec2525..51cf5adfe87 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt @@ -1076,4 +1076,21 @@ class ConversationDAOTest : BaseDatabaseTest() { Instant.DISTANT_FUTURE ) } + + @Test + fun givenLocalConversations_whenGettingAllConversations_thenShouldReturnsOnlyConversationsWithMetadata() = runTest { + conversationDAO.insertConversation(conversationEntity1) + conversationDAO.insertConversation(conversationEntity2) + + userDAO.insertUser(user1) // user with metadata + userDAO.insertUser(user2.copy(name = null)) // user without metadata + + conversationDAO.insertMember(member1, conversationEntity1.id) + conversationDAO.insertMember(member2, conversationEntity1.id) + + conversationDAO.getAllConversationDetails().first().let { + assertEquals(1, it.size) + assertEquals(conversationEntity1.id, it.first().id) + } + } } From 3a9374711255814690e2af9f90307ff41d89645b Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 8 May 2023 18:16:39 +0200 Subject: [PATCH 04/20] feat: persistence for getting users out of sync --- .../kalium/logic/data/user/UserRepository.kt | 12 +++++++ .../logic/sync/SyncMetadataOutOfSync.kt | 34 +++++++++++++++++++ .../com/wire/kalium/persistence/Users.sq | 5 +++ .../wire/kalium/persistence/dao/UserDAO.kt | 1 + .../kalium/persistence/dao/UserDAOImpl.kt | 6 ++++ 5 files changed, 58 insertions(+) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/SyncMetadataOutOfSync.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt index 8f3bd7c998b..461e8c83c6f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt @@ -117,6 +117,11 @@ internal interface UserRepository { * otherwise [Either.Left] with [NetworkFailure] */ suspend fun updateSelfEmail(email: String): Either + + /** + * Updates users without metadata from the server. + */ + suspend fun syncUsersWithoutMetadata(): Either } @Suppress("LongParameterList", "TooManyFunctions") @@ -463,6 +468,13 @@ internal class UserDataSource internal constructor( ) } + override suspend fun syncUsersWithoutMetadata(): Either = wrapStorageRequest { + userDAO.getUsersWithoutMetadata() + }.flatMap { usersWithoutMetadata -> + val userIds = usersWithoutMetadata.map { it.id.toModel() }.toSet() + fetchUsersByIds(userIds) + } + companion object { internal const val SELF_USER_ID_KEY = "selfUserID" internal val FEDERATED_USER_TTL = 5.minutes diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/SyncMetadataOutOfSync.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/SyncMetadataOutOfSync.kt new file mode 100644 index 00000000000..7d767e2b3f0 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/SyncMetadataOutOfSync.kt @@ -0,0 +1,34 @@ +/* + * Wire + * Copyright (C) 2023 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.sync + +import com.wire.kalium.logic.data.user.UserRepository + +/** + * Syncs metadata that is out of sync. + * For example conversations and users/participants. + */ +internal class SyncMetadataOutOfSync( + private val userRepository: UserRepository +) { + + suspend fun syncUsersWithoutMetadata() { + userRepository.syncUsersWithoutMetadata() + } + +} 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 b3b0b54f21b..dc43aad3085 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 @@ -147,3 +147,8 @@ SELECT * FROM User AS user updateUserDisplayName: UPDATE User SET name = ? WHERE qualified_id = ?; + +selectUsersWithoutMetadata: +SELECT * FROM User AS user +WHERE connection_status = "ACCEPTED" +AND (name IS NULL OR name = '' OR handle IS NULL OR handle = ''); diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAO.kt index 4cdceac1a41..0c0ce4098bb 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAO.kt @@ -192,4 +192,5 @@ interface UserDAO { suspend fun getUsersNotInConversationByHandle(conversationId: QualifiedIDEntity, handle: String): Flow> suspend fun getAllUsersByTeam(teamId: String): List suspend fun updateUserDisplayName(selfUserId: QualifiedIDEntity, displayName: String) + suspend fun getUsersWithoutMetadata(): List } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt index 262cf150bec..4b499ddadd6 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt @@ -385,4 +385,10 @@ class UserDAOImpl internal constructor( override suspend fun updateUserDisplayName(selfUserId: QualifiedIDEntity, displayName: String) = withContext(queriesContext) { userQueries.updateUserDisplayName(displayName, selfUserId) } + + override suspend fun getUsersWithoutMetadata() = withContext(queriesContext) { + userQueries.selectUsersWithoutMetadata() + .executeAsList() + .map(mapper::toModel) + } } From 83a6fedf9d153b82bcdcb1ff8e7994c78ad6baf8 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 9 May 2023 12:24:45 +0200 Subject: [PATCH 05/20] feat: persistence for getting users out of sync --- .../kalium/logic/data/user/UserRepository.kt | 1 + .../RefreshUsersWithoutMetadataUseCase.kt | 45 +++++++++++++++++++ .../kalium/logic/feature/user/UserScope.kt | 3 ++ .../com/wire/kalium/persistence/Users.sq | 4 +- 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/publicuser/RefreshUsersWithoutMetadataUseCase.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt index 461e8c83c6f..d6615694af9 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt @@ -471,6 +471,7 @@ internal class UserDataSource internal constructor( override suspend fun syncUsersWithoutMetadata(): Either = wrapStorageRequest { userDAO.getUsersWithoutMetadata() }.flatMap { usersWithoutMetadata -> + kaliumLogger.d("Numbers of users refreshed: ${usersWithoutMetadata.size}") val userIds = usersWithoutMetadata.map { it.id.toModel() }.toSet() fetchUsersByIds(userIds) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/publicuser/RefreshUsersWithoutMetadataUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/publicuser/RefreshUsersWithoutMetadataUseCase.kt new file mode 100644 index 00000000000..705c489aa57 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/publicuser/RefreshUsersWithoutMetadataUseCase.kt @@ -0,0 +1,45 @@ +/* + * Wire + * Copyright (C) 2023 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.publicuser + +import com.wire.kalium.logic.data.user.UserRepository +import com.wire.kalium.logic.functional.fold +import com.wire.kalium.logic.kaliumLogger + +/** + * Refresh users without metadata, only if necessary. + */ +interface RefreshUsersWithoutMetadataUseCase { + suspend fun syncUsersWithoutMetadata() +} + +internal class RefreshUsersWithoutMetadataUseCaseImpl( + private val userRepository: UserRepository +) : RefreshUsersWithoutMetadataUseCase { + + override suspend fun syncUsersWithoutMetadata() { + kaliumLogger.d("Started syncing users without metadata") + userRepository.syncUsersWithoutMetadata() + .fold({ + kaliumLogger.w("Error while syncing users without metadata $it") + }) { + kaliumLogger.d("Finished syncing users without metadata") + } + } + +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt index 537664e7a9d..d67cba5d426 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt @@ -45,6 +45,8 @@ import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCase import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCaseImpl import com.wire.kalium.logic.feature.publicuser.GetKnownUserUseCase import com.wire.kalium.logic.feature.publicuser.GetKnownUserUseCaseImpl +import com.wire.kalium.logic.feature.publicuser.RefreshUsersWithoutMetadataUseCase +import com.wire.kalium.logic.feature.publicuser.RefreshUsersWithoutMetadataUseCaseImpl import com.wire.kalium.logic.feature.publicuser.search.SearchKnownUsersUseCase import com.wire.kalium.logic.feature.publicuser.search.SearchKnownUsersUseCaseImpl import com.wire.kalium.logic.feature.publicuser.search.SearchPublicUsersUseCase @@ -98,6 +100,7 @@ class UserScope internal constructor( val getAllKnownUsers: GetAllContactsUseCase get() = GetAllContactsUseCaseImpl(userRepository) val getKnownUser: GetKnownUserUseCase get() = GetKnownUserUseCaseImpl(userRepository) val getUserInfo: GetUserInfoUseCase get() = GetUserInfoUseCaseImpl(userRepository, teamRepository) + val refreshUsersWithoutMetadata: RefreshUsersWithoutMetadataUseCase get() = RefreshUsersWithoutMetadataUseCaseImpl(userRepository) val updateSelfAvailabilityStatus: UpdateSelfAvailabilityStatusUseCase get() = UpdateSelfAvailabilityStatusUseCase(userRepository, messageSender, clientIdProvider, selfUserId) val getAllContactsNotInConversation: GetAllContactsNotInConversationUseCase 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 dc43aad3085..cffeb962e3b 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 @@ -3,8 +3,8 @@ import com.wire.kalium.persistence.dao.ConnectionEntity; import com.wire.kalium.persistence.dao.QualifiedIDEntity; import com.wire.kalium.persistence.dao.UserAvailabilityStatusEntity; import com.wire.kalium.persistence.dao.UserTypeEntity; -import kotlin.Int; import kotlin.Boolean; +import kotlin.Int; CREATE TABLE User ( qualified_id TEXT AS QualifiedIDEntity NOT NULL PRIMARY KEY, @@ -150,5 +150,5 @@ UPDATE User SET name = ? WHERE qualified_id = ?; selectUsersWithoutMetadata: SELECT * FROM User AS user -WHERE connection_status = "ACCEPTED" +WHERE connection_status = "ACCEPTED" AND deleted = 0 AND (name IS NULL OR name = '' OR handle IS NULL OR handle = ''); From d15c2d13042ab25a74c4fb13faef0f4142a5ad9e Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 9 May 2023 15:10:26 +0200 Subject: [PATCH 06/20] feat: pr comments single quotes --- .../com/wire/kalium/persistence/Conversations.sq | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq index 48e824726b6..5a3917da047 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq @@ -201,13 +201,13 @@ LEFT JOIN Call ON Call.id IS (SELECT id FROM Call WHERE Call.conversation_id = C selectAllConversationDetails: SELECT * FROM ConversationDetails -WHERE type IS NOT "SELF" +WHERE type IS NOT 'SELF' AND ( - type IS "GROUP" AND (name IS NOT NULL OR otherUserId IS NOT NULL) --filter deleted groups after first sync - OR (type IS "ONE_ON_ONE" AND (name IS NOT NULL AND otherUserId IS NOT NULL)) -- show 1:1 convos if they have user metadata - OR (type IS "ONE_ON_ONE" AND userDeleted = 1) -- show deleted 1:1 convos, to maintain prev, logic + type IS 'GROUP' AND (name IS NOT NULL OR otherUserId IS NOT NULL) --filter deleted groups after first sync + OR (type IS 'ONE_ON_ONE' AND (name IS NOT NULL AND otherUserId IS NOT NULL)) -- show 1:1 convos if they have user metadata + OR (type IS 'ONE_ON_ONE' AND userDeleted = 1) -- show deleted 1:1 convos, to maintain prev, logic ) -AND (protocol IS "PROTEUS" OR (protocol IS "MLS" AND mls_group_state IS "ESTABLISHED")) +AND (protocol IS 'PROTEUS' OR (protocol IS 'MLS' AND mls_group_state IS 'ESTABLISHED')) ORDER BY lastModifiedDate DESC, name COLLATE NOCASE ASC; selectAllConversations: From 506939436a53cd70c68a3b751e6c23a5aca4c485 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 9 May 2023 16:30:17 +0200 Subject: [PATCH 07/20] feat: invok operator --- .../publicuser/RefreshUsersWithoutMetadataUseCase.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/publicuser/RefreshUsersWithoutMetadataUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/publicuser/RefreshUsersWithoutMetadataUseCase.kt index 705c489aa57..075b9974b9b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/publicuser/RefreshUsersWithoutMetadataUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/publicuser/RefreshUsersWithoutMetadataUseCase.kt @@ -20,19 +20,23 @@ package com.wire.kalium.logic.feature.publicuser import com.wire.kalium.logic.data.user.UserRepository import com.wire.kalium.logic.functional.fold import com.wire.kalium.logic.kaliumLogger +import com.wire.kalium.util.KaliumDispatcher +import com.wire.kalium.util.KaliumDispatcherImpl +import kotlinx.coroutines.withContext /** * Refresh users without metadata, only if necessary. */ interface RefreshUsersWithoutMetadataUseCase { - suspend fun syncUsersWithoutMetadata() + suspend operator fun invoke() } internal class RefreshUsersWithoutMetadataUseCaseImpl( - private val userRepository: UserRepository + private val userRepository: UserRepository, + private val dispatchers: KaliumDispatcher = KaliumDispatcherImpl ) : RefreshUsersWithoutMetadataUseCase { - override suspend fun syncUsersWithoutMetadata() { + override suspend fun invoke() = withContext(dispatchers.io) { kaliumLogger.d("Started syncing users without metadata") userRepository.syncUsersWithoutMetadata() .fold({ From 86021e03feafac667ae03bd51e25c50e07fc2ec7 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Mon, 15 May 2023 11:32:36 +0200 Subject: [PATCH 08/20] feat: persist failed convos --- .../data/conversation/ConversationMapper.kt | 20 +++++++++++++++++++ .../conversation/ConversationRepository.kt | 17 +++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt index 99597b4f2c8..18cf6037748 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt @@ -20,6 +20,7 @@ package com.wire.kalium.logic.data.conversation import com.wire.kalium.logic.data.connection.ConnectionStatusMapper import com.wire.kalium.logic.data.id.IdMapper +import com.wire.kalium.logic.data.id.NetworkQualifiedId import com.wire.kalium.logic.data.id.TeamId import com.wire.kalium.logic.data.id.toApi import com.wire.kalium.logic.data.id.toDao @@ -75,6 +76,7 @@ interface ConversationMapper { fun toApiModel(name: String?, members: List, teamId: String?, options: ConversationOptions): CreateConversationRequest fun fromMigrationModel(conversation: Conversation): ConversationEntity + fun fromFailedConversationToEntity(conversationId: NetworkQualifiedId): ConversationEntity } @Suppress("TooManyFunctions", "LongParameterList") @@ -347,6 +349,24 @@ internal class ConversationMapperImpl( ) } + override fun fromFailedConversationToEntity(conversationId: NetworkQualifiedId): ConversationEntity = ConversationEntity( + id = conversationId.toDao(), + name = null, + type = ConversationEntity.Type.GROUP, + teamId = null, + protocolInfo = ProtocolInfo.Proteus, + mutedStatus = ConversationEntity.MutedStatus.ALL_ALLOWED, + mutedTime = 0, + removedBy = null, + creatorId = "", + lastNotificationDate = "1970-01-01T00:00:00.000Z".toInstant(), + lastModifiedDate = "1970-01-01T00:00:00.000Z".toInstant(), + lastReadDate = "1970-01-01T00:00:00.000Z".toInstant(), + access = emptyList(), + accessRole = emptyList(), + receiptMode = ConversationEntity.ReceiptMode.DISABLED + ) + private fun ConversationResponse.getProtocolInfo(mlsGroupState: GroupState?): ProtocolInfo { return when (protocol) { ConvProtocol.MLS -> ProtocolInfo.MLS( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt index 441ae06e626..42ea2dec4b7 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt @@ -26,6 +26,7 @@ import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.GroupID import com.wire.kalium.logic.data.id.IdMapper +import com.wire.kalium.logic.data.id.NetworkQualifiedId import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.id.toApi import com.wire.kalium.logic.data.id.toCrypto @@ -245,7 +246,8 @@ internal class ConversationDataSource internal constructor( }.onSuccess { conversations -> if (conversations.conversationsFailed.isNotEmpty()) { kaliumLogger.withFeatureId(CONVERSATIONS) - .d("Skipping ${conversations.conversationsFailed.size} conversations failed") + .d("Handling ${conversations.conversationsFailed.size} conversations failed") + handleFailedConversations(conversations.conversationsFailed) } if (conversations.conversationsNotFound.isNotEmpty()) { kaliumLogger.withFeatureId(CONVERSATIONS) @@ -599,6 +601,7 @@ internal class ConversationDataSource internal constructor( conversationDAO.deleteConversationByQualifiedID(conversationId.toDao()) } } + is Conversation.ProtocolInfo.Proteus -> wrapStorageRequest { conversationDAO.deleteConversationByQualifiedID(conversationId.toDao()) } @@ -686,6 +689,18 @@ internal class ConversationDataSource internal constructor( } } + private suspend fun handleFailedConversations( + conversationsFailed: List + ): Either { + return wrapStorageRequest { + if (conversationsFailed.isNotEmpty()) { + conversationDAO.insertConversations(conversationsFailed.map { conversationId -> + conversationMapper.fromFailedConversationToEntity(conversationId) + }) + } + } + } + companion object { const val DEFAULT_MEMBER_ROLE = "wire_member" } From 41be3f3f358e58ec5cad288431ac276056d50bec Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 16 May 2023 14:00:11 +0200 Subject: [PATCH 09/20] feat: cleanup --- .../logic/sync/SyncMetadataOutOfSync.kt | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/SyncMetadataOutOfSync.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/SyncMetadataOutOfSync.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/SyncMetadataOutOfSync.kt deleted file mode 100644 index 7d767e2b3f0..00000000000 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/SyncMetadataOutOfSync.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Wire - * Copyright (C) 2023 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.sync - -import com.wire.kalium.logic.data.user.UserRepository - -/** - * Syncs metadata that is out of sync. - * For example conversations and users/participants. - */ -internal class SyncMetadataOutOfSync( - private val userRepository: UserRepository -) { - - suspend fun syncUsersWithoutMetadata() { - userRepository.syncUsersWithoutMetadata() - } - -} From e17bb9b418c6fa5cad7a67f66bc2b2247a53ec3a Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 16 May 2023 15:28:15 +0200 Subject: [PATCH 10/20] feat: tests cov --- .../data/conversation/ConversationRepositoryTest.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryTest.kt index 6d6f0c4347c..b7c920bc4a9 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryTest.kt @@ -199,6 +199,16 @@ class ConversationRepositoryTest { conversationRepository.fetchConversations() // then + verify(arrangement.conversationDAO) + .suspendFunction(arrangement.conversationDAO::insertConversations) + .with( + matching { conversations -> + conversations.any { entity -> + entity.id.value == CONVERSATION_RESPONSE_DTO.conversationsFailed.first().value + } + } + ).wasInvoked(exactly = once) + verify(arrangement.conversationDAO) .suspendFunction(arrangement.conversationDAO::insertConversations) .with( From 3ea30ddc6d11256dc0a024c0e56aab661dc3e898 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 16 May 2023 15:41:11 +0200 Subject: [PATCH 11/20] feat: tests cov --- .../logic/data/user/UserRepositoryTest.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt index afdfc72e18b..81c59c5cf03 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt @@ -50,6 +50,7 @@ import io.mockative.classOf import io.mockative.configure import io.mockative.eq import io.mockative.given +import io.mockative.matching import io.mockative.mock import io.mockative.once import io.mockative.verify @@ -415,6 +416,31 @@ class UserRepositoryTest { } } + @Test + fun givenThereAreUsersWithoutMetadata_whenSyncingUsers_thenShouldUpdateThem() = runTest { + // given + val (arrangement, userRepository) = Arrangement() + .withDaoReturningNoMetadataUsers(listOf(TestUser.ENTITY.copy(name = null))) + .withSuccessfulGetMultipleUsersApiRequest(ListUsersDTO(emptyList(), listOf(TestUser.USER_PROFILE_DTO))) + .arrange() + + // when + userRepository.syncUsersWithoutMetadata() + .shouldSucceed() + + // then + verify(arrangement.userDetailsApi) + .suspendFunction(arrangement.userDetailsApi::getMultipleUsers) + .with(any()) + .wasInvoked(exactly = once) + verify(arrangement.userDAO) + .suspendFunction(arrangement.userDAO::upsertUsers) + .with(matching { + it.first().name != null + }) + .wasInvoked(exactly = once) + } + // TODO other UserRepository tests private class Arrangement { @@ -504,6 +530,12 @@ class UserRepositoryTest { .then { flowOf(userEntity) } } + fun withDaoReturningNoMetadataUsers(userEntity: List = emptyList()) = apply { + given(userDAO).suspendFunction(userDAO::getUsersWithoutMetadata) + .whenInvoked() + .then { userEntity } + } + fun withGetSelfUserId() = apply { given(metadataDAO) .suspendFunction(metadataDAO::valueByKey) From d9d8c5d3e6c76cb66291a832dcd882bcccd60b88 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 16 May 2023 15:42:33 +0200 Subject: [PATCH 12/20] feat: tests cov --- .../logic/data/user/UserRepositoryTest.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt index 81c59c5cf03..c9f9ea80edc 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt @@ -441,6 +441,28 @@ class UserRepositoryTest { .wasInvoked(exactly = once) } + @Test + fun givenThereAreNOUsersWithoutMetadata_whenSyncingUsers_thenShouldNOTUpdateThem() = runTest { + // given + val (arrangement, userRepository) = Arrangement() + .withDaoReturningNoMetadataUsers(listOf()) + .arrange() + + // when + userRepository.syncUsersWithoutMetadata() + .shouldSucceed() + + // then + verify(arrangement.userDetailsApi) + .suspendFunction(arrangement.userDetailsApi::getMultipleUsers) + .with(any()) + .wasNotInvoked() + verify(arrangement.userDAO) + .suspendFunction(arrangement.userDAO::upsertUsers) + .with(any()) + .wasNotInvoked() + } + // TODO other UserRepository tests private class Arrangement { From 3f8dd00108eaaa070c34c1388b8fc69d7aec318c Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Tue, 16 May 2023 16:08:22 +0200 Subject: [PATCH 13/20] feat: tests cov --- .../RefreshUsersWithoutMetadataUseCaseTest.kt | 64 +++++++++++++++++++ .../kalium/persistence/dao/UserDAOTest.kt | 13 ++++ 2 files changed, 77 insertions(+) create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/RefreshUsersWithoutMetadataUseCaseTest.kt diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/RefreshUsersWithoutMetadataUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/RefreshUsersWithoutMetadataUseCaseTest.kt new file mode 100644 index 00000000000..778365dbaa0 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/RefreshUsersWithoutMetadataUseCaseTest.kt @@ -0,0 +1,64 @@ +/* + * Wire + * Copyright (C) 2023 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.user + +import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.data.user.UserRepository +import com.wire.kalium.logic.feature.publicuser.RefreshUsersWithoutMetadataUseCaseImpl +import com.wire.kalium.logic.functional.Either +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.given +import io.mockative.mock +import io.mockative.once +import io.mockative.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +@ExperimentalCoroutinesApi +class RefreshUsersWithoutMetadataUseCaseTest { + + @Test + fun givenUsersWithoutMetadata_whenRefreshing_thenShouldRefreshThoseUsersInformation() = runTest { + val (arrangement, refreshUsersWithoutMetadata) = Arrangement() + .withResponse() + .arrange() + + refreshUsersWithoutMetadata() + + verify(arrangement.userRepository) + .suspendFunction(arrangement.userRepository::syncUsersWithoutMetadata) + .wasInvoked(once) + } + + private class Arrangement { + @Mock + val userRepository = mock(classOf()) + + fun withResponse(result: Either = Either.Right(Unit)) = apply { + given(userRepository) + .suspendFunction(userRepository::syncUsersWithoutMetadata) + .whenInvoked() + .thenReturn(result) + } + + fun arrange() = this to RefreshUsersWithoutMetadataUseCaseImpl(userRepository) + } +} diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserDAOTest.kt index f6739ad33a9..2d1a1a113dd 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserDAOTest.kt @@ -610,6 +610,19 @@ class UserDAOTest : BaseDatabaseTest() { assertEquals(expectedNewDisplayName, persistedUser?.name) } + @Test + fun givenExistingUserWithoutMetadata_whenQueryingThem_thenShouldReturnUsersWithoutMetadata() = runTest(dispatcher) { + // given + db.userDAO.insertUser(user1.copy(name = null, handle = null)) + + // when + val usersWithoutMetadata = db.userDAO.getUsersWithoutMetadata() + + // then + assertEquals(1, usersWithoutMetadata.size) + assertEquals(user1.id, usersWithoutMetadata.first().id) + } + private companion object { val USER_ENTITY_1 = newUserEntity(QualifiedIDEntity("1", "wire.com")) val USER_ENTITY_2 = newUserEntity(QualifiedIDEntity("2", "wire.com")) From 5bd25df3d5180afd91a9362b498783f490d7b0a1 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 17 May 2023 11:55:00 +0200 Subject: [PATCH 14/20] feat: refactor, persist users withoutmetadata with dedicated field --- .../wire/kalium/logic/data/user/UserMapper.kt | 27 ++++++++++++ .../kalium/logic/data/user/UserRepository.kt | 44 +++++++------------ .../logic/data/user/UserRepositoryTest.kt | 1 - .../com/wire/kalium/persistence/Users.sq | 14 +++--- .../persistence/dao/ConnectionDAOImpl.kt | 4 +- .../wire/kalium/persistence/dao/UserDAO.kt | 3 +- .../kalium/persistence/dao/UserDAOImpl.kt | 19 +++++--- 7 files changed, 70 insertions(+), 42 deletions(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserMapper.kt index 64f08812257..9dd771aba40 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserMapper.kt @@ -22,6 +22,7 @@ import com.wire.kalium.logic.data.client.ClientMapper import com.wire.kalium.logic.data.client.OtherUserClient import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.id.IdMapper +import com.wire.kalium.logic.data.id.NetworkQualifiedId import com.wire.kalium.logic.data.id.TeamId import com.wire.kalium.logic.data.id.toDao import com.wire.kalium.logic.data.id.toModel @@ -88,6 +89,8 @@ interface UserMapper { fun apiToEntity(user: UserProfileDTO, member: TeamsApi.TeamMemberDTO?, teamId: String?, selfUser: QualifiedID): UserEntity fun toUpdateDaoFromEvent(event: Event.User.Update, userEntity: UserEntity): UserEntity + + fun fromFailedUserToEntity(userId: NetworkQualifiedId): UserEntity } internal class UserMapperImpl( @@ -304,4 +307,28 @@ internal class UserMapperImpl( ) } } + + /** + * Default values and marked as [UserEntity.hasIncompleteMetadata] = true. + * So later we can re-fetch them. + */ + override fun fromFailedUserToEntity(userId: NetworkQualifiedId): UserEntity { + return UserEntity( + id = userId.toDao(), + name = null, + handle = null, + email = null, + phone = null, + accentId = 1, + team = null, + connectionStatus = ConnectionEntity.State.ACCEPTED, + previewAssetId = null, + completeAssetId = null, + availabilityStatus = UserAvailabilityStatusEntity.NONE, + userType = UserTypeEntity.STANDARD, + botService = null, + deleted = false, + hasIncompleteMetadata = true + ) + } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt index d6615694af9..14004e2d33e 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt @@ -26,6 +26,7 @@ import com.wire.kalium.logic.data.conversation.Recipient import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.IdMapper +import com.wire.kalium.logic.data.id.NetworkQualifiedId import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.data.id.toApi import com.wire.kalium.logic.data.id.toDao @@ -201,41 +202,30 @@ internal class UserDataSource internal constructor( return fetchUsersByIds(ids.toSet()) } - override suspend fun fetchUsersByIds(qualifiedUserIdList: Set): Either { - val selfUserDomain = selfUserId.domain - qualifiedUserIdList.groupBy { it.domain } - .filter { it.value.isNotEmpty() } - .map { (domain: String, usersOnDomain: List) -> - when (selfUserDomain == domain) { - true -> fetchMultipleUsers(usersOnDomain) - false -> { - usersOnDomain.forEach { userId -> - fetchUserInfo(userId).fold({ - kaliumLogger.w("Ignoring external users details") - }) { kaliumLogger.d("External users details saved") } - } - Either.Right(Unit) - } - } - } - - return Either.Right(Unit) - } - - private suspend fun fetchMultipleUsers(qualifiedUsersOnSameDomainList: List) = wrapApiRequest { - userDetailsApi.getMultipleUsers( - ListUserRequest.qualifiedIds(qualifiedUsersOnSameDomainList.map { userId -> userId.toApi() }) - ) - }.flatMap { listUserProfileDTO -> persistUsers(listUserProfileDTO.usersFound) } - override suspend fun fetchUserInfo(userId: UserId) = wrapApiRequest { userDetailsApi.getUserInfo(userId.toApi()) } .flatMap { userProfileDTO -> persistUsers(listOf(userProfileDTO)) } + override suspend fun fetchUsersByIds(qualifiedUserIdList: Set) = wrapApiRequest { + userDetailsApi.getMultipleUsers( + ListUserRequest.qualifiedIds(qualifiedUserIdList.map { userId -> userId.toApi() }) + ) + }.flatMap { listUserProfileDTO -> + if (listUserProfileDTO.usersFailed.isNotEmpty()) { + kaliumLogger.d("Handling ${listUserProfileDTO.usersFailed.size} failed users") + persistIncompleteUsers(listUserProfileDTO.usersFailed) + } + persistUsers(listUserProfileDTO.usersFound) + } + override suspend fun updateSelfEmail(email: String): Either = wrapApiRequest { selfApi.updateEmailAddress(email) } + private suspend fun persistIncompleteUsers(usersFailed: List) = wrapStorageRequest { + userDAO.insertOrIgnoreUsers(usersFailed.map { userMapper.fromFailedUserToEntity(it) }) + } + private suspend fun persistUsers(listUserProfileDTO: List) = wrapStorageRequest { val selfUserDomain = selfUserId.domain val selfUserTeamId = selfTeamIdProvider().getOrNull()?.value diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt index c9f9ea80edc..648f8d9741f 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt @@ -107,7 +107,6 @@ class UserRepositoryTest { ) val (arrangement, userRepository) = Arrangement() .withGetSelfUserId() - .withSuccessfulGetUsersInfo() .withSuccessfulGetUsersByQualifiedIdList(knownUserEntities) .withSuccessfulGetMultipleUsersApiRequest(ListUsersDTO(usersFailed = emptyList(), listOf(TestUser.USER_PROFILE_DTO))) .arrange() 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 cffeb962e3b..8127c2be8f9 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 @@ -20,7 +20,8 @@ CREATE TABLE User ( user_availability_status TEXT AS UserAvailabilityStatusEntity NOT NULL DEFAULT 'NONE', user_type TEXT AS UserTypeEntity NOT NULL DEFAULT 'STANDARD', bot_service TEXT AS BotEntity, - deleted INTEGER AS Boolean NOT NULL DEFAULT 0 + deleted INTEGER AS Boolean NOT NULL DEFAULT 0, + incomplete_metadata INTEGER AS Boolean NOT NULL DEFAULT 0 ); CREATE INDEX user_team_index ON User(team); @@ -31,8 +32,8 @@ deleteUser: DELETE FROM User WHERE qualified_id = ?; insertUser: -INSERT INTO User(qualified_id, name, handle, email, phone, accent_id, team, connection_status, preview_asset_id, complete_asset_id, user_type, bot_service, deleted) -VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +INSERT INTO User(qualified_id, name, handle, email, phone, accent_id, team, connection_status, preview_asset_id, complete_asset_id, user_type, bot_service, deleted, incomplete_metadata) +VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(qualified_id) DO UPDATE SET name = excluded.name, handle = excluded.handle, @@ -45,11 +46,12 @@ preview_asset_id = excluded.preview_asset_id, complete_asset_id = excluded.complete_asset_id, user_type = excluded.user_type, bot_service = excluded.bot_service, -deleted = excluded.deleted; +deleted = excluded.deleted, +incomplete_metadata = excluded.incomplete_metadata; insertOrIgnoreUser: -INSERT OR IGNORE INTO User(qualified_id, name, handle, email, phone, accent_id, team, connection_status, preview_asset_id, complete_asset_id, user_type, bot_service, deleted) -VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); +INSERT OR IGNORE INTO User(qualified_id, name, handle, email, phone, accent_id, team, connection_status, preview_asset_id, complete_asset_id, user_type, bot_service, deleted, incomplete_metadata) +VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); updateUser: UPDATE User diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConnectionDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConnectionDAOImpl.kt index f57f0189fa7..b4136e94379 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConnectionDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConnectionDAOImpl.kt @@ -67,7 +67,8 @@ private class ConnectionMapper { user_availability_status: UserAvailabilityStatusEntity?, user_type: UserTypeEntity?, bot_service: BotEntity?, - deleted: Boolean? + deleted: Boolean?, + incomplete_metadata: Boolean? ): ConnectionEntity = ConnectionEntity( conversationId = conversation_id, from = from_id, @@ -92,6 +93,7 @@ private class ConnectionMapper { userType = user_type.requireField("user_type"), botService = bot_service, deleted = deleted.requireField("deleted"), + hasIncompleteMetadata = incomplete_metadata.requireField("incomplete_metadata") ) else null ) diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAO.kt index 0c0ce4098bb..3d4dc46b75d 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAO.kt @@ -52,7 +52,8 @@ data class UserEntity( val availabilityStatus: UserAvailabilityStatusEntity, val userType: UserTypeEntity, val botService: BotEntity?, - val deleted: Boolean + val deleted: Boolean, + val hasIncompleteMetadata: Boolean = false ) data class UserEntityMinimized( diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt index 4b499ddadd6..39e4316f076 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt @@ -69,6 +69,7 @@ class UserMapper { userType: UserTypeEntity, botService: BotEntity?, deleted: Boolean, + hasIncompleteMetadata: Boolean, id: String?, teamName: String?, teamIcon: String?, @@ -87,7 +88,8 @@ class UserMapper { availabilityStatus = userAvailabilityStatus, userType = userType, botService = botService, - deleted = deleted + deleted = deleted, + hasIncompleteMetadata = hasIncompleteMetadata ) val teamEntity = if (team != null && teamName != null && teamIcon != null) { @@ -134,7 +136,8 @@ class UserDAOImpl internal constructor( user.completeAssetId, user.userType, user.botService, - user.deleted + user.deleted, + user.hasIncompleteMetadata ) } @@ -154,7 +157,8 @@ class UserDAOImpl internal constructor( user.completeAssetId, user.userType, user.botService, - user.deleted + user.deleted, + user.hasIncompleteMetadata ) } } @@ -190,7 +194,8 @@ class UserDAOImpl internal constructor( user.completeAssetId, user.userType, user.botService, - user.deleted + user.deleted, + user.hasIncompleteMetadata ) } } @@ -228,7 +233,8 @@ class UserDAOImpl internal constructor( user.completeAssetId, user.userType, user.botService, - user.deleted + user.deleted, + user.hasIncompleteMetadata ) } } @@ -254,7 +260,8 @@ class UserDAOImpl internal constructor( user.completeAssetId, user.userType, user.botService, - user.deleted + user.deleted, + user.hasIncompleteMetadata ) } } From 30e8e8b36dd5b2184ced85aef211a047f95945b4 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 17 May 2023 12:31:50 +0200 Subject: [PATCH 15/20] feat: refactor, relay on missing metadata field for refetch usres --- .../kotlin/com/wire/kalium/logic/data/user/UserMapper.kt | 3 ++- .../commonMain/db_user/com/wire/kalium/persistence/Users.sq | 3 +-- .../kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserMapper.kt index 9dd771aba40..1b919e35004 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserMapper.kt @@ -134,7 +134,8 @@ internal class UserMapperImpl( availabilityStatus = UserAvailabilityStatusEntity.NONE, userType = userTypeEntity ?: UserTypeEntity.STANDARD, botService = userProfileDTO.service?.let { BotEntity(it.id, it.provider) }, - deleted = userProfileDTO.deleted ?: false + deleted = userProfileDTO.deleted ?: false, + hasIncompleteMetadata = false ) } 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 8127c2be8f9..5c9a72bb310 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 @@ -152,5 +152,4 @@ UPDATE User SET name = ? WHERE qualified_id = ?; selectUsersWithoutMetadata: SELECT * FROM User AS user -WHERE connection_status = "ACCEPTED" AND deleted = 0 -AND (name IS NULL OR name = '' OR handle IS NULL OR handle = ''); +WHERE deleted = 0 AND incomplete_metadata = 1; diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt index 39e4316f076..e7d7ff5b854 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt @@ -49,7 +49,8 @@ class UserMapper { availabilityStatus = user.user_availability_status, userType = user.user_type, botService = user.bot_service, - deleted = user.deleted + deleted = user.deleted, + hasIncompleteMetadata = user.incomplete_metadata ) } From 6b0fdfeff9447b572aeb0eac28f5fd8db6b81fea Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 17 May 2023 15:06:47 +0200 Subject: [PATCH 16/20] feat: refactor, relay on missing metadata field for refetch conversations --- .../logic/data/conversation/ConversationMapper.kt | 6 ++++-- .../com/wire/kalium/persistence/Conversations.sq | 11 +++++++---- .../wire/kalium/persistence/dao/ConversationDAO.kt | 3 ++- .../kalium/persistence/dao/ConversationDAOImpl.kt | 3 ++- .../com/wire/kalium/persistence/dao/UserDAOTest.kt | 2 +- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt index 18cf6037748..f431ef0bc85 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt @@ -110,7 +110,8 @@ internal class ConversationMapperImpl( lastModifiedDate = apiModel.lastEventTime.toInstant(), access = apiModel.access.map { it.toDAO() }, accessRole = apiModel.accessRole.map { it.toDAO() }, - receiptMode = receiptModeMapper.fromApiToDaoModel(apiModel.receiptMode) + receiptMode = receiptModeMapper.fromApiToDaoModel(apiModel.receiptMode), + hasIncompleteMetadata = false ) override fun fromApiModelToDaoModel(apiModel: ConvProtocol): Protocol = when (apiModel) { @@ -364,7 +365,8 @@ internal class ConversationMapperImpl( lastReadDate = "1970-01-01T00:00:00.000Z".toInstant(), access = emptyList(), accessRole = emptyList(), - receiptMode = ConversationEntity.ReceiptMode.DISABLED + receiptMode = ConversationEntity.ReceiptMode.DISABLED, + hasIncompleteMetadata = true ) private fun ConversationResponse.getProtocolInfo(mlsGroupState: GroupState?): ProtocolInfo { diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq index 5a3917da047..ca438e1b13a 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq @@ -2,6 +2,7 @@ import com.wire.kalium.persistence.dao.ConversationEntity; import com.wire.kalium.persistence.dao.QualifiedIDEntity; import kotlin.collections.List; import kotlinx.datetime.Instant; +import kotlin.Boolean; CREATE TABLE Conversation ( qualified_id TEXT AS QualifiedIDEntity NOT NULL PRIMARY KEY, @@ -31,7 +32,8 @@ CREATE TABLE Conversation ( mls_last_keying_material_update_date INTEGER AS Instant DEFAULT 0 NOT NULL, mls_cipher_suite TEXT AS ConversationEntity.CipherSuite NOT NULL, receipt_mode TEXT AS ConversationEntity.ReceiptMode DEFAULT "DISABLED" NOT NULL, - guest_room_link TEXT + guest_room_link TEXT, + incomplete_metadata INTEGER AS Boolean NOT NULL DEFAULT 0 ); -- Optimise comparisons and sorting by dates: @@ -47,8 +49,8 @@ deleteConversation: DELETE FROM Conversation WHERE qualified_id = ?; insertConversation: -INSERT INTO Conversation(qualified_id, name, type, team_id, mls_group_id, mls_group_state, mls_epoch, protocol, muted_status, muted_time, creator_id, last_modified_date, last_notified_date, access_list, access_role_list, last_read_date, mls_last_keying_material_update_date, mls_cipher_suite, receipt_mode) -VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +INSERT INTO Conversation(qualified_id, name, type, team_id, mls_group_id, mls_group_state, mls_epoch, protocol, muted_status, muted_time, creator_id, last_modified_date, last_notified_date, access_list, access_role_list, last_read_date, mls_last_keying_material_update_date, mls_cipher_suite, receipt_mode, incomplete_metadata) +VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(qualified_id) DO UPDATE SET name = excluded.name, type = excluded.type, @@ -66,7 +68,8 @@ last_notified_date = last_notified_date, last_read_date = last_read_date, mls_last_keying_material_update_date = excluded.mls_last_keying_material_update_date, mls_cipher_suite = excluded.mls_cipher_suite, -receipt_mode = excluded.receipt_mode; +receipt_mode = excluded.receipt_mode, +incomplete_metadata = excluded.incomplete_metadata; updateConversation: UPDATE Conversation diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConversationDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConversationDAO.kt index c0183aba0f7..ccde1bd4d3d 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConversationDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConversationDAO.kt @@ -40,7 +40,8 @@ data class ConversationEntity( val access: List, val accessRole: List, val receiptMode: ReceiptMode, - val guestRoomLink: String? = null + val guestRoomLink: String? = null, + val hasIncompleteMetadata: Boolean = false, ) { enum class AccessRole { TEAM_MEMBER, NON_TEAM_MEMBER, GUEST, SERVICE, EXTERNAL; } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConversationDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConversationDAOImpl.kt index 2cec25b7c50..8f20dd5466c 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConversationDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConversationDAOImpl.kt @@ -264,7 +264,8 @@ class ConversationDAOImpl( else Instant.fromEpochMilliseconds(MLS_DEFAULT_LAST_KEY_MATERIAL_UPDATE_MILLI), if (protocolInfo is ConversationEntity.ProtocolInfo.MLS) protocolInfo.cipherSuite else MLS_DEFAULT_CIPHER_SUITE, - receiptMode + receiptMode, + hasIncompleteMetadata ) } } diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserDAOTest.kt index 2d1a1a113dd..24caed6e7cf 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserDAOTest.kt @@ -613,7 +613,7 @@ class UserDAOTest : BaseDatabaseTest() { @Test fun givenExistingUserWithoutMetadata_whenQueryingThem_thenShouldReturnUsersWithoutMetadata() = runTest(dispatcher) { // given - db.userDAO.insertUser(user1.copy(name = null, handle = null)) + db.userDAO.insertUser(user1.copy(name = null, handle = null, hasIncompleteMetadata = true)) // when val usersWithoutMetadata = db.userDAO.getUsersWithoutMetadata() From a88437ea9b5e08c743eb06a83807a4d44c5f43bf Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 17 May 2023 15:10:39 +0200 Subject: [PATCH 17/20] feat: refactor, relay on missing metadata field for refetch conversations --- .../wire/kalium/logic/data/conversation/ConversationMapper.kt | 4 ++-- .../kalium/logic/data/conversation/ConversationRepository.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt index f431ef0bc85..20bff3d9678 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt @@ -76,7 +76,7 @@ interface ConversationMapper { fun toApiModel(name: String?, members: List, teamId: String?, options: ConversationOptions): CreateConversationRequest fun fromMigrationModel(conversation: Conversation): ConversationEntity - fun fromFailedConversationToEntity(conversationId: NetworkQualifiedId): ConversationEntity + fun fromFailedGroupConversationToEntity(conversationId: NetworkQualifiedId): ConversationEntity } @Suppress("TooManyFunctions", "LongParameterList") @@ -350,7 +350,7 @@ internal class ConversationMapperImpl( ) } - override fun fromFailedConversationToEntity(conversationId: NetworkQualifiedId): ConversationEntity = ConversationEntity( + override fun fromFailedGroupConversationToEntity(conversationId: NetworkQualifiedId): ConversationEntity = ConversationEntity( id = conversationId.toDao(), name = null, type = ConversationEntity.Type.GROUP, diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt index 42ea2dec4b7..dfca5996bf4 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt @@ -695,7 +695,7 @@ internal class ConversationDataSource internal constructor( return wrapStorageRequest { if (conversationsFailed.isNotEmpty()) { conversationDAO.insertConversations(conversationsFailed.map { conversationId -> - conversationMapper.fromFailedConversationToEntity(conversationId) + conversationMapper.fromFailedGroupConversationToEntity(conversationId) }) } } From 3770919c2f42422e93fad8055460a8022cc688b7 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 17 May 2023 15:13:47 +0200 Subject: [PATCH 18/20] feat: refactor, relay on missing metadata field for refetch conversations --- .../wire/kalium/logic/data/conversation/ConversationMapper.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt index 20bff3d9678..687197fa739 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt @@ -350,6 +350,10 @@ internal class ConversationMapperImpl( ) } + /** + * Default values and marked as [ConversationEntity.hasIncompleteMetadata] = true. + * So later we can re-fetch them. + */ override fun fromFailedGroupConversationToEntity(conversationId: NetworkQualifiedId): ConversationEntity = ConversationEntity( id = conversationId.toDao(), name = null, From 8b02453ef4fff44d5f85e3bd3344b2270858fb8d Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Wed, 17 May 2023 16:30:34 +0200 Subject: [PATCH 19/20] feat: refactor, fixing tests --- .../kalium/logic/data/user/UserRepository.kt | 24 +++++++++----- .../logic/data/user/UserRepositoryTest.kt | 33 ++++++++++--------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt index 14004e2d33e..5227e428518 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt @@ -206,16 +206,22 @@ internal class UserDataSource internal constructor( wrapApiRequest { userDetailsApi.getUserInfo(userId.toApi()) } .flatMap { userProfileDTO -> persistUsers(listOf(userProfileDTO)) } - override suspend fun fetchUsersByIds(qualifiedUserIdList: Set) = wrapApiRequest { - userDetailsApi.getMultipleUsers( - ListUserRequest.qualifiedIds(qualifiedUserIdList.map { userId -> userId.toApi() }) - ) - }.flatMap { listUserProfileDTO -> - if (listUserProfileDTO.usersFailed.isNotEmpty()) { - kaliumLogger.d("Handling ${listUserProfileDTO.usersFailed.size} failed users") - persistIncompleteUsers(listUserProfileDTO.usersFailed) + override suspend fun fetchUsersByIds(qualifiedUserIdList: Set): Either { + if (qualifiedUserIdList.isEmpty()) { + return Either.Right(Unit) + } + + return wrapApiRequest { + userDetailsApi.getMultipleUsers( + ListUserRequest.qualifiedIds(qualifiedUserIdList.map { userId -> userId.toApi() }) + ) + }.flatMap { listUserProfileDTO -> + if (listUserProfileDTO.usersFailed.isNotEmpty()) { + kaliumLogger.d("Handling ${listUserProfileDTO.usersFailed.size} failed users") + persistIncompleteUsers(listUserProfileDTO.usersFailed) + } + persistUsers(listUserProfileDTO.usersFound) } - persistUsers(listUserProfileDTO.usersFound) } override suspend fun updateSelfEmail(email: String): Either = wrapApiRequest { diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt index 648f8d9741f..120501e2071 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt @@ -19,6 +19,7 @@ package com.wire.kalium.logic.data.user import com.wire.kalium.logic.data.id.QualifiedIdMapper +import com.wire.kalium.logic.data.id.toApi import com.wire.kalium.logic.data.session.SessionRepository import com.wire.kalium.logic.data.user.UserDataSource.Companion.SELF_USER_ID_KEY import com.wire.kalium.logic.failure.SelfUserDeleted @@ -32,9 +33,10 @@ import com.wire.kalium.logic.test_util.TestNetworkResponseError import com.wire.kalium.logic.util.shouldFail import com.wire.kalium.logic.util.shouldSucceed import com.wire.kalium.network.api.base.authenticated.self.SelfApi +import com.wire.kalium.network.api.base.authenticated.userDetails.ListUserRequest import com.wire.kalium.network.api.base.authenticated.userDetails.ListUsersDTO +import com.wire.kalium.network.api.base.authenticated.userDetails.QualifiedUserIdListRequest import com.wire.kalium.network.api.base.authenticated.userDetails.UserDetailsApi -import com.wire.kalium.network.api.base.model.QualifiedID import com.wire.kalium.network.exceptions.KaliumException import com.wire.kalium.network.utils.NetworkResponse import com.wire.kalium.persistence.dao.MetadataDAO @@ -98,13 +100,8 @@ class UserRepositoryTest { @Test fun givenAUserIsNotKnown_whenFetchingUsersIfUnknown_thenShouldFetchFromAPIAndSucceed() = runTest { val missingUserId = UserId(value = "id2", domain = "domain2") - val requestedUserIds = setOf( - UserId(value = "id1", domain = "domain1"), - missingUserId - ) - val knownUserEntities = listOf( - TestUser.ENTITY.copy(id = UserIDEntity(value = "id1", domain = "domain1")) - ) + val requestedUserIds = setOf(UserId(value = "id1", domain = "domain1"), missingUserId) + val knownUserEntities = listOf(TestUser.ENTITY.copy(id = UserIDEntity(value = "id1", domain = "domain1"))) val (arrangement, userRepository) = Arrangement() .withGetSelfUserId() .withSuccessfulGetUsersByQualifiedIdList(knownUserEntities) @@ -114,8 +111,10 @@ class UserRepositoryTest { userRepository.fetchUsersIfUnknownByIds(requestedUserIds).shouldSucceed() verify(arrangement.userDetailsApi) - .suspendFunction(arrangement.userDetailsApi::getUserInfo) - .with(eq(QualifiedID("id2", "domain2"))) + .suspendFunction(arrangement.userDetailsApi::getMultipleUsers) + .with(matching { request: ListUserRequest -> + (request as QualifiedUserIdListRequest).qualifiedIds.first() == missingUserId.toApi() + }) .wasInvoked(exactly = once) } @@ -177,6 +176,12 @@ class UserRepositoryTest { // given val requestedUserIds = emptySet() val (arrangement, userRepository) = Arrangement() + .withSuccessfulGetMultipleUsersApiRequest( + ListUsersDTO( + usersFailed = emptyList(), + usersFound = listOf(TestUser.USER_PROFILE_DTO) + ) + ) .arrange() // when userRepository.fetchUsersByIds(requestedUserIds).shouldSucceed() @@ -185,10 +190,6 @@ class UserRepositoryTest { .suspendFunction(arrangement.userDetailsApi::getMultipleUsers) .with(any()) .wasNotInvoked() - verify(arrangement.userDetailsApi) - .suspendFunction(arrangement.userDetailsApi::getUserInfo) - .with(any()) - .wasNotInvoked() } @Test @@ -199,7 +200,7 @@ class UserRepositoryTest { UserId(value = "id2", domain = "domain2") ) val (arrangement, userRepository) = Arrangement() - .withSuccessfulGetUsersInfo() + .withSuccessfulGetMultipleUsersApiRequest(ListUsersDTO(usersFailed = emptyList(), listOf(TestUser.USER_PROFILE_DTO))) .arrange() assertTrue { requestedUserIds.none { it.domain == arrangement.selfUserId.domain } } // when @@ -208,7 +209,7 @@ class UserRepositoryTest { verify(arrangement.userDetailsApi) .suspendFunction(arrangement.userDetailsApi::getMultipleUsers) .with(any()) - .wasNotInvoked() + .wasInvoked(exactly = once) } @Test From cc2e8293dc5c9e7d359890d9ce431b2fb7aec274 Mon Sep 17 00:00:00 2001 From: yamilmedina Date: Fri, 19 May 2023 09:02:58 +0200 Subject: [PATCH 20/20] chore: add migration tests --- persistence/src/commonMain/db_user/migrations/38.sqm | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 persistence/src/commonMain/db_user/migrations/38.sqm diff --git a/persistence/src/commonMain/db_user/migrations/38.sqm b/persistence/src/commonMain/db_user/migrations/38.sqm new file mode 100644 index 00000000000..440373388ae --- /dev/null +++ b/persistence/src/commonMain/db_user/migrations/38.sqm @@ -0,0 +1,4 @@ +import kotlin.Boolean; + +ALTER TABLE Conversation ADD COLUMN incomplete_metadata INTEGER AS Boolean NOT NULL DEFAULT 0; +ALTER TABLE User ADD COLUMN incomplete_metadata INTEGER AS Boolean NOT NULL DEFAULT 0;