Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add use case to check if backend supports personal to team migration (WPB-12022) #3114

Original file line number Diff line number Diff line change
Expand Up @@ -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<CoreFailure, ServerConfig>
suspend fun storeConfig(links: ServerConfig.Links, metadata: ServerConfig.MetaData): Either<StorageFailure, ServerConfig>

Expand All @@ -62,6 +65,7 @@ internal interface ServerConfigRepository {
* Return the server links and metadata for the given userId
*/
suspend fun configForUser(userId: UserId): Either<StorageFailure, ServerConfig>
suspend fun commonApiVersion(domain: String): Either<CoreFailure, Int>
}

@Suppress("LongParameterList", "TooManyFunctions")
Expand All @@ -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<CoreFailure, ServerConfig> =
wrapStorageRequest { dao.configByLinks(serverConfigMapper.toEntity(serverLinks)) }.fold({
fetchApiVersionAndStore(serverLinks)
Expand Down Expand Up @@ -127,13 +133,17 @@ internal class ServerConfigDataSource(
}

override suspend fun updateConfigApiVersion(serverConfig: ServerConfig): Either<CoreFailure, Unit> =
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<StorageFailure, ServerConfig> =
wrapStorageRequest { dao.configForUser(userId.toDao()) }
.map { serverConfigMapper.fromEntity(it) }

override suspend fun commonApiVersion(domain: String): Either<CoreFailure, Int> = wrapStorageRequest {
dao.getCommonApiVersion(domain)
}

private suspend fun fetchMetadata(serverLinks: ServerConfig.Links): Either<CoreFailure, ServerConfig.MetaData> =
wrapApiRequest { versionApi.fetchApiVersion(Url(serverLinks.api)) }
.flatMap {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,8 @@ class UserSessionScope internal constructor(
isE2EIEnabled,
certificateRevocationListRepository,
incrementalSyncRepository,
sessionManager,
selfTeamId,
checkRevocationList,
syncFeatureConfigsUseCase,
userScopedLogger
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -223,4 +230,11 @@ class UserScope internal constructor(
kaliumLogger = userScopedLogger,
)
}
val isPersonalToTeamAccountSupportedByBackend: CanMigrateFromPersonalToTeamUseCase by lazy {
CanMigrateFromPersonalToTeamUseCaseImpl(
sessionManager = sessionManager,
serverConfigRepository = serverConfigRepository,
selfTeamIdProvider = selfTeamIdProvider
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CoreFailure, TeamId?>) = 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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading