Updated on 2026-08-14
This commit is contained in:
parent
fbc15cbb8c
commit
563d0834c4
19 changed files with 99 additions and 1025 deletions
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
|
|
@ -97,12 +96,10 @@ internal object AccountDomainModule {
|
|||
fun provideIsAccountsModeEnabledUseCase(
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
): IsAccountsModeEnabledUseCase {
|
||||
return IsAccountsModeEnabledUseCase(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
crudRepository = accountsCRUDRepository,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,6 @@
|
|||
"name": "HOT_WALLET_CREATION_RESTRICTION_ENABLED",
|
||||
"version": "5.32.0"
|
||||
},
|
||||
{
|
||||
"name": "ACCOUNTS_FEATURE_ENABLED",
|
||||
"version": "5.33.0"
|
||||
},
|
||||
{
|
||||
"name": "APP_REDESIGN_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ import android.content.Context
|
|||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.data.account.converter.AccountConverterFactoryContainer
|
||||
import com.tangem.data.account.featuretoggle.DefaultAccountsFeatureToggles
|
||||
import com.tangem.data.account.fetcher.DefaultWalletAccountsFetcher
|
||||
import com.tangem.data.account.repository.AccountsExpandedDTO
|
||||
import com.tangem.data.account.repository.DefaultAccountsCRUDRepository
|
||||
|
|
@ -25,7 +23,6 @@ import com.tangem.datasource.local.datastore.RuntimeStateStore
|
|||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
||||
import com.tangem.datasource.utils.setTypes
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.repository.AccountsExpandedRepository
|
||||
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
||||
|
|
@ -43,12 +40,6 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object AccountDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAccountFeatureToggle(featureTogglesManager: FeatureTogglesManager): AccountsFeatureToggles {
|
||||
return DefaultAccountsFeatureToggles(featureTogglesManager = featureTogglesManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAccountsCRUDRepository(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.data.account.di
|
||||
|
||||
import com.tangem.data.account.producer.AccountListCryptoCurrenciesProducer
|
||||
import com.tangem.data.account.producer.DefaultMultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -17,10 +15,8 @@ internal object MultiWalletCryptoCurrenciesProducerModule {
|
|||
@Singleton
|
||||
@Provides
|
||||
fun provideMultiWalletCryptoCurrenciesProducerFactory(
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
defaultImpl: DefaultMultiWalletCryptoCurrenciesProducer.Factory,
|
||||
accountsImpl: AccountListCryptoCurrenciesProducer.Factory,
|
||||
): MultiWalletCryptoCurrenciesProducer.Factory {
|
||||
return if (accountsFeatureToggles.isFeatureEnabled) accountsImpl else defaultImpl
|
||||
return accountsImpl
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.data.account.featuretoggle
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
|
||||
internal class DefaultAccountsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : AccountsFeatureToggles {
|
||||
|
||||
override val isFeatureEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "ACCOUNTS_FEATURE_ENABLED")
|
||||
}
|
||||
|
|
@ -5,8 +5,6 @@ import com.tangem.blockchainsdk.utils.toBlockchain
|
|||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.data.common.tokens.getDefaultWalletBlockchains
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.getSyncStrict
|
||||
|
|
@ -23,17 +21,13 @@ import com.tangem.domain.models.wallet.isMultiCurrency
|
|||
*
|
||||
* @property demoConfig demo config
|
||||
* @property excludedBlockchains excluded blockchains
|
||||
* @property userWalletsStore user wallets store
|
||||
* @property userTokensResponseStore user tokens response store
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultCardCryptoCurrencyFactory(
|
||||
private val demoConfig: DemoConfig,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val walletAccountsFetcher: WalletAccountsFetcher,
|
||||
private val userTokensResponseStore: UserTokensResponseStore,
|
||||
private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
|
||||
) : CardCryptoCurrencyFactory {
|
||||
|
||||
|
|
@ -140,34 +134,21 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
userWallet: UserWallet,
|
||||
networks: Set<Network>,
|
||||
): Map<Network, List<CryptoCurrency>> {
|
||||
val existingNetworkWithCurrencies = if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
val response = walletAccountsFetcher.getSaved(userWallet.walletId)
|
||||
?: return emptyMap()
|
||||
val response = walletAccountsFetcher.getSaved(userWallet.walletId)
|
||||
?: return emptyMap()
|
||||
|
||||
response.accounts.flatMapTo(hashSetOf()) { accountDTO ->
|
||||
val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull()
|
||||
?: return@flatMapTo emptySet()
|
||||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
tokens = accountDTO.tokens.orEmpty().filter { token ->
|
||||
networks.any {
|
||||
it.backendId == token.networkId && it.derivationPath.value == token.derivationPath
|
||||
}
|
||||
},
|
||||
userWallet = userWallet,
|
||||
accountIndex = accountIndex,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
|
||||
?: return emptyMap()
|
||||
val existingNetworkWithCurrencies = response.accounts.flatMapTo(hashSetOf()) { accountDTO ->
|
||||
val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull()
|
||||
?: return@flatMapTo emptySet()
|
||||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
tokens = response.tokens.filter { token ->
|
||||
networks.any { it.backendId == token.networkId && it.derivationPath.value == token.derivationPath }
|
||||
tokens = accountDTO.tokens.orEmpty().filter { token ->
|
||||
networks.any {
|
||||
it.backendId == token.networkId && it.derivationPath.value == token.derivationPath
|
||||
}
|
||||
},
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
accountIndex = accountIndex,
|
||||
)
|
||||
}
|
||||
.groupBy(CryptoCurrency::network)
|
||||
|
|
@ -181,28 +162,17 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
): Map<Network.RawID, List<CryptoCurrency>> {
|
||||
val networkIds = rawIds.map { it.toBlockchain().toNetworkId() }
|
||||
|
||||
return if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
val response = walletAccountsFetcher.getSaved(userWallet.walletId)
|
||||
?: return emptyMap()
|
||||
val response = walletAccountsFetcher.getSaved(userWallet.walletId)
|
||||
?: return emptyMap()
|
||||
|
||||
response.accounts.flatMapTo(hashSetOf()) { accountDTO ->
|
||||
val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull()
|
||||
?: return@flatMapTo emptySet()
|
||||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
tokens = accountDTO.tokens.orEmpty().filter { token -> token.networkId in networkIds },
|
||||
userWallet = userWallet,
|
||||
accountIndex = accountIndex,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
|
||||
?: return emptyMap()
|
||||
return response.accounts.flatMapTo(hashSetOf()) { accountDTO ->
|
||||
val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull()
|
||||
?: return@flatMapTo emptySet()
|
||||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
tokens = response.tokens.filter { token -> token.networkId in networkIds },
|
||||
tokens = accountDTO.tokens.orEmpty().filter { token -> token.networkId in networkIds },
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
accountIndex = accountIndex,
|
||||
)
|
||||
}
|
||||
.groupBy { it.network.id.rawId }
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ import com.tangem.datasource.api.common.response.ApiResponseError
|
|||
import com.tangem.datasource.api.common.response.isNetworkError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletType
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.getSyncOrNull
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -30,8 +27,6 @@ class UserTokensSaver(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val addressesEnricher: UserTokensResponseAddressesEnricher,
|
||||
private val walletServerBinder: WalletServerBinder,
|
||||
private val appsFlyerStore: AppsFlyerStore,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val pushTokensRetryerPool: RetryerPool,
|
||||
) {
|
||||
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
|
||||
|
|
@ -68,22 +63,9 @@ class UserTokensSaver(
|
|||
return@withContext
|
||||
}
|
||||
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
val enrichedResponse = response.enrichIf(userWalletId = userWalletId, condition = useEnricher)
|
||||
val enrichedResponse = response.enrichIf(userWalletId = userWalletId, condition = useEnricher)
|
||||
|
||||
pushNew(userWallet = userWallet, response = enrichedResponse, onFailSend = onFailSend)
|
||||
} else {
|
||||
val conversionData = appsFlyerStore.get()
|
||||
|
||||
val enrichedResponse = response.enrichIf(userWalletId = userWalletId, condition = useEnricher).copy(
|
||||
walletName = userWallet.name.takeIf { it.isNotBlank() },
|
||||
walletType = WalletType.from(userWallet),
|
||||
refcode = conversionData?.refcode,
|
||||
campaign = conversionData?.campaign,
|
||||
)
|
||||
|
||||
pushLegacy(userWalletId = userWalletId, response = enrichedResponse, onFailSend = onFailSend)
|
||||
}
|
||||
push(userWallet = userWallet, response = enrichedResponse, onFailSend = onFailSend)
|
||||
}
|
||||
|
||||
suspend fun pushWithRetryer(
|
||||
|
|
@ -103,14 +85,7 @@ class UserTokensSaver(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun pushLegacy(userWalletId: UserWalletId, response: UserTokensResponse, onFailSend: () -> Unit) {
|
||||
safeApiCall(
|
||||
call = { tangemTechApi.saveUserTokens(userId = userWalletId.stringValue, userTokens = response).bind() },
|
||||
onError = { onFailSend() },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun pushNew(userWallet: UserWallet, response: UserTokensResponse, onFailSend: () -> Unit) {
|
||||
private suspend fun push(userWallet: UserWallet, response: UserTokensResponse, onFailSend: () -> Unit) {
|
||||
safeApiCall(
|
||||
call = {
|
||||
val apiResponse = tangemTechApi.saveTokens(
|
||||
|
|
@ -148,13 +123,7 @@ class UserTokensSaver(
|
|||
|
||||
return this
|
||||
.enrichByAddress(userWalletId = userWalletId)
|
||||
.let { response ->
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
response.enrichByAccountId(userWalletId = userWalletId)
|
||||
} else {
|
||||
response
|
||||
}
|
||||
}
|
||||
.enrichByAccountId(userWalletId = userWalletId)
|
||||
}
|
||||
|
||||
private suspend fun UserTokensResponse.enrichByAddress(userWalletId: UserWalletId): UserTokensResponse {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -37,18 +36,14 @@ internal object DataCommonModule {
|
|||
fun provideCardCryptoCurrencyFactory(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
walletAccountsFetcher: WalletAccountsFetcher,
|
||||
userTokensResponseStore: UserTokensResponseStore,
|
||||
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
|
||||
): CardCryptoCurrencyFactory {
|
||||
return DefaultCardCryptoCurrencyFactory(
|
||||
demoConfig = DemoConfig,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
walletAccountsFetcher = walletAccountsFetcher,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -76,8 +71,6 @@ internal object DataCommonModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
addressesEnricher: UserTokensResponseAddressesEnricher,
|
||||
walletServerBinder: WalletServerBinder,
|
||||
appsFlyerStore: AppsFlyerStore,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
): UserTokensSaver {
|
||||
return UserTokensSaver(
|
||||
tangemTechApi = tangemTechApi,
|
||||
|
|
@ -85,12 +78,10 @@ internal object DataCommonModule {
|
|||
userTokensResponseStore = userTokensResponseStore,
|
||||
dispatchers = dispatchers,
|
||||
addressesEnricher = addressesEnricher,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
pushTokensRetryerPool = RetryerPool(
|
||||
coroutineScope = CoroutineScope(SupervisorJob() + dispatchers.default),
|
||||
),
|
||||
walletServerBinder = walletServerBinder,
|
||||
appsFlyerStore = appsFlyerStore,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import com.tangem.common.test.domain.wallet.MockUserWalletFactory
|
|||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.configs.GenericCardConfig
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
|
|
@ -38,20 +38,16 @@ import org.junit.jupiter.params.ParameterizedTest
|
|||
internal class DefaultCardCryptoCurrencyFactoryTest {
|
||||
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk()
|
||||
private val userTokensResponseStore: UserTokensResponseStore = mockk()
|
||||
private val excludedBlockchains = ExcludedBlockchains()
|
||||
private val accountsFeatureToggles = mockk<AccountsFeatureToggles>()
|
||||
private val walletAccountsFetcher = mockk<WalletAccountsFetcher>()
|
||||
|
||||
private val factory = DefaultCardCryptoCurrencyFactory(
|
||||
demoConfig = DemoConfig,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
responseCryptoCurrenciesFactory = ResponseCryptoCurrenciesFactory(
|
||||
networkFactory = NetworkFactory(excludedBlockchains = excludedBlockchains),
|
||||
),
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
walletAccountsFetcher = walletAccountsFetcher,
|
||||
)
|
||||
|
||||
|
|
@ -64,7 +60,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
|
||||
@BeforeEach
|
||||
fun init() {
|
||||
clearMocks(userWalletsListRepository, userTokensResponseStore, accountsFeatureToggles, walletAccountsFetcher, iconUri)
|
||||
clearMocks(userWalletsListRepository, walletAccountsFetcher, iconUri)
|
||||
|
||||
mockkStatic(Uri::class)
|
||||
every { Uri.parse(any()) } returns iconUri
|
||||
|
|
@ -80,12 +76,11 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
// Arrange
|
||||
val userWallet = createMultiWallet()
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
val userTokensResponse = model.userTokensResponse
|
||||
val accountsResponse = model.accountsResponse
|
||||
val network = ethereum.network
|
||||
|
||||
every { accountsFeatureToggles.isFeatureEnabled } returns false
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse
|
||||
coEvery { walletAccountsFetcher.getSaved(userWallet.walletId) } returns accountsResponse
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId = userWallet.walletId, network = network)
|
||||
|
|
@ -97,25 +92,25 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
|
||||
walletAccountsFetcher.getSaved(userWalletId = userWallet.walletId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
CreateTestModel.MultiWallet(userTokensResponse = null, expected = emptyList()),
|
||||
CreateTestModel.MultiWallet(userTokensResponse = createUserTokensResponse(), expected = emptyList()),
|
||||
CreateTestModel.MultiWallet(accountsResponse = null, expected = emptyList()),
|
||||
CreateTestModel.MultiWallet(accountsResponse = createAccountsResponse(), expected = emptyList()),
|
||||
CreateTestModel.MultiWallet(
|
||||
userTokensResponse = createUserTokensResponse(currencies = listOf(ethereum)),
|
||||
accountsResponse = createAccountsResponse(currencies = listOf(ethereum)),
|
||||
expected = listOf(ethereum),
|
||||
),
|
||||
CreateTestModel.MultiWallet(
|
||||
userTokensResponse = createUserTokensResponse(listOf(element = bitcoin)),
|
||||
accountsResponse = createAccountsResponse(listOf(element = bitcoin)),
|
||||
expected = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
private fun createUserTokensResponse(currencies: List<CryptoCurrency> = emptyList()): UserTokensResponse {
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
private fun createAccountsResponse(currencies: List<CryptoCurrency> = emptyList()): GetWalletAccountsResponse {
|
||||
return createWalletAccountsResponse(
|
||||
currencies = currencies,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
|
|
@ -150,7 +145,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
walletAccountsFetcher.getSaved(userWalletId = any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +190,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
walletAccountsFetcher.getSaved(userWalletId = any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +213,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
val expected: List<CryptoCurrency>
|
||||
|
||||
data class MultiWallet(
|
||||
val userTokensResponse: UserTokensResponse?,
|
||||
val accountsResponse: GetWalletAccountsResponse?,
|
||||
override val expected: List<CryptoCurrency>,
|
||||
) : CreateTestModel
|
||||
|
||||
|
|
@ -245,10 +240,9 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
// Arrange
|
||||
val userWallet = model.multiWallet
|
||||
val networks = setOf(ethereum.network, bitcoin.network)
|
||||
val userTokensResponse = model.userTokensResponse
|
||||
val accountsResponse = model.accountsResponse
|
||||
|
||||
every { accountsFeatureToggles.isFeatureEnabled } returns false
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse
|
||||
coEvery { walletAccountsFetcher.getSaved(userWallet.walletId) } returns accountsResponse
|
||||
|
||||
// Act
|
||||
val actual = runCatching {
|
||||
|
|
@ -273,19 +267,19 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
private fun provideTestModels() = listOf(
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(),
|
||||
userTokensResponse = null,
|
||||
accountsResponse = null,
|
||||
expected = Result.success(emptyMap()),
|
||||
),
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(),
|
||||
userTokensResponse = createUserTokensResponse(),
|
||||
accountsResponse = createWalletAccountsResponse(emptyList(), false, false),
|
||||
expected = Result.success(
|
||||
setOf(ethereum.network, bitcoin.network).associateWith { emptyList() },
|
||||
),
|
||||
),
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(),
|
||||
userTokensResponse = createUserTokensResponse(currencies = listOf(bitcoin, ethereum)),
|
||||
accountsResponse = createWalletAccountsResponse(currencies = listOf(bitcoin, ethereum), false, false),
|
||||
expected = mapOf(
|
||||
bitcoin.network to listOf(bitcoin),
|
||||
ethereum.network to listOf(ethereum),
|
||||
|
|
@ -293,12 +287,12 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
),
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createSingleWallet(),
|
||||
userTokensResponse = null,
|
||||
accountsResponse = null,
|
||||
expected = Result.failure(IllegalArgumentException("It isn't multi-currency wallet")),
|
||||
),
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = MockUserWalletFactory.createSingleWalletWithToken(),
|
||||
userTokensResponse = null,
|
||||
accountsResponse = null,
|
||||
expected = Result.failure(IllegalArgumentException("It isn't multi-currency wallet")),
|
||||
),
|
||||
)
|
||||
|
|
@ -306,7 +300,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
|
||||
data class CreateCurrenciesForMultiWalletModel(
|
||||
val multiWallet: UserWallet,
|
||||
val userTokensResponse: UserTokensResponse?,
|
||||
val accountsResponse: GetWalletAccountsResponse?,
|
||||
val expected: Result<Map<Network, List<CryptoCurrency>>>,
|
||||
)
|
||||
|
||||
|
|
@ -546,11 +540,44 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
)!!
|
||||
}
|
||||
|
||||
private fun createUserTokensResponse(currencies: List<CryptoCurrency> = emptyList()): UserTokensResponse {
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = currencies,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
private fun createWalletAccountsResponse(
|
||||
currencies: List<CryptoCurrency>,
|
||||
isGroupedByNetwork: Boolean,
|
||||
isSortedByBalance: Boolean,
|
||||
): GetWalletAccountsResponse {
|
||||
val tokens = currencies.map { currency ->
|
||||
userTokensResponseFactory.createResponseToken(currency = currency, accountId = null)
|
||||
}
|
||||
|
||||
return GetWalletAccountsResponse(
|
||||
wallet = GetWalletAccountsResponse.Wallet(
|
||||
version = 0,
|
||||
group = if (isGroupedByNetwork) {
|
||||
UserTokensResponse.GroupType.NETWORK
|
||||
} else {
|
||||
UserTokensResponse.GroupType.NONE
|
||||
},
|
||||
sort = if (isSortedByBalance) {
|
||||
UserTokensResponse.SortType.BALANCE
|
||||
} else {
|
||||
UserTokensResponse.SortType.MANUAL
|
||||
},
|
||||
totalAccounts = 1,
|
||||
totalArchivedAccounts = 0,
|
||||
),
|
||||
accounts = listOf(
|
||||
WalletAccountDTO(
|
||||
id = "account_0",
|
||||
name = "Main",
|
||||
derivationIndex = 0,
|
||||
icon = "🏠",
|
||||
iconColor = "#000000",
|
||||
tokens = tokens,
|
||||
totalTokens = tokens.size,
|
||||
totalNetworks = currencies.map { it.network }.distinct().size,
|
||||
),
|
||||
),
|
||||
unassignedTokens = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import com.tangem.datasource.api.common.response.ApiResponseError
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletType
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -27,11 +25,7 @@ class UserTokensSaverTest {
|
|||
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true)
|
||||
private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxed = true)
|
||||
private val enricher: UserTokensResponseAddressesEnricher = mockk()
|
||||
private val accountsFeatureToggles = mockk<AccountsFeatureToggles> {
|
||||
every { this@mockk.isFeatureEnabled } returns true
|
||||
}
|
||||
private val walletServerBinder: WalletServerBinder = mockk()
|
||||
private val appsFlyerStore: AppsFlyerStore = mockk()
|
||||
|
||||
private val userTokensSaver: UserTokensSaver = UserTokensSaver(
|
||||
tangemTechApi = tangemTechApi,
|
||||
|
|
@ -40,8 +34,6 @@ class UserTokensSaverTest {
|
|||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
addressesEnricher = enricher,
|
||||
walletServerBinder = walletServerBinder,
|
||||
appsFlyerStore = appsFlyerStore,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
pushTokensRetryerPool = mockk(),
|
||||
)
|
||||
|
||||
|
|
@ -121,7 +113,6 @@ class UserTokensSaverTest {
|
|||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
|
||||
every { accountsFeatureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { enricher(userWalletId, response) } returns enrichedResponse
|
||||
coEvery { tangemTechApi.saveTokens(any(), any()) } returns ApiResponse.Error(error) as ApiResponse<Unit>
|
||||
|
|
|
|||
|
|
@ -1,129 +0,0 @@
|
|||
package com.tangem.data.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.data.tokens.utils.CustomTokensMerger
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.getSyncStrict
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.express.models.ExpressAsset
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher.Params
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Default implementation of [MultiWalletCryptoCurrenciesFetcher]
|
||||
*
|
||||
* @property tangemTechApi Tangem Tech API
|
||||
* @property userTokensResponseStore store of [UserTokensResponse]
|
||||
* @property userTokensSaver user tokens saver
|
||||
* @property cardCryptoCurrencyFactory factory for creating crypto currencies for specified card
|
||||
* @property expressServiceFetcher express service loader
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultMultiWalletCryptoCurrenciesFetcher(
|
||||
private val demoConfig: DemoConfig,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val customTokensMerger: CustomTokensMerger,
|
||||
private val userTokensResponseStore: UserTokensResponseStore,
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val expressServiceFetcher: ExpressServiceFetcher,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MultiWalletCryptoCurrenciesFetcher {
|
||||
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
|
||||
override suspend fun invoke(params: Params) = Either.catchOn(dispatchers.default) {
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(id = params.userWalletId)
|
||||
|
||||
if (!userWallet.isMultiCurrency) error("${this::class.simpleName} supports only multi-currency wallet")
|
||||
|
||||
val response = if (userWallet is UserWallet.Cold && userWallet.isDemoWalletWithoutSavedTokens()) {
|
||||
createDefaultUserTokensResponse(userWallet = userWallet)
|
||||
} else {
|
||||
safeApiCall(
|
||||
call = {
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.getUserTokens(userId = userWallet.walletId.stringValue).bind()
|
||||
}
|
||||
},
|
||||
onError = {
|
||||
handleFetchTokensError(error = it, userWallet = userWallet)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val compatibleUserTokensResponse = response
|
||||
.let { it.copy(tokens = it.tokens.distinct()) }
|
||||
.let { customTokensMerger.mergeIfPresented(userWalletId = userWallet.walletId, response = it) }
|
||||
|
||||
userTokensSaver.store(userWalletId = userWallet.walletId, response = compatibleUserTokensResponse)
|
||||
|
||||
fetchExpressAssetsByNetworkIds(userWallet = userWallet, userTokens = compatibleUserTokensResponse)
|
||||
}
|
||||
|
||||
private suspend fun UserWallet.Cold.isDemoWalletWithoutSavedTokens(): Boolean {
|
||||
val isDemoCard = demoConfig.isDemoCardId(cardId = cardId)
|
||||
|
||||
return if (isDemoCard) {
|
||||
val response = userTokensResponseStore.getSyncOrNull(userWalletId = walletId)
|
||||
|
||||
response == null
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleFetchTokensError(error: ApiResponseError, userWallet: UserWallet): UserTokensResponse {
|
||||
val userWalletId = userWallet.walletId
|
||||
|
||||
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
|
||||
?: createDefaultUserTokensResponse(userWallet = userWallet)
|
||||
|
||||
if (error is ApiResponseError.HttpException && error.code == ApiResponseError.HttpException.Code.NOT_FOUND) {
|
||||
Timber.w(error, "Requested currencies could not be found in the remote store for: $userWalletId")
|
||||
|
||||
userTokensSaver.push(userWalletId, response)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private suspend fun fetchExpressAssetsByNetworkIds(userWallet: UserWallet, userTokens: UserTokensResponse) {
|
||||
val tokens = userTokens.tokens.mapTo(hashSetOf()) { token ->
|
||||
ExpressAsset.ID(
|
||||
networkId = token.networkId,
|
||||
contractAddress = token.contractAddress,
|
||||
)
|
||||
}
|
||||
|
||||
expressServiceFetcher.fetch(userWallet = userWallet, assetIds = tokens)
|
||||
}
|
||||
|
||||
private fun createDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse {
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
accountId = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,8 @@
|
|||
package com.tangem.data.tokens.di
|
||||
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.data.tokens.AccountListCryptoCurrenciesFetcher
|
||||
import com.tangem.data.tokens.DefaultMultiWalletCryptoCurrenciesFetcher
|
||||
import com.tangem.data.tokens.utils.CustomTokensMerger
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -27,39 +19,16 @@ internal class MultiWalletCryptoCurrenciesFetcherModule {
|
|||
@Singleton
|
||||
@Provides
|
||||
fun provideMultiWalletCryptoCurrenciesFetcher(
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
tangemTechApi: TangemTechApi,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
userTokensResponseStore: UserTokensResponseStore,
|
||||
userTokensSaver: UserTokensSaver,
|
||||
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
expressServiceFetcher: ExpressServiceFetcher,
|
||||
walletAccountsFetcher: WalletAccountsFetcher,
|
||||
expressServiceFetcher: ExpressServiceFetcher,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): MultiWalletCryptoCurrenciesFetcher {
|
||||
return if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
AccountListCryptoCurrenciesFetcher(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
walletAccountsFetcher = walletAccountsFetcher,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
} else {
|
||||
DefaultMultiWalletCryptoCurrenciesFetcher(
|
||||
demoConfig = DemoConfig,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
tangemTechApi = tangemTechApi,
|
||||
customTokensMerger = CustomTokensMerger(
|
||||
tangemTechApi = tangemTechApi,
|
||||
userTokensSaver = userTokensSaver,
|
||||
dispatchers = dispatchers,
|
||||
),
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
userTokensSaver = userTokensSaver,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
return AccountListCryptoCurrenciesFetcher(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
walletAccountsFetcher = walletAccountsFetcher,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.TokenReceiveWarningActionStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
|
|
@ -48,7 +47,6 @@ internal object TokensDataModule {
|
|||
tokensSaver: UserTokensSaver,
|
||||
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
): CurrenciesRepository {
|
||||
return DefaultCurrenciesRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
|
|
@ -63,7 +61,6 @@ internal object TokensDataModule {
|
|||
userTokensSaver = tokensSaver,
|
||||
responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.tangem.data.tokens.repository
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchainsdk.compatibility.getL2CompatibilityTokenComparison
|
||||
import com.tangem.blockchainsdk.utils.*
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.common.currency.*
|
||||
|
|
@ -13,7 +15,6 @@ import com.tangem.datasource.api.common.response.getOrThrow
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
|
|
@ -51,7 +52,6 @@ internal class DefaultCurrenciesRepository(
|
|||
private val userTokensSaver: UserTokensSaver,
|
||||
private val userTokensResponseStore: UserTokensResponseStore,
|
||||
private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : CurrenciesRepository {
|
||||
|
|
@ -187,19 +187,7 @@ internal class DefaultCurrenciesRepository(
|
|||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
): CryptoCurrency.Coin {
|
||||
return if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
getNetworkCoinNew(userWalletId, networkId, derivationPath)
|
||||
} else {
|
||||
getNetworkCoinLegacy(userWalletId, networkId, derivationPath)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNetworkCoinNew(
|
||||
userWalletId: UserWalletId,
|
||||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
): CryptoCurrency.Coin = withContext(dispatchers.default) {
|
||||
multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
|
||||
return multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
|
||||
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
.orEmpty()
|
||||
|
|
@ -211,44 +199,6 @@ internal class DefaultCurrenciesRepository(
|
|||
?: error("Unable to find coin for network ID: $networkId")
|
||||
}
|
||||
|
||||
private suspend fun getNetworkCoinLegacy(
|
||||
userWalletId: UserWalletId,
|
||||
networkId: Network.ID,
|
||||
derivationPath: Network.DerivationPath,
|
||||
): CryptoCurrency.Coin {
|
||||
return withContext(dispatchers.io) {
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
|
||||
ensureIsCorrectUserWallet(userWallet = userWallet, isMultiCurrencyWalletExpected = true)
|
||||
|
||||
fetchTokensIfCacheExpired(userWallet = userWallet, refresh = false)
|
||||
|
||||
val storedTokens = requireNotNull(
|
||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||
lazyMessage = {
|
||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||
},
|
||||
)
|
||||
val blockchain = networkId.toBlockchain()
|
||||
val blockchainNetworkId = blockchain.toNetworkId()
|
||||
val coinId = blockchain.toCoinId()
|
||||
|
||||
val storedCoin = storedTokens.tokens
|
||||
.find { token ->
|
||||
token.networkId == blockchainNetworkId &&
|
||||
compareIdWithMigrations(token, coinId) &&
|
||||
token.derivationPath == derivationPath.value
|
||||
} ?: error("Coin in this network $networkId not found")
|
||||
|
||||
val coin = responseCryptoCurrenciesFactory.createCurrency(
|
||||
responseToken = storedCoin,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
)
|
||||
|
||||
coin as? CryptoCurrency.Coin ?: error("Unable to create currency")
|
||||
}
|
||||
}
|
||||
|
||||
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
|
||||
return channelFlow {
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
|
||||
|
|
@ -470,13 +420,6 @@ internal class DefaultCurrenciesRepository(
|
|||
)
|
||||
}
|
||||
|
||||
private fun compareIdWithMigrations(token: UserTokensResponse.Token, coinId: String): Boolean {
|
||||
return when {
|
||||
token.id == OLD_POLYGON_NAME -> NEW_POLYGON_NAME == coinId
|
||||
else -> token.id == coinId
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchTokens(userWallet: UserWallet) {
|
||||
val userWalletId = userWallet.walletId
|
||||
|
||||
|
|
|
|||
|
|
@ -1,549 +0,0 @@
|
|||
package com.tangem.data.tokens
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.data.tokens.utils.CustomTokensMerger
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.express.models.ExpressAsset
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
|
||||
import com.tangem.test.core.assertEither
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
||||
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true)
|
||||
private val tangemTechApi: TangemTechApi = mockk()
|
||||
private val customTokensMerger: CustomTokensMerger = mockk()
|
||||
private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxUnitFun = true)
|
||||
private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true)
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
private val expressServiceFetcher: ExpressServiceFetcher = mockk(relaxUnitFun = true)
|
||||
|
||||
private val fetcher = DefaultMultiWalletCryptoCurrenciesFetcher(
|
||||
demoConfig = DemoConfig,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
tangemTechApi = tangemTechApi,
|
||||
customTokensMerger = customTokensMerger,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
userTokensSaver = userTokensSaver,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(
|
||||
userWalletsListRepository,
|
||||
tangemTechApi,
|
||||
userTokensResponseStore,
|
||||
userTokensSaver,
|
||||
cardCryptoCurrencyFactory,
|
||||
expressServiceFetcher,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch failure if UserWallet ISN'T MULTI-CURRENCY wallet`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
|
||||
val mockUserWallet = mockk<UserWallet> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns false
|
||||
}
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(mockUserWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = IllegalStateException(
|
||||
"${DefaultMultiWalletCryptoCurrenciesFetcher::class.simpleName} supports only multi-currency wallet",
|
||||
).left()
|
||||
assertEither(actual, expected)
|
||||
|
||||
verifyOrder { userWalletsListRepository.userWallets }
|
||||
coVerify(inverse = true) {
|
||||
userTokensResponseStore.getSyncOrNull(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully if CARD IS DEMO and STORED TOKENS ARE EMPTY`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
|
||||
val mockUserWallet = mockk<UserWallet.Cold> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns true
|
||||
every { cardId } returns "AC01000000041225"
|
||||
}
|
||||
|
||||
val defaultCoins = listOf(
|
||||
cryptoCurrencyFactory.createCoin(Blockchain.Bitcoin),
|
||||
cryptoCurrencyFactory.createCoin(Blockchain.Ethereum),
|
||||
)
|
||||
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NONE,
|
||||
sort = UserTokensResponse.SortType.MANUAL,
|
||||
tokens = listOf(
|
||||
userTokensResponseFactory.createResponseToken(defaultCoins.first()),
|
||||
userTokensResponseFactory.createResponseToken(defaultCoins.last()),
|
||||
),
|
||||
)
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(mockUserWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId = params.userWalletId) } returns null
|
||||
every {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
} returns defaultCoins
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
} returns userTokensResponse
|
||||
|
||||
coEvery {
|
||||
expressServiceFetcher.fetch(
|
||||
userWallet = mockUserWallet,
|
||||
assetIds = userTokensResponse.toAssetId(),
|
||||
)
|
||||
} returns Unit.right()
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = params.userWalletId)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = userTokensResponse.toAssetId())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully if CARD IS DEMO and STORED TOKENS AREN'T EMPTY`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
|
||||
val mockUserWallet = mockk<UserWallet.Cold> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns true
|
||||
every { cardId } returns "AC01000000041225"
|
||||
}
|
||||
|
||||
val apiResponse = ApiResponse.Success(
|
||||
data = defaultResponse.copy(group = UserTokensResponse.GroupType.TOKEN),
|
||||
)
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(mockUserWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId = params.userWalletId) } returns defaultResponse
|
||||
coEvery { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) } returns apiResponse
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data)
|
||||
} returns apiResponse.data
|
||||
|
||||
coEvery {
|
||||
expressServiceFetcher.fetch(
|
||||
userWallet = mockUserWallet,
|
||||
assetIds = defaultResponse.toAssetId(),
|
||||
)
|
||||
} returns Unit.right()
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = params.userWalletId)
|
||||
tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue)
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data)
|
||||
userTokensSaver.store(userWalletId = params.userWalletId, response = apiResponse.data)
|
||||
expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId())
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully if CARD ISN'T DEMO`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
|
||||
val mockUserWallet = mockk<UserWallet.Cold> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns true
|
||||
every { cardId } returns "cardID"
|
||||
}
|
||||
|
||||
val apiResponse = ApiResponse.Success(
|
||||
data = defaultResponse.copy(group = UserTokensResponse.GroupType.TOKEN),
|
||||
)
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(mockUserWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) } returns apiResponse
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data)
|
||||
} returns apiResponse.data
|
||||
|
||||
coEvery {
|
||||
expressServiceFetcher.fetch(
|
||||
userWallet = mockUserWallet,
|
||||
assetIds = apiResponse.data.toAssetId(),
|
||||
)
|
||||
} returns Unit.right()
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue)
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = apiResponse.data)
|
||||
userTokensSaver.store(userWalletId = params.userWalletId, response = apiResponse.data)
|
||||
expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId())
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully if API request RETURNS TIMEOUT EXCEPTION and STORED TOKENS ARE EMPTY`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
|
||||
val mockUserWallet = mockk<UserWallet.Cold> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns true
|
||||
every { cardId } returns "cardID"
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val apiResponse = ApiResponse.Error(
|
||||
cause = ApiResponseError.TimeoutException(),
|
||||
) as ApiResponse<UserTokensResponse>
|
||||
|
||||
val defaultCoins = listOf(
|
||||
cryptoCurrencyFactory.createCoin(Blockchain.Bitcoin),
|
||||
cryptoCurrencyFactory.createCoin(Blockchain.Ethereum),
|
||||
)
|
||||
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NONE,
|
||||
sort = UserTokensResponse.SortType.MANUAL,
|
||||
tokens = listOf(
|
||||
userTokensResponseFactory.createResponseToken(defaultCoins.first()),
|
||||
userTokensResponseFactory.createResponseToken(defaultCoins.last()),
|
||||
),
|
||||
)
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(mockUserWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) } returns apiResponse
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) } returns null
|
||||
coEvery {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
} returns defaultCoins
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
} returns userTokensResponse
|
||||
|
||||
coEvery {
|
||||
expressServiceFetcher.fetch(
|
||||
userWallet = mockUserWallet,
|
||||
assetIds = userTokensResponse.toAssetId(),
|
||||
)
|
||||
} returns Unit.right()
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue)
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = userTokensResponse.toAssetId())
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
userTokensSaver.push(userWalletId = any(), response = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully if API request RETURNS TIMEOUT EXCEPTION and STORED TOKENS AREN'T EMPTY`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
|
||||
val mockUserWallet = mockk<UserWallet.Cold> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns true
|
||||
every { cardId } returns "cardID"
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val apiResponse = ApiResponse.Error(
|
||||
cause = ApiResponseError.TimeoutException(),
|
||||
) as ApiResponse<UserTokensResponse>
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(mockUserWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) } returns apiResponse
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) } returns defaultResponse
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse)
|
||||
} returns defaultResponse
|
||||
|
||||
coEvery {
|
||||
expressServiceFetcher.fetch(
|
||||
userWallet = mockUserWallet,
|
||||
assetIds = defaultResponse.toAssetId(),
|
||||
)
|
||||
} returns Unit.right()
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue)
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse)
|
||||
userTokensSaver.store(userWalletId = params.userWalletId, response = defaultResponse)
|
||||
expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId())
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
userTokensSaver.push(userWalletId = any(), response = any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully if API request RETURNS NOT FOUND EXCEPTION and STORED TOKENS ARE EMPTY`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
|
||||
val mockUserWallet = mockk<UserWallet.Cold> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns true
|
||||
every { cardId } returns "cardID"
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val apiResponse = ApiResponse.Error(
|
||||
cause = ApiResponseError.HttpException(
|
||||
code = ApiResponseError.HttpException.Code.NOT_FOUND,
|
||||
message = null,
|
||||
errorBody = null,
|
||||
),
|
||||
) as ApiResponse<UserTokensResponse>
|
||||
|
||||
val defaultCoins = listOf(
|
||||
cryptoCurrencyFactory.createCoin(Blockchain.Bitcoin),
|
||||
cryptoCurrencyFactory.createCoin(Blockchain.Ethereum),
|
||||
)
|
||||
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NONE,
|
||||
sort = UserTokensResponse.SortType.MANUAL,
|
||||
tokens = listOf(
|
||||
userTokensResponseFactory.createResponseToken(defaultCoins.first()),
|
||||
userTokensResponseFactory.createResponseToken(defaultCoins.last()),
|
||||
),
|
||||
)
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(mockUserWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) } returns apiResponse
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) } returns null
|
||||
coEvery {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
} returns defaultCoins
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
} returns userTokensResponse
|
||||
|
||||
coEvery {
|
||||
expressServiceFetcher.fetch(
|
||||
userWallet = mockUserWallet,
|
||||
assetIds = userTokensResponse.toAssetId(),
|
||||
)
|
||||
} returns Unit.right()
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue)
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
userTokensSaver.push(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = userTokensResponse.toAssetId())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully if API request RETURNS NOT FOUND EXCEPTION and STORED TOKENS AREN'T EMPTY`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
|
||||
val mockUserWallet = mockk<UserWallet.Cold> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns true
|
||||
every { cardId } returns "cardID"
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val apiResponse = ApiResponse.Error(
|
||||
cause = ApiResponseError.HttpException(
|
||||
code = ApiResponseError.HttpException.Code.NOT_FOUND,
|
||||
message = null,
|
||||
errorBody = null,
|
||||
),
|
||||
) as ApiResponse<UserTokensResponse>
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(mockUserWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) } returns apiResponse
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) } returns defaultResponse
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse)
|
||||
} returns defaultResponse
|
||||
|
||||
coEvery {
|
||||
expressServiceFetcher.fetch(
|
||||
userWallet = mockUserWallet,
|
||||
assetIds = defaultResponse.toAssetId(),
|
||||
)
|
||||
} returns Unit.right()
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue)
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
|
||||
userTokensSaver.push(userWalletId = params.userWalletId, response = defaultResponse)
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = defaultResponse)
|
||||
userTokensSaver.store(userWalletId = params.userWalletId, response = defaultResponse)
|
||||
expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = defaultResponse.toAssetId())
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val userWalletId = UserWalletId("011")
|
||||
|
||||
val defaultResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.NONE,
|
||||
sort = UserTokensResponse.SortType.MANUAL,
|
||||
tokens = listOf(
|
||||
UserTokensResponse.Token(
|
||||
id = null,
|
||||
networkId = "bitcoin",
|
||||
derivationPath = null,
|
||||
name = "Bitcoin",
|
||||
symbol = "BTC",
|
||||
decimals = 8,
|
||||
contractAddress = null,
|
||||
addresses = listOf(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
fun UserTokensResponse.toAssetId(): Set<ExpressAsset.ID> {
|
||||
return tokens.mapTo(hashSetOf()) { token ->
|
||||
ExpressAsset.ID(
|
||||
networkId = token.networkId,
|
||||
contractAddress = token.contractAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.domain.account.featuretoggle
|
||||
|
||||
/**
|
||||
* Accounts feature toggle
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface AccountsFeatureToggles {
|
||||
|
||||
val isFeatureEnabled: Boolean
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.domain.account.usecase
|
|||
|
||||
import arrow.core.Option
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.loadAndGet
|
||||
|
|
@ -18,20 +17,16 @@ import kotlinx.coroutines.flow.*
|
|||
*
|
||||
* @property crudRepository repository to perform CRUD operations on accounts.
|
||||
* @property userWalletsListRepository repository to get the list of user wallets.
|
||||
* @property accountsFeatureToggles feature toggles for accounts.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class IsAccountsModeEnabledUseCase(
|
||||
private val crudRepository: AccountsCRUDRepository,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(): Flow<Boolean> {
|
||||
if (!accountsFeatureToggles.isFeatureEnabled) return flowOf(value = false)
|
||||
|
||||
return userWalletsListRepository.loadAndGet()
|
||||
.flatMapLatest { userWallets ->
|
||||
val totalAccountsCountList = getTotalAccountsCountList(userWallets)
|
||||
|
|
@ -43,8 +38,6 @@ class IsAccountsModeEnabledUseCase(
|
|||
}
|
||||
|
||||
suspend fun invokeSync(): Boolean {
|
||||
if (!accountsFeatureToggles.isFeatureEnabled) return false
|
||||
|
||||
return userWalletsListRepository.userWallets.value.orEmpty()
|
||||
.map { userWallet ->
|
||||
// If the wallet does not support multiple currencies, we consider its account count as 0
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.domain.account.usecase
|
|||
import arrow.core.none
|
||||
import arrow.core.some
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -12,7 +11,6 @@ import com.tangem.domain.models.wallet.isMultiCurrency
|
|||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
|
|
@ -26,47 +24,26 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository = mockk()
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true)
|
||||
private val featureToggles: AccountsFeatureToggles = mockk()
|
||||
|
||||
private val useCase = IsAccountsModeEnabledUseCase(
|
||||
crudRepository = accountsCRUDRepository,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
accountsFeatureToggles = featureToggles,
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(userWalletsListRepository, accountsCRUDRepository, featureToggles)
|
||||
clearMocks(userWalletsListRepository, accountsCRUDRepository)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Invoke {
|
||||
|
||||
@Test
|
||||
fun `returns false when feature is disabled`() = runTest {
|
||||
// Arrange
|
||||
every { featureToggles.isFeatureEnabled } returns false
|
||||
|
||||
// Act
|
||||
val actual = useCase.invoke().firstOrNull()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isFalse()
|
||||
|
||||
verify(exactly = 1) { featureToggles.isFeatureEnabled }
|
||||
coVerify(inverse = true) {
|
||||
userWalletsListRepository.load()
|
||||
userWalletsListRepository.userWallets
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when loadAndGet emits one wallet with isMultiCurrency false`() = runTest {
|
||||
// Arrange
|
||||
val wallet = createUserWallet(isMultiCurrency = false)
|
||||
|
||||
every { featureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet))
|
||||
|
||||
// Act
|
||||
|
|
@ -76,7 +53,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
Truth.assertThat(actual).isFalse()
|
||||
|
||||
coVerifyOrder {
|
||||
featureToggles.isFeatureEnabled
|
||||
userWalletsListRepository.load()
|
||||
userWalletsListRepository.userWallets
|
||||
}
|
||||
|
|
@ -89,7 +65,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
// Arrange
|
||||
val wallet = createUserWallet(isMultiCurrency = true)
|
||||
|
||||
every { featureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet))
|
||||
every { accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) } returns flowOf(2.some())
|
||||
|
||||
|
|
@ -100,7 +75,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
Truth.assertThat(actual).isTrue()
|
||||
|
||||
coVerifyOrder {
|
||||
featureToggles.isFeatureEnabled
|
||||
userWalletsListRepository.load()
|
||||
userWalletsListRepository.userWallets
|
||||
accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId)
|
||||
|
|
@ -112,7 +86,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
// Arrange
|
||||
val wallet = createUserWallet(isMultiCurrency = true)
|
||||
|
||||
every { featureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet))
|
||||
every { accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) } returns flowOf(none())
|
||||
|
||||
|
|
@ -123,7 +96,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
Truth.assertThat(actual).isFalse()
|
||||
|
||||
coVerifyOrder {
|
||||
featureToggles.isFeatureEnabled
|
||||
userWalletsListRepository.load()
|
||||
userWalletsListRepository.userWallets
|
||||
accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId)
|
||||
|
|
@ -136,7 +108,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
val wallet1 = createUserWallet(isMultiCurrency = false)
|
||||
val wallet2 = createUserWallet(isMultiCurrency = true)
|
||||
|
||||
every { featureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet1, wallet2))
|
||||
every { accountsCRUDRepository.getTotalActiveAccountsCount(wallet2.walletId) } returns flowOf(2.some())
|
||||
|
||||
|
|
@ -147,7 +118,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
Truth.assertThat(actual).isTrue()
|
||||
|
||||
coVerifyOrder {
|
||||
featureToggles.isFeatureEnabled
|
||||
userWalletsListRepository.load()
|
||||
userWalletsListRepository.userWallets
|
||||
accountsCRUDRepository.getTotalActiveAccountsCount(wallet2.walletId)
|
||||
|
|
@ -161,25 +131,9 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class InvokeSync {
|
||||
|
||||
@Test
|
||||
fun `returns false when feature is disabled`() = runTest {
|
||||
// Arrange
|
||||
every { featureToggles.isFeatureEnabled } returns false
|
||||
|
||||
// Act
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isFalse()
|
||||
|
||||
verify(exactly = 1) { featureToggles.isFeatureEnabled }
|
||||
verify(inverse = true) { userWalletsListRepository.userWallets.value }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when getUserWalletsSync returns empty list`() = runTest {
|
||||
// Arrange
|
||||
every { featureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets.value } returns emptyList()
|
||||
|
||||
// Act
|
||||
|
|
@ -189,7 +143,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
Truth.assertThat(actual).isFalse()
|
||||
|
||||
verifyOrder {
|
||||
featureToggles.isFeatureEnabled
|
||||
userWalletsListRepository.userWallets.value
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +154,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
// Arrange
|
||||
val wallet = createUserWallet(isMultiCurrency = false)
|
||||
|
||||
every { featureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets.value } returns listOf(wallet)
|
||||
|
||||
// Act
|
||||
|
|
@ -211,7 +163,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
Truth.assertThat(actual).isFalse()
|
||||
|
||||
verifyOrder {
|
||||
featureToggles.isFeatureEnabled
|
||||
userWalletsListRepository.userWallets.value
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +174,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
// Arrange
|
||||
val wallet = createUserWallet(isMultiCurrency = true)
|
||||
|
||||
every { featureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets.value } returns listOf(wallet)
|
||||
coEvery { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) } returns 2.some()
|
||||
|
||||
|
|
@ -234,7 +184,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
Truth.assertThat(actual).isTrue()
|
||||
|
||||
coVerifyOrder {
|
||||
featureToggles.isFeatureEnabled
|
||||
userWalletsListRepository.userWallets.value
|
||||
accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId)
|
||||
}
|
||||
|
|
@ -245,7 +194,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
// Arrange
|
||||
val wallet = createUserWallet(isMultiCurrency = true)
|
||||
|
||||
every { featureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets.value } returns listOf(wallet)
|
||||
coEvery { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) } returns none()
|
||||
|
||||
|
|
@ -256,7 +204,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
Truth.assertThat(actual).isFalse()
|
||||
|
||||
coVerifyOrder {
|
||||
featureToggles.isFeatureEnabled
|
||||
userWalletsListRepository.userWallets.value
|
||||
accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId)
|
||||
}
|
||||
|
|
@ -268,7 +215,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
val wallet1 = createUserWallet(isMultiCurrency = false)
|
||||
val wallet2 = createUserWallet(isMultiCurrency = true)
|
||||
|
||||
every { featureToggles.isFeatureEnabled } returns true
|
||||
every { userWalletsListRepository.userWallets.value } returns listOf(wallet1, wallet2)
|
||||
coEvery { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet2.walletId) } returns 2.some()
|
||||
|
||||
|
|
@ -279,7 +225,6 @@ class IsAccountsModeEnabledUseCaseTest {
|
|||
Truth.assertThat(actual).isTrue()
|
||||
|
||||
coVerifyOrder {
|
||||
featureToggles.isFeatureEnabled
|
||||
userWalletsListRepository.userWallets.value
|
||||
accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet2.walletId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import kotlinx.serialization.Serializable
|
|||
/**
|
||||
* Temporary wrapper over ID to support a gradual migration between two modes:
|
||||
*
|
||||
* - [Wallet] — legacy flow, wallet design; used when the [AccountsFeatureToggles] is disabled.
|
||||
* - [Account] — new flow, wallet/account design; used when the [AccountsFeatureToggles] is enabled.
|
||||
* - [Wallet] — legacy flow, wallet design.
|
||||
* - [Account] — new flow, wallet/account design.
|
||||
*
|
||||
* ⚠️ When an [Account] you must verify the current app mode with [IsAccountsModeEnabledUseCase]
|
||||
* and then use wallet/account design
|
||||
* ⚠️ When using an [Account], you must verify the current app mode with [IsAccountsModeEnabledUseCase],
|
||||
* then use the wallet/account design.
|
||||
*
|
||||
* Intended to be removed after the full migration to the new mode.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue