Skip to content

Commit

Permalink
feat: add use case to check if backend supports personal to team migr…
Browse files Browse the repository at this point in the history
…ation (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
  • Loading branch information
ohassine authored Nov 22, 2024
1 parent 10cfb4b commit e271934
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 12 deletions.
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

0 comments on commit e271934

Please sign in to comment.