diff --git a/buildSrc/src/main/kotlin/com/wire/kalium/plugins/CommonJvmConfig.kt b/buildSrc/src/main/kotlin/com/wire/kalium/plugins/CommonJvmConfig.kt index 26a90d7efe5..574eac21bf9 100644 --- a/buildSrc/src/main/kotlin/com/wire/kalium/plugins/CommonJvmConfig.kt +++ b/buildSrc/src/main/kotlin/com/wire/kalium/plugins/CommonJvmConfig.kt @@ -20,7 +20,7 @@ package com.wire.kalium.plugins import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget -fun KotlinJvmTarget.commonJvmConfig(includeNativeInterop: Boolean) { +fun KotlinJvmTarget.commonJvmConfig(includeNativeInterop: Boolean, enableIntegrationTests: Boolean = false) { compilations.all { kotlinOptions.jvmTarget = "17" kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" @@ -35,4 +35,9 @@ fun KotlinJvmTarget.commonJvmConfig(includeNativeInterop: Boolean) { } } } + if (enableIntegrationTests) { + testRuns.getByName("integrationTest").executionTask.configure { + useJUnit() + } + } } diff --git a/buildSrc/src/main/kotlin/com/wire/kalium/plugins/LibraryPlugin.kt b/buildSrc/src/main/kotlin/com/wire/kalium/plugins/LibraryPlugin.kt index ea47ab11801..a6bbdbcba54 100644 --- a/buildSrc/src/main/kotlin/com/wire/kalium/plugins/LibraryPlugin.kt +++ b/buildSrc/src/main/kotlin/com/wire/kalium/plugins/LibraryPlugin.kt @@ -38,6 +38,7 @@ class LibraryPlugin : Plugin { val enableJs: Property val enableJsTests: Property val includeNativeInterop: Property + val enableIntegrationTests: Property } @get:Nested @@ -53,7 +54,8 @@ class LibraryPlugin : Plugin { enableApple = multiplatformConfiguration.enableApple.getOrElse(true), enableJs = multiplatformConfiguration.enableJs.getOrElse(true), enableJsTests = multiplatformConfiguration.enableJsTests.getOrElse(true), - includeNativeInterop = multiplatformConfiguration.includeNativeInterop.getOrElse(false) + includeNativeInterop = multiplatformConfiguration.includeNativeInterop.getOrElse(false), + enableIntegrationTests = multiplatformConfiguration.enableIntegrationTests.getOrElse(false) ) } } diff --git a/buildSrc/src/main/kotlin/com/wire/kalium/plugins/Multiplatform.kt b/buildSrc/src/main/kotlin/com/wire/kalium/plugins/Multiplatform.kt index 0918d9eedf6..fea73e6932c 100644 --- a/buildSrc/src/main/kotlin/com/wire/kalium/plugins/Multiplatform.kt +++ b/buildSrc/src/main/kotlin/com/wire/kalium/plugins/Multiplatform.kt @@ -36,6 +36,7 @@ fun Project.configureDefaultMultiplatform( enableJs: Boolean, enableJsTests: Boolean, includeNativeInterop: Boolean, + enableIntegrationTests: Boolean, androidNamespaceSuffix: String = this.name ) { val kotlinExtension = extensions.findByName("kotlin") as? KotlinMultiplatformExtension @@ -44,7 +45,7 @@ fun Project.configureDefaultMultiplatform( } kotlinExtension.apply { targetHierarchy.default() - jvm { commonJvmConfig(includeNativeInterop) } + jvm { commonJvmConfig(includeNativeInterop, enableIntegrationTests) } androidTarget { commmonKotlinAndroidTargetConfig() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b1c291bb2a9..355b2291050 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,7 @@ sqldelight = "2.0.0" sqlcipher-android = "4.5.5" pbandk = "0.14.2" turbine = "1.0.0" -avs = "9.4.13" +avs = "9.4.14" jna = "5.13.0" core-crypto = "1.0.0-rc.12" core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1" diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreLogic.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreLogic.kt index a3e96198b49..7e46502b0de 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreLogic.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreLogic.kt @@ -78,7 +78,8 @@ abstract class CoreLogicCommon internal constructor( proxyCredentials, getGlobalScope().serverConfigRepository, networkStateObserver, - kaliumConfigs::certPinningConfig + kaliumConfigs::certPinningConfig, + kaliumConfigs.kaliumMockEngine?.mockEngine ) @Suppress("MemberVisibilityCanBePrivate") // Can be used by other targets like iOS and JS diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/GlobalKaliumScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/GlobalKaliumScope.kt index 81bb6c8bfb1..5474d9b5634 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/GlobalKaliumScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/GlobalKaliumScope.kt @@ -101,7 +101,8 @@ class GlobalKaliumScope internal constructor( kaliumConfigs.developmentApiEnabled, userAgent, kaliumConfigs.ignoreSSLCertificatesForUnboundCalls, - kaliumConfigs.certPinningConfig + kaliumConfigs.certPinningConfig, + kaliumConfigs.kaliumMockEngine?.mockEngine ) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientMapper.kt index e17d63ffffd..b4b0bb166ae 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientMapper.kt @@ -83,7 +83,7 @@ class ClientMapper( deviceType = deviceType?.let { fromDeviceTypeEntity(deviceType) }, label = label, model = model, - isVerified = isVerified, + isVerified = isProteusVerified, isValid = isValid, mlsPublicKeys = mlsPublicKeys ) @@ -192,7 +192,7 @@ class ClientMapper( fun fromOtherUsersClientsDTO(otherUsersClients: List): List = otherUsersClients.map { - OtherUserClient(fromDeviceTypeEntity(it.deviceType), it.id, it.isValid, it.isVerified) + OtherUserClient(fromDeviceTypeEntity(it.deviceType), it.id, it.isValid, it.isProteusVerified) } private fun toDeviceTypeDTO(deviceType: DeviceType): DeviceTypeDTO = when (deviceType) { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientModel.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientModel.kt index e68a6a64fa9..d3c7a695dcc 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientModel.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientModel.kt @@ -84,5 +84,5 @@ data class OtherUserClient( val deviceType: DeviceType, val id: String, val isValid: Boolean, - val isVerified: Boolean + val isProteusVerified: Boolean ) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientRepository.kt index 54cac64e0b5..1a16bac4c59 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/client/ClientRepository.kt @@ -73,7 +73,7 @@ interface ClientRepository { suspend fun getClientsByUserId(userId: UserId): Either> suspend fun observeClientsByUserId(userId: UserId): Flow>> - suspend fun updateClientVerificationStatus( + suspend fun updateClientProteusVerificationStatus( userId: UserId, clientId: ClientId, verified: Boolean @@ -213,12 +213,12 @@ class ClientDataSource( .map { it.map { clientMapper.fromClientEntity(it) } } .wrapStorageRequest() - override suspend fun updateClientVerificationStatus( + override suspend fun updateClientProteusVerificationStatus( userId: UserId, clientId: ClientId, verified: Boolean ): Either = wrapStorageRequest { - clientDAO.updateClientVerificationStatus(userId.toDao(), clientId.value, verified) + clientDAO.updateClientProteusVerificationStatus(userId.toDao(), clientId.value, verified) } override suspend fun saveNewClientEvent( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/connection/ConnectionMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/connection/ConnectionMapper.kt index 7d7a01b5008..e6bc1fb71a1 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/connection/ConnectionMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/connection/ConnectionMapper.kt @@ -68,14 +68,14 @@ internal class ConnectionMapperImpl( qualifiedToId = qualifiedToId.toModel(), status = statusMapper.fromDaoModel(status), toId = toId, - fromUser = otherUser?.let { publicUserMapper.fromUserEntityToOtherUser(it) } + fromUser = otherUser?.let { publicUserMapper.fromUserDetailsEntityToOtherUser(it) } ) } override fun fromDaoToConversationDetails(connection: ConnectionEntity): ConversationDetails = with(connection) { ConversationDetails.Connection( conversationId = qualifiedConversationId.toModel(), - otherUser = otherUser?.let { publicUserMapper.fromUserEntityToOtherUser(it) }, + otherUser = otherUser?.let { publicUserMapper.fromUserDetailsEntityToOtherUser(it) }, userType = otherUser?.let { userTypeMapper.fromUserTypeEntity(it.userType) } ?: UserType.GUEST, lastModifiedDate = lastUpdateDate.toIsoDateTimeString(), connection = fromDaoToModel(this), 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 44d2f55e731..0b750ce4766 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 @@ -224,7 +224,8 @@ internal class ConversationMapperImpl( teamId = teamId?.let { TeamId(it) }, connectionStatus = connectionStatusMapper.fromDaoModel(connectionStatus), expiresAt = null, - defederated = userDefederated ?: false + defederated = userDefederated ?: false, + isProteusVerified = false ), legalHoldStatus = LegalHoldStatus.DISABLED, userType = domainUserTypeMapper.fromUserTypeEntity(userType), @@ -260,7 +261,8 @@ internal class ConversationMapperImpl( previewPicture = previewAssetId?.toModel(), teamId = teamId?.let { TeamId(it) }, expiresAt = null, - defederated = userDefederated ?: false + defederated = userDefederated ?: false, + isProteusVerified = false ) ConversationDetails.Connection( 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 ce947859fcc..b5550138239 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 @@ -238,6 +238,10 @@ interface ConversationRepository { suspend fun getConversationDetailsByMLSGroupId(mlsGroupId: GroupID): Either suspend fun observeUnreadArchivedConversationsCount(): Flow + suspend fun sendTypingIndicatorStatus( + conversationId: ConversationId, + typingStatus: Conversation.TypingIndicatorMode + ): Either } @Suppress("LongParameterList", "TooManyFunctions") @@ -869,6 +873,13 @@ internal class ConversationDataSource internal constructor( .wrapStorageRequest() .mapToRightOr(0L) + override suspend fun sendTypingIndicatorStatus( + conversationId: ConversationId, + typingStatus: Conversation.TypingIndicatorMode + ): Either = wrapApiRequest { + conversationApi.sendTypingIndicatorNotification(conversationId.toApi(), typingStatus.toStatusDto()) + } + private suspend fun persistIncompleteConversations( conversationsFailed: List ): Either { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorIncomingRepository.kt similarity index 76% rename from logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorRepository.kt rename to logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorIncomingRepository.kt index c3328debeb3..6177e2920ca 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorIncomingRepository.kt @@ -27,26 +27,25 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -internal interface TypingIndicatorRepository { +internal interface TypingIndicatorIncomingRepository { suspend fun addTypingUserInConversation(conversationId: ConversationId, userId: UserId) suspend fun removeTypingUserInConversation(conversationId: ConversationId, userId: UserId) - suspend fun observeUsersTyping(conversationId: ConversationId): Flow> + suspend fun observeUsersTyping(conversationId: ConversationId): Flow> + suspend fun clearExpiredTypingIndicators() } -internal class TypingIndicatorRepositoryImpl( - private val userTypingCache: ConcurrentMutableMap>, +internal class TypingIndicatorIncomingRepositoryImpl( + private val userTypingCache: ConcurrentMutableMap>, private val userPropertyRepository: UserPropertyRepository -) : TypingIndicatorRepository { +) : TypingIndicatorIncomingRepository { private val userTypingDataSourceFlow: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = BUFFER_SIZE, onBufferOverflow = BufferOverflow.DROP_OLDEST) override suspend fun addTypingUserInConversation(conversationId: ConversationId, userId: UserId) { if (userPropertyRepository.getTypingIndicatorStatus()) { - userTypingCache.safeComputeAndMutateSetValue(conversationId) { ExpiringUserTyping(userId, Clock.System.now()) } + userTypingCache.safeComputeAndMutateSetValue(conversationId) { userId } .also { userTypingDataSourceFlow.tryEmit(Unit) } @@ -55,36 +54,27 @@ internal class TypingIndicatorRepositoryImpl( override suspend fun removeTypingUserInConversation(conversationId: ConversationId, userId: UserId) { userTypingCache.block { entry -> - entry[conversationId]?.apply { this.removeAll { it.userId == userId } } + entry[conversationId]?.apply { this.removeAll { it == userId } } }.also { userTypingDataSourceFlow.tryEmit(Unit) } } - override suspend fun observeUsersTyping(conversationId: ConversationId): Flow> { + override suspend fun observeUsersTyping(conversationId: ConversationId): Flow> { return userTypingDataSourceFlow .map { userTypingCache[conversationId] ?: emptySet() } .onStart { emit(userTypingCache[conversationId] ?: emptySet()) } } - companion object { - const val BUFFER_SIZE = 32 // drop after this threshold - } -} - -// todo expire by worker -data class ExpiringUserTyping( - val userId: UserId, - val date: Instant -) { - override fun equals(other: Any?): Boolean { - return other != null && when (other) { - is ExpiringUserTyping -> other.userId == this.userId - else -> false + override suspend fun clearExpiredTypingIndicators() { + userTypingCache.block { entry -> + entry.clear() + }.also { + userTypingDataSourceFlow.tryEmit(Unit) } } - override fun hashCode(): Int { - return this.userId.hashCode() + companion object { + const val BUFFER_SIZE = 32 // drop after this threshold } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorOutgoingRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorOutgoingRepository.kt new file mode 100644 index 00000000000..91b08863df4 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorOutgoingRepository.kt @@ -0,0 +1,53 @@ +/* + * 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.data.conversation + +import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.properties.UserPropertyRepository +import com.wire.kalium.logic.functional.Either + +internal interface TypingIndicatorOutgoingRepository { + suspend fun sendTypingIndicatorStatus( + conversationId: ConversationId, + typingStatus: Conversation.TypingIndicatorMode + ): Either + +} + +internal class TypingIndicatorOutgoingRepositoryImpl( + private val typingIndicatorSenderHandler: TypingIndicatorSenderHandler, + private val userPropertyRepository: UserPropertyRepository +) : TypingIndicatorOutgoingRepository { + + override suspend fun sendTypingIndicatorStatus( + conversationId: ConversationId, + typingStatus: Conversation.TypingIndicatorMode + ): Either { + if (userPropertyRepository.getTypingIndicatorStatus()) { + when (typingStatus) { + Conversation.TypingIndicatorMode.STARTED -> + typingIndicatorSenderHandler.sendStartedAndEnqueueStoppingEvent(conversationId) + + Conversation.TypingIndicatorMode.STOPPED -> typingIndicatorSenderHandler.sendStoppingEvent(conversationId) + } + } + return Either.Right(Unit) + } + +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorSenderHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorSenderHandler.kt new file mode 100644 index 00000000000..702edfe205e --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorSenderHandler.kt @@ -0,0 +1,108 @@ +/* + * 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.data.conversation + +import com.wire.kalium.logic.data.id.ConversationId +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.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.coroutines.CoroutineContext +import kotlin.time.DurationUnit +import kotlin.time.toDuration + +/** + * Outgoing user typing sent events manager. + * + * - It will send started and stopped events. + * - For each started sent event, will 'enqueue' a stopped event after a timeout. + * + */ +internal interface TypingIndicatorSenderHandler { + fun sendStoppingEvent(conversationId: ConversationId) + fun sendStartedAndEnqueueStoppingEvent(conversationId: ConversationId) +} + +internal class TypingIndicatorSenderHandlerImpl( + private val conversationRepository: ConversationRepository, + private val kaliumDispatcher: KaliumDispatcher = KaliumDispatcherImpl, + userSessionCoroutineScope: CoroutineScope +) : TypingIndicatorSenderHandler, CoroutineScope by userSessionCoroutineScope { + override val coroutineContext: CoroutineContext + get() = kaliumDispatcher.default + + private val outgoingStoppedQueueTypingEventsMutex = Mutex() + private val outgoingStoppedQueueTypingEvents = mutableMapOf() + private val typingIndicatorTimeoutInSeconds = 10.toDuration(DurationUnit.SECONDS) + + /** + * Sends a stopping event and removes it from the 'queue'. + */ + override fun sendStoppingEvent(conversationId: ConversationId) { + launch { + outgoingStoppedQueueTypingEventsMutex.withLock { + if (!outgoingStoppedQueueTypingEvents.containsKey(conversationId)) { + return@launch + } + outgoingStoppedQueueTypingEvents.remove(conversationId) + sendTypingIndicatorStatus(conversationId, Conversation.TypingIndicatorMode.STOPPED) + } + } + } + + /** + * Sends a started event and enqueues a stopping event if sent successfully. + */ + override fun sendStartedAndEnqueueStoppingEvent(conversationId: ConversationId) { + launch { + val (_, isStartedSent) = outgoingStoppedQueueTypingEventsMutex.withLock { + if (outgoingStoppedQueueTypingEvents.containsKey(conversationId)) { + return@launch + } + val isSent = sendTypingIndicatorStatus(conversationId, Conversation.TypingIndicatorMode.STARTED) + this to isSent + } + + if (isStartedSent) { + enqueueStoppedEvent(conversationId) + } + } + } + + private suspend fun enqueueStoppedEvent(conversationId: ConversationId) { + outgoingStoppedQueueTypingEvents[conversationId] = Unit + delay(typingIndicatorTimeoutInSeconds) + sendStoppingEvent(conversationId) + } + + private suspend fun sendTypingIndicatorStatus( + conversationId: ConversationId, + typingStatus: Conversation.TypingIndicatorMode + ): Boolean = conversationRepository.sendTypingIndicatorStatus(conversationId, typingStatus).fold({ + kaliumLogger.w("Skipping failed to send typing indicator status: $typingStatus") + false + }) { + kaliumLogger.i("Successfully sent typing started indicator status: $typingStatus") + true + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorStatusMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorStatusMapper.kt new file mode 100644 index 00000000000..752eca14854 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorStatusMapper.kt @@ -0,0 +1,33 @@ +/* + * 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.data.conversation + +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatus +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatusDTO + +fun TypingIndicatorStatus.toModel(): Conversation.TypingIndicatorMode = when (this) { + TypingIndicatorStatus.STARTED -> Conversation.TypingIndicatorMode.STARTED + TypingIndicatorStatus.STOPPED -> Conversation.TypingIndicatorMode.STOPPED +} + +fun Conversation.TypingIndicatorMode.toApi(): TypingIndicatorStatus = when (this) { + Conversation.TypingIndicatorMode.STARTED -> TypingIndicatorStatus.STARTED + Conversation.TypingIndicatorMode.STOPPED -> TypingIndicatorStatus.STOPPED +} + +fun Conversation.TypingIndicatorMode.toStatusDto(): TypingIndicatorStatusDTO = TypingIndicatorStatusDTO(this.toApi()) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/EventMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/EventMapper.kt index c8435c5daaf..c915ca50684 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/EventMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/event/EventMapper.kt @@ -27,6 +27,7 @@ import com.wire.kalium.logic.data.conversation.ConversationRoleMapper import com.wire.kalium.logic.data.conversation.MemberMapper import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.conversation.ReceiptModeMapper +import com.wire.kalium.logic.data.conversation.toModel import com.wire.kalium.logic.data.event.Event.UserProperty.ReadReceiptModeSet import com.wire.kalium.logic.data.event.Event.UserProperty.TypingIndicatorModeSet import com.wire.kalium.logic.data.featureConfig.FeatureConfigMapper @@ -34,7 +35,6 @@ import com.wire.kalium.logic.data.id.SubconversationId import com.wire.kalium.logic.data.id.toModel import com.wire.kalium.logic.di.MapperProvider import com.wire.kalium.logic.util.Base64 -import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatus import com.wire.kalium.network.api.base.authenticated.featureConfigs.FeatureConfigData import com.wire.kalium.network.api.base.authenticated.notification.EventContentDTO import com.wire.kalium.network.api.base.authenticated.notification.EventResponse @@ -114,10 +114,7 @@ class EventMapper( transient, eventContentDTO.qualifiedFrom.toModel(), eventContentDTO.time, - when (eventContentDTO.status.status) { - TypingIndicatorStatus.STARTED -> Conversation.TypingIndicatorMode.STARTED - TypingIndicatorStatus.STOPPED -> Conversation.TypingIndicatorMode.STOPPED - } + eventContentDTO.status.status.toModel() ) private fun federationTerminated(id: String, eventContentDTO: EventContentDTO.Federation, transient: Boolean): Event = @@ -234,6 +231,7 @@ class EventMapper( ) } } + else -> unknown( id = id, transient = transient, diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/PublicUserMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/PublicUserMapper.kt index 302d72c24e5..46a1914da5b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/PublicUserMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/PublicUserMapper.kt @@ -39,6 +39,7 @@ import com.wire.kalium.network.api.base.model.UserProfileDTO import com.wire.kalium.network.api.base.model.getCompleteAssetOrNull import com.wire.kalium.network.api.base.model.getPreviewAssetOrNull import com.wire.kalium.persistence.dao.BotIdEntity +import com.wire.kalium.persistence.dao.UserDetailsEntity import com.wire.kalium.persistence.dao.UserEntity import com.wire.kalium.persistence.dao.UserEntityMinimized import kotlinx.datetime.toInstant @@ -54,6 +55,9 @@ interface PublicUserMapper { ): OtherUser fun fromEntityToUserSummary(userEntity: UserEntity): UserSummary + fun fromUserDetailsEntityToUserSummary(userDetailsEntity: UserDetailsEntity): UserSummary + fun fromUserDetailsEntityToOtherUser(userDetailsEntity: UserDetailsEntity): OtherUser + fun fromOtherToUserDetailsEntity(otherUser: OtherUser): UserDetailsEntity } class PublicUserMapperImpl( @@ -79,7 +83,28 @@ class PublicUserMapperImpl( botService = userEntity.botService?.let { BotService(it.id, it.provider) }, deleted = userEntity.deleted, expiresAt = userEntity.expiresAt, - defederated = userEntity.defederated + defederated = userEntity.defederated, + isProteusVerified = false + ) + + override fun fromUserDetailsEntityToOtherUser(userDetailsEntity: UserDetailsEntity) = OtherUser( + id = userDetailsEntity.id.toModel(), + name = userDetailsEntity.name, + handle = userDetailsEntity.handle, + email = userDetailsEntity.email, + phone = userDetailsEntity.phone, + accentId = userDetailsEntity.accentId, + teamId = userDetailsEntity.team?.let { TeamId(it) }, + connectionStatus = connectionStateMapper.fromDaoConnectionStateToUser(connectionState = userDetailsEntity.connectionStatus), + previewPicture = userDetailsEntity.previewAssetId?.toModel(), + completePicture = userDetailsEntity.completeAssetId?.toModel(), + availabilityStatus = availabilityStatusMapper.fromDaoAvailabilityStatusToModel(userDetailsEntity.availabilityStatus), + userType = domainUserTypeMapper.fromUserTypeEntity(userDetailsEntity.userType), + botService = userDetailsEntity.botService?.let { BotService(it.id, it.provider) }, + deleted = userDetailsEntity.deleted, + expiresAt = userDetailsEntity.expiresAt, + defederated = userDetailsEntity.defederated, + isProteusVerified = userDetailsEntity.isProteusVerified ) override fun fromOtherToUserEntity(otherUser: OtherUser): UserEntity = with(otherUser) { @@ -104,6 +129,29 @@ class PublicUserMapperImpl( ) } + override fun fromOtherToUserDetailsEntity(otherUser: OtherUser): UserDetailsEntity = with(otherUser) { + UserDetailsEntity( + id = id.toDao(), + name = name, + handle = handle, + email = email, + phone = phone, + accentId = accentId, + team = teamId?.value, + connectionStatus = connectionStateMapper.fromUserConnectionStateToDao(connectionStatus), + previewAssetId = previewPicture?.toDao(), + completeAssetId = completePicture?.toDao(), + availabilityStatus = availabilityStatusMapper.fromModelAvailabilityStatusToDao(availabilityStatus), + userType = userEntityTypeMapper.fromUserType(userType), + botService = botService?.let { BotIdEntity(it.id, it.provider) }, + deleted = deleted, + expiresAt = expiresAt, + hasIncompleteMetadata = false, + defederated = defederated, + isProteusVerified = otherUser.isProteusVerified + ) + } + override fun fromUserEntityToOtherUserMinimized(userEntity: UserEntityMinimized): OtherUserMinimized = OtherUserMinimized( id = userEntity.id.toModel(), @@ -131,7 +179,8 @@ class PublicUserMapperImpl( botService = userDetailResponse.service?.let { BotService(it.id, it.provider) }, deleted = userDetailResponse.deleted ?: false, expiresAt = userDetailResponse.expiresAt?.toInstant(), - defederated = false + defederated = false, + isProteusVerified = false ) override fun fromEntityToUserSummary(userEntity: UserEntity) = with(userEntity) { @@ -146,4 +195,17 @@ class PublicUserMapperImpl( connectionStatus = connectionStateMapper.fromDaoConnectionStateToUser(connectionStatus) ) } + + override fun fromUserDetailsEntityToUserSummary(userDetailsEntity: UserDetailsEntity): UserSummary = with(userDetailsEntity) { + UserSummary( + userId = UserId(id.value, id.domain), + userHandle = handle, + userName = name, + userPreviewAssetId = previewAssetId?.toModel(), + userType = domainUserTypeMapper.fromUserTypeEntity(userType), + isUserDeleted = deleted, + availabilityStatus = availabilityStatusMapper.fromDaoAvailabilityStatusToModel(availabilityStatus), + connectionStatus = connectionStateMapper.fromDaoConnectionStateToUser(connectionStatus) + ) + } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/SearchUserRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/SearchUserRepository.kt index 3f45a1ffe20..e59e1bf70ea 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/SearchUserRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/SearchUserRepository.kt @@ -39,7 +39,7 @@ import com.wire.kalium.persistence.dao.ConnectionEntity import com.wire.kalium.persistence.dao.MetadataDAO import com.wire.kalium.persistence.dao.QualifiedIDEntity import com.wire.kalium.persistence.dao.UserDAO -import com.wire.kalium.persistence.dao.UserEntity +import com.wire.kalium.persistence.dao.UserDetailsEntity import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterNotNull @@ -104,13 +104,13 @@ internal class SearchUserRepositoryImpl( handleSearchUsersOptions( searchUsersOptions, excluded = { conversationId -> - userDAO.getUsersNotInConversationByNameOrHandleOrEmail( + userDAO.getUsersDetailsNotInConversationByNameOrHandleOrEmail( conversationId = conversationId.toDao(), searchQuery = searchQuery ) }, default = { - userDAO.getUserByNameOrHandleOrEmailAndConnectionStates( + userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates( searchQuery = searchQuery, connectionStates = listOf(ConnectionEntity.State.ACCEPTED, ConnectionEntity.State.BLOCKED) ) @@ -124,13 +124,13 @@ internal class SearchUserRepositoryImpl( handleSearchUsersOptions( searchUsersOptions, excluded = { conversationId -> - userDAO.getUsersNotInConversationByHandle( + userDAO.getUsersDetailsNotInConversationByHandle( conversationId = conversationId.toDao(), handle = handle ) }, default = { - userDAO.getUserByHandleAndConnectionStates( + userDAO.getUserDetailsByHandleAndConnectionStates( handle = handle, connectionStates = listOf(ConnectionEntity.State.ACCEPTED, ConnectionEntity.State.BLOCKED) ) @@ -185,16 +185,16 @@ internal class SearchUserRepositoryImpl( .flatMapMerge { encodedValue -> val selfUserID: QualifiedIDEntity = Json.decodeFromString(string = encodedValue) - userDAO.getUserByQualifiedID(selfUserID) + userDAO.observeUserDetailsByQualifiedID(selfUserID) .filterNotNull() - .map(userMapper::fromUserEntityToSelfUser) + .map(userMapper::fromUserDetailsEntityToSelfUser) }.firstOrNull() ?: throw IllegalStateException() } private suspend fun handleSearchUsersOptions( localSearchUserOptions: SearchUsersOptions, - excluded: suspend (conversationId: ConversationId) -> Flow>, - default: suspend () -> Flow> + excluded: suspend (conversationId: ConversationId) -> Flow>, + default: suspend () -> Flow> ): Flow { val listFlow = when (val searchOptions = localSearchUserOptions.conversationExcluded) { ConversationMemberExcludedOptions.None -> default() @@ -202,7 +202,7 @@ internal class SearchUserRepositoryImpl( } return listFlow.map { - UserSearchResult(it.map(publicUserMapper::fromUserEntityToOtherUser)) + UserSearchResult(it.map(publicUserMapper::fromUserDetailsEntityToOtherUser)) } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/UserSearchApiWrapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/UserSearchApiWrapper.kt index aa251c4557f..14dd1809262 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/UserSearchApiWrapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/publicuser/UserSearchApiWrapper.kt @@ -42,7 +42,6 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.map import kotlinx.serialization.json.Json -import kotlinx.serialization.decodeFromString internal interface UserSearchApiWrapper { /* @@ -138,9 +137,9 @@ internal class UserSearchApiWrapperImpl( .flatMapMerge { encodedValue -> val selfUserID: QualifiedIDEntity = Json.decodeFromString(encodedValue) - userDAO.getUserByQualifiedID(selfUserID) + userDAO.observeUserDetailsByQualifiedID(selfUserID) .filterNotNull() - .map(userMapper::fromUserEntityToSelfUser) + .map(userMapper::fromUserDetailsEntityToSelfUser) }.firstOrNull() ?: throw IllegalStateException() } } 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 26d766c9b27..b9f86bc910d 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 @@ -39,13 +39,16 @@ import com.wire.kalium.persistence.dao.BotIdEntity 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.UserDetailsEntity import com.wire.kalium.persistence.dao.UserEntity import com.wire.kalium.persistence.dao.UserTypeEntity import kotlinx.datetime.toInstant +@Suppress("TooManyFunctions") interface UserMapper { fun fromSelfUserDtoToUserEntity(userDTO: SelfUserDTO): UserEntity fun fromUserEntityToSelfUser(userEntity: UserEntity): SelfUser + fun fromUserDetailsEntityToSelfUser(userEntity: UserDetailsEntity): SelfUser fun fromSelfUserToUserEntity(selfUser: SelfUser): UserEntity /** @@ -74,6 +77,7 @@ interface UserMapper { ): UserEntity fun fromUserUpdateEventToUserEntity(event: Event.User.Update, userEntity: UserEntity): UserEntity + fun fromUserUpdateEventToUserEntity(event: Event.User.Update, userEntity: UserDetailsEntity): UserEntity fun fromUserProfileDtoToUserEntity( userProfile: UserProfileDTO, @@ -82,8 +86,19 @@ interface UserMapper { ): UserEntity fun fromFailedUserToEntity(userId: NetworkQualifiedId): UserEntity + fun fromSelfUserToUserDetailsEntity(selfUser: SelfUser): UserDetailsEntity + fun fromSelfUserDtoToUserDetailsEntity(userDTO: SelfUserDTO): UserDetailsEntity + + fun fromUserProfileDtoToUserDetailsEntity( + userProfile: UserProfileDTO, + connectionState: ConnectionEntity.State, + userTypeEntity: UserTypeEntity + ): UserDetailsEntity + + fun fromFailedUserToDetailsEntity(userId: NetworkQualifiedId): UserDetailsEntity } +@Suppress("TooManyFunctions") internal class UserMapperImpl( private val idMapper: IdMapper = MapperProvider.idMapper(), private val availabilityStatusMapper: AvailabilityStatusMapper = MapperProvider.availabilityStatusMapper(), @@ -108,6 +123,23 @@ internal class UserMapperImpl( ) } + override fun fromUserDetailsEntityToSelfUser(userEntity: UserDetailsEntity): SelfUser = with(userEntity) { + SelfUser( + id.toModel(), + name, + handle, + email, + phone, + accentId, + team?.let { TeamId(it) }, + connectionStateMapper.fromDaoConnectionStateToUser(connectionState = connectionStatus), + previewAssetId?.toModel(), + completeAssetId?.toModel(), + availabilityStatusMapper.fromDaoAvailabilityStatusToModel(availabilityStatus), + expiresAt = expiresAt + ) + } + override fun fromSelfUserToUserEntity(selfUser: SelfUser): UserEntity = with(selfUser) { UserEntity( id = id.toDao(), @@ -129,6 +161,28 @@ internal class UserMapperImpl( ) } + override fun fromSelfUserToUserDetailsEntity(selfUser: SelfUser): UserDetailsEntity = with(selfUser) { + UserDetailsEntity( + id = id.toDao(), + name = name, + handle = handle, + email = email, + phone = phone, + accentId = accentId, + team = teamId?.value, + connectionStatus = connectionStateMapper.fromUserConnectionStateToDao(connectionStatus), + previewAssetId = previewPicture?.toDao(), + completeAssetId = completePicture?.toDao(), + availabilityStatus = availabilityStatusMapper.fromModelAvailabilityStatusToDao(availabilityStatus), + userType = UserTypeEntity.STANDARD, + botService = null, + deleted = false, + expiresAt = expiresAt, + defederated = false, + isProteusVerified = false + ) + } + override fun fromSelfUserDtoToUserEntity(userDTO: SelfUserDTO): UserEntity = with(userDTO) { UserEntity( id = idMapper.fromApiToDao(id), @@ -149,6 +203,27 @@ internal class UserMapperImpl( ) } + override fun fromSelfUserDtoToUserDetailsEntity(userDTO: SelfUserDTO): UserDetailsEntity = with(userDTO) { + UserDetailsEntity( + id = idMapper.fromApiToDao(id), + name = name, + handle = handle, + email = email, + phone = phone, + accentId = accentId, + team = teamId, + previewAssetId = assets.getPreviewAssetOrNull()?.let { QualifiedIDEntity(it.key, id.domain) }, + completeAssetId = assets.getCompleteAssetOrNull()?.let { QualifiedIDEntity(it.key, id.domain) }, + availabilityStatus = UserAvailabilityStatusEntity.NONE, + userType = UserTypeEntity.STANDARD, + botService = null, + deleted = userDTO.deleted ?: false, + expiresAt = expiresAt?.toInstant(), + defederated = false, + isProteusVerified = false + ) + } + override fun fromModelToUpdateApiModel( user: SelfUser, newName: String?, @@ -234,6 +309,32 @@ internal class UserMapperImpl( defederated = false ) + override fun fromUserProfileDtoToUserDetailsEntity( + userProfile: UserProfileDTO, + connectionState: ConnectionEntity.State, + userTypeEntity: UserTypeEntity + ) = UserDetailsEntity( + id = idMapper.fromApiToDao(userProfile.id), + name = userProfile.name, + handle = userProfile.handle, + email = userProfile.email, + phone = null, + accentId = userProfile.accentId, + team = userProfile.teamId, + previewAssetId = userProfile.assets.getPreviewAssetOrNull() + ?.let { QualifiedIDEntity(it.key, userProfile.id.domain) }, + completeAssetId = userProfile.assets.getCompleteAssetOrNull() + ?.let { QualifiedIDEntity(it.key, userProfile.id.domain) }, + connectionStatus = connectionState, + availabilityStatus = UserAvailabilityStatusEntity.NONE, + userType = userTypeEntity, + botService = userProfile.service?.let { BotIdEntity(it.id, it.provider) }, + deleted = userProfile.deleted ?: false, + expiresAt = userProfile.expiresAt?.toInstant(), + defederated = false, + isProteusVerified = false + ) + override fun fromUserUpdateEventToUserEntity(event: Event.User.Update, userEntity: UserEntity): UserEntity { return userEntity.let { persistedEntity -> persistedEntity.copy( @@ -249,6 +350,31 @@ internal class UserMapperImpl( } } + override fun fromUserUpdateEventToUserEntity(event: Event.User.Update, userEntity: UserDetailsEntity): UserEntity = + userEntity.let { persistedEntity -> + UserEntity( + id = persistedEntity.id, + name = event.name ?: persistedEntity.name, + handle = event.handle ?: persistedEntity.handle, + email = event.email ?: persistedEntity.email, + phone = persistedEntity.phone, + accentId = event.accentId ?: persistedEntity.accentId, + team = persistedEntity.team, + connectionStatus = persistedEntity.connectionStatus, + previewAssetId = event.previewAssetId?.let { QualifiedIDEntity(it, persistedEntity.id.domain) } + ?: persistedEntity.previewAssetId, + completeAssetId = event.completeAssetId?.let { QualifiedIDEntity(it, persistedEntity.id.domain) } + ?: persistedEntity.completeAssetId, + availabilityStatus = persistedEntity.availabilityStatus, + userType = persistedEntity.userType, + botService = persistedEntity.botService, + deleted = persistedEntity.deleted, + hasIncompleteMetadata = persistedEntity.hasIncompleteMetadata, + expiresAt = persistedEntity.expiresAt, + defederated = persistedEntity.defederated + ) + } + /** * Default values and marked as [UserEntity.hasIncompleteMetadata] = true. * So later we can re-fetch them. @@ -274,4 +400,27 @@ internal class UserMapperImpl( defederated = false ) } + + override fun fromFailedUserToDetailsEntity(userId: NetworkQualifiedId): UserDetailsEntity { + return UserDetailsEntity( + 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, + expiresAt = null, + defederated = false, + isProteusVerified = false + ) + } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserModel.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserModel.kt index f8da8c0f958..ce501d4b2ee 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserModel.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserModel.kt @@ -152,7 +152,8 @@ data class OtherUser( val botService: BotService?, val deleted: Boolean, val defederated: Boolean, - override val expiresAt: Instant? = null + override val expiresAt: Instant? = null, + val isProteusVerified: Boolean ) : User() { /** 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 2ddcfacb088..7aca95cf31f 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 @@ -184,9 +184,9 @@ internal class UserDataSource internal constructor( sessionRepository.updateSsoIdAndScimInfo(userDTO.id.toModel(), idMapper.toSsoId(userDTO.ssoID), userDTO.managedByDTO) override suspend fun getKnownUser(userId: UserId): Flow = - userDAO.getUserByQualifiedID(qualifiedID = userId.toDao()) + userDAO.observeUserDetailsByQualifiedID(qualifiedID = userId.toDao()) .map { userEntity -> - userEntity?.let { publicUserMapper.fromUserEntityToOtherUser(userEntity) } + userEntity?.let { publicUserMapper.fromUserDetailsEntityToOtherUser(userEntity) } }.onEach { otherUser -> processFederatedUserRefresh(userId, otherUser) } @@ -283,7 +283,7 @@ internal class UserDataSource internal constructor( override suspend fun fetchUsersIfUnknownByIds(ids: Set): Either = wrapStorageRequest { val qualifiedIDList = ids.map { it.toDao() } - val knownUsers = userDAO.getUsersByQualifiedIDList(ids.map { it.toDao() }) + val knownUsers = userDAO.getUsersDetailsByQualifiedIDList(ids.map { it.toDao() }) // TODO we should differentiate users with incomplete data not by checking if name isNullOrBlank // TODO but add separate property (when federated backend is down) qualifiedIDList.filterNot { knownUsers.any { userEntity -> userEntity.id == it && !userEntity.name.isNullOrBlank() } } @@ -307,9 +307,9 @@ internal class UserDataSource internal constructor( } }.filterNotNull().flatMapMerge { encodedValue -> val selfUserID: QualifiedIDEntity = Json.decodeFromString(encodedValue) - userDAO.getUserByQualifiedID(selfUserID) + userDAO.observeUserDetailsByQualifiedID(selfUserID) .filterNotNull() - .map(userMapper::fromUserEntityToSelfUser) + .map(userMapper::fromUserDetailsEntityToSelfUser) } } @@ -317,10 +317,10 @@ internal class UserDataSource internal constructor( override suspend fun observeSelfUserWithTeam(): Flow> { return metadataDAO.valueByKeyFlow(SELF_USER_ID_KEY).filterNotNull().flatMapMerge { encodedValue -> val selfUserID: QualifiedIDEntity = Json.decodeFromString(encodedValue) - userDAO.getUserWithTeamByQualifiedID(selfUserID) + userDAO.getUserDetailsWithTeamByQualifiedID(selfUserID) .filterNotNull() .map { (user, team) -> - userMapper.fromUserEntityToSelfUser(user) to team?.let { teamMapper.fromDaoModelToTeam(it) } + userMapper.fromUserDetailsEntityToSelfUser(user) to team?.let { teamMapper.fromDaoModelToTeam(it) } } } } @@ -348,12 +348,12 @@ internal class UserDataSource internal constructor( override suspend fun observeAllKnownUsers(): Flow>> { val selfUserId = selfUserId.toDao() - return userDAO.observeAllUsersByConnectionStatus(connectionState = ConnectionEntity.State.ACCEPTED) + return userDAO.observeAllUsersDetailsByConnectionStatus(connectionState = ConnectionEntity.State.ACCEPTED) .wrapStorageRequest() .mapRight { users -> users .filter { it.id != selfUserId && !it.deleted && !it.hasIncompleteMetadata } - .map { userEntity -> publicUserMapper.fromUserEntityToOtherUser(userEntity) } + .map { userEntity -> publicUserMapper.fromUserDetailsEntityToOtherUser(userEntity) } } } @@ -364,13 +364,13 @@ internal class UserDataSource internal constructor( } override suspend fun observeUser(userId: UserId): Flow = - userDAO.getUserByQualifiedID(qualifiedID = userId.toDao()) + userDAO.observeUserDetailsByQualifiedID(qualifiedID = userId.toDao()) .map { userEntity -> // TODO: cache SelfUserId so it's not fetched from DB every single time if (userId == selfUserId) { - userEntity?.let { userMapper.fromUserEntityToSelfUser(userEntity) } + userEntity?.let { userMapper.fromUserDetailsEntityToSelfUser(userEntity) } } else { - userEntity?.let { publicUserMapper.fromUserEntityToOtherUser(userEntity) } + userEntity?.let { publicUserMapper.fromUserDetailsEntityToOtherUser(userEntity) } } } @@ -399,19 +399,19 @@ internal class UserDataSource internal constructor( override fun observeAllKnownUsersNotInConversation( conversationId: ConversationId ): Flow>> { - return userDAO.observeUsersNotInConversation(conversationId.toDao()) + return userDAO.observeUsersDetailsNotInConversation(conversationId.toDao()) .wrapStorageRequest() .mapRight { users -> users .filter { !it.deleted && !it.hasIncompleteMetadata } - .map { publicUserMapper.fromUserEntityToOtherUser(it) } + .map { publicUserMapper.fromUserDetailsEntityToOtherUser(it) } } } override suspend fun getAllRecipients(): Either, List>> = selfTeamIdProvider().flatMap { teamId -> val teamMateIds = teamId?.value?.let { selfTeamId -> - wrapStorageRequest { userDAO.getAllUsersByTeam(selfTeamId).map { it.id.toModel() } } + wrapStorageRequest { userDAO.getAllUsersDetailsByTeam(selfTeamId).map { it.id.toModel() } } }?.getOrNull() ?: listOf() wrapStorageRequest { @@ -430,7 +430,7 @@ internal class UserDataSource internal constructor( override suspend fun updateUserFromEvent(event: Event.User.Update): Either = wrapStorageRequest { val userId = qualifiedIdMapper.fromStringToQualifiedID(event.userId) val user = - userDAO.getUserByQualifiedID(userId.toDao()).firstOrNull() ?: return Either.Left(StorageFailure.DataNotFound) + userDAO.observeUserDetailsByQualifiedID(userId.toDao()).firstOrNull() ?: return Either.Left(StorageFailure.DataNotFound) userDAO.updateUser(userMapper.fromUserUpdateEventToUserEntity(event, user)) } @@ -459,7 +459,7 @@ internal class UserDataSource internal constructor( } override suspend fun syncUsersWithoutMetadata(): Either = wrapStorageRequest { - userDAO.getUsersWithoutMetadata() + userDAO.getUsersDetailsWithoutMetadata() }.flatMap { usersWithoutMetadata -> kaliumLogger.d("Numbers of users to refresh: ${usersWithoutMetadata.size}") val userIds = usersWithoutMetadata.map { it.id.toModel() }.toSet() @@ -472,8 +472,8 @@ internal class UserDataSource internal constructor( override suspend fun getUsersSummaryByIds(userIds: List): Either> = wrapStorageRequest { - userDAO.getUsersByQualifiedIDList(userIds.map { it.toDao() }).map { - publicUserMapper.fromEntityToUserSummary(it) + userDAO.getUsersDetailsByQualifiedIDList(userIds.map { it.toDao() }).map { + publicUserMapper.fromUserDetailsEntityToUserSummary(it) } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index bf72978ad2a..c1d9b125ac4 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -156,8 +156,6 @@ import com.wire.kalium.logic.feature.connection.SyncConnectionsUseCaseImpl import com.wire.kalium.logic.feature.conversation.ConversationScope import com.wire.kalium.logic.feature.conversation.ConversationsRecoveryManager import com.wire.kalium.logic.feature.conversation.ConversationsRecoveryManagerImpl -import com.wire.kalium.logic.feature.conversation.ObserveOtherUserSecurityClassificationLabelUseCase -import com.wire.kalium.logic.feature.conversation.ObserveOtherUserSecurityClassificationLabelUseCaseImpl import com.wire.kalium.logic.feature.conversation.JoinExistingMLSConversationUseCase import com.wire.kalium.logic.feature.conversation.JoinExistingMLSConversationUseCaseImpl import com.wire.kalium.logic.feature.conversation.JoinExistingMLSConversationsUseCase @@ -170,17 +168,22 @@ import com.wire.kalium.logic.feature.conversation.MLSConversationsRecoveryManage import com.wire.kalium.logic.feature.conversation.MLSConversationsRecoveryManagerImpl import com.wire.kalium.logic.feature.conversation.MLSConversationsVerificationStatusesHandler import com.wire.kalium.logic.feature.conversation.MLSConversationsVerificationStatusesHandlerImpl +import com.wire.kalium.logic.feature.conversation.ObserveOtherUserSecurityClassificationLabelUseCase +import com.wire.kalium.logic.feature.conversation.ObserveOtherUserSecurityClassificationLabelUseCaseImpl import com.wire.kalium.logic.feature.conversation.ObserveSecurityClassificationLabelUseCase import com.wire.kalium.logic.feature.conversation.ObserveSecurityClassificationLabelUseCaseImpl import com.wire.kalium.logic.feature.conversation.RecoverMLSConversationsUseCase import com.wire.kalium.logic.feature.conversation.RecoverMLSConversationsUseCaseImpl import com.wire.kalium.logic.feature.conversation.SyncConversationsUseCase import com.wire.kalium.logic.feature.conversation.SyncConversationsUseCaseImpl +import com.wire.kalium.logic.feature.conversation.TypingIndicatorSyncManager import com.wire.kalium.logic.feature.conversation.keyingmaterials.KeyingMaterialsManager import com.wire.kalium.logic.feature.conversation.keyingmaterials.KeyingMaterialsManagerImpl import com.wire.kalium.logic.feature.debug.DebugScope import com.wire.kalium.logic.feature.e2ei.EnrollE2EIUseCase import com.wire.kalium.logic.feature.e2ei.EnrollE2EIUseCaseImpl +import com.wire.kalium.logic.feature.featureConfig.SyncFeatureConfigsUseCase +import com.wire.kalium.logic.feature.featureConfig.SyncFeatureConfigsUseCaseImpl import com.wire.kalium.logic.feature.featureConfig.handler.AppLockConfigHandler import com.wire.kalium.logic.feature.featureConfig.handler.ClassifiedDomainsConfigHandler import com.wire.kalium.logic.feature.featureConfig.handler.ConferenceCallingConfigHandler @@ -190,8 +193,6 @@ import com.wire.kalium.logic.feature.featureConfig.handler.GuestRoomConfigHandle import com.wire.kalium.logic.feature.featureConfig.handler.MLSConfigHandler import com.wire.kalium.logic.feature.featureConfig.handler.SecondFactorPasswordChallengeConfigHandler import com.wire.kalium.logic.feature.featureConfig.handler.SelfDeletingMessagesConfigHandler -import com.wire.kalium.logic.feature.featureConfig.SyncFeatureConfigsUseCase -import com.wire.kalium.logic.feature.featureConfig.SyncFeatureConfigsUseCaseImpl import com.wire.kalium.logic.feature.keypackage.KeyPackageManager import com.wire.kalium.logic.feature.keypackage.KeyPackageManagerImpl import com.wire.kalium.logic.feature.message.AddSystemMessageToAllConversationsUseCase @@ -463,7 +464,8 @@ class UserSessionScope internal constructor( sessionManager, UserIdDTO(userId.value, userId.domain), userAgent, - certificatePinning = kaliumConfigs.certPinningConfig + certificatePinning = kaliumConfigs.certPinningConfig, + mockEngine = kaliumConfigs.kaliumMockEngine?.mockEngine ) private val featureSupport: FeatureSupport = FeatureSupportImpl( kaliumConfigs, @@ -474,7 +476,8 @@ class UserSessionScope internal constructor( sessionManager.getProxyCredentials(), globalScope.serverConfigRepository, networkStateObserver, - kaliumConfigs::certPinningConfig + kaliumConfigs::certPinningConfig, + mockEngine = kaliumConfigs.kaliumMockEngine?.mockEngine ) private val userConfigRepository: UserConfigRepository @@ -1162,7 +1165,7 @@ class UserSessionScope internal constructor( ) private val typingIndicatorHandler: TypingIndicatorHandler - get() = TypingIndicatorHandlerImpl(userId, conversations.typingIndicatorRepository) + get() = TypingIndicatorHandlerImpl(userId, conversations.typingIndicatorIncomingRepository) private val conversationEventReceiver: ConversationEventReceiver by lazy { ConversationEventReceiverImpl( @@ -1532,6 +1535,9 @@ class UserSessionScope internal constructor( MLSConversationsVerificationStatusesHandlerImpl(conversationRepository, persistMessage, mlsConversationRepository, userId) } + private val typingIndicatorSyncManager: TypingIndicatorSyncManager = + TypingIndicatorSyncManager(lazy { conversations.typingIndicatorIncomingRepository }, observeSyncState) + init { launch { apiMigrationManager.performMigrations() @@ -1571,6 +1577,10 @@ class UserSessionScope internal constructor( launch { mlsConversationsVerificationStatusesHandler.invoke() } + + launch { + typingIndicatorSyncManager.execute() + } } fun onDestroy() { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/appVersioning/ObserveIfAppUpdateRequiredUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/appVersioning/ObserveIfAppUpdateRequiredUseCase.kt index 350c554d463..6ab438ee1c2 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/appVersioning/ObserveIfAppUpdateRequiredUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/appVersioning/ObserveIfAppUpdateRequiredUseCase.kt @@ -61,6 +61,7 @@ class ObserveIfAppUpdateRequiredUseCaseImpl internal constructor( private val kaliumConfigs: KaliumConfigs ) : ObserveIfAppUpdateRequiredUseCase { + @Suppress("ComplexMethod", "LongMethod") @OptIn(ExperimentalCoroutinesApi::class) override suspend fun invoke(currentAppVersion: Int): Flow { val currentDate = DateTimeUtil.currentIsoDateTimeString() @@ -105,7 +106,8 @@ class ObserveIfAppUpdateRequiredUseCaseImpl internal constructor( proxyCredentials, serverConfigRepository, networkStateObserver, - kaliumConfigs::certPinningConfig + kaliumConfigs::certPinningConfig, + kaliumConfigs.kaliumMockEngine?.mockEngine ) .checkIfUpdateRequired(currentAppVersion, serverConfig.links.blackList) serverConfig.id to isUpdateRequired diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/AuthenticationScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/AuthenticationScope.kt index 2b772c099fc..2d4b5783558 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/AuthenticationScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/AuthenticationScope.kt @@ -42,6 +42,7 @@ import com.wire.kalium.logic.util.safeComputeIfAbsent import com.wire.kalium.network.NetworkStateObserver import com.wire.kalium.network.networkContainer.UnauthenticatedNetworkContainer import com.wire.kalium.network.session.CertificatePinning +import io.ktor.client.engine.HttpClientEngine class AuthenticationScopeProvider internal constructor( private val userAgent: String @@ -52,12 +53,14 @@ class AuthenticationScopeProvider internal constructor( ConcurrentMutableMap() } + @Suppress("LongParameterList") internal fun provide( serverConfig: ServerConfig, proxyCredentials: ProxyCredentials?, serverConfigRepository: ServerConfigRepository, networkStateObserver: NetworkStateObserver, - certConfig: () -> CertificatePinning + certConfig: () -> CertificatePinning, + mockEngine: HttpClientEngine? ): AuthenticationScope = authenticationScopeStorage.safeComputeIfAbsent(serverConfig to proxyCredentials) { AuthenticationScope( @@ -66,18 +69,21 @@ class AuthenticationScopeProvider internal constructor( proxyCredentials, serverConfigRepository, networkStateObserver, - certConfig + certConfig, + mockEngine ) } } +@Suppress("LongParameterList") class AuthenticationScope internal constructor( private val userAgent: String, private val serverConfig: ServerConfig, private val proxyCredentials: ProxyCredentials?, private val serverConfigRepository: ServerConfigRepository, private val networkStateObserver: NetworkStateObserver, - certConfig: () -> CertificatePinning + certConfig: () -> CertificatePinning, + mockEngine: HttpClientEngine? ) { private val unauthenticatedNetworkContainer: UnauthenticatedNetworkContainer by lazy { UnauthenticatedNetworkContainer.create( @@ -85,7 +91,8 @@ class AuthenticationScope internal constructor( MapperProvider.serverConfigMapper().toDTO(serverConfig), proxyCredentials?.let { MapperProvider.sessionMapper().fromModelToProxyCredentialsDTO(it) }, userAgent, - certificatePinning = certConfig() + certificatePinning = certConfig(), + mockEngine ) } private val loginRepository: LoginRepository diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/UpdateClientVerificationStatusUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/UpdateClientVerificationStatusUseCase.kt index 077cad40dfc..866fdbef99c 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/UpdateClientVerificationStatusUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/client/UpdateClientVerificationStatusUseCase.kt @@ -35,7 +35,7 @@ class UpdateClientVerificationStatusUseCase internal constructor( private val clientRepository: ClientRepository ) { suspend operator fun invoke(userId: UserId, clientId: ClientId, verified: Boolean): Result = - clientRepository.updateClientVerificationStatus(userId, clientId, verified).fold( + clientRepository.updateClientProteusVerificationStatus(userId, clientId, verified).fold( { error -> Result.Failure(error) }, { Result.Success } ) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ClearUsersTypingEventsUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ClearUsersTypingEventsUseCase.kt new file mode 100644 index 00000000000..08db619803e --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ClearUsersTypingEventsUseCase.kt @@ -0,0 +1,41 @@ +/* + * 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.conversation + +import com.wire.kalium.logic.data.conversation.TypingIndicatorIncomingRepository +import com.wire.kalium.util.KaliumDispatcher +import com.wire.kalium.util.KaliumDispatcherImpl +import kotlinx.coroutines.withContext + +/** + * Use case for clearing and drop orphaned typing indicators + */ +interface ClearUsersTypingEventsUseCase { + suspend operator fun invoke() +} + +internal class ClearUsersTypingEventsUseCaseImpl( + private val typingIndicatorIncomingRepository: TypingIndicatorIncomingRepository, + private val dispatcher: KaliumDispatcher = KaliumDispatcherImpl +) : ClearUsersTypingEventsUseCase { + override suspend operator fun invoke() { + withContext(dispatcher.io) { + typingIndicatorIncomingRepository.clearExpiredTypingIndicators() + } + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt index 7e07482cadb..8885c74faf5 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt @@ -27,7 +27,10 @@ import com.wire.kalium.logic.data.conversation.ConversationRepository import com.wire.kalium.logic.data.conversation.MLSConversationRepository import com.wire.kalium.logic.data.conversation.NewGroupConversationSystemMessagesCreator import com.wire.kalium.logic.data.conversation.NewGroupConversationSystemMessagesCreatorImpl -import com.wire.kalium.logic.data.conversation.TypingIndicatorRepositoryImpl +import com.wire.kalium.logic.data.conversation.TypingIndicatorIncomingRepositoryImpl +import com.wire.kalium.logic.data.conversation.TypingIndicatorOutgoingRepositoryImpl +import com.wire.kalium.logic.data.conversation.TypingIndicatorSenderHandler +import com.wire.kalium.logic.data.conversation.TypingIndicatorSenderHandlerImpl import com.wire.kalium.logic.data.conversation.UpdateKeyingMaterialThresholdProvider import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.data.message.PersistMessageUseCase @@ -266,9 +269,28 @@ class ConversationScope internal constructor( val observeArchivedUnreadConversationsCount: ObserveArchivedUnreadConversationsCountUseCase get() = ObserveArchivedUnreadConversationsCountUseCaseImpl(conversationRepository) - internal val typingIndicatorRepository = TypingIndicatorRepositoryImpl(ConcurrentMutableMap(), userPropertyRepository) + private val typingIndicatorSenderHandler: TypingIndicatorSenderHandler = + TypingIndicatorSenderHandlerImpl(conversationRepository = conversationRepository, userSessionCoroutineScope = scope) + + internal val typingIndicatorIncomingRepository = + TypingIndicatorIncomingRepositoryImpl( + ConcurrentMutableMap(), + userPropertyRepository + ) + + internal val typingIndicatorOutgoingRepository = + TypingIndicatorOutgoingRepositoryImpl( + typingIndicatorSenderHandler, + userPropertyRepository + ) + + val sendTypingEvent: SendTypingEventUseCase + get() = SendTypingEventUseCaseImpl(typingIndicatorOutgoingRepository) val observeUsersTyping: ObserveUsersTypingUseCase - get() = ObserveUsersTypingUseCaseImpl(typingIndicatorRepository, userRepository) + get() = ObserveUsersTypingUseCaseImpl(typingIndicatorIncomingRepository, userRepository) + + val clearUsersTypingEvents: ClearUsersTypingEventsUseCase + get() = ClearUsersTypingEventsUseCaseImpl(typingIndicatorIncomingRepository) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveUsersTypingUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveUsersTypingUseCase.kt index e7a1e9ee880..abc7dabe6ee 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveUsersTypingUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveUsersTypingUseCase.kt @@ -17,7 +17,7 @@ */ package com.wire.kalium.logic.feature.conversation -import com.wire.kalium.logic.data.conversation.TypingIndicatorRepository +import com.wire.kalium.logic.data.conversation.TypingIndicatorIncomingRepository import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.message.UserSummary import com.wire.kalium.logic.data.user.UserRepository @@ -38,13 +38,13 @@ interface ObserveUsersTypingUseCase { } internal class ObserveUsersTypingUseCaseImpl( - private val typingIndicatorRepository: TypingIndicatorRepository, + private val typingIndicatorIncomingRepository: TypingIndicatorIncomingRepository, private val userRepository: UserRepository, private val dispatcher: KaliumDispatcher = KaliumDispatcherImpl ) : ObserveUsersTypingUseCase { override suspend operator fun invoke(conversationId: ConversationId): Flow> = withContext(dispatcher.io) { - typingIndicatorRepository.observeUsersTyping(conversationId).map { usersEntries -> - userRepository.getUsersSummaryByIds(usersEntries.map { it.userId }).fold({ + typingIndicatorIncomingRepository.observeUsersTyping(conversationId).map { usersEntries -> + userRepository.getUsersSummaryByIds(usersEntries.map { it }).fold({ kaliumLogger.w("Users not found locally, skipping... $it") emptySet() }, { it.toSet() }) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/SendTypingEventUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/SendTypingEventUseCase.kt new file mode 100644 index 00000000000..d1c49e02790 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/SendTypingEventUseCase.kt @@ -0,0 +1,44 @@ +/* + * 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.conversation + +import com.wire.kalium.logic.data.conversation.Conversation +import com.wire.kalium.logic.data.conversation.TypingIndicatorOutgoingRepository +import com.wire.kalium.logic.data.id.ConversationId + +/** + * UseCase for sending a typing event to a specific [ConversationId] + * + * Underlying implementation will take care of enqueuing the event for a [Conversation.TypingIndicatorMode.STOPPED] + * after a certain amount of time. + * + */ +interface SendTypingEventUseCase { + suspend operator fun invoke( + conversationId: ConversationId, + typingStatus: Conversation.TypingIndicatorMode + ) +} + +internal class SendTypingEventUseCaseImpl( + private val typingIndicatorRepository: TypingIndicatorOutgoingRepository +) : SendTypingEventUseCase { + override suspend fun invoke(conversationId: ConversationId, typingStatus: Conversation.TypingIndicatorMode) { + typingIndicatorRepository.sendTypingIndicatorStatus(conversationId, typingStatus) + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/TypingIndicatorSyncManager.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/TypingIndicatorSyncManager.kt new file mode 100644 index 00000000000..aed94d8eee6 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/TypingIndicatorSyncManager.kt @@ -0,0 +1,39 @@ +/* + * 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.conversation + +import com.wire.kalium.logic.data.conversation.TypingIndicatorIncomingRepository +import com.wire.kalium.logic.kaliumLogger +import com.wire.kalium.logic.sync.ObserveSyncStateUseCase +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged + +internal class TypingIndicatorSyncManager( + private val typingIndicatorIncomingRepository: Lazy, + private val observeSyncStateUseCase: ObserveSyncStateUseCase +) { + /** + * Periodically clears and drop orphaned typing indicators, so we don't keep them forever. + */ + suspend fun execute() { + observeSyncStateUseCase().distinctUntilChanged().collectLatest { + kaliumLogger.d("Starting clear of orphaned typing indicators...") + typingIndicatorIncomingRepository.value.clearExpiredTypingIndicators() + } + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCase.kt index 87aab1af348..2ccd979b3dc 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCase.kt @@ -19,6 +19,7 @@ package com.wire.kalium.logic.feature.conversation import com.wire.kalium.logic.data.conversation.ConversationRepository +import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.fold @@ -63,6 +64,27 @@ internal class UpdateConversationArchivedStatusUseCaseImpl( }, { kaliumLogger.d("Successfully updated remotely and locally convId (${conversationId.toLogString()}) archiving " + "status to archived = ($shouldArchiveConversation)") + + // Now we should make sure the conversation gets muted if it's archived or un-muted if it's unarchived + val updatedMutedStatus = if (shouldArchiveConversation) { + MutedConversationStatus.AllMuted + } else { + MutedConversationStatus.AllAllowed + } + conversationRepository.updateMutedStatusRemotely(conversationId, updatedMutedStatus, archivedStatusTimestamp) + .flatMap { + conversationRepository.updateMutedStatusLocally(conversationId, updatedMutedStatus, archivedStatusTimestamp) + }.fold({ + kaliumLogger.e( + "Something went wrong when updating the muting status of the convId: " + + "(${conversationId.toLogString()}) to (${updatedMutedStatus.status}" + ) + }, { + kaliumLogger.d( + "Successfully updated remotely and locally the muting status of the convId: " + + "(${conversationId.toLogString()}) to (${updatedMutedStatus.status}") + }) + // Even if the muting status update fails, we should still return success as the archived status update was successful ArchiveStatusUpdateResult.Success }) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/featureFlags/KaliumConfigs.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/featureFlags/KaliumConfigs.kt index da4232ac83a..e14a6fd9d44 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/featureFlags/KaliumConfigs.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/featureFlags/KaliumConfigs.kt @@ -18,6 +18,9 @@ package com.wire.kalium.logic.featureFlags +import com.wire.kalium.logic.util.KaliumMockEngine +import com.wire.kalium.network.NetworkStateObserver + data class KaliumConfigs( val forceConstantBitrateCalls: Boolean = false, val fileRestrictionState: BuildFileRestrictionState = BuildFileRestrictionState.NoRestriction, @@ -35,7 +38,9 @@ data class KaliumConfigs( val wipeOnDeviceRemoval: Boolean = false, val wipeOnRootedDevice: Boolean = false, val isWebSocketEnabledByDefault: Boolean = false, - val certPinningConfig: Map> = emptyMap() + val certPinningConfig: Map> = emptyMap(), + val kaliumMockEngine: KaliumMockEngine? = null, + val mockNetworkStateObserver: NetworkStateObserver? = null ) sealed interface BuildFileRestrictionState { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/TypingIndicatorHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/TypingIndicatorHandler.kt index 6242cde9899..158c2b8dd27 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/TypingIndicatorHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/TypingIndicatorHandler.kt @@ -20,7 +20,7 @@ package com.wire.kalium.logic.sync.receiver.handler import com.wire.kalium.logger.KaliumLogger.Companion.ApplicationFlow.EVENT_RECEIVER import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.conversation.Conversation -import com.wire.kalium.logic.data.conversation.TypingIndicatorRepository +import com.wire.kalium.logic.data.conversation.TypingIndicatorIncomingRepository import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.event.EventLoggingStatus import com.wire.kalium.logic.data.event.logEventProcessing @@ -34,7 +34,7 @@ internal interface TypingIndicatorHandler { internal class TypingIndicatorHandlerImpl( private val selfUserId: UserId, - private val typingIndicatorRepository: TypingIndicatorRepository + private val typingIndicatorIncomingRepository: TypingIndicatorIncomingRepository ) : TypingIndicatorHandler { override suspend fun handle(event: Event.Conversation.TypingIndicator): Either { if (event.senderUserId == selfUserId) { @@ -43,12 +43,12 @@ internal class TypingIndicatorHandlerImpl( } when (event.typingIndicatorMode) { - Conversation.TypingIndicatorMode.STARTED -> typingIndicatorRepository.addTypingUserInConversation( + Conversation.TypingIndicatorMode.STARTED -> typingIndicatorIncomingRepository.addTypingUserInConversation( event.conversationId, event.senderUserId ) - Conversation.TypingIndicatorMode.STOPPED -> typingIndicatorRepository.removeTypingUserInConversation( + Conversation.TypingIndicatorMode.STOPPED -> typingIndicatorIncomingRepository.removeTypingUserInConversation( event.conversationId, event.senderUserId ) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/util/KaliumMockEngine.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/util/KaliumMockEngine.kt new file mode 100644 index 00000000000..47cca9860c1 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/util/KaliumMockEngine.kt @@ -0,0 +1,24 @@ +/* + * 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.util + +import io.ktor.client.engine.HttpClientEngine + +data class KaliumMockEngine( + val mockEngine: HttpClientEngine +) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/client/ClientRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/client/ClientRepositoryTest.kt index 07dd9bb8e07..a1eb24cc5aa 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/client/ClientRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/client/ClientRepositoryTest.kt @@ -359,7 +359,7 @@ class ClientRepositoryTest { deviceType = DeviceTypeEntity.Desktop, label = null, model = null, - isVerified = false, + isProteusVerified = false, isValid = true, userId = userId, mlsPublicKeys = null diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/connection/ConnectionRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/connection/ConnectionRepositoryTest.kt index 3411ba5a868..247311a6c02 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/connection/ConnectionRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/connection/ConnectionRepositoryTest.kt @@ -397,25 +397,7 @@ class ConnectionRepositoryTest { service = null ) - val stubUserEntity = UserEntity( - id = QualifiedIDEntity("value", "domain"), - name = null, - handle = null, - email = null, - phone = null, - accentId = 0, - team = null, - connectionStatus = ConnectionEntity.State.NOT_CONNECTED, - previewAssetId = null, - completeAssetId = null, - availabilityStatus = UserAvailabilityStatusEntity.AVAILABLE, - userType = UserTypeEntity.EXTERNAL, - botService = null, - deleted = false, - hasIncompleteMetadata = false, - expiresAt = null, - defederated = false - ) + val stubUserEntity = TestUser.DETAILS_ENTITY val stubConversationID1 = QualifiedIDEntity("conversationId1", "domain") val stubConversationID2 = QualifiedIDEntity("conversationId2", "domain") @@ -528,7 +510,7 @@ class ConnectionRepositoryTest { withUpdateOrInsertOneOnOneMemberWithConnectionStatusSuccess() - given(userDAO).suspendFunction(userDAO::getUserByQualifiedID) + given(userDAO).suspendFunction(userDAO::observeUserDetailsByQualifiedID) .whenInvokedWith(any()) .then { flowOf(stubUserEntity) } @@ -542,7 +524,7 @@ class ConnectionRepositoryTest { fun withSuccessfulGetUserById(id: QualifiedIDEntity): Arrangement { given(userDAO) - .suspendFunction(userDAO::getUserByQualifiedID) + .suspendFunction(userDAO::observeUserDetailsByQualifiedID) .whenInvokedWith(eq(id)) .then { flowOf(stubUserEntity) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorIncomingRepositoryTest.kt similarity index 81% rename from logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorRepositoryTest.kt rename to logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorIncomingRepositoryTest.kt index 7e2171ef149..2ca01b4fd80 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorIncomingRepositoryTest.kt @@ -19,6 +19,7 @@ package com.wire.kalium.logic.data.conversation import co.touchlab.stately.collections.ConcurrentMutableMap import com.wire.kalium.logic.data.properties.UserPropertyRepository +import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.framework.TestConversation import com.wire.kalium.logic.test_util.TestKaliumDispatcher import io.mockative.Mock @@ -30,7 +31,7 @@ import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals -class TypingIndicatorRepositoryTest { +class TypingIndicatorIncomingRepositoryTest { @Test fun givenUsersInOneConversation_whenTheyAreTyping_thenAddItToTheListOfUsersTypingInConversation() = @@ -44,7 +45,7 @@ class TypingIndicatorRepositoryTest { assertEquals( setOf(expectedUserTypingOne, expectedUserTypingTwo), - typingIndicatorRepository.observeUsersTyping(conversationOne).firstOrNull()?.map { it.userId }?.toSet() + typingIndicatorRepository.observeUsersTyping(conversationOne).firstOrNull()?.map { it }?.toSet() ) verify(arrangement.userPropertyRepository) .suspendFunction(arrangement.userPropertyRepository::getTypingIndicatorStatus) @@ -63,7 +64,7 @@ class TypingIndicatorRepositoryTest { assertEquals( expectedUserTyping, - typingIndicatorRepository.observeUsersTyping(conversationOne).firstOrNull()?.map { it.userId }?.toSet() + typingIndicatorRepository.observeUsersTyping(conversationOne).firstOrNull()?.map { it }?.toSet() ) verify(arrangement.userPropertyRepository) .suspendFunction(arrangement.userPropertyRepository::getTypingIndicatorStatus) @@ -79,17 +80,34 @@ class TypingIndicatorRepositoryTest { assertEquals( setOf(expectedUserTypingOne), - typingIndicatorRepository.observeUsersTyping(conversationOne).firstOrNull()?.map { it.userId }?.toSet() + typingIndicatorRepository.observeUsersTyping(conversationOne).firstOrNull()?.map { it }?.toSet() ) assertEquals( setOf(expectedUserTypingTwo), - typingIndicatorRepository.observeUsersTyping(conversationTwo).firstOrNull()?.map { it.userId }?.toSet() + typingIndicatorRepository.observeUsersTyping(conversationTwo).firstOrNull()?.map { it }?.toSet() ) verify(arrangement.userPropertyRepository) .suspendFunction(arrangement.userPropertyRepository::getTypingIndicatorStatus) .wasInvoked() } + @Test + fun givenUsersTypingInAConversation_whenClearExpiredItsCalled_thenShouldNotBePresentAnyInCached() = + runTest(TestKaliumDispatcher.default) { + val expectedUserTyping = setOf() + val (_, typingIndicatorRepository) = Arrangement().withTypingIndicatorStatus().arrange() + + typingIndicatorRepository.addTypingUserInConversation(conversationOne, TestConversation.USER_1) + typingIndicatorRepository.addTypingUserInConversation(conversationOne, TestConversation.USER_2) + + typingIndicatorRepository.clearExpiredTypingIndicators() + + assertEquals( + expectedUserTyping, + typingIndicatorRepository.observeUsersTyping(conversationOne).firstOrNull()?.map { it }?.toSet() + ) + } + private class Arrangement { @Mock val userPropertyRepository: UserPropertyRepository = mock(UserPropertyRepository::class) @@ -101,7 +119,7 @@ class TypingIndicatorRepositoryTest { .thenReturn(enabled) } - fun arrange() = this to TypingIndicatorRepositoryImpl( + fun arrange() = this to TypingIndicatorIncomingRepositoryImpl( userTypingCache = ConcurrentMutableMap(), userPropertyRepository = userPropertyRepository ) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorOutgoingRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorOutgoingRepositoryTest.kt new file mode 100644 index 00000000000..f4ec25590a4 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/TypingIndicatorOutgoingRepositoryTest.kt @@ -0,0 +1,130 @@ +/* + * 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.data.conversation + +import com.wire.kalium.logic.data.properties.UserPropertyRepository +import com.wire.kalium.logic.framework.TestConversation +import com.wire.kalium.logic.test_util.TestKaliumDispatcher +import io.mockative.Mock +import io.mockative.any +import io.mockative.given +import io.mockative.mock +import io.mockative.verify +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class TypingIndicatorOutgoingRepositoryTest { + + @Test + fun givenAStartedTypingEvent_whenUserConfigNotEnabled_thenShouldNotSendAnyIndication() = + runTest(TestKaliumDispatcher.default) { + val (arrangement, typingIndicatorRepository) = Arrangement() + .withTypingIndicatorStatus(false) + .withSenderHandlerCall() + .arrange() + + typingIndicatorRepository.sendTypingIndicatorStatus(conversationOne, Conversation.TypingIndicatorMode.STARTED) + + verify(arrangement.userPropertyRepository) + .suspendFunction(arrangement.userPropertyRepository::getTypingIndicatorStatus) + .wasInvoked() + + verify(arrangement.typingIndicatorSenderHandler) + .function(arrangement.typingIndicatorSenderHandler::sendStartedAndEnqueueStoppingEvent) + .with(any()) + .wasNotInvoked() + } + + @Test + fun givenAStartedTypingEvent_whenUserConfigIsEnabled_thenShouldSendAnyIndication() = + runTest(TestKaliumDispatcher.default) { + val (arrangement, typingIndicatorRepository) = Arrangement() + .withTypingIndicatorStatus(true) + .withSenderHandlerCall() + .arrange() + + typingIndicatorRepository.sendTypingIndicatorStatus(conversationOne, Conversation.TypingIndicatorMode.STARTED) + + verify(arrangement.userPropertyRepository) + .suspendFunction(arrangement.userPropertyRepository::getTypingIndicatorStatus) + .wasInvoked() + + verify(arrangement.typingIndicatorSenderHandler) + .function(arrangement.typingIndicatorSenderHandler::sendStartedAndEnqueueStoppingEvent) + .with(any()) + .wasInvoked() + } + + @Test + fun givenStoppedTypingEvent_whenCalled_thenShouldDelegateCallToHandler() = + runTest(TestKaliumDispatcher.default) { + val (arrangement, typingIndicatorRepository) = Arrangement() + .withTypingIndicatorStatus(true) + .withSenderHandlerStoppedCall() + .arrange() + + typingIndicatorRepository.sendTypingIndicatorStatus(conversationOne, Conversation.TypingIndicatorMode.STOPPED) + + verify(arrangement.userPropertyRepository) + .suspendFunction(arrangement.userPropertyRepository::getTypingIndicatorStatus) + .wasInvoked() + + verify(arrangement.typingIndicatorSenderHandler) + .function(arrangement.typingIndicatorSenderHandler::sendStoppingEvent) + .with(any()) + .wasInvoked() + } + + private class Arrangement { + @Mock + val userPropertyRepository: UserPropertyRepository = mock(UserPropertyRepository::class) + + @Mock + val typingIndicatorSenderHandler: TypingIndicatorSenderHandler = mock(TypingIndicatorSenderHandler::class) + + fun withTypingIndicatorStatus(enabled: Boolean = true) = apply { + given(userPropertyRepository) + .suspendFunction(userPropertyRepository::getTypingIndicatorStatus) + .whenInvoked() + .thenReturn(enabled) + } + + fun withSenderHandlerStoppedCall() = apply { + given(typingIndicatorSenderHandler) + .function(typingIndicatorSenderHandler::sendStoppingEvent) + .whenInvokedWith(any()) + .thenReturn(Unit) + } + + fun withSenderHandlerCall() = apply { + given(typingIndicatorSenderHandler) + .function(typingIndicatorSenderHandler::sendStartedAndEnqueueStoppingEvent) + .whenInvokedWith(any()) + .thenReturn(Unit) + } + + fun arrange() = this to TypingIndicatorOutgoingRepositoryImpl( + userPropertyRepository = userPropertyRepository, + typingIndicatorSenderHandler = typingIndicatorSenderHandler + ) + } + + private companion object { + val conversationOne = TestConversation.ID + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/publicuser/SearchUserRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/publicuser/SearchUserRepositoryTest.kt index e4b4acd4666..be726620552 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/publicuser/SearchUserRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/publicuser/SearchUserRepositoryTest.kt @@ -21,16 +21,11 @@ package com.wire.kalium.logic.data.publicuser import com.wire.kalium.logic.NetworkFailure import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.IdMapper -import com.wire.kalium.logic.data.id.QualifiedID -import com.wire.kalium.logic.data.id.TeamId import com.wire.kalium.logic.data.publicuser.model.UserSearchResult -import com.wire.kalium.logic.data.user.ConnectionState -import com.wire.kalium.logic.data.user.OtherUser -import com.wire.kalium.logic.data.user.SelfUser -import com.wire.kalium.logic.data.user.UserAvailabilityStatus import com.wire.kalium.logic.data.user.UserMapper import com.wire.kalium.logic.data.user.type.DomainUserTypeMapper import com.wire.kalium.logic.data.user.type.UserType +import com.wire.kalium.logic.framework.TestUser import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.test_util.TestNetworkResponseError import com.wire.kalium.network.api.base.authenticated.search.ContactDTO @@ -41,13 +36,8 @@ import com.wire.kalium.network.api.base.authenticated.userDetails.UserDetailsApi import com.wire.kalium.network.api.base.model.LegalHoldStatusResponse import com.wire.kalium.network.api.base.model.UserProfileDTO import com.wire.kalium.network.utils.NetworkResponse -import com.wire.kalium.persistence.dao.ConnectionEntity import com.wire.kalium.persistence.dao.MetadataDAO -import com.wire.kalium.persistence.dao.QualifiedIDEntity -import com.wire.kalium.persistence.dao.UserAvailabilityStatusEntity import com.wire.kalium.persistence.dao.UserDAO -import com.wire.kalium.persistence.dao.UserEntity -import com.wire.kalium.persistence.dao.UserTypeEntity import io.mockative.Mock import io.mockative.Times import io.mockative.any @@ -260,21 +250,21 @@ class SearchUserRepositoryTest { given(publicUserMapper) .function(publicUserMapper::fromUserProfileDtoToOtherUser) .whenInvokedWith(any(), any()) - .then { _, _ -> PUBLIC_USER } + .then { _, _ -> TestUser.OTHER } given(metadataDAO) .suspendFunction(metadataDAO::valueByKeyFlow) .whenInvokedWith(any()) .then { flowOf(JSON_QUALIFIED_ID) } - given(userDAO).suspendFunction(userDAO::getUserByQualifiedID) + given(userDAO).suspendFunction(userDAO::observeUserDetailsByQualifiedID) .whenInvokedWith(any()) - .then { flowOf(USER_ENTITY) } + .then { flowOf(TestUser.DETAILS_ENTITY) } given(userMapper) - .function(userMapper::fromUserEntityToSelfUser) + .function(userMapper::fromUserDetailsEntityToSelfUser) .whenInvokedWith(any()) - .then { SELF_USER } + .then { TestUser.SELF.copy(teamId = null) } given(domainUserTypeMapper) .invocation { @@ -282,7 +272,7 @@ class SearchUserRepositoryTest { "domain", null, "team", - "someId", + "domain", false ) }.then { UserType.FEDERATED } @@ -311,21 +301,21 @@ class SearchUserRepositoryTest { given(publicUserMapper) .function(publicUserMapper::fromUserProfileDtoToOtherUser) .whenInvokedWith(any(), any()) - .then { _, _ -> PUBLIC_USER } + .then { _, _ -> TestUser.OTHER } given(metadataDAO) .suspendFunction(metadataDAO::valueByKeyFlow) .whenInvokedWith(any()) .then { flowOf(JSON_QUALIFIED_ID) } - given(userDAO).suspendFunction(userDAO::getUserByQualifiedID) + given(userDAO).suspendFunction(userDAO::observeUserDetailsByQualifiedID) .whenInvokedWith(any()) - .then { flowOf(USER_ENTITY) } + .then { flowOf(TestUser.DETAILS_ENTITY) } given(userMapper) - .function(userMapper::fromUserEntityToSelfUser) + .function(userMapper::fromUserDetailsEntityToSelfUser) .whenInvokedWith(any()) - .then { SELF_USER } + .then { TestUser.SELF.copy(teamId = null) } given(domainUserTypeMapper) .invocation { @@ -333,13 +323,13 @@ class SearchUserRepositoryTest { "domain", null, "team", - "someId", + "domain", false ) }.then { UserType.FEDERATED } val expectedResult = UserSearchResult( - result = listOf(PUBLIC_USER) + result = listOf(TestUser.OTHER) ) // when val actual = searchUserRepository.searchUserDirectory(TEST_QUERY, TEST_DOMAIN) @@ -367,14 +357,14 @@ class SearchUserRepositoryTest { .whenInvokedWith(any()) .then { flowOf(JSON_QUALIFIED_ID) } - given(userDAO).suspendFunction(userDAO::getUserByQualifiedID) + given(userDAO).suspendFunction(userDAO::observeUserDetailsByQualifiedID) .whenInvokedWith(any()) - .then { flowOf(USER_ENTITY) } + .then { flowOf(TestUser.DETAILS_ENTITY) } given(userMapper) - .function(userMapper::fromUserEntityToSelfUser) + .function(userMapper::fromUserDetailsEntityToSelfUser) .whenInvokedWith(any()) - .then { SELF_USER } + .then { TestUser.SELF } val expectedResult = UserSearchResult( result = emptyList() @@ -391,12 +381,12 @@ class SearchUserRepositoryTest { runTest { // given given(userDAO) - .suspendFunction(userDAO::getUsersNotInConversationByNameOrHandleOrEmail) + .suspendFunction(userDAO::getUsersDetailsNotInConversationByNameOrHandleOrEmail) .whenInvokedWith(anything(), anything()) .then { _, _ -> flowOf(listOf()) } given(userDAO) - .suspendFunction(userDAO::getUserByNameOrHandleOrEmailAndConnectionStates) + .suspendFunction(userDAO::getUserDetailsByNameOrHandleOrEmailAndConnectionStates) .whenInvokedWith(anything(), anything()) .then { _, _ -> flowOf(listOf()) } @@ -412,12 +402,12 @@ class SearchUserRepositoryTest { ) verify(userDAO) - .suspendFunction(userDAO::getUserByNameOrHandleOrEmailAndConnectionStates) + .suspendFunction(userDAO::getUserDetailsByNameOrHandleOrEmailAndConnectionStates) .with(anything(), anything()) .wasNotInvoked() verify(userDAO) - .suspendFunction(userDAO::getUsersNotInConversationByNameOrHandleOrEmail) + .suspendFunction(userDAO::getUsersDetailsNotInConversationByNameOrHandleOrEmail) .with(anything(), anything()) .wasInvoked(Times(1)) } @@ -426,12 +416,12 @@ class SearchUserRepositoryTest { fun givenASearchWithConversationExcludedOption_WhenSearchingUsersByHandle_ThenSearchForUsersNotInTheConversation() = runTest { // given given(userDAO) - .suspendFunction(userDAO::getUserByHandleAndConnectionStates) + .suspendFunction(userDAO::getUserDetailsByHandleAndConnectionStates) .whenInvokedWith(anything(), anything()) .then { _, _ -> flowOf(listOf()) } given(userDAO) - .suspendFunction(userDAO::getUsersNotInConversationByHandle) + .suspendFunction(userDAO::getUsersDetailsNotInConversationByHandle) .whenInvokedWith(anything(), anything()) .then { _, _ -> flowOf(listOf()) } @@ -448,12 +438,12 @@ class SearchUserRepositoryTest { // then verify(userDAO) - .suspendFunction(userDAO::getUserByHandleAndConnectionStates) + .suspendFunction(userDAO::getUserDetailsByHandleAndConnectionStates) .with(anything(), anything()) .wasNotInvoked() verify(userDAO) - .suspendFunction(userDAO::getUsersNotInConversationByHandle) + .suspendFunction(userDAO::getUsersDetailsNotInConversationByHandle) .with(anything(), anything()) .wasInvoked(exactly = once) } @@ -501,25 +491,6 @@ class SearchUserRepositoryTest { } } - val PUBLIC_USER = OtherUser( - id = com.wire.kalium.logic.data.user.UserId(value = "value", domain = "domain"), - name = "name", - handle = "handle", - email = "email", - phone = "phone", - accentId = 1, - teamId = TeamId("team"), - previewPicture = null, - completePicture = null, - availabilityStatus = UserAvailabilityStatus.NONE, - userType = UserType.FEDERATED, - connectionStatus = ConnectionState.NOT_CONNECTED, - botService = null, - deleted = false, - expiresAt = null, - defederated = false - ) - val CONTACT_SEARCH_RESPONSE = UserSearchResponse( documents = CONTACTS, found = CONTACTS.size, @@ -558,40 +529,6 @@ class SearchUserRepositoryTest { const val JSON_QUALIFIED_ID = """{"value":"test" , "domain":"test" }""" - val USER_ENTITY = UserEntity( - id = QualifiedIDEntity("value", "domain"), - name = null, - handle = null, - email = null, - phone = null, - accentId = 0, - team = null, - connectionStatus = ConnectionEntity.State.NOT_CONNECTED, - previewAssetId = null, - completeAssetId = null, - availabilityStatus = UserAvailabilityStatusEntity.AVAILABLE, - userType = UserTypeEntity.EXTERNAL, - botService = null, - deleted = false, - expiresAt = null, - defederated = false - ) - - val SELF_USER = SelfUser( - id = QualifiedID("someValue", "someId"), - name = null, - handle = null, - email = null, - phone = null, - accentId = 0, - teamId = null, - connectionStatus = ConnectionState.NOT_CONNECTED, - previewPicture = null, - completePicture = null, - availabilityStatus = UserAvailabilityStatus.AVAILABLE, - expiresAt = null - ) - } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/publicuser/UserSearchApiWrapperTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/publicuser/UserSearchApiWrapperTest.kt index 7cf3a88aff8..27c43e06f3c 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/publicuser/UserSearchApiWrapperTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/publicuser/UserSearchApiWrapperTest.kt @@ -24,6 +24,7 @@ import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.SelfUser import com.wire.kalium.logic.data.user.UserAvailabilityStatus import com.wire.kalium.logic.data.user.UserMapper +import com.wire.kalium.logic.framework.TestUser import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.util.arrangement.dao.MemberDAOArrangement import com.wire.kalium.logic.util.arrangement.dao.MemberDAOArrangementImpl @@ -348,12 +349,12 @@ class UserSearchApiWrapperTest { .then { flowOf(JSON_QUALIFIED_ID) } given(userDAO) - .suspendFunction(userDAO::getUserByQualifiedID) + .suspendFunction(userDAO::observeUserDetailsByQualifiedID) .whenInvokedWith(any()) - .then { flowOf(USER_ENTITY) } + .then { flowOf(TestUser.DETAILS_ENTITY) } given(userMapper) - .function(userMapper::fromUserEntityToSelfUser) + .function(userMapper::fromUserDetailsEntityToSelfUser) .whenInvokedWith(any()) .then { selfUser } @@ -381,12 +382,12 @@ class UserSearchApiWrapperTest { .then { flowOf(JSON_QUALIFIED_ID) } given(userDAO) - .suspendFunction(userDAO::getUserByQualifiedID) + .suspendFunction(userDAO::observeUserDetailsByQualifiedID) .whenInvokedWith(any()) - .then { flowOf(USER_ENTITY) } + .then { flowOf(TestUser.DETAILS_ENTITY) } given(userMapper) - .function(userMapper::fromUserEntityToSelfUser) + .function(userMapper::fromUserDetailsEntityToSelfUser) .whenInvokedWith(any()) .then { selfUser } 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 1f08fd8d994..01955dd8977 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 @@ -47,6 +47,7 @@ import com.wire.kalium.network.utils.NetworkResponse import com.wire.kalium.persistence.dao.MetadataDAO import com.wire.kalium.persistence.dao.QualifiedIDEntity import com.wire.kalium.persistence.dao.UserDAO +import com.wire.kalium.persistence.dao.UserDetailsEntity import com.wire.kalium.persistence.dao.UserEntity import com.wire.kalium.persistence.dao.UserIDEntity import com.wire.kalium.persistence.dao.UserTypeEntity @@ -81,15 +82,15 @@ class UserRepositoryTest { UserId(value = "id2", domain = "domain2") ) val knownUserEntities = listOf( - TestUser.ENTITY.copy(id = UserIDEntity(value = "id1", domain = "domain1")), - TestUser.ENTITY.copy(id = UserIDEntity(value = "id2", domain = "domain2")) + TestUser.DETAILS_ENTITY.copy(id = UserIDEntity(value = "id1", domain = "domain1")), + TestUser.DETAILS_ENTITY.copy(id = UserIDEntity(value = "id2", domain = "domain2")) ) val (arrangement, userRepository) = Arrangement() .withSuccessfulGetUsersByQualifiedIdList(knownUserEntities) .arrange() given(arrangement.userDAO) - .suspendFunction(arrangement.userDAO::getUsersByQualifiedIDList) + .suspendFunction(arrangement.userDAO::getUsersDetailsByQualifiedIDList) .whenInvokedWith(any()) .thenReturn(knownUserEntities) @@ -105,7 +106,7 @@ class UserRepositoryTest { 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 knownUserEntities = listOf(TestUser.DETAILS_ENTITY.copy(id = UserIDEntity(value = "id1", domain = "domain1"))) val (arrangement, userRepository) = Arrangement() .withGetSelfUserId() .withSuccessfulGetUsersByQualifiedIdList(knownUserEntities) @@ -138,7 +139,7 @@ class UserRepositoryTest { .with(any()) .wasInvoked(exactly = once) verify(arrangement.userDAO) - .suspendFunction(arrangement.userDAO::getUserByQualifiedID) + .suspendFunction(arrangement.userDAO::observeUserDetailsByQualifiedID) .with(any()) .wasInvoked(exactly = once) verify(arrangement.userDAO) @@ -165,7 +166,7 @@ class UserRepositoryTest { .with(any()) .wasInvoked(exactly = once) verify(arrangement.userDAO) - .suspendFunction(arrangement.userDAO::getUserByQualifiedID) + .suspendFunction(arrangement.userDAO::observeUserDetailsByQualifiedID) .with(any()) .wasInvoked(exactly = once) verify(arrangement.userDAO) @@ -282,7 +283,7 @@ class UserRepositoryTest { @Test fun givenAKnownFederatedUser_whenGettingFromDbAndCacheExpiredOrNotPresent_thenShouldRefreshItsDataFromAPI() = runTest { val (arrangement, userRepository) = Arrangement() - .withUserDaoReturning(TestUser.ENTITY.copy(userType = UserTypeEntity.FEDERATED)) + .withUserDaoReturning(TestUser.DETAILS_ENTITY.copy(userType = UserTypeEntity.FEDERATED)) .withSuccessfulGetUsersInfo() .arrange() @@ -307,7 +308,7 @@ class UserRepositoryTest { @Test fun givenAKnownNOTFederatedUser_whenGettingFromDb_thenShouldNotRefreshItsDataFromAPI() = runTest { val (arrangement, userRepository) = Arrangement() - .withUserDaoReturning(TestUser.ENTITY.copy(userType = UserTypeEntity.STANDARD)) + .withUserDaoReturning(TestUser.DETAILS_ENTITY.copy(userType = UserTypeEntity.STANDARD)) .withSuccessfulGetUsersInfo() .arrange() @@ -332,7 +333,7 @@ class UserRepositoryTest { @Test fun givenAKnownFederatedUser_whenGettingFromDbAndCacheValid_thenShouldNOTRefreshItsDataFromAPI() = runTest { val (arrangement, userRepository) = Arrangement() - .withUserDaoReturning(TestUser.ENTITY.copy(userType = UserTypeEntity.FEDERATED)) + .withUserDaoReturning(TestUser.DETAILS_ENTITY.copy(userType = UserTypeEntity.FEDERATED)) .withSuccessfulGetUsersInfo() .arrange() @@ -374,7 +375,7 @@ class UserRepositoryTest { fun givenThereAreUsersWithoutMetadata_whenSyncingUsers_thenShouldUpdateThem() = runTest { // given val (arrangement, userRepository) = Arrangement() - .withDaoReturningNoMetadataUsers(listOf(TestUser.ENTITY.copy(name = null))) + .withDaoReturningNoMetadataUsers(listOf(TestUser.DETAILS_ENTITY.copy(name = null))) .withSuccessfulGetMultipleUsersApiRequest(ListUsersDTO(emptyList(), listOf(TestUser.USER_PROFILE_DTO))) .arrange() @@ -443,8 +444,8 @@ class UserRepositoryTest { .withGetSelfUserId() .withDaoObservingByConnectionStatusReturning( listOf( - TestUser.ENTITY.copy(id = QualifiedIDEntity("id-valid", "domain2"), hasIncompleteMetadata = false), - TestUser.ENTITY.copy(id = QualifiedIDEntity("id2", "domain2"), hasIncompleteMetadata = true) + TestUser.DETAILS_ENTITY.copy(id = QualifiedIDEntity("id-valid", "domain2"), hasIncompleteMetadata = false), + TestUser.DETAILS_ENTITY.copy(id = QualifiedIDEntity("id2", "domain2"), hasIncompleteMetadata = true) ) ) .arrange() @@ -461,7 +462,7 @@ class UserRepositoryTest { } verify(arrangement.userDAO) - .suspendFunction(arrangement.userDAO::observeAllUsersByConnectionStatus) + .suspendFunction(arrangement.userDAO::observeAllUsersDetailsByConnectionStatus) .with(any()) .wasInvoked(once) } @@ -473,8 +474,8 @@ class UserRepositoryTest { .withGetSelfUserId() .withDaoObservingNotInConversationReturning( listOf( - TestUser.ENTITY.copy(id = QualifiedIDEntity("id-valid", "domain2"), hasIncompleteMetadata = false), - TestUser.ENTITY.copy(id = QualifiedIDEntity("id2", "domain2"), hasIncompleteMetadata = true) + TestUser.DETAILS_ENTITY.copy(id = QualifiedIDEntity("id-valid", "domain2"), hasIncompleteMetadata = false), + TestUser.DETAILS_ENTITY.copy(id = QualifiedIDEntity("id2", "domain2"), hasIncompleteMetadata = true) ) ) .arrange() @@ -491,7 +492,7 @@ class UserRepositoryTest { } verify(arrangement.userDAO) - .function(arrangement.userDAO::observeUsersNotInConversation) + .function(arrangement.userDAO::observeUsersDetailsNotInConversation) .with(any()) .wasInvoked(once) } @@ -521,8 +522,8 @@ class UserRepositoryTest { UserId(value = "id2", domain = "domain2") ) val knownUserEntities = listOf( - TestUser.ENTITY.copy(id = UserIDEntity(value = "id1", domain = "domain1")), - TestUser.ENTITY.copy(id = UserIDEntity(value = "id2", domain = "domain2")) + TestUser.DETAILS_ENTITY.copy(id = UserIDEntity(value = "id1", domain = "domain1")), + TestUser.DETAILS_ENTITY.copy(id = UserIDEntity(value = "id2", domain = "domain2")) ) val (arrangement, userRepository) = Arrangement() .withSuccessfulGetUsersByQualifiedIdList(knownUserEntities) @@ -533,7 +534,7 @@ class UserRepositoryTest { // Then verify(arrangement.userDAO) - .suspendFunction(arrangement.userDAO::getUsersByQualifiedIDList) + .suspendFunction(arrangement.userDAO::getUsersDetailsByQualifiedIDList) .with(any()) .wasInvoked(once) } @@ -581,9 +582,9 @@ class UserRepositoryTest { init { withSelfUserIdFlowMetadataReturning(flowOf(TestUser.JSON_QUALIFIED_ID)) - given(userDAO).suspendFunction(userDAO::getUserByQualifiedID) + given(userDAO).suspendFunction(userDAO::observeUserDetailsByQualifiedID) .whenInvokedWith(any()) - .then { flowOf(TestUser.ENTITY) } + .then { flowOf(TestUser.DETAILS_ENTITY) } given(selfTeamIdProvider) .suspendFunction(selfTeamIdProvider::invoke) @@ -598,16 +599,16 @@ class UserRepositoryTest { .thenReturn(selfUserIdStringFlow) } - fun withDaoObservingByConnectionStatusReturning(userEntities: List) = apply { + fun withDaoObservingByConnectionStatusReturning(userEntities: List) = apply { given(userDAO) - .suspendFunction(userDAO::observeAllUsersByConnectionStatus) + .suspendFunction(userDAO::observeAllUsersDetailsByConnectionStatus) .whenInvokedWith(any()) .thenReturn(flowOf(userEntities)) } - fun withDaoObservingNotInConversationReturning(userEntities: List) = apply { + fun withDaoObservingNotInConversationReturning(userEntities: List) = apply { given(userDAO) - .function(userDAO::observeUsersNotInConversation) + .function(userDAO::observeUsersDetailsNotInConversation) .whenInvokedWith(any()) .thenReturn(flowOf(userEntities)) } @@ -619,9 +620,9 @@ class UserRepositoryTest { .thenReturn(NetworkResponse.Success(TestUser.USER_PROFILE_DTO, mapOf(), 200)) } - fun withSuccessfulGetUsersByQualifiedIdList(knownUserEntities: List) = apply { + fun withSuccessfulGetUsersByQualifiedIdList(knownUserEntities: List) = apply { given(userDAO) - .suspendFunction(userDAO::getUsersByQualifiedIDList) + .suspendFunction(userDAO::getUsersDetailsByQualifiedIDList) .whenInvokedWith(any()) .thenReturn(knownUserEntities) } @@ -633,14 +634,14 @@ class UserRepositoryTest { .thenReturn(com.wire.kalium.logic.data.id.QualifiedID("alice", "wonderland")) } - fun withUserDaoReturning(userEntity: UserEntity? = TestUser.ENTITY) = apply { - given(userDAO).suspendFunction(userDAO::getUserByQualifiedID) + fun withUserDaoReturning(userEntity: UserDetailsEntity? = TestUser.DETAILS_ENTITY) = apply { + given(userDAO).suspendFunction(userDAO::observeUserDetailsByQualifiedID) .whenInvokedWith(any()) .then { flowOf(userEntity) } } - fun withDaoReturningNoMetadataUsers(userEntity: List = emptyList()) = apply { - given(userDAO).suspendFunction(userDAO::getUsersWithoutMetadata) + fun withDaoReturningNoMetadataUsers(userEntity: List = emptyList()) = apply { + given(userDAO).suspendFunction(userDAO::getUsersDetailsWithoutMetadata) .whenInvoked() .then { userEntity } } @@ -694,9 +695,9 @@ class UserRepositoryTest { .then { Either.Right(Unit) } } - fun withSuccessfulGetAllUsers(userEntities: List) = apply { + fun withSuccessfulGetAllUsers(userEntities: List) = apply { given(userDAO) - .suspendFunction(userDAO::getAllUsers) + .suspendFunction(userDAO::getAllUsersDetails) .whenInvoked() .then { flowOf(userEntities) } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/EndCallOnConversationChangeUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/EndCallOnConversationChangeUseCaseTest.kt index 56370caa99c..7b0c5ad8183 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/EndCallOnConversationChangeUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/EndCallOnConversationChangeUseCaseTest.kt @@ -170,7 +170,8 @@ class EndCallOnConversationChangeUseCaseTest { userType = UserType.INTERNAL, botService = null, deleted = true, - defederated = false + defederated = false, + isProteusVerified = false ) private val groupConversationDetail = ConversationDetails.Group( diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/UpdateClientVerificationStatusUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/UpdateClientVerificationStatusUseCaseTest.kt index bfeee87d834..d8a953d5d33 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/UpdateClientVerificationStatusUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/UpdateClientVerificationStatusUseCaseTest.kt @@ -18,25 +18,25 @@ package com.wire.kalium.logic.feature.client import com.wire.kalium.logic.StorageFailure -import com.wire.kalium.logic.data.client.ClientRepository +import com.wire.kalium.logic.data.client.DeviceType +import com.wire.kalium.logic.data.client.OtherUserClient import com.wire.kalium.logic.data.conversation.ClientId import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.functional.Either -import io.mockative.Mock +import com.wire.kalium.logic.util.arrangement.repository.ClientRepositoryArrangement +import com.wire.kalium.logic.util.arrangement.repository.ClientRepositoryArrangementImpl +import com.wire.kalium.logic.util.arrangement.repository.UserRepositoryArrangement +import com.wire.kalium.logic.util.arrangement.repository.UserRepositoryArrangementImpl import io.mockative.any import io.mockative.eq -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 okio.FileNotFoundException import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs -@OptIn(ExperimentalCoroutinesApi::class) class UpdateClientVerificationStatusUseCaseTest { @Test @@ -44,14 +44,14 @@ class UpdateClientVerificationStatusUseCaseTest { val userId = UserId("userId", "domain") val clientID = ClientId("clientId") - val (arrangement, useCase) = Arrangement() - .withUpdateClientVerification(Either.Right(Unit)) - .arrange() + val (arrangement, useCase) = arrange { + withUpdateClientProteusVerificationStatus(Either.Right(Unit)) + } useCase(userId, clientID, true) verify(arrangement.clientRepository) - .suspendFunction(arrangement.clientRepository::updateClientVerificationStatus) + .suspendFunction(arrangement.clientRepository::updateClientProteusVerificationStatus) .with(eq(userId), eq(clientID), eq(true)) .wasInvoked(exactly = once) } @@ -61,16 +61,16 @@ class UpdateClientVerificationStatusUseCaseTest { val userId = UserId("userId", "domain") val clientID = ClientId("clientId") - val (arrangement, useCase) = Arrangement() - .withUpdateClientVerification(Either.Right(Unit)) - .arrange() + val (arrangement, useCase) = arrange { + withUpdateClientProteusVerificationStatus(Either.Right(Unit)) + } useCase(userId, clientID, true).also { assertIs(it) } verify(arrangement.clientRepository) - .suspendFunction(arrangement.clientRepository::updateClientVerificationStatus) + .suspendFunction(arrangement.clientRepository::updateClientProteusVerificationStatus) .with(eq(userId), eq(clientID), eq(true)) .wasInvoked(exactly = once) } @@ -82,9 +82,9 @@ class UpdateClientVerificationStatusUseCaseTest { val expectedError = StorageFailure.Generic(FileNotFoundException()) - val (arrangement, useCase) = Arrangement() - .withUpdateClientVerification(Either.Left(expectedError)) - .arrange() + val (arrangement, useCase) = arrange { + withUpdateClientProteusVerificationStatus(Either.Left(expectedError)) + } useCase(userId, clientID, true).also { assertIs(it) @@ -92,24 +92,30 @@ class UpdateClientVerificationStatusUseCaseTest { } verify(arrangement.clientRepository) - .suspendFunction(arrangement.clientRepository::updateClientVerificationStatus) + .suspendFunction(arrangement.clientRepository::updateClientProteusVerificationStatus) .with(eq(userId), eq(clientID), eq(true)) .wasInvoked(exactly = once) } - private class Arrangement { - @Mock - val clientRepository = mock(ClientRepository::class) + private fun arrange(block: Arrangement.() -> Unit) = Arrangement(block).arrange() - private val useCase = UpdateClientVerificationStatusUseCase(clientRepository) + private class Arrangement( + private val block: Arrangement.() -> Unit + ) : ClientRepositoryArrangement by ClientRepositoryArrangementImpl() { - fun withUpdateClientVerification(result: Either) = apply { - given(clientRepository) - .suspendFunction(clientRepository::updateClientVerificationStatus) - .whenInvokedWith(any(), any(), any()) - .thenReturn(result) + fun arrange() = block().run { + this@Arrangement to UpdateClientVerificationStatusUseCase( + clientRepository = clientRepository + ) } + } - fun arrange() = this to useCase + companion object { + private val OTHER_USER_CLIENT = OtherUserClient( + deviceType = DeviceType.Phone, + id = "some_id", + isValid = true, + isProteusVerified = true + ) } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ClearUsersTypingEventsUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ClearUsersTypingEventsUseCaseTest.kt new file mode 100644 index 00000000000..835001dc473 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/ClearUsersTypingEventsUseCaseTest.kt @@ -0,0 +1,50 @@ +/* + * 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.conversation + +import com.wire.kalium.logic.data.conversation.TypingIndicatorIncomingRepository +import io.mockative.Mock +import io.mockative.mock +import io.mockative.once +import io.mockative.verify +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class ClearUsersTypingEventsUseCaseTest { + + @Test + fun givenClearingTypingIndicatorSucceeds_whenInvoking_thenShouldDelegateToRepositoryCall() = runTest { + val (arrangement, useCase) = Arrangement().arrange() + + useCase() + + verify(arrangement.typingIndicatorIncomingRepository) + .suspendFunction(arrangement.typingIndicatorIncomingRepository::clearExpiredTypingIndicators) + .wasInvoked(once) + } + + private class Arrangement { + + @Mock + val typingIndicatorIncomingRepository: TypingIndicatorIncomingRepository = mock(TypingIndicatorIncomingRepository::class) + + fun arrange() = this to ClearUsersTypingEventsUseCaseImpl( + typingIndicatorIncomingRepository = typingIndicatorIncomingRepository + ) + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetAllContactsNotInTheConversationUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetAllContactsNotInTheConversationUseCaseTest.kt index cdd0bae9cf1..af3dae1fdc2 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetAllContactsNotInTheConversationUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GetAllContactsNotInTheConversationUseCaseTest.kt @@ -92,7 +92,8 @@ class GetAllContactsNotInTheConversationUseCaseTest { userType = UserType.INTERNAL, botService = null, deleted = false, - defederated = false + defederated = false, + isProteusVerified = false ), OtherUser( id = QualifiedID("someAllContactsValue1", "someAllContactsDomain1"), @@ -109,7 +110,8 @@ class GetAllContactsNotInTheConversationUseCaseTest { userType = UserType.INTERNAL, botService = null, deleted = false, - defederated = false + defederated = false, + isProteusVerified = false ) ) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/MembersToMentionUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/MembersToMentionUseCaseTest.kt index 88ff0d9c8c8..b1d1c6e0ea6 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/MembersToMentionUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/MembersToMentionUseCaseTest.kt @@ -139,7 +139,8 @@ class MembersToMentionUseCaseTest { userType = UserType.INTERNAL, botService = null, deleted = false, - defederated = false + defederated = false, + isProteusVerified = false ) val members = listOf( diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/SendTypingEventUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/SendTypingEventUseCaseTest.kt new file mode 100644 index 00000000000..9ad7c63342b --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/SendTypingEventUseCaseTest.kt @@ -0,0 +1,87 @@ +/* + * 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.conversation + +import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.data.conversation.Conversation +import com.wire.kalium.logic.data.conversation.TypingIndicatorOutgoingRepository +import com.wire.kalium.logic.framework.TestConversation +import com.wire.kalium.logic.functional.Either +import io.mockative.Mock +import io.mockative.any +import io.mockative.eq +import io.mockative.given +import io.mockative.mock +import io.mockative.verify +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class SendTypingEventUseCaseTest { + + @Test + fun givenATypingEvent_whenCallingSendSucceed_thenReturnSuccess() = runTest { + val (arrangement, useCase) = Arrangement() + .withTypingIndicatorStatusAndResult(Conversation.TypingIndicatorMode.STOPPED) + .arrange() + + useCase(TestConversation.ID, Conversation.TypingIndicatorMode.STOPPED) + + verify(arrangement.typingIndicatorRepository) + .suspendFunction(arrangement.typingIndicatorRepository::sendTypingIndicatorStatus) + .with(eq(TestConversation.ID), eq(Conversation.TypingIndicatorMode.STOPPED)) + .wasInvoked() + } + + @Test + fun givenATypingEvent_whenCallingSendFails_thenReturnIgnoringFailure() = runTest { + val (arrangement, useCase) = Arrangement() + .withTypingIndicatorStatusAndResult( + Conversation.TypingIndicatorMode.STARTED, + Either.Left(CoreFailure.Unknown(RuntimeException("Some error"))) + ) + .arrange() + + val result = useCase(TestConversation.ID, Conversation.TypingIndicatorMode.STARTED) + + verify(arrangement.typingIndicatorRepository) + .suspendFunction(arrangement.typingIndicatorRepository::sendTypingIndicatorStatus) + .with(eq(TestConversation.ID), eq(Conversation.TypingIndicatorMode.STARTED)) + .wasInvoked() + assertEquals(Unit, result) + } + + private class Arrangement { + @Mock + val typingIndicatorRepository: TypingIndicatorOutgoingRepository = mock(TypingIndicatorOutgoingRepository::class) + + fun withTypingIndicatorStatusAndResult( + typingMode: Conversation.TypingIndicatorMode, + result: Either = Either.Right(Unit) + ) = apply { + given(typingIndicatorRepository) + .suspendFunction(typingIndicatorRepository::sendTypingIndicatorStatus) + .whenInvokedWith(any(), eq(typingMode)) + .thenReturn(result) + } + + fun arrange() = this to SendTypingEventUseCaseImpl( + typingIndicatorRepository + ) + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCaseTest.kt index cffbb95f80f..c910116e34b 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationArchivedStatusUseCaseTest.kt @@ -31,20 +31,11 @@ import io.mockative.mock import io.mockative.once import io.mockative.verify import kotlinx.coroutines.test.runTest -import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class UpdateConversationArchivedStatusUseCaseTest { - @Mock - private val conversationRepository: ConversationRepository = mock(ConversationRepository::class) - - private lateinit var updateConversationArchivedStatus: UpdateConversationArchivedStatusUseCase - - @BeforeTest - fun setup() { - updateConversationArchivedStatus = UpdateConversationArchivedStatusUseCaseImpl(conversationRepository) - } @Test fun givenAConversationId_whenInvokingAnArchivedStatusChange_thenShouldDelegateTheCallAndReturnASuccessResult() = runTest { @@ -52,28 +43,25 @@ class UpdateConversationArchivedStatusUseCaseTest { val isConversationArchived = true val archivedStatusTimestamp = 123456789L - given(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusRemotely) - .whenInvokedWith(any(), any(), any()) - .thenReturn(Either.Right(Unit)) - - given(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusLocally) - .whenInvokedWith(any(), any(), any()) - .thenReturn(Either.Right(Unit)) + val (arrangement, updateConversationArchivedStatus) = Arrangement() + .withSuccessfulMutingUpdates() + .withUpdateArchivedStatusFullSuccess() + .arrange() val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) assertEquals(ArchiveStatusUpdateResult.Success::class, result::class) - verify(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusRemotely) - .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) - .wasInvoked(exactly = once) - - verify(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusLocally) - .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) - .wasInvoked(exactly = once) + with(arrangement) { + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusRemotely) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusLocally) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + } } @Test @@ -82,24 +70,25 @@ class UpdateConversationArchivedStatusUseCaseTest { val isConversationArchived = true val archivedStatusTimestamp = 123456789L - given(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusRemotely) - .whenInvokedWith(any(), any(), any()) - .thenReturn(Either.Left(NetworkFailure.ServerMiscommunication(RuntimeException("some error")))) + val (arrangement, updateConversationArchivedStatus) = Arrangement() + .withSuccessfulMutingUpdates() + .withRemoteUpdateArchivedStatusFailure() + .arrange() val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) assertEquals(ArchiveStatusUpdateResult.Failure::class, result::class) - verify(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusRemotely) - .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) - .wasInvoked(exactly = once) - - verify(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusLocally) - .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) - .wasNotInvoked() - + with(arrangement) { + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusRemotely) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusLocally) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasNotInvoked() + } } @Test @@ -108,26 +97,148 @@ class UpdateConversationArchivedStatusUseCaseTest { val isConversationArchived = true val archivedStatusTimestamp = 123456789L - given(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusRemotely) - .whenInvokedWith(any(), any(), any()) - .thenReturn(Either.Right(Unit)) - given(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusLocally) - .whenInvokedWith(any(), any(), any()) - .thenReturn(Either.Left(StorageFailure.DataNotFound)) + val (arrangement, updateConversationArchivedStatus) = Arrangement() + .withLocalUpdateArchivedStatusFailure() + .withSuccessfulMutingUpdates() + .arrange() val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) assertEquals(ArchiveStatusUpdateResult.Failure::class, result::class) - verify(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusRemotely) - .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) - .wasInvoked(exactly = once) + with(arrangement) { + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusRemotely) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusLocally) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + } + } - verify(conversationRepository) - .suspendFunction(conversationRepository::updateArchivedStatusLocally) - .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) - .wasInvoked(exactly = once) + @Test + fun givenAConversationId_whenInvokingAnArchivedStatusChangeAndFailsUpdatingRemoteMutingStatus_thenItShouldStillReturnASuccessResult() = + runTest { + val conversationId = TestConversation.ID + val isConversationArchived = true + val archivedStatusTimestamp = 123456789L + + val (arrangement, updateConversationArchivedStatus) = Arrangement() + .withUpdateArchivedStatusFullSuccess() + .withRemoteErrorMutingUpdates() + .arrange() + + val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) + assertTrue(result is ArchiveStatusUpdateResult.Success) + + with(arrangement) { + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusRemotely) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusLocally) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + } + } + + @Test + fun givenAConversationId_whenInvokingAnArchivedStatusChangeAndFailsUpdatingLocalMutingStatus_thenItShouldStillReturnASuccessResult() = + runTest { + val conversationId = TestConversation.ID + val isConversationArchived = true + val archivedStatusTimestamp = 123456789L + + val (arrangement, updateConversationArchivedStatus) = Arrangement() + .withUpdateArchivedStatusFullSuccess() + .withLocalErrorMutingUpdates() + .arrange() + + val result = updateConversationArchivedStatus(conversationId, isConversationArchived, archivedStatusTimestamp) + assertTrue(result is ArchiveStatusUpdateResult.Success) + + with(arrangement) { + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusRemotely) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + + verify(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusLocally) + .with(any(), eq(isConversationArchived), matching { it == archivedStatusTimestamp }) + .wasInvoked(exactly = once) + } + } + + private class Arrangement { + @Mock + val conversationRepository: ConversationRepository = mock(ConversationRepository::class) + + private val updateArchivedStatus = UpdateConversationArchivedStatusUseCaseImpl(conversationRepository) + + fun withUpdateArchivedStatusFullSuccess() = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusRemotely) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Right(Unit)) + + given(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusLocally) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Right(Unit)) + } + + fun withRemoteUpdateArchivedStatusFailure() = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusRemotely) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Left(NetworkFailure.ServerMiscommunication(RuntimeException("some error")))) + } + + fun withLocalUpdateArchivedStatusFailure() = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusRemotely) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Right(Unit)) + given(conversationRepository) + .suspendFunction(conversationRepository::updateArchivedStatusLocally) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Left(StorageFailure.DataNotFound)) + } + + fun withSuccessfulMutingUpdates() = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::updateMutedStatusRemotely) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Right(Unit)) + given(conversationRepository) + .suspendFunction(conversationRepository::updateMutedStatusLocally) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Right(Unit)) + } + + fun withRemoteErrorMutingUpdates() = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::updateMutedStatusRemotely) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Left(NetworkFailure.NoNetworkConnection(RuntimeException("some error")))) + } + + fun withLocalErrorMutingUpdates() = apply { + given(conversationRepository) + .suspendFunction(conversationRepository::updateMutedStatusRemotely) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Right(Unit)) + given(conversationRepository) + .suspendFunction(conversationRepository::updateMutedStatusLocally) + .whenInvokedWith(any(), any(), any()) + .thenReturn(Either.Left(StorageFailure.DataNotFound)) + } + + fun arrange() = this to updateArchivedStatus } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/SearchKnownUserUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/SearchKnownUserUseCaseTest.kt index ad465377c89..ee3128fc633 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/SearchKnownUserUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/SearchKnownUserUseCaseTest.kt @@ -153,7 +153,8 @@ class SearchKnownUserUseCaseTest { userType = UserType.EXTERNAL, botService = null, deleted = false, - defederated = false + defederated = false, + isProteusVerified = false ) val (_, searchKnownUsersUseCase) = Arrangement() @@ -343,7 +344,8 @@ class SearchKnownUserUseCaseTest { userType = UserType.EXTERNAL, botService = null, deleted = false, - defederated = false + defederated = false, + isProteusVerified = false ) ) ) @@ -378,7 +380,8 @@ class SearchKnownUserUseCaseTest { userType = UserType.FEDERATED, botService = null, deleted = false, - defederated = false + defederated = false, + isProteusVerified = false ) ) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/SearchUserUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/SearchUserUseCaseTest.kt index 1a3518b6ec6..dffb5733459 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/SearchUserUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/SearchUserUseCaseTest.kt @@ -301,7 +301,8 @@ class SearchUserUseCaseTest { userType = UserType.FEDERATED, botService = null, deleted = false, - defederated = false + defederated = false, + isProteusVerified = false ) } ) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestUser.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestUser.kt index 07a37025937..62ae24be2b9 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestUser.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestUser.kt @@ -36,6 +36,7 @@ import com.wire.kalium.network.api.base.model.UserProfileDTO 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.UserDetailsEntity import com.wire.kalium.persistence.dao.UserEntity import com.wire.kalium.persistence.dao.UserTypeEntity @@ -84,7 +85,8 @@ object TestUser { userType = UserType.EXTERNAL, botService = null, deleted = false, - defederated = false + defederated = false, + isProteusVerified = false ) val ENTITY = UserEntity( @@ -106,6 +108,26 @@ object TestUser { defederated = false ) + val DETAILS_ENTITY = UserDetailsEntity( + id = ENTITY_ID, + name = "username", + handle = "handle", + email = "email", + phone = "phone", + accentId = 0, + team = "teamId", + connectionStatus = ConnectionEntity.State.ACCEPTED, + previewAssetId = QualifiedIDEntity("value1", ENTITY_ID.domain), + completeAssetId = QualifiedIDEntity("value2", ENTITY_ID.domain), + availabilityStatus = UserAvailabilityStatusEntity.NONE, + userType = UserTypeEntity.EXTERNAL, + botService = null, + deleted = false, + expiresAt = null, + defederated = false, + isProteusVerified = false + ) + val USER_PROFILE_DTO = UserProfileDTO( id = NETWORK_ID, name = "username", diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/TypingIndicatorHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/TypingIndicatorHandlerTest.kt index 4cb6a1f281e..4cbe5429581 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/TypingIndicatorHandlerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/TypingIndicatorHandlerTest.kt @@ -18,8 +18,7 @@ package com.wire.kalium.logic.sync.receiver.handler import com.wire.kalium.logic.data.conversation.Conversation -import com.wire.kalium.logic.data.conversation.ExpiringUserTyping -import com.wire.kalium.logic.data.conversation.TypingIndicatorRepository +import com.wire.kalium.logic.data.conversation.TypingIndicatorIncomingRepository import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.framework.TestConversation import com.wire.kalium.logic.framework.TestEvent @@ -33,7 +32,6 @@ import io.mockative.once import io.mockative.verify import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest -import kotlinx.datetime.Clock import kotlin.test.Test class TypingIndicatorHandlerTest { @@ -47,8 +45,8 @@ class TypingIndicatorHandlerTest { val result = handler.handle(TestEvent.typingIndicator(Conversation.TypingIndicatorMode.STARTED)) result.shouldSucceed() - verify(arrangement.typingIndicatorRepository) - .function(arrangement.typingIndicatorRepository::addTypingUserInConversation) + verify(arrangement.typingIndicatorIncomingRepository) + .function(arrangement.typingIndicatorIncomingRepository::addTypingUserInConversation) .with(eq(TestConversation.ID), eq(TestUser.SELF.id)) .wasNotInvoked() } @@ -62,8 +60,8 @@ class TypingIndicatorHandlerTest { val result = handler.handle(TestEvent.typingIndicator(Conversation.TypingIndicatorMode.STARTED)) result.shouldSucceed() - verify(arrangement.typingIndicatorRepository) - .function(arrangement.typingIndicatorRepository::addTypingUserInConversation) + verify(arrangement.typingIndicatorIncomingRepository) + .function(arrangement.typingIndicatorIncomingRepository::addTypingUserInConversation) .with(eq(TestConversation.ID), eq(TestUser.OTHER_USER_ID)) .wasInvoked(once) } @@ -77,8 +75,8 @@ class TypingIndicatorHandlerTest { val result = handler.handle(TestEvent.typingIndicator(Conversation.TypingIndicatorMode.STOPPED)) result.shouldSucceed() - verify(arrangement.typingIndicatorRepository) - .function(arrangement.typingIndicatorRepository::removeTypingUserInConversation) + verify(arrangement.typingIndicatorIncomingRepository) + .function(arrangement.typingIndicatorIncomingRepository::removeTypingUserInConversation) .with(eq(TestConversation.ID), eq(TestUser.OTHER_USER_ID)) .wasInvoked(once) } @@ -92,24 +90,24 @@ class TypingIndicatorHandlerTest { val result = handler.handle(TestEvent.typingIndicator(Conversation.TypingIndicatorMode.STOPPED)) result.shouldSucceed() - verify(arrangement.typingIndicatorRepository) - .function(arrangement.typingIndicatorRepository::removeTypingUserInConversation) + verify(arrangement.typingIndicatorIncomingRepository) + .function(arrangement.typingIndicatorIncomingRepository::removeTypingUserInConversation) .with(eq(TestConversation.ID), eq(TestUser.SELF.id)) .wasNotInvoked() } private class Arrangement { @Mock - val typingIndicatorRepository: TypingIndicatorRepository = mock(TypingIndicatorRepository::class) + val typingIndicatorIncomingRepository: TypingIndicatorIncomingRepository = mock(TypingIndicatorIncomingRepository::class) fun withTypingIndicatorObserve(usersId: Set) = apply { - given(typingIndicatorRepository) - .suspendFunction(typingIndicatorRepository::observeUsersTyping) + given(typingIndicatorIncomingRepository) + .suspendFunction(typingIndicatorIncomingRepository::observeUsersTyping) .whenInvokedWith(eq(TestConversation.ID)) - .thenReturn(flowOf(usersId.map { ExpiringUserTyping(it, Clock.System.now()) }.toSet())) + .thenReturn(flowOf(usersId)) } - fun arrange() = this to TypingIndicatorHandlerImpl(TestUser.SELF.id, typingIndicatorRepository) + fun arrange() = this to TypingIndicatorHandlerImpl(TestUser.SELF.id, typingIndicatorIncomingRepository) } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/ClientRepositoryArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/ClientRepositoryArrangement.kt new file mode 100644 index 00000000000..3bad44870e5 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/ClientRepositoryArrangement.kt @@ -0,0 +1,53 @@ +/* + * 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.util.arrangement.repository + +import com.wire.kalium.logic.StorageFailure +import com.wire.kalium.logic.data.client.ClientRepository +import com.wire.kalium.logic.data.client.OtherUserClient +import com.wire.kalium.logic.functional.Either +import io.mockative.Mock +import io.mockative.any +import io.mockative.given +import io.mockative.mock + +internal interface ClientRepositoryArrangement { + val clientRepository: ClientRepository + + fun withUpdateClientProteusVerificationStatus(result: Either): ClientRepositoryArrangementImpl + fun withClientsByUserId(result: Either>): ClientRepositoryArrangementImpl +} + +internal open class ClientRepositoryArrangementImpl : ClientRepositoryArrangement { + @Mock + override val clientRepository: ClientRepository = mock(ClientRepository::class) + + override fun withUpdateClientProteusVerificationStatus(result: Either) = apply { + given(clientRepository) + .suspendFunction(clientRepository::updateClientProteusVerificationStatus) + .whenInvokedWith(any(), any(), any()) + .thenReturn(result) + } + + override fun withClientsByUserId(result: Either>) = apply { + given(clientRepository) + .suspendFunction(clientRepository::getClientsByUserId) + .whenInvokedWith(any()) + .thenReturn(result) + } +} diff --git a/logic/src/jvmMain/kotlin/com/wire/kalium/logic/CoreLogic.kt b/logic/src/jvmMain/kotlin/com/wire/kalium/logic/CoreLogic.kt index 564e95956d8..2faa42a6e51 100644 --- a/logic/src/jvmMain/kotlin/com/wire/kalium/logic/CoreLogic.kt +++ b/logic/src/jvmMain/kotlin/com/wire/kalium/logic/CoreLogic.kt @@ -64,7 +64,7 @@ actual class CoreLogic( override val globalCallManager: GlobalCallManager = GlobalCallManager(PlatformContext()) override val globalWorkScheduler: GlobalWorkScheduler = GlobalWorkSchedulerImpl(this) - override val networkStateObserver: NetworkStateObserver = NetworkStateObserverImpl() + override val networkStateObserver: NetworkStateObserver = kaliumConfigs.mockNetworkStateObserver ?: NetworkStateObserverImpl() override val userSessionScopeProvider: Lazy = lazy { UserSessionScopeProviderImpl( authenticationScopeProvider, diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/conversation/ConversationApi.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/conversation/ConversationApi.kt index 27092e6727d..6058888dd8a 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/conversation/ConversationApi.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/authenticated/conversation/ConversationApi.kt @@ -147,4 +147,9 @@ interface ConversationApi { conversationId: ConversationId, messageTimer: Long? ): NetworkResponse + + suspend fun sendTypingIndicatorNotification( + conversationId: ConversationId, + typingIndicatorMode: TypingIndicatorStatusDTO + ): NetworkResponse } diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ConversationApiV0.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ConversationApiV0.kt index bdf36b87299..49bba38c383 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ConversationApiV0.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ConversationApiV0.kt @@ -34,6 +34,7 @@ import com.wire.kalium.network.api.base.authenticated.conversation.CreateConvers import com.wire.kalium.network.api.base.authenticated.conversation.MemberUpdateDTO import com.wire.kalium.network.api.base.authenticated.conversation.SubconversationDeleteRequest import com.wire.kalium.network.api.base.authenticated.conversation.SubconversationResponse +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatusDTO import com.wire.kalium.network.api.base.authenticated.conversation.UpdateConversationAccessRequest import com.wire.kalium.network.api.base.authenticated.conversation.UpdateConversationAccessResponse import com.wire.kalium.network.api.base.authenticated.conversation.UpdateConversationReceiptModeResponse @@ -384,6 +385,16 @@ internal open class ConversationApiV0 internal constructor( } } + override suspend fun sendTypingIndicatorNotification( + conversationId: ConversationId, + typingIndicatorMode: TypingIndicatorStatusDTO + ): NetworkResponse = + wrapKaliumResponse { + httpClient.post("$PATH_CONVERSATIONS/${conversationId.value}/$PATH_TYPING_NOTIFICATION") { + setBody(typingIndicatorMode) + } + } + protected companion object { const val PATH_CONVERSATIONS = "conversations" const val PATH_SELF = "self" @@ -404,6 +415,7 @@ internal open class ConversationApiV0 internal constructor( const val QUERY_KEY_START = "start" const val QUERY_KEY_SIZE = "size" const val QUERY_KEY_IDS = "qualified_ids" + const val PATH_TYPING_NOTIFICATION = "typing" const val MAX_CONVERSATION_DETAILS_COUNT = 1000 } diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/networkContainer/AuthenticatedNetworkContainerV0.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/networkContainer/AuthenticatedNetworkContainerV0.kt index 667e1521cbe..e00b74ad884 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/networkContainer/AuthenticatedNetworkContainerV0.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/networkContainer/AuthenticatedNetworkContainerV0.kt @@ -72,7 +72,8 @@ internal class AuthenticatedNetworkContainerV0 internal constructor( private val networkStateObserver: NetworkStateObserver, private val sessionManager: SessionManager, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( serverConfigDTOApiProxy = sessionManager.serverConfig().links.apiProxy, proxyCredentials = sessionManager.proxyCredentials(), certificatePinning = certificatePinning diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV0.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV0.kt index cc218bd0546..02d01312209 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV0.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV0.kt @@ -45,7 +45,8 @@ class UnauthenticatedNetworkContainerV0 internal constructor( backendLinks: ServerConfigDTO, proxyCredentials: ProxyCredentialsDTO?, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( serverConfigDTOApiProxy = backendLinks.links.apiProxy, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v2/authenticated/networkContainer/AuthenticatedNetworkContainerV2.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v2/authenticated/networkContainer/AuthenticatedNetworkContainerV2.kt index 4bb236c7be5..932d102011f 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v2/authenticated/networkContainer/AuthenticatedNetworkContainerV2.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v2/authenticated/networkContainer/AuthenticatedNetworkContainerV2.kt @@ -74,7 +74,8 @@ internal class AuthenticatedNetworkContainerV2 internal constructor( private val sessionManager: SessionManager, private val selfUserId: UserId, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( sessionManager.serverConfig().links.apiProxy, certificatePinning = certificatePinning ) diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v2/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV2.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v2/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV2.kt index 159aeeeef42..caa5b347b12 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v2/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV2.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v2/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV2.kt @@ -45,7 +45,8 @@ class UnauthenticatedNetworkContainerV2 internal constructor( backendLinks: ServerConfigDTO, proxyCredentials: ProxyCredentialsDTO?, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( serverConfigDTOApiProxy = backendLinks.links.apiProxy, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v3/authenticated/networkContainer/AuthenticatedNetworkContainerV3.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v3/authenticated/networkContainer/AuthenticatedNetworkContainerV3.kt index ac04615186b..9cb423f5077 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v3/authenticated/networkContainer/AuthenticatedNetworkContainerV3.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v3/authenticated/networkContainer/AuthenticatedNetworkContainerV3.kt @@ -75,7 +75,8 @@ internal class AuthenticatedNetworkContainerV3 internal constructor( private val sessionManager: SessionManager, private val selfUserId: UserId, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( serverConfigDTOApiProxy = sessionManager.serverConfig().links.apiProxy, proxyCredentials = sessionManager.proxyCredentials(), certificatePinning = certificatePinning diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v3/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV3.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v3/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV3.kt index f17e476e992..7ab4887bce0 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v3/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV3.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v3/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV3.kt @@ -45,11 +45,12 @@ class UnauthenticatedNetworkContainerV3 internal constructor( backendLinks: ServerConfigDTO, proxyCredentials: ProxyCredentialsDTO?, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( - serverConfigDTOApiProxy = backendLinks.links.apiProxy, - proxyCredentials = proxyCredentials, - certificatePinning = certificatePinning - ), + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( + serverConfigDTOApiProxy = backendLinks.links.apiProxy, + proxyCredentials = proxyCredentials, + certificatePinning = certificatePinning + ) ) : UnauthenticatedNetworkContainer, UnauthenticatedNetworkClientProvider by UnauthenticatedNetworkClientProviderImpl( networkStateObserver, diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/authenticated/ConversationApiV4.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/authenticated/ConversationApiV4.kt index a1f7ec00002..7154b65db19 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/authenticated/ConversationApiV4.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/authenticated/ConversationApiV4.kt @@ -23,6 +23,7 @@ import com.wire.kalium.network.api.base.authenticated.conversation.AddConversati import com.wire.kalium.network.api.base.authenticated.conversation.ConversationMemberAddedResponse import com.wire.kalium.network.api.base.authenticated.conversation.ConversationResponseV3 import com.wire.kalium.network.api.base.authenticated.conversation.CreateConversationRequest +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatusDTO import com.wire.kalium.network.api.base.authenticated.conversation.model.ConversationCodeInfo import com.wire.kalium.network.api.base.authenticated.notification.EventContentDTO import com.wire.kalium.network.api.base.model.ApiModelMapper @@ -107,6 +108,16 @@ internal open class ConversationApiV4 internal constructor( } } + override suspend fun sendTypingIndicatorNotification( + conversationId: ConversationId, + typingIndicatorMode: TypingIndicatorStatusDTO + ): NetworkResponse = + wrapKaliumResponse { + httpClient.post("$PATH_CONVERSATIONS/${conversationId.domain}/${conversationId.value}/$PATH_TYPING_NOTIFICATION") { + setBody(typingIndicatorMode) + } + } + companion object { const val PATH_GROUP_INFO = "groupinfo" const val PATH_SUBCONVERSATIONS = "subconversations" diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/authenticated/networkContainer/AuthenticatedNetworkContainerV4.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/authenticated/networkContainer/AuthenticatedNetworkContainerV4.kt index bfa063d2f2b..6e0b002ead3 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/authenticated/networkContainer/AuthenticatedNetworkContainerV4.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/authenticated/networkContainer/AuthenticatedNetworkContainerV4.kt @@ -74,7 +74,8 @@ internal class AuthenticatedNetworkContainerV4 internal constructor( private val sessionManager: SessionManager, private val selfUserId: UserId, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( serverConfigDTOApiProxy = sessionManager.serverConfig().links.apiProxy, proxyCredentials = sessionManager.proxyCredentials(), certificatePinning = certificatePinning diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV4.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV4.kt index 7a4fff6f917..df2c7325a47 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV4.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v4/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV4.kt @@ -45,11 +45,12 @@ class UnauthenticatedNetworkContainerV4 internal constructor( backendLinks: ServerConfigDTO, proxyCredentials: ProxyCredentialsDTO?, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( serverConfigDTOApiProxy = backendLinks.links.apiProxy, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning - ), + ) ) : UnauthenticatedNetworkContainer, UnauthenticatedNetworkClientProvider by UnauthenticatedNetworkClientProviderImpl( networkStateObserver, diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v5/authenticated/networkContainer/AuthenticatedNetworkContainerV5.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v5/authenticated/networkContainer/AuthenticatedNetworkContainerV5.kt index 3e83f3686c6..c6a9f3df2a0 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v5/authenticated/networkContainer/AuthenticatedNetworkContainerV5.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v5/authenticated/networkContainer/AuthenticatedNetworkContainerV5.kt @@ -74,7 +74,8 @@ internal class AuthenticatedNetworkContainerV5 internal constructor( private val sessionManager: SessionManager, private val selfUserId: UserId, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( serverConfigDTOApiProxy = sessionManager.serverConfig().links.apiProxy, proxyCredentials = sessionManager.proxyCredentials(), certificatePinning = certificatePinning diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v5/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV5.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v5/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV5.kt index 413d4c46d1f..b8af2da7412 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v5/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV5.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v5/unauthenticated/networkContainer/UnauthenticatedNetworkContainerV5.kt @@ -45,11 +45,12 @@ class UnauthenticatedNetworkContainerV5 internal constructor( backendLinks: ServerConfigDTO, proxyCredentials: ProxyCredentialsDTO?, certificatePinning: CertificatePinning, - engine: HttpClientEngine = defaultHttpEngine( + mockEngine: HttpClientEngine?, + engine: HttpClientEngine = mockEngine ?: defaultHttpEngine( serverConfigDTOApiProxy = backendLinks.links.apiProxy, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning - ), + ) ) : UnauthenticatedNetworkContainer, UnauthenticatedNetworkClientProvider by UnauthenticatedNetworkClientProviderImpl( networkStateObserver, diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/AuthenticatedNetworkContainer.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/AuthenticatedNetworkContainer.kt index d5c2202e49f..23cf78dc3b5 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/AuthenticatedNetworkContainer.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/AuthenticatedNetworkContainer.kt @@ -106,12 +106,15 @@ interface AuthenticatedNetworkContainer { val propertiesApi: PropertiesApi companion object { + + @Suppress("LongParameterList") fun create( networkStateObserver: NetworkStateObserver, sessionManager: SessionManager, selfUserId: UserId, userAgent: String, - certificatePinning: CertificatePinning + certificatePinning: CertificatePinning, + mockEngine: HttpClientEngine? ): AuthenticatedNetworkContainer { KaliumUserAgentProvider.setUserAgent(userAgent) @@ -120,41 +123,47 @@ interface AuthenticatedNetworkContainer { 0 -> AuthenticatedNetworkContainerV0( networkStateObserver, sessionManager, - certificatePinning + certificatePinning, + mockEngine ) 1 -> AuthenticatedNetworkContainerV0( networkStateObserver, sessionManager, - certificatePinning + certificatePinning, + mockEngine ) 2 -> AuthenticatedNetworkContainerV2( networkStateObserver, sessionManager, selfUserId, - certificatePinning + certificatePinning, + mockEngine ) 3 -> AuthenticatedNetworkContainerV3( networkStateObserver, sessionManager, selfUserId, - certificatePinning + certificatePinning, + mockEngine ) 4 -> AuthenticatedNetworkContainerV4( networkStateObserver, sessionManager, selfUserId, - certificatePinning + certificatePinning, + mockEngine ) 5 -> AuthenticatedNetworkContainerV5( networkStateObserver, sessionManager, selfUserId, - certificatePinning + certificatePinning, + mockEngine ) else -> error("Unsupported version: $version") diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/UnauthenticatedNetworkContainer.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/UnauthenticatedNetworkContainer.kt index ea0aa6a7753..23be6cd2fe8 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/UnauthenticatedNetworkContainer.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/UnauthenticatedNetworkContainer.kt @@ -36,7 +36,7 @@ import com.wire.kalium.network.session.CertificatePinning import com.wire.kalium.network.tools.ServerConfigDTO import io.ktor.client.engine.HttpClientEngine -@Suppress("MagicNumber") +@Suppress("MagicNumber", "LongParameterList") interface UnauthenticatedNetworkContainer { val loginApi: LoginApi val registerApi: RegisterApi @@ -51,7 +51,8 @@ interface UnauthenticatedNetworkContainer { serverConfigDTO: ServerConfigDTO, proxyCredentials: ProxyCredentialsDTO?, userAgent: String, - certificatePinning: CertificatePinning + certificatePinning: CertificatePinning, + mockEngine: HttpClientEngine? ): UnauthenticatedNetworkContainer { KaliumUserAgentProvider.setUserAgent(userAgent) @@ -62,6 +63,7 @@ interface UnauthenticatedNetworkContainer { serverConfigDTO, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning, + mockEngine ) 1 -> UnauthenticatedNetworkContainerV0( @@ -69,6 +71,7 @@ interface UnauthenticatedNetworkContainer { serverConfigDTO, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning, + mockEngine ) 2 -> UnauthenticatedNetworkContainerV2( @@ -76,6 +79,7 @@ interface UnauthenticatedNetworkContainer { serverConfigDTO, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning, + mockEngine ) 3 -> UnauthenticatedNetworkContainerV3( @@ -83,6 +87,7 @@ interface UnauthenticatedNetworkContainer { serverConfigDTO, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning, + mockEngine ) 4 -> UnauthenticatedNetworkContainerV4( @@ -90,6 +95,7 @@ interface UnauthenticatedNetworkContainer { serverConfigDTO, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning, + mockEngine ) 5 -> UnauthenticatedNetworkContainerV5( @@ -97,6 +103,7 @@ interface UnauthenticatedNetworkContainer { serverConfigDTO, proxyCredentials = proxyCredentials, certificatePinning = certificatePinning, + mockEngine ) else -> error("Unsupported version: ${serverConfigDTO.metaData.commonApiVersion.version}") diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/UnboundNetworkContainer.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/UnboundNetworkContainer.kt index b9aa2934465..200ac759b21 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/UnboundNetworkContainer.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/networkContainer/UnboundNetworkContainer.kt @@ -60,12 +60,13 @@ class UnboundNetworkContainerCommon( private val developmentApiEnabled: Boolean, userAgent: String, private val ignoreSSLCertificates: Boolean, - certificatePinning: CertificatePinning + certificatePinning: CertificatePinning, + mockEngine: HttpClientEngine? ) : UnboundNetworkContainer, UnboundNetworkClientProvider by UnboundNetworkClientProviderImpl( networkStateObserver = networkStateObserver, userAgent = userAgent, - engine = defaultHttpEngine( + engine = mockEngine ?: defaultHttpEngine( ignoreSSLCertificates = ignoreSSLCertificates, certificatePinning = certificatePinning ) diff --git a/network/src/commonTest/kotlin/com/wire/kalium/api/ApiTest.kt b/network/src/commonTest/kotlin/com/wire/kalium/api/ApiTest.kt index f4d5791f9f2..936c6328297 100644 --- a/network/src/commonTest/kotlin/com/wire/kalium/api/ApiTest.kt +++ b/network/src/commonTest/kotlin/com/wire/kalium/api/ApiTest.kt @@ -113,7 +113,8 @@ internal abstract class ApiTest { engine = mockEngine, sessionManager = TEST_SESSION_MANAGER, networkStateObserver = networkStateObserver, - certificatePinning = emptyMap() + certificatePinning = emptyMap(), + mockEngine = null ).networkClient } @@ -127,7 +128,8 @@ internal abstract class ApiTest { engine = mockEngine, sessionManager = TEST_SESSION_MANAGER, networkStateObserver = networkStateObserver, - certificatePinning = emptyMap() + certificatePinning = emptyMap(), + mockEngine = null ).websocketClient } @@ -181,7 +183,8 @@ internal abstract class ApiTest { engine = mockEngine, proxyCredentials = null, networkStateObserver = networkStateObserver, - certificatePinning = emptyMap() + certificatePinning = emptyMap(), + mockEngine = null ).unauthenticatedNetworkClient } @@ -220,7 +223,8 @@ internal abstract class ApiTest { engine = mockEngine, proxyCredentials = null, networkStateObserver = networkStateObserver, - certificatePinning = emptyMap() + certificatePinning = emptyMap(), + mockEngine = null ).unauthenticatedNetworkClient } @@ -249,7 +253,8 @@ internal abstract class ApiTest { engine = mockEngine, sessionManager = TEST_SESSION_MANAGER, networkStateObserver = networkStateObserver, - certificatePinning = emptyMap() + certificatePinning = emptyMap(), + mockEngine = null ).networkClient } diff --git a/network/src/commonTest/kotlin/com/wire/kalium/api/v0/conversation/ConversationApiV0Test.kt b/network/src/commonTest/kotlin/com/wire/kalium/api/v0/conversation/ConversationApiV0Test.kt index e7ed016c22c..b1c0d474712 100644 --- a/network/src/commonTest/kotlin/com/wire/kalium/api/v0/conversation/ConversationApiV0Test.kt +++ b/network/src/commonTest/kotlin/com/wire/kalium/api/v0/conversation/ConversationApiV0Test.kt @@ -27,12 +27,15 @@ import com.wire.kalium.model.conversation.ConversationListIdsResponseJson import com.wire.kalium.model.conversation.ConversationResponseJson import com.wire.kalium.model.conversation.CreateConversationRequestJson import com.wire.kalium.model.conversation.MemberUpdateRequestJson +import com.wire.kalium.model.conversation.SendTypingStatusNotificationRequestJson import com.wire.kalium.model.conversation.UpdateConversationAccessRequestJson import com.wire.kalium.network.api.base.authenticated.conversation.AddConversationMembersRequest import com.wire.kalium.network.api.base.authenticated.conversation.AddServiceRequest import com.wire.kalium.network.api.base.authenticated.conversation.ConversationApi import com.wire.kalium.network.api.base.authenticated.conversation.ConversationMemberAddedResponse import com.wire.kalium.network.api.base.authenticated.conversation.ReceiptMode +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatus +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatusDTO import com.wire.kalium.network.api.base.authenticated.conversation.UpdateConversationAccessRequest import com.wire.kalium.network.api.base.authenticated.conversation.UpdateConversationAccessResponse import com.wire.kalium.network.api.base.authenticated.conversation.model.ConversationMemberRoleDTO @@ -415,6 +418,30 @@ internal class ConversationApiV0Test : ApiTest() { assertIs>(response) } + @Test + fun givenTypingNotificationRequest_whenSendingStatus_thenTheRequestShouldBeConfiguredCorrectly() = runTest { + // given + val conversationId = ConversationId("conversationId", "conversationDomain") + val request = TypingIndicatorStatusDTO(TypingIndicatorStatus.STOPPED) + + val networkClient = mockAuthenticatedNetworkClient( + ByteArray(0), + statusCode = HttpStatusCode.OK, + assertion = { + assertPost() + assertPathEqual("${PATH_CONVERSATIONS}/${conversationId.value}/${PATH_TYPING_NOTIFICATION}") + assertJsonBodyContent(SendTypingStatusNotificationRequestJson.createValid(TypingIndicatorStatus.STOPPED).rawJson) + } + ) + val conversationApi = ConversationApiV0(networkClient) + + // when + val response = conversationApi.sendTypingIndicatorNotification(conversationId, request) + + // then + assertIs>(response) + } + private companion object { const val PATH_CONVERSATIONS = "/conversations" const val PATH_CONVERSATIONS_LIST_V2 = "/conversations/list/v2" @@ -425,6 +452,7 @@ internal class ConversationApiV0Test : ApiTest() { const val PATH_JOIN = "join" const val PATH_RECEIPT_MODE = "receipt-mode" const val PATH_CODE = "code" + const val PATH_TYPING_NOTIFICATION = "typing" val CREATE_CONVERSATION_RESPONSE = ConversationResponseJson.v0.rawJson val CREATE_CONVERSATION_REQUEST = CreateConversationRequestJson.v0 val CREATE_CONVERSATION_IDS_REQUEST = ConversationListIdsResponseJson.validRequestIds diff --git a/network/src/commonTest/kotlin/com/wire/kalium/api/v4/ConversationApiV4Test.kt b/network/src/commonTest/kotlin/com/wire/kalium/api/v4/ConversationApiV4Test.kt index a843d43fdb5..e4176395c38 100644 --- a/network/src/commonTest/kotlin/com/wire/kalium/api/v4/ConversationApiV4Test.kt +++ b/network/src/commonTest/kotlin/com/wire/kalium/api/v4/ConversationApiV4Test.kt @@ -22,12 +22,16 @@ import com.wire.kalium.api.ApiTest import com.wire.kalium.api.json.model.ErrorResponseJson import com.wire.kalium.model.EventContentDTOJson import com.wire.kalium.model.conversation.CreateConversationRequestJson +import com.wire.kalium.model.conversation.SendTypingStatusNotificationRequestJson import com.wire.kalium.network.api.base.authenticated.conversation.AddConversationMembersRequest +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatus +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatusDTO import com.wire.kalium.network.api.base.model.ConversationId import com.wire.kalium.network.api.base.model.FederationConflictResponse import com.wire.kalium.network.api.base.model.UserId import com.wire.kalium.network.api.v4.authenticated.ConversationApiV4 import com.wire.kalium.network.exceptions.KaliumException +import com.wire.kalium.network.utils.NetworkResponse import com.wire.kalium.network.utils.UnreachableRemoteBackends import com.wire.kalium.network.utils.isSuccessful import io.ktor.http.HttpStatusCode @@ -35,6 +39,7 @@ import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertIs import kotlin.test.assertTrue internal class ConversationApiV4Test : ApiTest() { @@ -122,9 +127,30 @@ internal class ConversationApiV4Test : ApiTest() { ) } + @Test + fun givenTypingNotificationRequest_whenSendingStatus_thenTheRequestShouldBeConfiguredCorrectly() = runTest { + val conversationId = ConversationId("conversationId", "conversationDomain") + val request = TypingIndicatorStatusDTO(TypingIndicatorStatus.STARTED) + + val networkClient = mockAuthenticatedNetworkClient( + ByteArray(0), + statusCode = HttpStatusCode.OK, + assertion = { + assertPost() + assertPathEqual("${PATH_CONVERSATIONS}/${conversationId.domain}/${conversationId.value}/${PATH_TYPING_NOTIFICATION}") + assertJsonBodyContent(SendTypingStatusNotificationRequestJson.createValid(TypingIndicatorStatus.STARTED).rawJson) + } + ) + val conversationApi = ConversationApiV4(networkClient) + conversationApi.sendTypingIndicatorNotification(conversationId, request).also { + assertIs>(it) + } + } + private companion object { const val PATH_CONVERSATIONS = "/conversations" const val PATH_MEMBERS = "members" + const val PATH_TYPING_NOTIFICATION = "typing" val CREATE_CONVERSATION_REQUEST = CreateConversationRequestJson.v3 } } diff --git a/network/src/commonTest/kotlin/com/wire/kalium/model/conversation/SendTypingStatusNotificationRequestJson.kt b/network/src/commonTest/kotlin/com/wire/kalium/model/conversation/SendTypingStatusNotificationRequestJson.kt new file mode 100644 index 00000000000..78d6d7db5b8 --- /dev/null +++ b/network/src/commonTest/kotlin/com/wire/kalium/model/conversation/SendTypingStatusNotificationRequestJson.kt @@ -0,0 +1,37 @@ +/* + * 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.model.conversation + +import com.wire.kalium.api.json.ValidJsonProvider +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatus +import com.wire.kalium.network.api.base.authenticated.conversation.TypingIndicatorStatusDTO + +object SendTypingStatusNotificationRequestJson { + + private val jsonProvider = { serializable: TypingIndicatorStatusDTO -> + """ + |{ + | "status": "${serializable.status.value}" + |} + """.trimMargin() + } + + fun createValid(typingIndicatorStatus: TypingIndicatorStatus) = + ValidJsonProvider(TypingIndicatorStatusDTO(typingIndicatorStatus), jsonProvider) + +} diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Clients.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Clients.sq index e28e564c20a..4138cb864a2 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Clients.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Clients.sq @@ -11,6 +11,7 @@ CREATE TABLE Client ( id TEXT NOT NULL, device_type TEXT AS DeviceTypeEntity, is_valid INTEGER AS Boolean NOT NULL DEFAULT 1, + -- is_proteus_verified is_verified INTEGER AS Boolean NOT NULL DEFAULT 0, client_type TEXT AS ClientTypeEntity DEFAULT NULL, registration_date INTEGER AS Instant DEFAULT NULL, @@ -67,7 +68,7 @@ UPDATE OR IGNORE Client SET is_valid = 0 WHERE user_id = :user_id AND id IN :cl conversationRecipets: SELECT * FROM Client WHERE user_id IN (SELECT user FROM Member WHERE conversation = :conversation_id) AND is_valid = 1; -updateClientVerificatioStatus: +updateClientProteusVerificationStatus: UPDATE Client SET is_verified = :is_verified WHERE user_id = :user_id AND id = :client_id; selectRecipientsByConversationAndUserId: 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 c2e564040b2..5aae6b1f07e 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 @@ -100,23 +100,53 @@ VALUES(?, ?) ON CONFLICT(qualified_id) DO UPDATE SET connection_status = excluded.connection_status; +CREATE VIEW IF NOT EXISTS UserDetails AS +SELECT +User.qualified_id, +User.name, +User.handle, +User.email, +User.phone, +User.accent_id, +User.team, +User.connection_status, +User.preview_asset_id, +User.complete_asset_id, +User.user_availability_status, +User.user_type, +User.bot_service, +User.deleted, +User.incomplete_metadata, +User.expires_at, +User.defederated, +CASE + WHEN SUM(Client.is_verified) = COUNT(*) THEN 1 + ELSE 0 +END AS is_proteus_verified +FROM User +LEFT JOIN Client ON User.qualified_id = Client.user_id +GROUP BY User.qualified_id; + selectAllUsers: -SELECT * FROM User; +SELECT * FROM UserDetails; selectAllUsersWithConnectionStatus: -SELECT * FROM User WHERE connection_status = ? AND defederated = 0; +SELECT * FROM UserDetails WHERE connection_status = ? AND defederated = 0; selectByQualifiedId: SELECT * FROM User WHERE qualified_id IN ?; +selectDetailsByQualifiedId: +SELECT * FROM UserDetails WHERE qualified_id IN ?; + selectMinimizedByQualifiedId: SELECT qualified_id, name, complete_asset_id, user_type FROM User WHERE qualified_id IN ?; selectWithTeamByQualifiedId: -SELECT * FROM User LEFT JOIN Team ON User.team == Team.id WHERE User.qualified_id IN ?; +SELECT * FROM UserDetails LEFT JOIN Team ON UserDetails.team == Team.id WHERE UserDetails.qualified_id IN ?; selectByNameOrHandleOrEmailAndConnectionState: -SELECT * FROM User +SELECT * FROM UserDetails WHERE (name LIKE ('%' || :searchQuery || '%') OR handle LIKE ('%' || :searchQuery || '%') OR email LIKE ('%' || :searchQuery || '%')) @@ -124,13 +154,13 @@ AND connection_status IN :connectionStatuses AND defederated = 0; selectByHandleAndConnectionState: -SELECT * FROM User +SELECT * FROM UserDetails WHERE handle LIKE ('%' || :searchQuery || '%') AND connection_status IN :connectionStatuses AND defederated = 0; selectUsersByTeam: -SELECT * FROM User WHERE team = ?; +SELECT * FROM UserDetails WHERE team = ?; updateUserhandle: UPDATE User SET handle = ? WHERE qualified_id = ?; @@ -145,7 +175,7 @@ updateUserAvailabilityStatus: UPDATE User SET user_availability_status = ? WHERE qualified_id = ?; getUsersNotInConversationByNameOrHandleOrEmail: -SELECT * FROM User AS user +SELECT * FROM UserDetails AS user WHERE NOT EXISTS (SELECT user FROM Member AS member WHERE member.conversation == :converastion_id AND user.qualified_id == member.user) AND (name LIKE ('%' || :searchQuery || '%') OR handle LIKE ('%' || :searchQuery || '%') @@ -154,14 +184,14 @@ AND connection_status = "ACCEPTED" AND defederated = 0; getUsersNotInConversationByHandle: -SELECT * FROM User AS user +SELECT * FROM UserDetails AS user WHERE NOT EXISTS (SELECT user FROM Member AS member WHERE member.conversation == :converastion_id AND user.qualified_id == member.user) AND handle LIKE ('%' || :searchQuery || '%') AND connection_status = "ACCEPTED" AND defederated = 0; getUsersNotPartOfTheConversation: -SELECT * FROM User AS user +SELECT * FROM UserDetails AS user WHERE NOT EXISTS (SELECT user FROM Member AS member WHERE member.conversation == :converastion_id AND user.qualified_id == member.user) AND connection_status = "ACCEPTED" AND defederated = 0; @@ -170,7 +200,7 @@ updateUserDisplayName: UPDATE User SET name = ? WHERE qualified_id = ?; selectUsersWithoutMetadata: -SELECT * FROM User AS user +SELECT * FROM UserDetails AS user WHERE deleted = 0 AND incomplete_metadata = 1; userIdsWithoutSelf: diff --git a/persistence/src/commonMain/db_user/migrations/59.sqm b/persistence/src/commonMain/db_user/migrations/59.sqm new file mode 100644 index 00000000000..e57471c28e6 --- /dev/null +++ b/persistence/src/commonMain/db_user/migrations/59.sqm @@ -0,0 +1,26 @@ +CREATE VIEW IF NOT EXISTS UserDetails AS +SELECT +User.qualified_id, +User.name, +User.handle, +User.email, +User.phone, +User.accent_id, +User.team, +User.connection_status, +User.preview_asset_id, +User.complete_asset_id, +User.user_availability_status, +User.user_type, +User.bot_service, +User.deleted, +User.incomplete_metadata, +User.expires_at, +User.defederated, +CASE + WHEN SUM(Client.is_verified) = COUNT(*) THEN 1 + ELSE 0 +END AS is_proteus_verified +FROM User +LEFT JOIN Client ON User.qualified_id = Client.user_id +GROUP BY User.qualified_id; diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConnectionDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConnectionDAO.kt index 667f5488004..75fd72ef9aa 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConnectionDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/ConnectionDAO.kt @@ -30,7 +30,7 @@ data class ConnectionEntity( val status: State, val toId: String, val shouldNotify: Boolean? = null, - val otherUser: UserEntity? = null + val otherUser: UserDetailsEntity? = null ) { enum class State { 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 854735d810c..b3dc2963c3e 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 @@ -80,7 +80,7 @@ private class ConnectionMapper { status = status, toId = to_id, shouldNotify = should_notify, - otherUser = if (qualified_id != null) UserEntity( + otherUser = if (qualified_id != null) UserDetailsEntity( id = qualified_id, name = name, handle = handle, @@ -97,7 +97,8 @@ private class ConnectionMapper { deleted = deleted.requireField("deleted"), hasIncompleteMetadata = incomplete_metadata.requireField("incomplete_metadata"), expiresAt = expires_at, - defederated = defederated.requireField("defederated") + defederated = defederated.requireField("defederated"), + isProteusVerified = false ) 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 7ab3603e9be..c5c6542df3c 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 @@ -74,6 +74,49 @@ data class UserEntity( val defederated: Boolean ) +data class UserDetailsEntity( + val id: QualifiedIDEntity, + val name: String?, + val handle: String?, + val email: String?, + val phone: String?, + val accentId: Int, + val team: String?, + val connectionStatus: ConnectionEntity.State = ConnectionEntity.State.NOT_CONNECTED, + val previewAssetId: UserAssetIdEntity?, + val completeAssetId: UserAssetIdEntity?, + // for now availabilityStatus is stored only locally and ignored for API models, + // later, when API start supporting it, it should be added into API model too + val availabilityStatus: UserAvailabilityStatusEntity, + val userType: UserTypeEntity, + val botService: BotIdEntity?, + val deleted: Boolean, + val hasIncompleteMetadata: Boolean = false, + val expiresAt: Instant?, + val defederated: Boolean, + val isProteusVerified: Boolean +) { + fun toSimpleEntity() = UserEntity( + id = id, + name = name, + handle = handle, + email = email, + phone = phone, + accentId = accentId, + team = team, + connectionStatus = connectionStatus, + previewAssetId = previewAssetId, + completeAssetId = completeAssetId, + availabilityStatus = availabilityStatus, + userType = userType, + botService = botService, + deleted = deleted, + hasIncompleteMetadata = hasIncompleteMetadata, + expiresAt = expiresAt, + defederated = defederated + ) +} + data class UserEntityMinimized( val id: QualifiedIDEntity, val name: String?, @@ -181,41 +224,41 @@ interface UserDAO { * [UserEntity.completeAssetId] */ suspend fun updateUser(user: UserEntity) - suspend fun getAllUsers(): Flow> - suspend fun observeAllUsersByConnectionStatus(connectionState: ConnectionEntity.State): Flow> - suspend fun getUserByQualifiedID(qualifiedID: QualifiedIDEntity): Flow - suspend fun getUserWithTeamByQualifiedID(qualifiedID: QualifiedIDEntity): Flow?> + suspend fun getAllUsersDetails(): Flow> + suspend fun observeAllUsersDetailsByConnectionStatus(connectionState: ConnectionEntity.State): Flow> + suspend fun observeUserDetailsByQualifiedID(qualifiedID: QualifiedIDEntity): Flow + suspend fun getUserDetailsWithTeamByQualifiedID(qualifiedID: QualifiedIDEntity): Flow?> suspend fun getUserMinimizedByQualifiedID(qualifiedID: QualifiedIDEntity): UserEntityMinimized? - suspend fun getUsersByQualifiedIDList(qualifiedIDList: List): List - suspend fun getUserByNameOrHandleOrEmailAndConnectionStates( + suspend fun getUsersDetailsByQualifiedIDList(qualifiedIDList: List): List + suspend fun getUserDetailsByNameOrHandleOrEmailAndConnectionStates( searchQuery: String, connectionStates: List - ): Flow> + ): Flow> - suspend fun getUserByHandleAndConnectionStates( + suspend fun getUserDetailsByHandleAndConnectionStates( handle: String, connectionStates: List - ): Flow> + ): Flow> suspend fun deleteUserByQualifiedID(qualifiedID: QualifiedIDEntity) suspend fun markUserAsDeleted(qualifiedID: QualifiedIDEntity) suspend fun markUserAsDefederated(qualifiedID: QualifiedIDEntity) suspend fun updateUserHandle(qualifiedID: QualifiedIDEntity, handle: String) suspend fun updateUserAvailabilityStatus(qualifiedID: QualifiedIDEntity, status: UserAvailabilityStatusEntity) - fun observeUsersNotInConversation(conversationId: QualifiedIDEntity): Flow> + fun observeUsersDetailsNotInConversation(conversationId: QualifiedIDEntity): Flow> suspend fun insertOrIgnoreUserWithConnectionStatus(qualifiedID: QualifiedIDEntity, connectionStatus: ConnectionEntity.State) - suspend fun getUsersNotInConversationByNameOrHandleOrEmail( + suspend fun getUsersDetailsNotInConversationByNameOrHandleOrEmail( conversationId: QualifiedIDEntity, searchQuery: String, - ): Flow> + ): Flow> - suspend fun getUsersNotInConversationByHandle(conversationId: QualifiedIDEntity, handle: String): Flow> - suspend fun getAllUsersByTeam(teamId: String): List + suspend fun getUsersDetailsNotInConversationByHandle(conversationId: QualifiedIDEntity, handle: String): Flow> + suspend fun getAllUsersDetailsByTeam(teamId: String): List suspend fun updateUserDisplayName(selfUserId: QualifiedIDEntity, displayName: String) suspend fun removeUserAsset(assetId: QualifiedIDEntity) - suspend fun getUsersWithoutMetadata(): List + suspend fun getUsersDetailsWithoutMetadata(): List /** * @return [List] of [UserIDEntity] of all other users. 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 eda6d32457a..d9471b8721f 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 @@ -33,8 +33,32 @@ import kotlinx.coroutines.withContext import kotlinx.datetime.Instant import kotlin.coroutines.CoroutineContext import com.wire.kalium.persistence.User as SQLDelightUser +import com.wire.kalium.persistence.UserDetails as SQLDelightUserDetails class UserMapper { + fun toDetailsModel(user: SQLDelightUserDetails): UserDetailsEntity { + return UserDetailsEntity( + id = user.qualified_id, + name = user.name, + handle = user.handle, + email = user.email, + phone = user.phone, + accentId = user.accent_id, + team = user.team, + connectionStatus = user.connection_status, + previewAssetId = user.preview_asset_id, + completeAssetId = user.complete_asset_id, + availabilityStatus = user.user_availability_status, + userType = user.user_type, + botService = user.bot_service, + deleted = user.deleted, + hasIncompleteMetadata = user.incomplete_metadata, + expiresAt = user.expires_at, + defederated = user.defederated, + isProteusVerified = user.is_proteus_verified == 1L + ) + } + fun toModel(user: SQLDelightUser): UserEntity { return UserEntity( id = user.qualified_id, @@ -76,11 +100,12 @@ class UserMapper { hasIncompleteMetadata: Boolean, expiresAt: Instant?, defederated: Boolean, + isVerifiedProteus: Long, id: String?, teamName: String?, teamIcon: String?, - ): Pair { - val userEntity = UserEntity( + ): Pair { + val userEntity = UserDetailsEntity( id = qualifiedId, name = name, handle = handle, @@ -97,7 +122,8 @@ class UserMapper { deleted = deleted, hasIncompleteMetadata = hasIncompleteMetadata, expiresAt = expiresAt, - defederated = defederated + defederated = defederated, + isProteusVerified = isVerifiedProteus == 1L ) val teamEntity = if (team != null && teamName != null && teamIcon != null) { @@ -123,7 +149,7 @@ class UserMapper { @Suppress("TooManyFunctions") class UserDAOImpl internal constructor( private val userQueries: UsersQueries, - private val userCache: Cache>, + private val userCache: Cache>, private val databaseScope: CoroutineScope, private val queriesContext: CoroutineContext ) : UserDAO { @@ -295,21 +321,22 @@ class UserDAOImpl internal constructor( ) } - override suspend fun getAllUsers(): Flow> = userQueries.selectAllUsers() + override suspend fun getAllUsersDetails(): Flow> = userQueries.selectAllUsers() .asFlow() .flowOn(queriesContext) .mapToList() - .map { entryList -> entryList.map(mapper::toModel) } + .map { entryList -> entryList.map(mapper::toDetailsModel) } - override suspend fun getUserByQualifiedID(qualifiedID: QualifiedIDEntity): Flow = userCache.get(qualifiedID) { - userQueries.selectByQualifiedId(listOf(qualifiedID)) - .asFlow() - .mapToOneOrNull() - .map { it?.let { mapper.toModel(it) } } - .shareIn(databaseScope, Lazily, 1) - } + override suspend fun observeUserDetailsByQualifiedID(qualifiedID: QualifiedIDEntity): Flow = + userCache.get(qualifiedID) { + userQueries.selectDetailsByQualifiedId(listOf(qualifiedID)) + .asFlow() + .mapToOneOrNull() + .map { it?.let { mapper.toDetailsModel(it) } } + .shareIn(databaseScope, Lazily, 1) + } - override suspend fun getUserWithTeamByQualifiedID(qualifiedID: QualifiedIDEntity): Flow?> = + override suspend fun getUserDetailsWithTeamByQualifiedID(qualifiedID: QualifiedIDEntity): Flow?> = userQueries.selectWithTeamByQualifiedId(listOf(qualifiedID), mapper::toUserAndTeamPairModel) .asFlow() .mapToOneOrNull() @@ -321,30 +348,30 @@ class UserDAOImpl internal constructor( }.executeAsOneOrNull() } - override suspend fun getUsersByQualifiedIDList(qualifiedIDList: List): List = + override suspend fun getUsersDetailsByQualifiedIDList(qualifiedIDList: List): List = withContext(queriesContext) { - userQueries.selectByQualifiedId(qualifiedIDList) + userQueries.selectDetailsByQualifiedId(qualifiedIDList) .executeAsList() - .map { mapper.toModel(it) } + .map { mapper.toDetailsModel(it) } } - override suspend fun getUserByNameOrHandleOrEmailAndConnectionStates( + override suspend fun getUserDetailsByNameOrHandleOrEmailAndConnectionStates( searchQuery: String, connectionStates: List - ): Flow> = userQueries.selectByNameOrHandleOrEmailAndConnectionState(searchQuery, connectionStates) + ): Flow> = userQueries.selectByNameOrHandleOrEmailAndConnectionState(searchQuery, connectionStates) .asFlow() .flowOn(queriesContext) .mapToList() - .map { it.map(mapper::toModel) } + .map { it.map(mapper::toDetailsModel) } - override suspend fun getUserByHandleAndConnectionStates( + override suspend fun getUserDetailsByHandleAndConnectionStates( handle: String, connectionStates: List ) = userQueries.selectByHandleAndConnectionState(handle, connectionStates) .asFlow() .flowOn(queriesContext) .mapToList() - .map { it.map(mapper::toModel) } + .map { it.map(mapper::toDetailsModel) } override suspend fun deleteUserByQualifiedID(qualifiedID: QualifiedIDEntity) = withContext(queriesContext) { userQueries.deleteUser(qualifiedID) @@ -367,48 +394,51 @@ class UserDAOImpl internal constructor( userQueries.updateUserAvailabilityStatus(status, qualifiedID) } - override fun observeUsersNotInConversation(conversationId: QualifiedIDEntity): Flow> = + override fun observeUsersDetailsNotInConversation(conversationId: QualifiedIDEntity): Flow> = userQueries.getUsersNotPartOfTheConversation(conversationId) .asFlow() .flowOn(queriesContext) .mapToList() - .map { it.map(mapper::toModel) } + .map { it.map(mapper::toDetailsModel) } - override suspend fun getUsersNotInConversationByNameOrHandleOrEmail( + override suspend fun getUsersDetailsNotInConversationByNameOrHandleOrEmail( conversationId: QualifiedIDEntity, searchQuery: String - ): Flow> = + ): Flow> = userQueries.getUsersNotInConversationByNameOrHandleOrEmail(conversationId, searchQuery) .asFlow() .flowOn(queriesContext) .mapToList() - .map { it.map(mapper::toModel) } + .map { it.map(mapper::toDetailsModel) } - override suspend fun getUsersNotInConversationByHandle(conversationId: QualifiedIDEntity, handle: String): Flow> = + override suspend fun getUsersDetailsNotInConversationByHandle( + conversationId: QualifiedIDEntity, + handle: String + ): Flow> = userQueries.getUsersNotInConversationByHandle(conversationId, handle) .asFlow() .flowOn(queriesContext) .mapToList() - .map { it.map(mapper::toModel) } + .map { it.map(mapper::toDetailsModel) } override suspend fun insertOrIgnoreUserWithConnectionStatus(qualifiedID: QualifiedIDEntity, connectionStatus: ConnectionEntity.State) = withContext(queriesContext) { userQueries.insertOrIgnoreUserIdWithConnectionStatus(qualifiedID, connectionStatus) } - override suspend fun observeAllUsersByConnectionStatus(connectionState: ConnectionEntity.State): Flow> = + override suspend fun observeAllUsersDetailsByConnectionStatus(connectionState: ConnectionEntity.State): Flow> = withContext(queriesContext) { userQueries.selectAllUsersWithConnectionStatus(connectionState) .asFlow() .flowOn(queriesContext) .mapToList() - .map { it.map(mapper::toModel) } + .map { it.map(mapper::toDetailsModel) } } - override suspend fun getAllUsersByTeam(teamId: String): List = withContext(queriesContext) { + override suspend fun getAllUsersDetailsByTeam(teamId: String): List = withContext(queriesContext) { userQueries.selectUsersByTeam(teamId) .executeAsList() - .map(mapper::toModel) + .map(mapper::toDetailsModel) } override suspend fun updateUserDisplayName(selfUserId: QualifiedIDEntity, displayName: String) = withContext(queriesContext) { @@ -419,13 +449,14 @@ class UserDAOImpl internal constructor( userQueries.updateUserAsset(null, null, assetId) } - override suspend fun getUsersWithoutMetadata() = withContext(queriesContext) { + override suspend fun getUsersDetailsWithoutMetadata() = withContext(queriesContext) { userQueries.selectUsersWithoutMetadata() .executeAsList() - .map(mapper::toModel) + .map(mapper::toDetailsModel) } override suspend fun allOtherUsersId(): List = withContext(queriesContext) { userQueries.userIdsWithoutSelf().executeAsList() } + } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/client/ClientDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/client/ClientDAO.kt index acedb9bf83f..697f5fda77d 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/client/ClientDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/client/ClientDAO.kt @@ -29,7 +29,7 @@ data class Client( val deviceType: DeviceTypeEntity?, val clientType: ClientTypeEntity?, val isValid: Boolean, - val isVerified: Boolean, + val isProteusVerified: Boolean, val registrationDate: Instant?, val lastActive: Instant?, val label: String?, @@ -77,7 +77,7 @@ interface ClientDAO { suspend fun conversationRecipient(ids: QualifiedIDEntity): Map> suspend fun insertClientsAndRemoveRedundant(clients: List) suspend fun tryMarkInvalid(invalidClientsList: List>>) - suspend fun updateClientVerificationStatus(userId: QualifiedIDEntity, clientId: String, verified: Boolean) + suspend fun updateClientProteusVerificationStatus(userId: QualifiedIDEntity, clientId: String, verified: Boolean) suspend fun observeClient(userId: QualifiedIDEntity, clientId: String): Flow /** diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOImpl.kt index b7863224871..7b58087e7dc 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOImpl.kt @@ -50,7 +50,7 @@ internal object ClientMapper { deviceType = device_type, clientType = client_type, isValid = is_valid, - isVerified = is_verified, + isProteusVerified = is_verified, registrationDate = registration_date, label = label, model = model, @@ -113,9 +113,9 @@ internal class ClientDAOImpl internal constructor( } } - override suspend fun updateClientVerificationStatus(userId: QualifiedIDEntity, clientId: String, verified: Boolean) = + override suspend fun updateClientProteusVerificationStatus(userId: QualifiedIDEntity, clientId: String, verified: Boolean) = withContext(queriesContext) { - clientsQueries.updateClientVerificatioStatus(verified, userId, clientId) + clientsQueries.updateClientProteusVerificationStatus(verified, userId, clientId) } override suspend fun observeClient(userId: QualifiedIDEntity, clientId: String): Flow = 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 7c2f8734d68..282dae7e286 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 @@ -41,7 +41,7 @@ import com.wire.kalium.persistence.dao.TeamDAO import com.wire.kalium.persistence.dao.TeamDAOImpl import com.wire.kalium.persistence.dao.UserDAO import com.wire.kalium.persistence.dao.UserDAOImpl -import com.wire.kalium.persistence.dao.UserEntity +import com.wire.kalium.persistence.dao.UserDetailsEntity import com.wire.kalium.persistence.dao.UserIDEntity import com.wire.kalium.persistence.dao.asset.AssetDAO import com.wire.kalium.persistence.dao.asset.AssetDAOImpl @@ -161,7 +161,7 @@ class UserDatabaseBuilder internal constructor( } private val databaseScope = CoroutineScope(SupervisorJob() + dispatcher) - private val userCache = LRUCache>(USER_CACHE_SIZE) + private val userCache = LRUCache>(USER_CACHE_SIZE) val userDAO: UserDAO get() = UserDAOImpl(database.usersQueries, userCache, databaseScope, queriesContext) diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/DatabaseImporterTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/DatabaseImporterTest.kt index bc2ae29bb0a..994b54aacd7 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/DatabaseImporterTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/DatabaseImporterTest.kt @@ -462,7 +462,7 @@ class DatabaseImporterTest : BaseDatabaseTest() { userDatabaseBuilder.databaseImporter.importFromFile(databasePath(backupUserIdEntity), false) // then - val restoredUsers = userDatabaseBuilder.userDAO.getAllUsers().first() + val restoredUsers = userDatabaseBuilder.userDAO.getAllUsersDetails().first() assertEquals(userToBackup, restoredUsers) } @@ -475,7 +475,7 @@ class DatabaseImporterTest : BaseDatabaseTest() { userDatabaseBuilder.databaseImporter.importFromFile(databasePath(backupUserIdEntity), false) // then - val restoredUsers = userDatabaseBuilder.userDAO.getAllUsers().first() + val restoredUsers = userDatabaseBuilder.userDAO.getAllUsersDetails().first() assertEquals(usersPresent, restoredUsers) } @@ -489,7 +489,7 @@ class DatabaseImporterTest : BaseDatabaseTest() { userDatabaseBuilder.databaseImporter.importFromFile(databasePath(backupUserIdEntity), false) // then - val restoredUsers = userDatabaseBuilder.userDAO.getAllUsers().first() + val restoredUsers = userDatabaseBuilder.userDAO.getAllUsersDetails().first() assertTrue(restoredUsers.containsAll(userToBackup)) assertEquals(usersPresent.size + userToBackup.size, restoredUsers.size) } @@ -504,15 +504,15 @@ class DatabaseImporterTest : BaseDatabaseTest() { val uniqueBackupUsers = backupDatabaseDataGenerator.generateAndInsertUsers(uniqueBackupUsersAmount) uniqueBackupUsers.forEach { userEntity -> - backupDatabaseBuilder.userDAO.insertUser(userEntity) + backupDatabaseBuilder.userDAO.insertUser(userEntity.toSimpleEntity()) } // when userDatabaseBuilder.databaseImporter.importFromFile(databasePath(backupUserIdEntity), false) // then - val restoredUsers = userDatabaseBuilder.userDAO.getAllUsers().first() - assertEquals(restoredUsers, uniqueUsers + uniqueBackupUsers) + val restoredUsers = userDatabaseBuilder.userDAO.getAllUsersDetails().first() + assertEquals(restoredUsers, uniqueBackupUsers + uniqueUsers) assertEquals(uniqueUsersAmount + uniqueBackupUsersAmount, restoredUsers.size) } diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/UserDatabaseDataGenerator.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/UserDatabaseDataGenerator.kt index 6515095b145..be098ed8dc3 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/UserDatabaseDataGenerator.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/backup/UserDatabaseDataGenerator.kt @@ -25,6 +25,7 @@ import com.wire.kalium.persistence.dao.ConversationIDEntity import com.wire.kalium.persistence.dao.QualifiedIDEntity import com.wire.kalium.persistence.dao.TeamEntity import com.wire.kalium.persistence.dao.UserAvailabilityStatusEntity +import com.wire.kalium.persistence.dao.UserDetailsEntity import com.wire.kalium.persistence.dao.UserEntity import com.wire.kalium.persistence.dao.UserIDEntity import com.wire.kalium.persistence.dao.UserTypeEntity @@ -543,14 +544,14 @@ class UserDatabaseDataGenerator( return members } - suspend fun generateAndInsertUsers(amount: Int): List { + suspend fun generateAndInsertUsers(amount: Int): List { for (index in generatedUsersCount + 1..amount) { val user = generateUser() userDatabaseBuilder.userDAO.insertUser(user) } - return userDatabaseBuilder.userDAO.getAllUsers().first() + return userDatabaseBuilder.userDAO.getAllUsersDetails().first() } @Suppress("StringTemplate") diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/MemberDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/MemberDAOTest.kt index 605bae10d9c..91d6f0c97e9 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/MemberDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/MemberDAOTest.kt @@ -139,7 +139,7 @@ class MemberDAOTest : BaseDatabaseTest() { assertEquals( listOf(member1), memberDAO.observeConversationMembers(conversationEntity1.id).first() ) - assertNotNull(userDAO.getUserByQualifiedID(member1.user).firstOrNull()) + assertNotNull(userDAO.observeUserDetailsByQualifiedID(member1.user).firstOrNull()) } @Test @@ -147,6 +147,7 @@ class MemberDAOTest : BaseDatabaseTest() { val conversationEntity1 = TestStubs.conversationEntity1 val member1 = TestStubs.member1 val user = TestStubs.user1.copy(connectionStatus = ConnectionEntity.State.NOT_CONNECTED) + val userDetails = TestStubs.userDetails1.copy(connectionStatus = ConnectionEntity.State.NOT_CONNECTED) userDAO.insertUser(user) conversationDAO.insertConversation(conversationEntity1) @@ -161,8 +162,8 @@ class MemberDAOTest : BaseDatabaseTest() { memberDAO.observeConversationMembers(conversationEntity1.id).first() ) assertEquals( - user.copy(connectionStatus = ConnectionEntity.State.ACCEPTED), - userDAO.getUserByQualifiedID(member1.user).firstOrNull() + userDetails.copy(connectionStatus = ConnectionEntity.State.ACCEPTED), + userDAO.observeUserDetailsByQualifiedID(member1.user).firstOrNull() ) } @@ -198,8 +199,8 @@ class MemberDAOTest : BaseDatabaseTest() { ) assertEquals(listOf(member1), memberDAO.observeConversationMembers(conversationEntity1.id).first()) - assertEquals(ConnectionEntity.State.SENT, userDAO.getUserByQualifiedID(user1.id).first()?.connectionStatus) - assertEquals(user1.name, userDAO.getUserByQualifiedID(user1.id).first()?.name) + assertEquals(ConnectionEntity.State.SENT, userDAO.observeUserDetailsByQualifiedID(user1.id).first()?.connectionStatus) + assertEquals(user1.name, userDAO.observeUserDetailsByQualifiedID(user1.id).first()?.name) } @Test @@ -367,7 +368,7 @@ class MemberDAOTest : BaseDatabaseTest() { ) // then - val member = userDAO.getUserByQualifiedID(user1.id).first() + val member = userDAO.observeUserDetailsByQualifiedID(user1.id).first() assertEquals(true, member?.hasIncompleteMetadata) } diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserClientDAOIntegrationTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserClientDAOIntegrationTest.kt index 9a85944eb5f..ee8452d8e76 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserClientDAOIntegrationTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserClientDAOIntegrationTest.kt @@ -71,7 +71,7 @@ class UserClientDAOIntegrationTest : BaseDatabaseTest() { id = "id1", deviceType = null, isValid = true, - isVerified = false, + isProteusVerified = false, registrationDate = null, lastActive = null, label = null, diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserConversationDAOIntegrationTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserConversationDAOIntegrationTest.kt index 4305a9ded2e..d9866f5a0b7 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserConversationDAOIntegrationTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserConversationDAOIntegrationTest.kt @@ -64,8 +64,8 @@ class UserConversationDAOIntegrationTest : BaseDatabaseTest() { conversationDAO.insertConversation(conversationEntity1) memberDAO.insertMember(member1, conversationEntity1.id) - val result = userDAO.getUserByQualifiedID(user1.id).first() - assertEquals(user1, result) + val result = userDAO.observeUserDetailsByQualifiedID(user1.id).first() + assertEquals(user1, result?.toSimpleEntity()) } @Test @@ -89,10 +89,10 @@ class UserConversationDAOIntegrationTest : BaseDatabaseTest() { ) // when - userDAO.observeUsersNotInConversation(conversationId).test { + userDAO.observeUsersDetailsNotInConversation(conversationId).test { val result = awaitItem() // then - assertTrue { result == (allUsers - userThatIsPartOfConversation) } + assertEquals((allUsers - userThatIsPartOfConversation), result.map { it.toSimpleEntity() }) cancelAndIgnoreRemainingEvents() } } @@ -117,7 +117,7 @@ class UserConversationDAOIntegrationTest : BaseDatabaseTest() { // when - userDAO.observeUsersNotInConversation(conversationId).test { + userDAO.observeUsersDetailsNotInConversation(conversationId).test { // then val result = awaitItem() assertTrue { result.isEmpty() } @@ -135,10 +135,10 @@ class UserConversationDAOIntegrationTest : BaseDatabaseTest() { createTestConversation(conversationId, emptyList()) // when - userDAO.observeUsersNotInConversation(conversationId).test { + userDAO.observeUsersDetailsNotInConversation(conversationId).test { // then val result = awaitItem() - assertTrue { result == listOf(user1, user2) } + assertEquals(listOf(user1, user2), result.map { it.toSimpleEntity() }) cancelAndIgnoreRemainingEvents() } } @@ -170,11 +170,11 @@ class UserConversationDAOIntegrationTest : BaseDatabaseTest() { // when - userDAO.getUsersNotInConversationByHandle(conversationId, "handleMatch") + userDAO.getUsersDetailsNotInConversationByHandle(conversationId, "handleMatch") .test { // then val result = awaitItem() - assertTrue { result == (allUsers - userThatIsPartOfConversation) } + assertEquals((allUsers - userThatIsPartOfConversation), result.map { it.toSimpleEntity() }) cancelAndIgnoreRemainingEvents() } } @@ -206,11 +206,11 @@ class UserConversationDAOIntegrationTest : BaseDatabaseTest() { // when - userDAO.getUsersNotInConversationByNameOrHandleOrEmail(conversationId, "emailMatch") + userDAO.getUsersDetailsNotInConversationByNameOrHandleOrEmail(conversationId, "emailMatch") .test { // then val result = awaitItem() - assertTrue { result == (allUsers - userThatIsPartOfConversation) } + assertEquals((allUsers - userThatIsPartOfConversation), result.map { it.toSimpleEntity() }) cancelAndIgnoreRemainingEvents() } } @@ -242,11 +242,11 @@ class UserConversationDAOIntegrationTest : BaseDatabaseTest() { // when - userDAO.getUsersNotInConversationByNameOrHandleOrEmail(conversationId, "nameMatch") + userDAO.getUsersDetailsNotInConversationByNameOrHandleOrEmail(conversationId, "nameMatch") .test { // then val result = awaitItem() - assertTrue { result == (allUsers - userThatIsPartOfConversation) } + assertEquals((allUsers - userThatIsPartOfConversation), result.map { it.toSimpleEntity() }) cancelAndIgnoreRemainingEvents() } } 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 e71a7255450..b486991391e 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 @@ -55,26 +55,26 @@ class UserDAOTest : BaseDatabaseTest() { @Test fun givenUser_ThenUserCanBeInserted() = runTest(dispatcher) { db.userDAO.insertUser(user1) - val result = db.userDAO.getUserByQualifiedID(user1.id).first() - assertEquals(result, user1) + val result = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() + assertEquals(result?.toSimpleEntity(), user1) } @Test fun givenListOfUsers_ThenMultipleUsersCanBeInsertedAtOnce() = runTest(dispatcher) { db.userDAO.upsertUsers(listOf(user1, user2, user3)) - val result1 = db.userDAO.getUserByQualifiedID(user1.id).first() - val result2 = db.userDAO.getUserByQualifiedID(user2.id).first() - val result3 = db.userDAO.getUserByQualifiedID(user3.id).first() - assertEquals(result1, user1) - assertEquals(result2, user2) - assertEquals(result3, user3) + val result1 = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() + val result2 = db.userDAO.observeUserDetailsByQualifiedID(user2.id).first() + val result3 = db.userDAO.observeUserDetailsByQualifiedID(user3.id).first() + assertEquals(result1?.toSimpleEntity(), user1) + assertEquals(result2?.toSimpleEntity(), user2) + assertEquals(result3?.toSimpleEntity(), user3) } @Test fun givenExistingUser_ThenUserCanBeDeleted() = runTest(dispatcher) { db.userDAO.insertUser(user1) db.userDAO.deleteUserByQualifiedID(user1.id) - val result = db.userDAO.getUserByQualifiedID(user1.id).first() + val result = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() assertNull(result) } @@ -101,8 +101,8 @@ class UserDAOTest : BaseDatabaseTest() { defederated = false ) db.userDAO.updateUser(updatedUser1) - val result = db.userDAO.getUserByQualifiedID(user1.id).first() - assertEquals(result, updatedUser1) + val result = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() + assertEquals(result?.toSimpleEntity(), updatedUser1) } @Test @@ -131,8 +131,8 @@ class UserDAOTest : BaseDatabaseTest() { defederated = false ) - db.userDAO.getUserByQualifiedID(user1.id).take(2).collect { - collectedValues.add(it) + db.userDAO.observeUserDetailsByQualifiedID(user1.id).take(2).collect { + collectedValues.add(it?.toSimpleEntity()) if (collectedValues.size == 1) { db.userDAO.updateUser(updatedUser1) } @@ -151,7 +151,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.updateUserHandle(user1.id, updatedHandle) // then - val result = db.userDAO.getUserByQualifiedID(user1.id).first() + val result = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() assertEquals(updatedHandle, result?.handle) } @@ -165,7 +165,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.updateUserHandle(nonExistingQualifiedID, updatedHandle) // then - val result = db.userDAO.getUserByQualifiedID(nonExistingQualifiedID).first() + val result = db.userDAO.observeUserDetailsByQualifiedID(nonExistingQualifiedID).first() assertNull(result) } @@ -178,13 +178,13 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(listOf(user1, user2, user3)) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates( + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates( user2.email!!, listOf(ConnectionEntity.State.ACCEPTED) ).test { // then val searchResult = awaitItem() - assertEquals(searchResult, listOf(user2)) + assertEquals(searchResult.map { it.toSimpleEntity() }, listOf(user2)) cancelAndIgnoreRemainingEvents() } } @@ -199,14 +199,14 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(listOf(user1, user2, user3)) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates( + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates( user3.handle!!, listOf(ConnectionEntity.State.ACCEPTED) ) .test { // then val searchResult = awaitItem() - assertEquals(searchResult, listOf(user3)) + assertEquals(searchResult.map { it.toSimpleEntity() }, listOf(user3)) cancelAndIgnoreRemainingEvents() } } @@ -221,10 +221,10 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(listOf(user1, user2, user3)) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates(user1.name!!, listOf(ConnectionEntity.State.ACCEPTED)) + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates(user1.name!!, listOf(ConnectionEntity.State.ACCEPTED)) .test { val searchResult = awaitItem() - assertEquals(searchResult, listOf(user1)) + assertEquals(searchResult.map { it.toSimpleEntity() }, listOf(user1)) cancelAndIgnoreRemainingEvents() } } @@ -285,11 +285,11 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(mockUsers) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates(commonEmailPrefix, listOf(ConnectionEntity.State.ACCEPTED)) + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates(commonEmailPrefix, listOf(ConnectionEntity.State.ACCEPTED)) .test { // then val searchResult = awaitItem() - assertEquals(searchResult, commonEmailUsers) + assertEquals(searchResult.map { it.toSimpleEntity() }, commonEmailUsers) cancelAndIgnoreRemainingEvents() } } @@ -304,7 +304,7 @@ class UserDAOTest : BaseDatabaseTest() { val nonExistingEmailQuery = "doesnotexist@wire.com" // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates( + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates( nonExistingEmailQuery, listOf(ConnectionEntity.State.ACCEPTED) ).test { @@ -324,7 +324,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(mockUsers) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates( + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates( commonEmailPrefix, listOf(ConnectionEntity.State.ACCEPTED) ).test { @@ -346,7 +346,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(mockUsers) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates( + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates( commonHandlePrefix, listOf(ConnectionEntity.State.ACCEPTED) ).test { @@ -368,7 +368,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(mockUsers) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates( + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates( commonNamePrefix, listOf(ConnectionEntity.State.ACCEPTED) ).test { @@ -395,11 +395,11 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(mockUsers) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates(commonPrefix, listOf(ConnectionEntity.State.ACCEPTED)) + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates(commonPrefix, listOf(ConnectionEntity.State.ACCEPTED)) .test { // then val searchResult = awaitItem() - assertEquals(mockUsers, searchResult) + assertEquals(mockUsers, searchResult.map { it.toSimpleEntity() }) cancelAndIgnoreRemainingEvents() } } @@ -418,7 +418,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(mockUsers) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates( + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates( commonPrefix, listOf(ConnectionEntity.State.ACCEPTED) ).test { @@ -446,7 +446,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(mockUsers) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates(commonPrefix, listOf(ConnectionEntity.State.ACCEPTED)) + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates(commonPrefix, listOf(ConnectionEntity.State.ACCEPTED)) .test { // then val searchResult = awaitItem() @@ -475,13 +475,13 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertUsers(mockUsers) // when - db.userDAO.getUserByNameOrHandleOrEmailAndConnectionStates( + db.userDAO.getUserDetailsByNameOrHandleOrEmailAndConnectionStates( commonPrefix, listOf(ConnectionEntity.State.ACCEPTED) ).test { // then val searchResult = awaitItem() - assertEquals(expectedResult, searchResult) + assertEquals(expectedResult, searchResult.map { it.toSimpleEntity() }) cancelAndIgnoreRemainingEvents() } } @@ -504,13 +504,13 @@ class UserDAOTest : BaseDatabaseTest() { // when - db.userDAO.getUserByHandleAndConnectionStates( + db.userDAO.getUserDetailsByHandleAndConnectionStates( "some", listOf(ConnectionEntity.State.ACCEPTED) ).test { // then val searchResult = awaitItem() - assertEquals(expectedResult, searchResult) + assertEquals(expectedResult, searchResult.map { it.toSimpleEntity() }) cancelAndIgnoreRemainingEvents() } } @@ -526,8 +526,8 @@ class UserDAOTest : BaseDatabaseTest() { val updatedUser3 = user3.copy(name = newNameB) db.userDAO.upsertUsers(listOf(updatedUser1, updatedUser3)) // then - val updated1 = db.userDAO.getUserByQualifiedID(updatedUser1.id) - val updated3 = db.userDAO.getUserByQualifiedID(updatedUser3.id) + val updated1 = db.userDAO.observeUserDetailsByQualifiedID(updatedUser1.id) + val updated3 = db.userDAO.observeUserDetailsByQualifiedID(updatedUser3.id) assertEquals(newNameA, updated1.first()?.name) assertEquals(newNameB, updated3.first()?.name) } @@ -541,8 +541,8 @@ class UserDAOTest : BaseDatabaseTest() { val updatedUser1 = user1.copy(name = newNameA) db.userDAO.upsertUsers(listOf(updatedUser1, user2)) // then - val updated1 = db.userDAO.getUserByQualifiedID(updatedUser1.id) - val inserted2 = db.userDAO.getUserByQualifiedID(user2.id) + val updated1 = db.userDAO.observeUserDetailsByQualifiedID(updatedUser1.id) + val inserted2 = db.userDAO.observeUserDetailsByQualifiedID(user2.id) assertEquals(newNameA, updated1.first()?.name) assertNotNull(inserted2) } @@ -556,8 +556,8 @@ class UserDAOTest : BaseDatabaseTest() { val updatedUser1 = user1.copy(team = newTeamId) db.userDAO.upsertTeamMembersTypes(listOf(updatedUser1, user2)) // then - val updated1 = db.userDAO.getUserByQualifiedID(updatedUser1.id) - val inserted2 = db.userDAO.getUserByQualifiedID(user2.id) + val updated1 = db.userDAO.observeUserDetailsByQualifiedID(updatedUser1.id) + val inserted2 = db.userDAO.observeUserDetailsByQualifiedID(user2.id) assertEquals(newTeamId, updated1.first()?.team) assertNotNull(inserted2) } @@ -570,7 +570,7 @@ class UserDAOTest : BaseDatabaseTest() { // when db.userDAO.upsertTeamMembers(listOf(user1)) // then - val updated1 = db.userDAO.getUserByQualifiedID(user1.id) + val updated1 = db.userDAO.observeUserDetailsByQualifiedID(user1.id) assertEquals(UserTypeEntity.EXTERNAL, updated1.first()?.userType) } @@ -584,7 +584,7 @@ class UserDAOTest : BaseDatabaseTest() { db.memberDAO.insertMember(MemberEntity(teamMember.id, MemberEntity.Role.Member), conversation.id) // then - db.userDAO.getAllUsers().first().also { + db.userDAO.getAllUsersDetails().first().also { assertNotNull(it) it.firstOrNull { it.id == teamMember.id }.also { assertNotNull(it) @@ -596,7 +596,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.upsertTeamMembers(listOf(teamMember)) // then - db.userDAO.getAllUsers().first().also { + db.userDAO.getAllUsersDetails().first().also { assertNotNull(it) it.firstOrNull { it.id == teamMember.id }.also { assertNotNull(it) @@ -614,8 +614,8 @@ class UserDAOTest : BaseDatabaseTest() { val updatedUser1 = user1.copy(team = newTeamId) db.userDAO.upsertUsers(listOf(updatedUser1, user2)) // then - val updated1 = db.userDAO.getUserByQualifiedID(updatedUser1.id) - val inserted2 = db.userDAO.getUserByQualifiedID(user2.id) + val updated1 = db.userDAO.observeUserDetailsByQualifiedID(updatedUser1.id) + val inserted2 = db.userDAO.observeUserDetailsByQualifiedID(user2.id) assertEquals(newTeamId, updated1.first()?.team) assertEquals(ConnectionEntity.State.ACCEPTED, updated1.first()?.connectionStatus) assertNotNull(inserted2) @@ -626,9 +626,9 @@ class UserDAOTest : BaseDatabaseTest() { val users = listOf(user1, user2) val requestedIds = (users + user3).map { it.id } db.userDAO.upsertUsers(users) - val result = db.userDAO.getUsersByQualifiedIDList(requestedIds) - assertEquals(result, users) - assertTrue(!result.contains(user3)) + val result = db.userDAO.getUsersDetailsByQualifiedIDList(requestedIds) + assertEquals(result.map { it.toSimpleEntity() }, users) + assertTrue(!result.map { it.toSimpleEntity() }.contains(user3)) } @Test @@ -637,8 +637,8 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.insertUser(user) val deletedUser = user1.copy(deleted = true, team = null, userType = UserTypeEntity.NONE) db.userDAO.markUserAsDeleted(user1.id) - val result = db.userDAO.getUserByQualifiedID(user1.id).first() - assertEquals(result, deletedUser) + val result = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() + assertEquals(result?.toSimpleEntity(), deletedUser) } @@ -650,8 +650,8 @@ class UserDAOTest : BaseDatabaseTest() { // when db.userDAO.insertOrIgnoreUsers(usersToInsert) // then - val persistedUsers = db.userDAO.getAllUsers().first() - assertEquals(expected, persistedUsers) + val persistedUsers = db.userDAO.getAllUsersDetails().first() + assertEquals(expected, persistedUsers.map { it.toSimpleEntity() }) } @Test @@ -664,8 +664,8 @@ class UserDAOTest : BaseDatabaseTest() { // when db.userDAO.insertOrIgnoreUsers(usersToInsert) // then - val persistedUsers = db.userDAO.getAllUsers().first() - assertEquals(expected, persistedUsers) + val persistedUsers = db.userDAO.getAllUsersDetails().first() + assertEquals(expected, persistedUsers.map { it.toSimpleEntity() }) } @Test @@ -678,7 +678,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.updateUserDisplayName(user1.id, expectedNewDisplayName) // then - val persistedUser = db.userDAO.getUserByQualifiedID(user1.id).first() + val persistedUser = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() assertEquals(expectedNewDisplayName, persistedUser?.name) } @@ -688,7 +688,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.insertUser(user1.copy(name = null, handle = null, hasIncompleteMetadata = true)) // when - val usersWithoutMetadata = db.userDAO.getUsersWithoutMetadata() + val usersWithoutMetadata = db.userDAO.getUsersDetailsWithoutMetadata() // then assertEquals(1, usersWithoutMetadata.size) @@ -706,8 +706,8 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.removeUserAsset(assetId) // then - val result = db.userDAO.getUserByQualifiedID(user1.id).first() - assertEquals(result, updatedUser1.copy(previewAssetId = null)) + val result = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() + assertEquals(result?.toSimpleEntity(), updatedUser1.copy(previewAssetId = null)) } @Test @@ -719,7 +719,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.removeUserAsset(assetId) // when - val result = db.userDAO.getUserByQualifiedID(user1.id).first() + val result = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() assertNull(result) } @@ -747,7 +747,7 @@ class UserDAOTest : BaseDatabaseTest() { fun givenExistingUser_ThenUserCanBeDefederated() = runTest(dispatcher) { db.userDAO.insertUser(user1) db.userDAO.markUserAsDefederated(user1.id) - val result = db.userDAO.getUserByQualifiedID(user1.id).first() + val result = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() assertNotNull(result) assertEquals(true, result.defederated) } @@ -757,7 +757,7 @@ class UserDAOTest : BaseDatabaseTest() { db.userDAO.insertUser(user1) db.userDAO.markUserAsDefederated(user1.id) db.userDAO.insertUser(user1) - val result = db.userDAO.getUserByQualifiedID(user1.id).first() + val result = db.userDAO.observeUserDetailsByQualifiedID(user1.id).first() assertNotNull(result) assertEquals(false, result.defederated) } diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt index 0b1149b154c..90f70094237 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/client/ClientDAOTest.kt @@ -68,7 +68,7 @@ class ClientDAOTest : BaseDatabaseTest() { fun givenClientIsInserted_whenFetchingClientsByUserId_thenTheRelevantClientIsReturned() = runTest { val insertedClient = insertedClient1.copy(user.id, "id1", deviceType = null) - val expected = client1.copy(user.id, "id1", deviceType = null, isValid = true, isVerified = false) + val expected = client1.copy(user.id, "id1", deviceType = null, isValid = true, isProteusVerified = false) userDAO.insertUser(user) clientDAO.insertClient(insertedClient) @@ -232,7 +232,7 @@ class ClientDAOTest : BaseDatabaseTest() { val user = user userDAO.insertUser(user) clientDAO.insertClient(insertedClient) - assertFalse { clientDAO.getClientsOfUserByQualifiedID(userId).first().isVerified } + assertFalse { clientDAO.getClientsOfUserByQualifiedID(userId).first().isProteusVerified } } @Test @@ -240,11 +240,11 @@ class ClientDAOTest : BaseDatabaseTest() { val user = user userDAO.insertUser(user) clientDAO.insertClient(insertedClient) - clientDAO.updateClientVerificationStatus(user.id, insertedClient.id, true) - assertTrue { clientDAO.getClientsOfUserByQualifiedID(userId).first().isVerified } + clientDAO.updateClientProteusVerificationStatus(user.id, insertedClient.id, true) + assertTrue { clientDAO.getClientsOfUserByQualifiedID(userId).first().isProteusVerified } - clientDAO.updateClientVerificationStatus(user.id, insertedClient.id, false) - assertFalse { clientDAO.getClientsOfUserByQualifiedID(userId).first().isVerified } + clientDAO.updateClientProteusVerificationStatus(user.id, insertedClient.id, false) + assertFalse { clientDAO.getClientsOfUserByQualifiedID(userId).first().isProteusVerified } } @Test @@ -270,13 +270,13 @@ class ClientDAOTest : BaseDatabaseTest() { userDAO.insertUser(user) clientDAO.insertClient(insertedClient) - assertFalse { clientDAO.getClientsOfUserByQualifiedID(userId).first().isVerified } + assertFalse { clientDAO.getClientsOfUserByQualifiedID(userId).first().isProteusVerified } - clientDAO.updateClientVerificationStatus(user.id, insertedClient.id, true) - assertTrue { clientDAO.getClientsOfUserByQualifiedID(userId).first().isVerified } + clientDAO.updateClientProteusVerificationStatus(user.id, insertedClient.id, true) + assertTrue { clientDAO.getClientsOfUserByQualifiedID(userId).first().isProteusVerified } clientDAO.insertClient(insertedClient) - assertTrue { clientDAO.getClientsOfUserByQualifiedID(userId).first().isVerified } + assertTrue { clientDAO.getClientsOfUserByQualifiedID(userId).first().isProteusVerified } } @Test @@ -381,7 +381,7 @@ private fun InsertClientParam.toClient(): Client = deviceType = deviceType, clientType = clientType, isValid = true, - isVerified = false, + isProteusVerified = false, label = label, model = model, registrationDate = registrationDate, diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/ClientStubs.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/ClientStubs.kt new file mode 100644 index 00000000000..2625404f071 --- /dev/null +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/ClientStubs.kt @@ -0,0 +1,33 @@ +/* + * 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.persistence.utils.stubs + +import com.wire.kalium.persistence.dao.QualifiedIDEntity +import com.wire.kalium.persistence.dao.client.InsertClientParam + +fun insertedClient(userId: QualifiedIDEntity = QualifiedIDEntity("test", "wire.com")) = InsertClientParam( + userId = userId, + id = "id0", + deviceType = null, + clientType = null, + label = null, + model = null, + registrationDate = null, + lastActive = null, + mlsPublicKeys = null +) diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/TestStubs.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/TestStubs.kt index 06ad49d36e1..7d42c742480 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/TestStubs.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/TestStubs.kt @@ -31,6 +31,7 @@ internal object TestStubs { val user1 = newUserEntity(id = "1").copy(team = teamId) val user2 = newUserEntity(id = "2").copy(team = teamId) val user3 = newUserEntity(id = "3").copy(team = teamId) + val userDetails1 = newUserDetailsEntity(id = "1").copy(team = teamId) val messageTimer = 5000L val team = TeamEntity(teamId, "teamName", "") diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/UserStubs.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/UserStubs.kt index 38e8c4fe1c2..2e0a150d2f7 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/UserStubs.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/utils/stubs/UserStubs.kt @@ -21,6 +21,7 @@ package com.wire.kalium.persistence.utils.stubs 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.UserDetailsEntity import com.wire.kalium.persistence.dao.UserEntity import com.wire.kalium.persistence.dao.UserTypeEntity @@ -65,3 +66,25 @@ fun newUserEntity(qualifiedID: QualifiedIDEntity, id: String = "test") = expiresAt = null, defederated = false ) + +fun newUserDetailsEntity(id: String = "test") = + UserDetailsEntity( + id = QualifiedIDEntity(id, "wire.com"), + name = "user$id", + handle = "handle$id", + email = "email$id", + phone = "phone$id", + accentId = 1, + team = "team", + ConnectionEntity.State.ACCEPTED, + null, + null, + UserAvailabilityStatusEntity.NONE, + UserTypeEntity.STANDARD, + botService = null, + deleted = false, + hasIncompleteMetadata = false, + expiresAt = null, + defederated = false, + isProteusVerified = false + ) diff --git a/tango-tests/.gitignore b/tango-tests/.gitignore new file mode 100644 index 00000000000..b63da4551b2 --- /dev/null +++ b/tango-tests/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/tango-tests/build.gradle.kts b/tango-tests/build.gradle.kts new file mode 100644 index 00000000000..91d351754b8 --- /dev/null +++ b/tango-tests/build.gradle.kts @@ -0,0 +1,60 @@ +/* + * 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/. + */ + +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + kotlin("jvm") +} + +kotlin { + sourceSets { + val test by getting { + kotlin.srcDir("src/integrationTest/kotlin") + dependencies { + implementation(project(":network")) + implementation(project(":logic")) + implementation(project(":persistence")) + implementation(kotlin("test")) + implementation(libs.settings.kmpTest) + + implementation(libs.ktor.utils) + implementation(libs.coroutines.core) + implementation(libs.ktxDateTime) + // coroutines + implementation(libs.coroutines.test) + implementation(libs.turbine) + implementation(libs.ktxSerialization) + implementation(libs.ktor.serialization) + implementation(libs.ktor.okHttp) + implementation(libs.ktor.contentNegotiation) + implementation(libs.ktor.json) + implementation(libs.ktor.authClient) + implementation(libs.okhttp.loggingInterceptor) + + implementation(libs.faker) + + // ktor test + implementation(libs.ktor.mock) + } + } + + tasks.withType { + jvmArgs = listOf("-Djava.library.path=/usr/local/lib/:./native/libs") + } + } +} diff --git a/tango-tests/src/integrationTest/kotlin/PocIntegrationTest.kt b/tango-tests/src/integrationTest/kotlin/PocIntegrationTest.kt new file mode 100644 index 00000000000..02a7fd8da39 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/PocIntegrationTest.kt @@ -0,0 +1,142 @@ +/* + * 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/. + */ + +import action.ACMEActions +import action.ClientActions +import action.LoginActions +import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.logic.configuration.server.ServerConfig +import com.wire.kalium.logic.data.logout.LogoutReason +import com.wire.kalium.logic.feature.auth.AuthenticationScope +import com.wire.kalium.logic.feature.auth.autoVersioningAuth.AutoVersionAuthScopeUseCase +import com.wire.kalium.logic.featureFlags.KaliumConfigs +import com.wire.kalium.logic.util.KaliumMockEngine +import com.wire.kalium.network.NetworkState +import com.wire.kalium.network.tools.ServerConfigDTO +import io.ktor.client.engine.mock.MockEngine +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test +import util.MockUnboundNetworkClient +import util.MockUnboundNetworkClient.createMockEngine + +class PocIntegrationTest { + + @Test + fun givenApiWhenGettingACMEDirectoriesThenReturnAsExpectedBasedOnNetworkState() = runTest { + val mockEngine = createMockEngine( + listOf(ACMEActions.acmeGetDirectoriesRequestSuccess) + ) + + val coreLogic = createCoreLogic(mockEngine) + + launch { + TestNetworkStateObserver.DEFAULT_TEST_NETWORK_STATE_OBSERVER.updateNetworkState(NetworkState.NotConnected) + + ACMEActions.acmeDirectoriesErrorNotConnected( + coreLogic = coreLogic + ) + + TestNetworkStateObserver.DEFAULT_TEST_NETWORK_STATE_OBSERVER.updateNetworkState(NetworkState.ConnectedWithInternet) + + ACMEActions.acmeDirectoriesSuccess( + coreLogic = coreLogic + ) + + TestNetworkStateObserver.DEFAULT_TEST_NETWORK_STATE_OBSERVER.updateNetworkState(NetworkState.ConnectedWithoutInternet) + + ACMEActions.acmeDirectoriesConnectNoInternet( + coreLogic = coreLogic + ) + } + } + + @Test + fun givenEmailAndPasswordWhenLoggingInThenRegisterClientAndLogout() = runTest { + val mockEngine = createMockEngine( + mutableListOf().apply { + addAll(LoginActions.loginRequestResponseSuccess) + addAll(ClientActions.clientRequestResponseSuccess) + } + ) + + val coreLogic = createCoreLogic(mockEngine) + + TestNetworkStateObserver.DEFAULT_TEST_NETWORK_STATE_OBSERVER.updateNetworkState(NetworkState.ConnectedWithInternet) + + launch { + val authScope = getAuthScope(coreLogic, MockUnboundNetworkClient.TEST_BACKEND_CONFIG.links) + + val loginAuthToken = LoginActions.loginAndAddAuthenticatedUser( + email = USER_EMAIL, + password = USER_PASSWORD, + coreLogic = coreLogic, + authScope = authScope + ) + + val userSessionScope = ClientActions.registerClient( + password = USER_PASSWORD, + userId = loginAuthToken.userId, + coreLogic = coreLogic + ) + + val x = userSessionScope.client.selfClients() + println(x.toString()) + + userSessionScope.logout.invoke(LogoutReason.SELF_SOFT_LOGOUT) + } + } + + private suspend fun getAuthScope(coreLogic: CoreLogic, backend: ServerConfigDTO.Links): AuthenticationScope { + val result = coreLogic.versionedAuthenticationScope( + ServerConfig.Links( + api = backend.api, + accounts = backend.accounts, + webSocket = backend.webSocket, + blackList = backend.blackList, + teams = backend.teams, + website = backend.website, + title = backend.title, + isOnPremises = true, + apiProxy = null + ) + ).invoke() + if (result !is AutoVersionAuthScopeUseCase.Result.Success) { + error("Failed getting AuthScope: $result") + } + return result.authenticationScope + } + + companion object { + private val HOME_DIRECTORY: String = System.getProperty("user.home") + private val USER_EMAIL = "user@domain.com" + private val USER_PASSWORD = "password" + + fun createCoreLogic(mockEngine: MockEngine) = CoreLogic( + rootPath = "$HOME_DIRECTORY/.kalium/accounts-test", + kaliumConfigs = KaliumConfigs( + developmentApiEnabled = true, + encryptProteusStorage = true, + isMLSSupportEnabled = true, + wipeOnDeviceRemoval = true, + kaliumMockEngine = KaliumMockEngine(mockEngine = mockEngine), + mockNetworkStateObserver = TestNetworkStateObserver.DEFAULT_TEST_NETWORK_STATE_OBSERVER + ), "Wire Integration Tests" + ) + } +} diff --git a/tango-tests/src/integrationTest/kotlin/TestNetworkStateObserver.kt b/tango-tests/src/integrationTest/kotlin/TestNetworkStateObserver.kt new file mode 100644 index 00000000000..2d649431f44 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/TestNetworkStateObserver.kt @@ -0,0 +1,35 @@ +/* + * 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/. + */ + +import com.wire.kalium.network.NetworkState +import com.wire.kalium.network.NetworkStateObserver +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class TestNetworkStateObserver(initialState: NetworkState = NetworkState.ConnectedWithInternet) : NetworkStateObserver { + + private val networkState = MutableStateFlow(initialState) + + override fun observeNetworkState(): MutableStateFlow = networkState + + suspend fun updateNetworkState(state: NetworkState) { networkState.emit(state) } + + companion object { + val DEFAULT_TEST_NETWORK_STATE_OBSERVER = TestNetworkStateObserver() + } +} diff --git a/tango-tests/src/integrationTest/kotlin/action/ACMEActions.kt b/tango-tests/src/integrationTest/kotlin/action/ACMEActions.kt new file mode 100644 index 00000000000..fbf60941aca --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/action/ACMEActions.kt @@ -0,0 +1,74 @@ +/* + * 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 action + +import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.network.api.base.unbound.acme.AcmeDirectoriesResponse +import com.wire.kalium.network.exceptions.KaliumException +import com.wire.kalium.network.utils.NetworkResponse +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import util.MockUnboundNetworkClient +import kotlin.test.assertEquals +import kotlin.test.assertIs + +object ACMEActions { + + suspend fun acmeDirectoriesErrorNotConnected(coreLogic: CoreLogic) = coreLogic.getGlobalScope() + .unboundNetworkContainer + .acmeApi.getACMEDirectories().also { actual -> + assertIs(actual) + assertIs(actual.kException.cause) + } + + suspend fun acmeDirectoriesSuccess(coreLogic: CoreLogic, expected: AcmeDirectoriesResponse = ACME_DIRECTORIES_SAMPLE) = + coreLogic.getGlobalScope() + .unboundNetworkContainer + .acmeApi.getACMEDirectories().also { actual -> + assertIs>(actual) + assertEquals(expected, actual.value) + } + + suspend fun acmeDirectoriesConnectNoInternet(coreLogic: CoreLogic) = coreLogic.getGlobalScope().unboundNetworkContainer + .acmeApi.getACMEDirectories().also { actual -> + assertIs(actual) + assertIs(actual.kException.cause) + } + + /** + * URL Paths + */ + private const val ACME_DIRECTORIES_PATH = "https://balderdash.hogwash.work:9000/acme/google-android/directory" + + /** + * JSON Response + */ + private val ACME_DIRECTORIES_RESPONSE = ACMEApiResponseJsonSample.validAcmeDirectoriesResponse + private val ACME_DIRECTORIES_SAMPLE = ACMEApiResponseJsonSample.ACME_DIRECTORIES_SAMPLE + + /** + * Request / Responses + */ + + val acmeGetDirectoriesRequestSuccess = MockUnboundNetworkClient.TestRequestHandler( + path = ACME_DIRECTORIES_PATH, + httpMethod = HttpMethod.Get, + responseBody = ACME_DIRECTORIES_RESPONSE.rawJson, + statusCode = HttpStatusCode.OK + ) +} diff --git a/tango-tests/src/integrationTest/kotlin/action/ClientActions.kt b/tango-tests/src/integrationTest/kotlin/action/ClientActions.kt new file mode 100644 index 00000000000..5dd768d136c --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/action/ClientActions.kt @@ -0,0 +1,87 @@ +/* + * 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 action + +import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.logic.data.client.ClientType +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.UserSessionScope +import com.wire.kalium.logic.feature.client.RegisterClientResult +import com.wire.kalium.logic.feature.client.RegisterClientUseCase +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import util.ClientResponseJson +import util.ListOfClientsResponseJson +import util.MockUnboundNetworkClient + +object ClientActions { + + suspend fun registerClient( + password: String, + userId: UserId, + coreLogic: CoreLogic + ): UserSessionScope { + val userSession = coreLogic.getSessionScope(userId) + val registerClientParam = RegisterClientUseCase.RegisterClientParam( + password = password, + capabilities = emptyList(), + clientType = ClientType.Temporary + ) + val registerResult = userSession.client.getOrRegister(registerClientParam) + if (registerResult is RegisterClientResult.Failure) { + error("Failed registering client of monkey : $registerResult") + } + + return userSession + } + + /** + * URL Paths + */ + private const val PATH_CLIENTS = "${CommonResponses.BASE_PATH_V1}clients" + private const val PATH_ACCESS = "${CommonResponses.BASE_PATH_V1}access" + + /** + * Request / Responses + */ + private val registerClientsRequestSuccess = MockUnboundNetworkClient.TestRequestHandler( + path = PATH_CLIENTS, + httpMethod = HttpMethod.Post, + responseBody = ClientResponseJson.valid.rawJson, + statusCode = HttpStatusCode.OK, + ) + private val getClientsRequestSuccess = MockUnboundNetworkClient.TestRequestHandler( + path = PATH_CLIENTS, + httpMethod = HttpMethod.Get, + responseBody = ListOfClientsResponseJson.valid.rawJson, + statusCode = HttpStatusCode.OK, + ) + private val accessApiRequestSuccess = MockUnboundNetworkClient.TestRequestHandler( + path = PATH_ACCESS, + httpMethod = HttpMethod.Post, + responseBody = CommonResponses.VALID_ACCESS_TOKEN_RESPONSE.rawJson, + statusCode = HttpStatusCode.OK, + ) + + val clientRequestResponseSuccess = listOf( + registerClientsRequestSuccess, + getClientsRequestSuccess, + accessApiRequestSuccess + ) + +} diff --git a/tango-tests/src/integrationTest/kotlin/action/CommonResponses.kt b/tango-tests/src/integrationTest/kotlin/action/CommonResponses.kt new file mode 100644 index 00000000000..4c77272112f --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/action/CommonResponses.kt @@ -0,0 +1,66 @@ +/* + * 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 action + +import com.wire.kalium.network.api.base.model.AccessTokenDTO +import com.wire.kalium.network.api.base.model.QualifiedID +import util.AccessTokenDTOJson + +/* + * 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/. + */ +object CommonResponses { + + /** + * URL Paths + */ + const val BASE_PATH = "https://test.api.com/" + const val BASE_PATH_V1 = "${BASE_PATH}v1/" + + /** + * DTO + */ + val userID = QualifiedID("user_id", "user.domain.io") + private val accessTokenDTO = AccessTokenDTO( + userId = userID.value, + value = "Nlrhltkj-NgJUjEVevHz8Ilgy_pyWCT2b0kQb-GlnamyswanghN9DcC3an5RUuA7sh1_nC3hv2ZzMRlIhPM7Ag==.v=1.k=1.d=1637254939." + + "t=a.l=.u=75ebeb16-a860-4be4-84a7-157654b492cf.c=18401233206926541098", + expiresIn = 900, + tokenType = "Bearer" + ) + + /** + * JSON Response + */ + const val REFRESH_TOKEN = "415a5306-a476-41bc-af36-94ab075fd881" + val VALID_ACCESS_TOKEN_RESPONSE = AccessTokenDTOJson.createValid(accessTokenDTO) +} diff --git a/tango-tests/src/integrationTest/kotlin/action/LoginActions.kt b/tango-tests/src/integrationTest/kotlin/action/LoginActions.kt new file mode 100644 index 00000000000..179654c4846 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/action/LoginActions.kt @@ -0,0 +1,131 @@ +/* + * 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 action + +import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase +import com.wire.kalium.logic.feature.auth.AuthTokens +import com.wire.kalium.logic.feature.auth.AuthenticationResult +import com.wire.kalium.logic.feature.auth.AuthenticationScope +import com.wire.kalium.network.api.base.model.SelfUserDTO +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import util.ListUsersResponseJson +import util.MockUnboundNetworkClient +import util.ServerConfigDTOJson +import util.UserDTOJson + +object LoginActions { + + suspend fun loginAndAddAuthenticatedUser( + email: String, + password: String, + coreLogic: CoreLogic, + authScope: AuthenticationScope, + ): AuthTokens { + val loginResult = authScope.login(email, password, true) + if (loginResult !is AuthenticationResult.Success) { + error("User creds didn't work ($email, $password)") + } + + coreLogic.globalScope { + val storeResult = addAuthenticatedAccount( + serverConfigId = loginResult.serverConfigId, + ssoId = loginResult.ssoID, + authTokens = loginResult.authData, + proxyCredentials = loginResult.proxyCredentials, + replace = true + ) + if (storeResult !is AddAuthenticatedUserUseCase.Result.Success) { + error("Failed to store user. $storeResult") + } + } + + return loginResult.authData + } + + /** + * URL Paths + */ + private const val PATH_LOGIN = "${CommonResponses.BASE_PATH_V1}login?persist=true" + private const val PATH_SELF = "${CommonResponses.BASE_PATH_V1}self" + private const val PATH_LOGOUT = "${CommonResponses.BASE_PATH_V1}logout" + private const val PATH_API_VERSION = "${CommonResponses.BASE_PATH}api-version" + + /** + * JSON Response + */ + private val listUsersResponseJson = ListUsersResponseJson.v0 + private val selfUserDTO = SelfUserDTO( + id = CommonResponses.userID, + name = "user_name_123", + accentId = 2, + assets = listOf(), + deleted = null, + email = "test@testio.test", + handle = "mrtestio", + service = null, + teamId = null, + expiresAt = "2026-03-25T14:17:27.364Z", + nonQualifiedId = "", + locale = "", + managedByDTO = null, + phone = null, + ssoID = null + ) + private val VALID_SELF_RESPONSE = UserDTOJson.createValid(selfUserDTO) + + /** + * Requests / Responses + */ + private val loginRequestSuccess = MockUnboundNetworkClient.TestRequestHandler( + path = PATH_LOGIN, + responseBody = CommonResponses.VALID_ACCESS_TOKEN_RESPONSE.rawJson, + statusCode = HttpStatusCode.OK, + httpMethod = HttpMethod.Post, + headers = mapOf("set-cookie" to "zuid=${CommonResponses.REFRESH_TOKEN}") + ) + + private val selfRequestSuccess = MockUnboundNetworkClient.TestRequestHandler( + path = PATH_SELF, + responseBody = VALID_SELF_RESPONSE.rawJson, + httpMethod = HttpMethod.Get, + statusCode = HttpStatusCode.OK, + ) + + private val userDetailsRequestSuccess = MockUnboundNetworkClient.TestRequestHandler( + path = PATH_LOGOUT, + responseBody = listUsersResponseJson.rawJson, + httpMethod = HttpMethod.Post, + statusCode = HttpStatusCode.Forbidden, + ) + + private val apiVersionRequestSuccess = MockUnboundNetworkClient.TestRequestHandler( + path = PATH_API_VERSION, + responseBody = ServerConfigDTOJson.validServerConfigResponse.rawJson, + httpMethod = HttpMethod.Get, + statusCode = HttpStatusCode.OK, + ) + + val loginRequestResponseSuccess = listOf( + loginRequestSuccess, + selfRequestSuccess, + userDetailsRequestSuccess, + apiVersionRequestSuccess + ) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/ACMEApiResponseJsonSample.kt b/tango-tests/src/integrationTest/kotlin/util/ACMEApiResponseJsonSample.kt new file mode 100644 index 00000000000..7ad1b288b24 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/ACMEApiResponseJsonSample.kt @@ -0,0 +1,75 @@ + +import com.wire.kalium.network.api.base.unbound.acme.AcmeDirectoriesResponse +import com.wire.kalium.network.api.base.unbound.acme.ChallengeResponse +import util.ValidJsonProvider + +object ACMEApiResponseJsonSample { + + const val ACME_BASE_URL = "https://balderdash.hogwash.work:9000" + + + val ACME_DIRECTORIES_SAMPLE = AcmeDirectoriesResponse( + newNonce = "$ACME_BASE_URL/acme/wire/new-nonce", + newAccount = "$ACME_BASE_URL/acme/wire/new-account", + newOrder = "$ACME_BASE_URL/acme/wire/new-order", + revokeCert = "$ACME_BASE_URL/acme/wire/revoke-cert", + keyChange = "$ACME_BASE_URL/acme/wire/key-change" + ) + + private val jsonProviderAcmeDirectories = { serializable: AcmeDirectoriesResponse -> + """ + |{ + | "newNonce": "${serializable.newNonce}", + | "newAccount": "${serializable.newAccount}", + | "newOrder": "${serializable.newOrder}" + | "revokeCert": "${serializable.revokeCert}" + | "keyChange": "${serializable.keyChange}" + |} + """.trimMargin() + } + + val validAcmeDirectoriesResponse = ValidJsonProvider( + ACME_DIRECTORIES_SAMPLE, + jsonProviderAcmeDirectories + ) + + private val jsonProviderAcmeResponse = { serializable: AcmeDirectoriesResponse -> + """ + |{ + | "sampleBody": "sample", + |} + """.trimMargin() + } + + val ACME_RESPONSE_SAMPLE = ValidJsonProvider( + ACME_DIRECTORIES_SAMPLE, + jsonProviderAcmeResponse + ) + + val ACME_CHALLENGE_RESPONSE_SAMPLE = ChallengeResponse( + type = "wire-dpop-01", + url = "https://example.com/acme/chall/prV_B7yEyA4", + status = "valid", + token = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0", + nonce = "random-nonce" + ) + + private val jsonProviderAcmeChallenge = { serializable: ChallengeResponse -> + """ + |{ + | "type": "${serializable.type}", + | "url": "${serializable.url}", + | "status": "${serializable.status}", + | "token": "${serializable.token}" + | "nonce": "${serializable.nonce}" + |} + """.trimMargin() + } + + val jsonProviderAcmeChallengeResponse = ValidJsonProvider( + ACME_CHALLENGE_RESPONSE_SAMPLE, + jsonProviderAcmeChallenge + ) + + +} diff --git a/tango-tests/src/integrationTest/kotlin/util/AccessTokenDTOJson.kt b/tango-tests/src/integrationTest/kotlin/util/AccessTokenDTOJson.kt new file mode 100644 index 00000000000..6f724fcefa4 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/AccessTokenDTOJson.kt @@ -0,0 +1,83 @@ +/* + * 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 util + +import com.wire.kalium.network.api.base.model.AccessTokenDTO + +object AccessTokenDTOJson { + private val jsonProvider = { serializable: AccessTokenDTO -> + """ + |{ + | "expires_in": ${serializable.expiresIn}, + | "access_token": "${serializable.value}", + | "user": "${serializable.userId}", + | "token_type": "${serializable.tokenType}" + |} + """.trimMargin() + } + + fun createValid(accessTokenDTO: AccessTokenDTO) = ValidJsonProvider(accessTokenDTO, jsonProvider) + + val valid = ValidJsonProvider( + AccessTokenDTO( + userId = "user_id", + value = "Nlrhltkj-NgJUjEVevHz8Ilgy_pyWCT2b0kQb-GlnamyswanghN9DcC3an5RUuA7sh1_nC3hv2ZzMRlIhPM7Ag==.v=1.k=1.d=1637254939." + + "t=a.l=.u=75ebeb16-a860-4be4-84a7-157654b492cf.c=18401233206926541098", + expiresIn = 900, + tokenType = "Bearer" + ), jsonProvider + ) + + val missingAccessToken = FaultyJsonProvider( + """ + |{ + | "expires_in": 900, + | "user": "75ebeb16-a860-4be4-84a7-157654b492", + | "token_type": "Bearer" + |} + """.trimMargin() + ) + val missingTokenType = FaultyJsonProvider( + """ + |{ + | "expires_in": 900, + | "access_token": "Nlrhltkj-NgJUjEVevHz8Ilgy_pyWCT2b0kQb-GlnamyswanghN9DcC3an5RUuA7sh1_nC3hv2ZzMRlIhPM7Ag==.v=1.k=1.d=1637254939.t=a.l=.u=75ebeb16-a860-4be4-84a7-157654b492cf.c=18401233206926541098", + | "user": "75ebeb16-a860-4be4-84a7-157654b492" + |} + """.trimMargin() + ) + val missingUser = FaultyJsonProvider( + """ + |{ + | "expires_in": 900, + | "access_token": "Nlrhltkj-NgJUjEVevHz8Ilgy_pyWCT2b0kQb-GlnamyswanghN9DcC3an5RUuA7sh1_nC3hv2ZzMRlIhPM7Ag==.v=1.k=1.d=1637254939.t=a.l=.u=75ebeb16-a860-4be4-84a7-157654b492cf.c=18401233206926541098", + | "token_type": "Bearer" + |} + """.trimMargin() + ) + val missingExpiration = FaultyJsonProvider( + """ + |{ + | "access_token": "Nlrhltkj-NgJUjEVevHz8Ilgy_pyWCT2b0kQb-GlnamyswanghN9DcC3an5RUuA7sh1_nC3hv2ZzMRlIhPM7Ag==.v=1.k=1.d=1637254939.t=a.l=.u=75ebeb16-a860-4be4-84a7-157654b492cf.c=18401233206926541098", + | "user": "75ebeb16-a860-4be4-84a7-157654b492", + | "token_type": "Bearer" + |} + """.trimMargin() + ) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/ClientResponseJson.kt b/tango-tests/src/integrationTest/kotlin/util/ClientResponseJson.kt new file mode 100644 index 00000000000..fceb88bcb55 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/ClientResponseJson.kt @@ -0,0 +1,71 @@ +/* + * 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 util + +import com.wire.kalium.network.api.base.authenticated.client.Capabilities +import com.wire.kalium.network.api.base.authenticated.client.ClientCapabilityDTO +import com.wire.kalium.network.api.base.authenticated.client.ClientDTO +import com.wire.kalium.network.api.base.authenticated.client.ClientTypeDTO +import com.wire.kalium.network.api.base.authenticated.client.DeviceTypeDTO +import com.wire.kalium.network.api.base.model.LocationResponse + +object ClientResponseJson { + private val jsonProvider = { serializable: ClientDTO -> + """ + |{ + | "id": "${serializable.clientId}", + | "type": "${serializable.type}", + | "time": "${serializable.registrationTime}", + | "last_active": "${serializable.lastActive}", + | "class": "${serializable.deviceType}", + | "label": "${serializable.label}", + | "cookie": "${serializable.cookie}", + | "location": { + | "lat": ${serializable.location!!.latitude}, + | "lon": ${serializable.location!!.longitude} + | }, + | "model": "${serializable.model}", + | "capabilities": { + | "capabilities": [ + | "${serializable.capabilities!!.capabilities[0]}" + | ] + | } + |} + """.trimMargin() + } + + val valid = ValidJsonProvider( + ClientDTO( + clientId = "defkrr8e7grgsoufhg8", + type = ClientTypeDTO.Permanent, + deviceType = DeviceTypeDTO.Phone, + registrationTime = "2023-05-12T10:52:02.671Z", + lastActive = "2023-05-12T10:52:02.671Z", + location = LocationResponse(latitude = "1.2345", longitude = "6.7890"), + label = "label", + cookie = "sldkfmdeklmwldwlek23kl44mntiuepfojfndkjd", + capabilities = Capabilities(listOf(ClientCapabilityDTO.LegalHoldImplicitConsent)), + model = "model", + mlsPublicKeys = null + ), + jsonProvider + ) + + fun createValid(client: ClientDTO) = ValidJsonProvider(client, jsonProvider) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/ErrorResponseJson.kt b/tango-tests/src/integrationTest/kotlin/util/ErrorResponseJson.kt new file mode 100644 index 00000000000..596cf100ecb --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/ErrorResponseJson.kt @@ -0,0 +1,57 @@ +/* + * 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 util + +import com.wire.kalium.network.api.base.model.ErrorResponse +import com.wire.kalium.network.api.base.model.FederationConflictResponse + +object ErrorResponseJson { + private val jsonProvider = { serializable: ErrorResponse -> + """ + |{ + | "code": ${serializable.code}, + | "label": "${serializable.label}", + | "message": "${serializable.message}" + |} + """.trimMargin() + } + + private val federationConflictJsonProvider = { serializable: FederationConflictResponse -> + """ + |{ + | "non_federating_backends": ${serializable.nonFederatingBackends} + |} + """.trimMargin() + } + + val valid = ValidJsonProvider( + ErrorResponse(code = 499, label = "error_label", message = "error_message"), + jsonProvider + ) + + fun valid(error: ErrorResponse) = ValidJsonProvider( + error, + jsonProvider + ) + + fun validFederationConflictingBackends(error: FederationConflictResponse) = ValidJsonProvider( + error, + federationConflictJsonProvider + ) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/ListOfClientsResponseJson.kt b/tango-tests/src/integrationTest/kotlin/util/ListOfClientsResponseJson.kt new file mode 100644 index 00000000000..1995cd90eda --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/ListOfClientsResponseJson.kt @@ -0,0 +1,71 @@ +/* + * 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 util + +import com.wire.kalium.network.api.base.authenticated.client.Capabilities +import com.wire.kalium.network.api.base.authenticated.client.ClientCapabilityDTO +import com.wire.kalium.network.api.base.authenticated.client.ClientDTO +import com.wire.kalium.network.api.base.authenticated.client.ClientTypeDTO +import com.wire.kalium.network.api.base.authenticated.client.DeviceTypeDTO +import com.wire.kalium.network.api.base.model.LocationResponse + +object ListOfClientsResponseJson { + private val jsonProvider = { serializable: ClientDTO -> + """ + |[{ + | "id": "${serializable.clientId}", + | "type": "${serializable.type}", + | "time": "${serializable.registrationTime}", + | "last_active": "${serializable.lastActive}", + | "class": "${serializable.deviceType}", + | "label": "${serializable.label}", + | "cookie": "${serializable.cookie}", + | "location": { + | "lat": ${serializable.location!!.latitude}, + | "lon": ${serializable.location!!.longitude} + | }, + | "model": "${serializable.model}", + | "capabilities": { + | "capabilities": [ + | "${serializable.capabilities!!.capabilities[0]}" + | ] + | } + |}] + """.trimMargin() + } + + val valid = ValidJsonProvider( + ClientDTO( + clientId = "defkrr8e7grgsoufhg8", + type = ClientTypeDTO.Permanent, + deviceType = DeviceTypeDTO.Phone, + registrationTime = "2023-05-12T10:52:02.671Z", + lastActive = "2023-05-12T10:52:02.671Z", + location = LocationResponse(latitude = "1.2345", longitude = "6.7890"), + label = "label", + cookie = "sldkfmdeklmwldwlek23kl44mntiuepfojfndkjd", + capabilities = Capabilities(listOf(ClientCapabilityDTO.LegalHoldImplicitConsent)), + model = "model", + mlsPublicKeys = null + ), + jsonProvider + ) + + fun createValid(client: ClientDTO) = ValidJsonProvider(client, jsonProvider) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/ListUsersResponseJson.kt b/tango-tests/src/integrationTest/kotlin/util/ListUsersResponseJson.kt new file mode 100644 index 00000000000..33453af2308 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/ListUsersResponseJson.kt @@ -0,0 +1,127 @@ +/* + * 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 util + +import com.wire.kalium.network.api.base.authenticated.userDetails.ListUsersDTO +import com.wire.kalium.network.api.base.model.LegalHoldStatusResponse +import com.wire.kalium.network.api.base.model.UserId +import com.wire.kalium.network.api.base.model.UserProfileDTO + +object ListUsersResponseJson { + + private val USER_1 = UserId("user10d0-000b-9c1a-000d-a4130002c221", "domain1.example.com") + private val USER_2 = UserId("user20d0-000b-9c1a-000d-a4130002c221", "domain2.example.com") + private val USER_3 = UserId("user30d0-000b-9c1a-000d-a4130002c220", "domain3.example.com") + + private val expectedUsersResponse = listOf( + UserProfileDTO( + id = USER_1, + name = "usera", + handle = "user_a", + accentId = 2147483647, + legalHoldStatus = LegalHoldStatusResponse.ENABLED, + teamId = null, + assets = emptyList(), + deleted = false, + email = null, + expiresAt = null, + nonQualifiedId = USER_1.value, + service = null + ), + UserProfileDTO( + id = USER_2, + name = "userb", + handle = "user_b", + accentId = 2147483647, + legalHoldStatus = LegalHoldStatusResponse.ENABLED, + teamId = null, + assets = emptyList(), + deleted = false, + email = null, + expiresAt = null, + nonQualifiedId = USER_2.value, + service = null + ), + ) + + private val validUserInfoProvider = { userInfo: List -> + """ + |[ + | { + | "accent_id": ${userInfo[0].accentId}, + | "handle": "${userInfo[0].handle}", + | "legalhold_status": "enabled", + | "name": "${userInfo[0].name}", + | "assets": ${userInfo[0].assets}, + | "id": "${userInfo[0].id.value}", + | "deleted": "false", + | "qualified_id": { + | "domain": "${userInfo[0].id.domain}", + | "id": "${userInfo[0].id.value}" + | } + | }, + | { + | "accent_id": ${userInfo[1].accentId}, + | "handle": "${userInfo[1].handle}", + | "legalhold_status": "enabled", + | "name": "${userInfo[1].name}", + | "assets": ${userInfo[1].assets}, + | "id": "${userInfo[1].id.value}", + | "deleted": "false", + | "qualified_id": { + | "domain": "${userInfo[1].id.domain}", + | "id": "${userInfo[1].id.value}" + | } + | } + |] + """.trimMargin() + } + + val v0 = ValidJsonProvider( + expectedUsersResponse + ) { + validUserInfoProvider(it) + } + + val v4_withFailedUsers = ValidJsonProvider( + ListUsersDTO(listOf(USER_3), expectedUsersResponse) + ) { + """ + |{ + | "failed": [ + | { + | "domain": "${it.usersFailed[0].domain}", + | "id": "${it.usersFailed[0].value}" + | } + | ], + | "found": ${validUserInfoProvider(it.usersFound)} + |} + """.trimMargin() + } + + val v4 = ValidJsonProvider( + ListUsersDTO(listOf(USER_3), expectedUsersResponse) + ) { + """ + |{ + | "found": ${validUserInfoProvider(it.usersFound)} + |} + """.trimMargin() + } +} diff --git a/tango-tests/src/integrationTest/kotlin/util/LoginWithEmailRequestJson.kt b/tango-tests/src/integrationTest/kotlin/util/LoginWithEmailRequestJson.kt new file mode 100644 index 00000000000..b620f5eb86f --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/LoginWithEmailRequestJson.kt @@ -0,0 +1,79 @@ +/* + * 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 util + +import com.wire.kalium.network.api.base.unauthenticated.LoginApi +import kotlinx.serialization.json.buildJsonObject + +object LoginWithEmailRequestJson { + private val jsonProvider = { serializable: LoginApi.LoginParam -> + buildJsonObject { + "password" to serializable.password + "label" to serializable.label + when (serializable) { + is LoginApi.LoginParam.LoginWithEmail -> "email" to serializable.email + is LoginApi.LoginParam.LoginWithHandle -> "handle" to serializable.handle + } + }.toString() + } + + val validLoginWithEmail = ValidJsonProvider( + LoginApi.LoginParam.LoginWithEmail( + email = "user@email.de", + label = "label", + password = "password", + verificationCode = "verificationCode" + ), jsonProvider + ) + + val validLoginWithHandle = ValidJsonProvider( + LoginApi.LoginParam.LoginWithHandle( + handle = "cool_user_name", + label = "label", + password = "password", + ), jsonProvider + ) + + val missingEmailAndHandel = FaultyJsonProvider( + """ + |{ + | "label": "label", + | "password": "password", + |} + """.trimMargin() + ) + + val missingLabel = FaultyJsonProvider( + """ + |{ + | "email": "user@email.de", + | "password": "password", + |} + """.trimMargin() + ) + + val missingPassword = FaultyJsonProvider( + """ + |{ + | "label": "label", + | "email": "user@email.de", + |} + """.trimMargin() + ) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/MockUnboundNetworkClient.kt b/tango-tests/src/integrationTest/kotlin/util/MockUnboundNetworkClient.kt new file mode 100644 index 00000000000..05c88dcee15 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/MockUnboundNetworkClient.kt @@ -0,0 +1,158 @@ +/* + * 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 util + +import com.wire.kalium.network.networkContainer.KaliumUserAgentProvider +import com.wire.kalium.network.tools.ApiVersionDTO +import com.wire.kalium.network.tools.ServerConfigDTO +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.respond +import io.ktor.client.request.HttpRequestData +import io.ktor.http.HeadersImpl +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.utils.io.ByteReadChannel +import kotlin.test.fail + +/* + * 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/. + */ +object MockUnboundNetworkClient { + init { + KaliumUserAgentProvider.setUserAgent("test/useragent") + } + + /** + * Creates a mock Ktor Http client + * @param responseBody the response body as a ByteArray + * @param statusCode the response http status code + * @param assertion lambda function to apply assertions to the request + * @return mock Ktor http client + */ + class TestRequestHandler( + val path: String, + val responseBody: String, + val statusCode: HttpStatusCode, + val assertion: (HttpRequestData.() -> Unit) = {}, + val headers: Map? = null, + val httpMethod: HttpMethod? = null + ) + + fun createMockEngine( + expectedRequests: List + ): MockEngine = MockEngine { currentRequest -> + expectedRequests.forEach { request -> + val head: Map> = (request.headers?.let { + mutableMapOf(HttpHeaders.ContentType to "application/json").plus(request.headers).mapValues { listOf(it.value) } + } ?: run { + mapOf(HttpHeaders.ContentType to "application/json").mapValues { listOf(it.value) } + }) + if (request.path == currentRequest.url.toString() && request.httpMethod == currentRequest.method) { + return@MockEngine respond( + content = ByteReadChannel(request.responseBody), + status = request.statusCode, + headers = HeadersImpl(head) + ) + } + } + fail("no expected response was found for ${currentRequest.method.value}:${currentRequest.url}") + } + + fun createMockEngine2( + responseBody: String, + statusCode: HttpStatusCode, + assertion: (HttpRequestData.() -> Unit) = {}, + headers: Map? = null + ): MockEngine { + + val newHeaders: Map> = (headers?.let { + headers.mapValues { listOf(it.value) } + } ?: run { + mapOf(HttpHeaders.ContentType to "application/json").mapValues { listOf(it.value) } + }) + + return MockEngine { request -> + request.assertion() + respond( + content = ByteReadChannel(responseBody), + status = statusCode, + headers = HeadersImpl(newHeaders) + ) + } + } + + val TEST_BACKEND_CONFIG = + ServerConfigDTO( + id = "id", + ServerConfigDTO.Links( + "https://test.api.com", + "https://test.account.com", + "https://test.ws.com", + "https://test.blacklist", + "https://test.teams.com", + "https://test.wire.com", + "Test Title", + false, + null + ), + ServerConfigDTO.MetaData( + false, + ApiVersionDTO.Valid(1), + null + ) + ) + + val TEST_BACKEND_LINKS = + ServerConfigDTO.Links( + "https://test.api.com", + "https://test.account.com", + "https://test.ws.com", + "https://test.blacklist", + "https://test.teams.com", + "https://test.wire.com", + "Test Title", + false, + null + ) + + val TEST_BACKEND = + ServerConfigDTO( + id = "id", + links = TEST_BACKEND_LINKS, + metaData = ServerConfigDTO.MetaData( + false, + ApiVersionDTO.Valid(0), + domain = null + ) + ) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/RegisterClientRequestJson.kt b/tango-tests/src/integrationTest/kotlin/util/RegisterClientRequestJson.kt new file mode 100644 index 00000000000..0d7978a8628 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/RegisterClientRequestJson.kt @@ -0,0 +1,296 @@ +/* + * 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 util + +import com.wire.kalium.network.api.base.authenticated.client.ClientCapabilityDTO +import com.wire.kalium.network.api.base.authenticated.client.ClientTypeDTO +import com.wire.kalium.network.api.base.authenticated.client.DeviceTypeDTO +import com.wire.kalium.network.api.base.authenticated.client.RegisterClientRequest +import com.wire.kalium.network.api.base.authenticated.prekey.PreKeyDTO +import kotlinx.serialization.json.add +import kotlinx.serialization.json.addJsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray +import kotlinx.serialization.json.putJsonObject + +object RegisterClientRequestJson { + + private val jsonProvider = { serializable: RegisterClientRequest -> + buildJsonObject { + // FIXME: Data is being created without being added to the json object + // Doing "key" to "value" does not add the key to the json object. + // We need to call put("key", "value") instead. + putJsonArray("prekeys") { + serializable.preKeys.forEach { + addJsonObject { + "id" to it.id + "key" to it.key + } + } + } + putJsonObject("lastkey") { + "id" to serializable.lastKey.id + "key" to serializable.lastKey.key + } + "type" to serializable.type + serializable.password?.let { "password" to it } + serializable.deviceType?.let { "class" to it } + serializable.label?.let { "label" to it } + serializable.model?.let { "model" to it } + serializable.cookieLabel?.let { "cookie" to it } + serializable.capabilities?.let { + putJsonArray("capabilities") { + it.forEach { clientCapabilityDTO -> + add(clientCapabilityDTO.toString()) + } + } + } + serializable.secondFactorVerificationCode?.let { + put("verification_code", it) + } + }.toString() + } + + val valid = ValidJsonProvider( + RegisterClientRequest( + password = "password", + deviceType = DeviceTypeDTO.Desktop, + type = ClientTypeDTO.Permanent, + label = "label", + preKeys = listOf(PreKeyDTO(1, "preykey_1"), PreKeyDTO(2, "prekey_2")), + lastKey = PreKeyDTO(999, "last_prekey"), + capabilities = listOf(ClientCapabilityDTO.LegalHoldImplicitConsent), + model = "model", + cookieLabel = "cookie label", + secondFactorVerificationCode = "123456" + ), + jsonProvider + ) + + val missingDeviceType = FaultyJsonProvider( + """ + |{ + | "label": "label", + | "lastkey": { + | "key": "last_prekey", + | "id": 9999 + | }, + | "password": "password", + | "prekeys": [ + | { + | "id": 1, + | "key": "prekey_1" + | }, + | { + | "id": 2, + | "key": "prekey_2"" + | } + | ], + | "type": "permanent", + | "capabilities": [ + | "legalhold-implicit-consent" + | ], + | "model": "model" + |} + """.trimMargin() + ) + + val missingLabel = FaultyJsonProvider( + """ + |{ + | "class": "desktop", + | "lastkey": { + | "key": "last_prekey", + | "id": 9999 + | }, + | "password": "password", + | "prekeys": [ + | { + | "id": 1, + | "key": "prekey_1" + | }, + | { + | "id": 2, + | "key": "prekey_2"" + | } + | ], + | "type": "desktop", + | "capabilities": [ + | "legalhold-implicit-consent" + | ], + | "model": "model" + |} + """.trimMargin() + ) + + val missingPassword = FaultyJsonProvider( + """ + |{ + | "class": "desktop", + | "label": "label", + | "lastkey": { + | "key": "last_prekey", + | "id": 9999 + | }, + | "prekeys": [ + | { + | "id": 1, + | "key": "prekey_1" + | }, + | { + | "id": 2, + | "key": "prekey_2"" + | } + | ], + | "type": "desktop", + | "capabilities": [ + | "legalhold-implicit-consent" + | ], + | "model": "model" + |} + """.trimMargin() + ) + + val missingTye = FaultyJsonProvider( + """ + |{ + | "class": "desktop", + | "label": "label", + | "lastkey": { + | "key": "last_prekey", + | "id": 9999 + | }, + | "password": "password", + | "prekeys": [ + | { + | "id": 1, + | "key": "prekey_1" + | }, + | { + | "id": 2, + | "key": "prekey_2"" + | } + | ], + | "capabilities": [ + | "legalhold-implicit-consent" + | ], + | "model": "model" + |} + """.trimMargin() + ) + + val missingPreKeys = FaultyJsonProvider( + """ + |{ + | "class": "desktop", + | "label": "label", + | "lastkey": { + | "key": "last_prekey", + | "id": 9999 + | }, + | "password": "password", + | "type": "desktop", + | "capabilities": [ + | "legalhold-implicit-consent" + | ], + | "model": "model" + |} + """.trimMargin() + ) + + val missingLastKey = FaultyJsonProvider( + """ + |{ + | "class": "desktop", + | "label": "label", + | "password": "password", + | "prekeys": [ + | { + | "id": 1, + | "key": "prekey_1" + | }, + | { + | "id": 2, + | "key": "prekey_2"" + | } + | ], + | "type": "desktop", + | "capabilities": [ + | "legalhold-implicit-consent" + | ], + | "model": "model" + |} + """.trimMargin() + ) + + val missingCapabilities = FaultyJsonProvider( + """ + |{ + | "class": "desktop", + | "label": "label", + | "lastkey": { + | "key": "last_prekey", + | "id": 9999 + | }, + | "password": "password", + | "prekeys": [ + | { + | "id": 1, + | "key": "prekey_1" + | }, + | { + | "id": 2, + | "key": "prekey_2"" + | } + | ], + | "type": "permanent", + | "model": "model" + |} + """.trimMargin() + ) + + val missingModel = FaultyJsonProvider( + """ + |{ + | "class": "desktop", + | "label": "label", + | "lastkey": { + | "key": "last_prekey", + | "id": 9999 + | }, + | "password": "password", + | "prekeys": [ + | { + | "id": 1, + | "key": "prekey_1" + | }, + | { + | "id": 2, + | "key": "prekey_2"" + | } + | ], + | "type": "permanent", + | "capabilities": [ + | "legalhold-implicit-consent" + | ] + |} + """.trimMargin() + ) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/ServerConfigDTOJson.kt b/tango-tests/src/integrationTest/kotlin/util/ServerConfigDTOJson.kt new file mode 100644 index 00000000000..e2555472e89 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/ServerConfigDTOJson.kt @@ -0,0 +1,43 @@ +/* + * 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 util + +import com.wire.kalium.network.tools.ApiVersionDTO +import com.wire.kalium.network.tools.ServerConfigDTO +import io.ktor.websocket.Serializer + +object ServerConfigDTOJson { + + private val jsonProviderServerConfig = { serializable: Serializer-> + """ + |{ + | "domain":"example.com", + | "federation":false, + | "supported":[ + | 0, + | 1 + | ] + |} + """.trimMargin() + } + + val validServerConfigResponse = ValidJsonProvider( + Serializer(), + jsonProviderServerConfig + ) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/UserDTOJson.kt b/tango-tests/src/integrationTest/kotlin/util/UserDTOJson.kt new file mode 100644 index 00000000000..5967b5efea8 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/UserDTOJson.kt @@ -0,0 +1,96 @@ +/* + * 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 util + +import com.wire.kalium.network.api.base.model.SelfUserDTO +import com.wire.kalium.network.api.base.model.UserId +import kotlinx.serialization.json.addJsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray +import kotlinx.serialization.json.putJsonObject + +object UserDTOJson { + + private val jsonProvider = { serializable: SelfUserDTO -> + buildJsonObject { + put("accent_id", serializable.accentId) + put("id", serializable.nonQualifiedId) + putJsonObject("qualified_id") { + put("id", serializable.id.value) + put("domain", serializable.id.domain) + } + put("name", serializable.name) + put("locale", serializable.locale) + putJsonArray("assets") { + if (serializable.assets.isNotEmpty()) { + addJsonObject { + serializable.assets.forEach { userAsset -> + put("key", userAsset.key) + put("type", userAsset.type.toString()) + userAsset.size?.let { put("size", it.toString()) } + } + } + } + } + serializable.deleted?.let { put("deleted", it) } + serializable.email?.let { put("email", it) } + serializable.phone?.let { put("phone", it) } + serializable.expiresAt?.let { put("expires_at", it) } + serializable.handle?.let { put("handle", it) } + serializable.service?.let { service -> + putJsonObject("service") { + put("id", service.id) + put("provider", service.provider) + } + } + serializable.teamId?.let { put("team", it) } + serializable.managedByDTO?.let { put("managed_by", it.toString()) } + serializable.ssoID?.let { userSsoID -> + putJsonObject("sso_id") { + userSsoID.subject?.let { put("subject", it) } + userSsoID.scimExternalId?.let { put("scim_external_id", it) } + userSsoID.tenant?.let { put("tenant", it) } + } + } + }.toString() + } + + fun createValid(userDTO: SelfUserDTO) = ValidJsonProvider(userDTO, jsonProvider) + + val valid = ValidJsonProvider( + SelfUserDTO( + id = UserId("user_id", "domain.com"), + name = "user_name_123", + accentId = 2, + assets = listOf(), + deleted = null, + email = null, + handle = null, + service = null, + teamId = null, + expiresAt = "", + nonQualifiedId = "", + locale = "", + managedByDTO = null, + phone = null, + ssoID = null + ), jsonProvider + ) +} diff --git a/tango-tests/src/integrationTest/kotlin/util/ValidSingleJsonObjectProvider.kt b/tango-tests/src/integrationTest/kotlin/util/ValidSingleJsonObjectProvider.kt new file mode 100644 index 00000000000..eeb0d2ec761 --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/ValidSingleJsonObjectProvider.kt @@ -0,0 +1,39 @@ +/* + * 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 util + +sealed interface JsonProvider { + val rawJson: String +} + +data class FaultyJsonProvider(override val rawJson: String) : JsonProvider + +data class ValidJsonProvider( + val serializableData: Serializable, + private val jsonProvider: (Serializable) -> String +) : JsonProvider { + override val rawJson: String = jsonProvider(serializableData).replace("\\s".toRegex(), "") +} + +data class AnyResponseProvider( + val data: T, + private val jsonProvider: (T) -> String +) : JsonProvider { + override val rawJson: String = jsonProvider(data).replace("\\s".toRegex(), "") +} diff --git a/tango-tests/src/integrationTest/kotlin/util/VersionInfoDTOJson.kt b/tango-tests/src/integrationTest/kotlin/util/VersionInfoDTOJson.kt new file mode 100644 index 00000000000..e6a943b639d --- /dev/null +++ b/tango-tests/src/integrationTest/kotlin/util/VersionInfoDTOJson.kt @@ -0,0 +1,49 @@ +/* + * 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 util + +import com.wire.kalium.network.api.base.unbound.versioning.VersionInfoDTO +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray + +object VersionInfoDTOJson { + private val defaultParametersJson = { serializable: VersionInfoDTO -> + buildJsonObject { + serializable.developmentSupported?.let { + putJsonArray("development") { + it.forEach { add(it) } + } + } + putJsonArray("supported") { + serializable.supported.forEach { add(it) } + } + put("federation", serializable.federation) + serializable.domain?.let { put("domain", it) } + }.toString() + } + + val valid404Result = VersionInfoDTO(null, null, false, listOf(0)) + + val valid = ValidJsonProvider( + VersionInfoDTO(listOf(1), "test.api.com", true, listOf(0, 1)), + defaultParametersJson + ) +}