From e271934a097c0e64d6085e11090286318ab10767 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Fri, 22 Nov 2024 09:31:37 +0100 Subject: [PATCH] feat: add use case to check if backend supports personal to team migration (WPB-12022) (#3114) * feat: add use case to check if backend supports personal to team migration * feat: detekt * chore: unit test * chore: address comment * chore: cleanup * chore: address comments * chore: address comments * chore: cleanup * chore: cleanup * chore: use selfTeamIdProvider --- .../server/ServerConfigRepository.kt | 18 ++- .../kalium/logic/feature/UserSessionScope.kt | 2 + .../CanMigrateFromPersonalToTeamUseCase.kt | 49 ++++++ .../kalium/logic/feature/user/UserScope.kt | 30 +++- .../logic/network/SessionManagerImpl.kt | 1 + ...CanMigrateFromPersonalToTeamUseCaseTest.kt | 149 ++++++++++++++++++ .../kalium/persistence/ServerConfiguration.sq | 3 + .../daokaliumdb/ServerConfigurationDAO.kt | 5 + 8 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/personaltoteamaccount/CanMigrateFromPersonalToTeamUseCase.kt create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/personaltoteamaccount/CanMigrateFromPersonalToTeamUseCaseTest.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt index 54f10b8df38..faef6688735 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt @@ -31,15 +31,18 @@ import com.wire.kalium.logic.functional.fold import com.wire.kalium.logic.functional.map import com.wire.kalium.logic.wrapApiRequest import com.wire.kalium.logic.wrapStorageRequest -import com.wire.kalium.network.api.unbound.configuration.ApiVersionDTO +import com.wire.kalium.network.api.base.authenticated.UpgradePersonalToTeamApi.Companion.MIN_API_VERSION import com.wire.kalium.network.api.base.unbound.versioning.VersionApi +import com.wire.kalium.network.api.unbound.configuration.ApiVersionDTO import com.wire.kalium.persistence.daokaliumdb.ServerConfigurationDAO import com.wire.kalium.util.KaliumDispatcher import com.wire.kalium.util.KaliumDispatcherImpl import io.ktor.http.Url import kotlinx.coroutines.withContext -internal interface ServerConfigRepository { +interface ServerConfigRepository { + val minimumApiVersionForPersonalToTeamAccountMigration: Int + suspend fun getOrFetchMetadata(serverLinks: ServerConfig.Links): Either suspend fun storeConfig(links: ServerConfig.Links, metadata: ServerConfig.MetaData): Either @@ -62,6 +65,7 @@ internal interface ServerConfigRepository { * Return the server links and metadata for the given userId */ suspend fun configForUser(userId: UserId): Either + suspend fun commonApiVersion(domain: String): Either } @Suppress("LongParameterList", "TooManyFunctions") @@ -72,6 +76,8 @@ internal class ServerConfigDataSource( private val dispatchers: KaliumDispatcher = KaliumDispatcherImpl ) : ServerConfigRepository { + override val minimumApiVersionForPersonalToTeamAccountMigration = MIN_API_VERSION + override suspend fun getOrFetchMetadata(serverLinks: ServerConfig.Links): Either = wrapStorageRequest { dao.configByLinks(serverConfigMapper.toEntity(serverLinks)) }.fold({ fetchApiVersionAndStore(serverLinks) @@ -127,13 +133,17 @@ internal class ServerConfigDataSource( } override suspend fun updateConfigApiVersion(serverConfig: ServerConfig): Either = - fetchMetadata(serverConfig.links) - .flatMap { wrapStorageRequest { dao.updateApiVersion(serverConfig.id, it.commonApiVersion.version) } } + fetchMetadata(serverConfig.links) + .flatMap { wrapStorageRequest { dao.updateApiVersion(serverConfig.id, it.commonApiVersion.version) } } override suspend fun configForUser(userId: UserId): Either = wrapStorageRequest { dao.configForUser(userId.toDao()) } .map { serverConfigMapper.fromEntity(it) } + override suspend fun commonApiVersion(domain: String): Either = wrapStorageRequest { + dao.getCommonApiVersion(domain) + } + private suspend fun fetchMetadata(serverLinks: ServerConfig.Links): Either = wrapApiRequest { versionApi.fetchApiVersion(Url(serverLinks.api)) } .flatMap { 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 ae927be25bb..451a14b6779 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 @@ -1889,6 +1889,8 @@ class UserSessionScope internal constructor( isE2EIEnabled, certificateRevocationListRepository, incrementalSyncRepository, + sessionManager, + selfTeamId, checkRevocationList, syncFeatureConfigsUseCase, userScopedLogger diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/personaltoteamaccount/CanMigrateFromPersonalToTeamUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/personaltoteamaccount/CanMigrateFromPersonalToTeamUseCase.kt new file mode 100644 index 00000000000..be386f4e509 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/personaltoteamaccount/CanMigrateFromPersonalToTeamUseCase.kt @@ -0,0 +1,49 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +@file:Suppress("konsist.useCasesShouldNotAccessNetworkLayerDirectly") + +package com.wire.kalium.logic.feature.personaltoteamaccount + +import com.wire.kalium.logic.configuration.server.ServerConfigRepository +import com.wire.kalium.logic.data.id.SelfTeamIdProvider +import com.wire.kalium.logic.functional.fold +import com.wire.kalium.network.session.SessionManager + +/** + * Use case to check if the user can migrate from personal to team account. + * The user can migrate if the user is not in a team and the server supports the migration. + */ +interface CanMigrateFromPersonalToTeamUseCase { + suspend operator fun invoke(): Boolean +} + +internal class CanMigrateFromPersonalToTeamUseCaseImpl( + val sessionManager: SessionManager, + val serverConfigRepository: ServerConfigRepository, + val selfTeamIdProvider: SelfTeamIdProvider +) : CanMigrateFromPersonalToTeamUseCase { + override suspend fun invoke(): Boolean { + val commonApiVersion = sessionManager.serverConfig().metaData.commonApiVersion.version + val minApi = serverConfigRepository.minimumApiVersionForPersonalToTeamAccountMigration + return selfTeamIdProvider().fold( + { false }, + { teamId -> teamId == null && commonApiVersion >= minApi } + ) + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt index 82c9a0bd336..9bae507ae54 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ -@file:Suppress("konsist.useCasesShouldNotAccessDaoLayerDirectly") +@file:Suppress("konsist.useCasesShouldNotAccessDaoLayerDirectly", "konsist.useCasesShouldNotAccessNetworkLayerDirectly") package com.wire.kalium.logic.feature.user @@ -31,6 +31,7 @@ import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepository import com.wire.kalium.logic.data.e2ei.E2EIRepository import com.wire.kalium.logic.data.e2ei.RevocationListChecker import com.wire.kalium.logic.data.id.CurrentClientIdProvider +import com.wire.kalium.logic.data.id.SelfTeamIdProvider import com.wire.kalium.logic.data.properties.UserPropertyRepository import com.wire.kalium.logic.data.session.SessionRepository import com.wire.kalium.logic.data.sync.IncrementalSyncRepository @@ -67,6 +68,8 @@ import com.wire.kalium.logic.feature.featureConfig.FeatureFlagSyncWorkerImpl import com.wire.kalium.logic.feature.featureConfig.FeatureFlagsSyncWorker import com.wire.kalium.logic.feature.featureConfig.SyncFeatureConfigsUseCase import com.wire.kalium.logic.feature.message.MessageSender +import com.wire.kalium.logic.feature.personaltoteamaccount.CanMigrateFromPersonalToTeamUseCase +import com.wire.kalium.logic.feature.personaltoteamaccount.CanMigrateFromPersonalToTeamUseCaseImpl import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCase import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCaseImpl import com.wire.kalium.logic.feature.publicuser.GetKnownUserUseCase @@ -81,6 +84,7 @@ import com.wire.kalium.logic.feature.user.typingIndicator.ObserveTypingIndicator import com.wire.kalium.logic.feature.user.typingIndicator.PersistTypingIndicatorStatusConfigUseCase import com.wire.kalium.logic.feature.user.typingIndicator.PersistTypingIndicatorStatusConfigUseCaseImpl import com.wire.kalium.logic.sync.SyncManager +import com.wire.kalium.network.session.SessionManager import com.wire.kalium.persistence.dao.MetadataDAO @Suppress("LongParameterList") @@ -109,6 +113,8 @@ class UserScope internal constructor( private val isE2EIEnabledUseCase: IsE2EIEnabledUseCase, private val certificateRevocationListRepository: CertificateRevocationListRepository, private val incrementalSyncRepository: IncrementalSyncRepository, + private val sessionManager: SessionManager, + private val selfTeamIdProvider: SelfTeamIdProvider, private val checkRevocationList: RevocationListChecker, private val syncFeatureConfigs: SyncFeatureConfigsUseCase, private val userScopedLogger: KaliumLogger @@ -208,13 +214,14 @@ class UserScope internal constructor( kaliumLogger = userScopedLogger, ) - val syncCertificateRevocationListUseCase: SyncCertificateRevocationListUseCase get() = - SyncCertificateRevocationListUseCase( - certificateRevocationListRepository = certificateRevocationListRepository, - incrementalSyncRepository = incrementalSyncRepository, - revocationListChecker = checkRevocationList, - kaliumLogger = userScopedLogger, - ) + val syncCertificateRevocationListUseCase: SyncCertificateRevocationListUseCase + get() = + SyncCertificateRevocationListUseCase( + certificateRevocationListRepository = certificateRevocationListRepository, + incrementalSyncRepository = incrementalSyncRepository, + revocationListChecker = checkRevocationList, + kaliumLogger = userScopedLogger, + ) val featureFlagsSyncWorker: FeatureFlagsSyncWorker by lazy { FeatureFlagSyncWorkerImpl( @@ -223,4 +230,11 @@ class UserScope internal constructor( kaliumLogger = userScopedLogger, ) } + val isPersonalToTeamAccountSupportedByBackend: CanMigrateFromPersonalToTeamUseCase by lazy { + CanMigrateFromPersonalToTeamUseCaseImpl( + sessionManager = sessionManager, + serverConfigRepository = serverConfigRepository, + selfTeamIdProvider = selfTeamIdProvider + ) + } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/network/SessionManagerImpl.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/network/SessionManagerImpl.kt index fb655297cb8..7249c4da12f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/network/SessionManagerImpl.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/network/SessionManagerImpl.kt @@ -50,6 +50,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext +// TODO: Move this class to logic module @OptIn(ExperimentalCoroutinesApi::class) @Suppress("LongParameterList") class SessionManagerImpl internal constructor( diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/personaltoteamaccount/CanMigrateFromPersonalToTeamUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/personaltoteamaccount/CanMigrateFromPersonalToTeamUseCaseTest.kt new file mode 100644 index 00000000000..9a806ec5816 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/personaltoteamaccount/CanMigrateFromPersonalToTeamUseCaseTest.kt @@ -0,0 +1,149 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.feature.personaltoteamaccount + +import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.configuration.server.ServerConfigRepository +import com.wire.kalium.logic.data.id.SelfTeamIdProvider +import com.wire.kalium.logic.data.id.TeamId +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.network.api.unbound.configuration.ApiVersionDTO +import com.wire.kalium.network.session.SessionManager +import com.wire.kalium.network.utils.TestRequestHandler.Companion.TEST_BACKEND_CONFIG +import io.mockative.Mock +import io.mockative.coEvery +import io.mockative.every +import io.mockative.mock +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class CanMigrateFromPersonalToTeamUseCaseTest { + + @Test + fun givenAPIVersionBelowMinimumAndUserNotInATeam_whenInvoking_thenReturnsFalse() = runTest { + // Given + val (_, useCase) = Arrangement() + .withRepositoryReturningMinimumApiVersion() + .withTeamId(Either.Right(null)) + .withServerConfig(6) + .arrange() + + // When + val result = useCase.invoke() + + // Then + assertFalse(result) + } + + @Test + fun givenAPIVersionEqualToMinimumAndUserNotInATeam_whenInvoking_thenReturnsTrue() = + runTest { + // Given + val (_, useCase) = Arrangement() + .withRepositoryReturningMinimumApiVersion() + .withServerConfig(7) + .withTeamId(Either.Right(null)) + .arrange() + + // When + val result = useCase.invoke() + + // Then + assertTrue(result) + } + + @Test + fun givenAPIVersionAboveMinimumAndUserInATeam_whenInvoking_thenReturnsFalse() = runTest { + // Given + val (_, useCase) = Arrangement() + .withRepositoryReturningMinimumApiVersion() + .withTeamId(Either.Right(TeamId("teamId"))) + .withServerConfig(9) + .arrange() + + // When + val result = useCase.invoke() + + // Then + assertFalse(result) + } + + + @Test + fun givenSelfTeamIdProviderFailure_whenInvoking_thenReturnsFalse() = runTest { + // Given + val (_, useCase) = Arrangement() + .withRepositoryReturningMinimumApiVersion() + .withTeamId(Either.Left(CoreFailure.MissingClientRegistration)) + .withServerConfig(9) + .arrange() + + // When + val result = useCase.invoke() + + // Then + assertFalse(result) + } + + private class Arrangement { + + @Mock + val serverConfigRepository = mock(ServerConfigRepository::class) + + @Mock + val sessionManager = mock(SessionManager::class) + + @Mock + val selfTeamIdProvider = mock(SelfTeamIdProvider::class) + + suspend fun withTeamId(result: Either) = apply { + coEvery { + selfTeamIdProvider() + }.returns(result) + } + + fun withRepositoryReturningMinimumApiVersion() = apply { + every { + serverConfigRepository.minimumApiVersionForPersonalToTeamAccountMigration + }.returns(MIN_API_VERSION) + } + + fun withServerConfig(apiVersion: Int) = apply { + val backendConfig = TEST_BACKEND_CONFIG.copy( + metaData = TEST_BACKEND_CONFIG.metaData.copy( + commonApiVersion = ApiVersionDTO.Valid(apiVersion) + ) + ) + every { + sessionManager.serverConfig() + }.returns(backendConfig) + } + + fun arrange() = this to CanMigrateFromPersonalToTeamUseCaseImpl( + sessionManager = sessionManager, + serverConfigRepository = serverConfigRepository, + selfTeamIdProvider = selfTeamIdProvider + ) + } + + companion object { + private const val MIN_API_VERSION = 7 + } +} diff --git a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq index b09efc6e29a..48a8d2fe6fe 100644 --- a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq +++ b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq @@ -35,6 +35,9 @@ UPDATE ServerConfiguration SET commonApiVersion = ? WHERE id = ?; updateApiVersionAndDomain: UPDATE ServerConfiguration SET commonApiVersion = ?, domain = ? WHERE id = ?; +getCommonApiVersionByDomain: +SELECT commonApiVersion FROM ServerConfiguration WHERE domain = ?; + updateLastBlackListCheckByIds: UPDATE ServerConfiguration SET lastBlackListCheck = ? WHERE id IN ?; diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt index 707e857eb1c..7774eae162a 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt @@ -126,6 +126,7 @@ interface ServerConfigurationDAO { fun configById(id: String): ServerConfigEntity? suspend fun configByLinks(links: ServerConfigEntity.Links): ServerConfigEntity? suspend fun updateApiVersion(id: String, commonApiVersion: Int) + suspend fun getCommonApiVersion(domain: String): Int suspend fun updateApiVersionAndDomain(id: String, domain: String, commonApiVersion: Int) suspend fun configForUser(userId: UserIDEntity): ServerConfigEntity? suspend fun setFederationToTrue(id: String) @@ -213,6 +214,10 @@ internal class ServerConfigurationDAOImpl internal constructor( queries.updateApiVersion(commonApiVersion, id) } + override suspend fun getCommonApiVersion(domain: String): Int = withContext(queriesContext) { + queries.getCommonApiVersionByDomain(domain).executeAsOne() + } + override suspend fun updateApiVersionAndDomain(id: String, domain: String, commonApiVersion: Int) = withContext(queriesContext) { queries.updateApiVersionAndDomain(commonApiVersion, domain, id)