Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-03 17:26:16 +04:00
parent e6530aa1ee
commit 584be65dd2
7 changed files with 118 additions and 354 deletions

View file

@ -20,7 +20,18 @@ interface CardCryptoCurrencyFactory {
* @param network network
*/
@Throws
suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency>
suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency> {
return create(userWalletId = userWalletId, networks = setOf(network))[network].orEmpty()
}
/**
* Universal method for creating list of [CryptoCurrency] in [networks] for any card
*
* @param userWalletId user wallet id that determines type of card
* @param networks networks
*/
@Throws
suspend fun create(userWalletId: UserWalletId, networks: Set<Network>): Map<Network, List<CryptoCurrency>>
/**
* Universal method for creating list of [CryptoCurrency] in [network] for any card

View file

@ -2,7 +2,6 @@ package com.tangem.data.common.currency
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.datasource.local.token.UserTokensResponseStore
@ -36,40 +35,45 @@ internal class DefaultCardCryptoCurrencyFactory(
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) }
override suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency> {
override suspend fun create(
userWalletId: UserWalletId,
networks: Set<Network>,
): Map<Network, List<CryptoCurrency>> {
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
val blockchain = Blockchain.fromNetworkId(networkId = network.backendId)
// multi-currency wallet
if (userWallet !is UserWallet.Cold || userWallet.isMultiCurrency) {
return getMultiWalletCurrencies(userWallet = userWallet, networks = setOf(network))[network].orEmpty()
return getMultiWalletCurrencies(userWallet = userWallet, networks = networks)
}
// check if the blockchain of single-currency wallet is the same as network
val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
if (cardBlockchain != blockchain) return emptyList()
val cardNetworkId = userWallet.scanResponse.cardTypesResolver.getBlockchain().toNetworkId()
val cardNetwork = networks.firstOrNull { it.backendId == cardNetworkId }
if (cardNetwork == null) return emptyMap()
// single-currency wallet with token (NODL)
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
return createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
val currencies = createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
return mapOf(cardNetwork to currencies)
}
// single-currency wallet
return createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
val primaryCurrency = createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
return mapOf(cardNetwork to listOf(primaryCurrency))
}
override suspend fun createByRawId(userWalletId: UserWalletId, networkRawId: Network.RawID): List<CryptoCurrency> {
override suspend fun createByRawId(userWalletId: UserWalletId, network: Network.RawID): List<CryptoCurrency> {
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
val blockchain = networkRawId.toBlockchain()
val blockchain = network.toBlockchain()
// multi-currency wallet
if (userWallet.isMultiCurrency || userWallet !is UserWallet.Cold) {
return getMultiWalletCurrenciesByRawId(
userWallet = userWallet,
rawIds = setOf(networkRawId),
)[networkRawId].orEmpty()
rawIds = setOf(network),
)[network].orEmpty()
}
// check if the blockchain of single-currency wallet is the same as network
@ -143,13 +147,15 @@ internal class DefaultCardCryptoCurrencyFactory(
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
?: return emptyMap()
return responseCryptoCurrenciesFactory.createCurrencies(
val existingNetworkWithCurrencies = responseCryptoCurrenciesFactory.createCurrencies(
tokens = response.tokens.filter { token ->
networks.any { it.backendId == token.networkId && it.derivationPath.value == token.derivationPath }
},
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
)
.groupBy(CryptoCurrency::network)
return networks.associateWith { emptyList<CryptoCurrency>() } + existingNetworkWithCurrencies
}
private suspend fun getMultiWalletCurrenciesByRawId(

View file

@ -266,7 +266,9 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
CreateCurrenciesForMultiWalletModel(
multiWallet = createMultiWallet(),
userTokensResponse = createUserTokensResponse(),
expected = Result.success(emptyMap()),
expected = Result.success(
setOf(ethereum.network, bitcoin.network).associateWith { emptyList() },
),
),
CreateCurrenciesForMultiWalletModel(
multiWallet = createMultiWallet(),

View file

@ -7,11 +7,10 @@ import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
import com.tangem.data.networks.store.NetworksStatusesStore
import com.tangem.data.networks.store.setSourceAsCache
import com.tangem.data.networks.store.setSourceAsOnlyCache
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.core.utils.eitherOn
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -22,7 +21,6 @@ import javax.inject.Inject
* Default implementation of [MultiNetworkStatusFetcher]
*
* @property networksStatusesStore networks statuses store
* @property userWalletsStore user wallets store
* @property cardCryptoCurrencyFactory card crypto currency factory
* @property commonNetworkStatusFetcher common network status fetcher
* @property dispatchers dispatchers
@ -32,7 +30,6 @@ import javax.inject.Inject
@Suppress("LongParameterList")
internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
private val networksStatusesStore: NetworksStatusesStore,
private val userWalletsStore: UserWalletsStore,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher,
private val dispatchers: CoroutineDispatcherProvider,
@ -41,8 +38,8 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
override suspend fun invoke(params: MultiNetworkStatusFetcher.Params) = eitherOn(dispatchers.default) {
networksStatusesStore.setSourceAsCache(userWalletId = params.userWalletId, networks = params.networks)
val userWallet = catch(
block = { userWalletsStore.getSyncStrict(key = params.userWalletId) },
val networksCurrencies = catch(
block = { createNetworksCurrenciesMap(params) },
catch = {
networksStatusesStore.setSourceAsOnlyCache(
userWalletId = params.userWalletId,
@ -53,31 +50,6 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
},
)
val cardTypesResolver = userWallet.requireColdWallet().cardTypesResolver // TODO [REDACTED_TASK_KEY]
val isWalletSupported = with(cardTypesResolver) {
isMultiwalletAllowed() || isSingleWalletWithToken()
}
ensure(isWalletSupported) {
networksStatusesStore.setSourceAsOnlyCache(
userWalletId = params.userWalletId,
networks = params.networks,
)
IllegalStateException("User wallet is not multi-currency")
}
val networksCurrencies = if (cardTypesResolver.isMultiwalletAllowed()) {
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(
userWallet = userWallet,
networks = params.networks,
)
} else {
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
scanResponse = userWallet.scanResponse,
)
.groupBy { it.network }
}
val result = coroutineScope {
params.networks
.map { network ->
@ -98,4 +70,13 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
IllegalStateException("Failed to fetch network statuses")
}
}
private suspend fun createNetworksCurrenciesMap(
params: MultiNetworkStatusFetcher.Params,
): Map<Network, List<CryptoCurrency>> {
return cardCryptoCurrencyFactory.create(
userWalletId = params.userWalletId,
networks = params.networks,
)
}
}

View file

@ -1,47 +1,27 @@
package com.tangem.data.networks.single
import arrow.core.Either
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
import com.tangem.data.networks.store.NetworksStatusesStore
import com.tangem.data.networks.store.setSourceAsCache
import com.tangem.domain.core.utils.catchOn
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
/**
* Default implementation of [SingleNetworkStatusFetcher]
*
* @property commonNetworkStatusFetcher common network status fetcher
* @property networksStatusesStore networks statuses store
* @property cardCryptoCurrencyFactory card crypto currency factory
* @property dispatchers dispatchers
* @property multiNetworkStatusFetcher multi network status fetcher
*
[REDACTED_AUTHOR]
*/
internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher,
private val networksStatusesStore: NetworksStatusesStore,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val dispatchers: CoroutineDispatcherProvider,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
) : SingleNetworkStatusFetcher {
override suspend fun invoke(params: SingleNetworkStatusFetcher.Params): Either<Throwable, Unit> {
return Either.catchOn(dispatchers.default) {
networksStatusesStore.setSourceAsCache(userWalletId = params.userWalletId, network = params.network)
val networkCurrencies = cardCryptoCurrencyFactory.create(
return multiNetworkStatusFetcher(
params = MultiNetworkStatusFetcher.Params(
userWalletId = params.userWalletId,
network = params.network,
)
commonNetworkStatusFetcher.fetch(
userWalletId = params.userWalletId,
network = params.network,
networkCurrencies = networkCurrencies.toSet(),
)
.onLeft { throw it }
}
networks = setOf(params.network),
),
)
}
}

View file

@ -1,19 +1,15 @@
package com.tangem.data.networks.multi
import arrow.core.Either
import com.google.common.truth.Truth
import arrow.core.left
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
import com.tangem.common.test.utils.assertEither
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
import com.tangem.data.networks.store.NetworksStatusesStore
import com.tangem.data.networks.store.setSourceAsCache
import com.tangem.data.networks.store.setSourceAsOnlyCache
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
@ -29,13 +25,11 @@ import org.junit.jupiter.api.TestInstance
internal class DefaultMultiNetworkStatusFetcherTest {
private val networksStatusesStore: NetworksStatusesStore = mockk(relaxUnitFun = true)
private val userWalletsStore: UserWalletsStore = mockk()
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher = mockk()
private val fetcher = DefaultMultiNetworkStatusFetcher(
networksStatusesStore = networksStatusesStore,
userWalletsStore = userWalletsStore,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
commonNetworkStatusFetcher = commonNetworkStatusFetcher,
dispatchers = TestingCoroutineDispatcherProvider(),
@ -43,16 +37,17 @@ internal class DefaultMultiNetworkStatusFetcherTest {
@BeforeEach
fun resetMocks() {
clearMocks(networksStatusesStore, userWalletsStore, cardCryptoCurrencyFactory, commonNetworkStatusFetcher)
clearMocks(networksStatusesStore, cardCryptoCurrencyFactory, commonNetworkStatusFetcher)
}
@Test
fun `fetch successfully for multi-currency card`() = runTest {
fun `fetch successfully`() = runTest {
// Arrange
val networks = setOf(ethereum.network, cardano.network)
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
val userWallet = MockUserWalletFactory.create()
val cardTypesResolver = mockk<CardTypesResolver>()
val params = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = setOf(ethereum.network, cardano.network),
)
val networksCurrencies = mapOf(
ethereum.network to listOf(ethereum),
cardano.network to listOf(cardano),
@ -60,16 +55,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
val ethereumFetcherResult = Either.Right(Unit)
val cardanoFetcherResult = Either.Right(Unit)
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
mockkStatic(UserWallet.Cold::cardTypesResolver)
every { userWallet.cardTypesResolver } returns cardTypesResolver
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns true
coEvery {
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
} returns networksCurrencies
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } returns networksCurrencies
coEvery {
commonNetworkStatusFetcher.fetch(
@ -92,14 +78,11 @@ internal class DefaultMultiNetworkStatusFetcherTest {
// Assert
val expected = Either.Right(Unit)
Truth.assertThat(actual).isEqualTo(expected)
assertEither(actual, expected)
coVerifyOrder {
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
userWalletsStore.getSyncStrict(key = userWalletId)
cardTypesResolver.isMultiwalletAllowed()
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
cardCryptoCurrencyFactory.create(params.userWalletId, params.networks)
commonNetworkStatusFetcher.fetch(
userWalletId = params.userWalletId,
network = ethereum.network,
@ -114,75 +97,17 @@ internal class DefaultMultiNetworkStatusFetcherTest {
coVerify(inverse = true) {
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
cardTypesResolver.isSingleWalletWithToken()
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
}
}
@Test
fun `fetch successfully for single-currency card with token`() = runTest {
// Arrange
val networks = setOf(ethereum.network)
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
val userWallet = MockUserWalletFactory.create()
val cardTypesResolver = mockk<CardTypesResolver>()
val networksCurrencies = listOf(ethereum)
val ethereumFetcherResult = Either.Right(Unit)
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
mockkStatic(UserWallet.Cold::cardTypesResolver)
every { userWallet.cardTypesResolver } returns cardTypesResolver
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns false
coEvery { cardTypesResolver.isSingleWalletWithToken() } returns true
coEvery {
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
} returns networksCurrencies
coEvery {
commonNetworkStatusFetcher.fetch(
userWalletId = params.userWalletId,
network = ethereum.network,
networkCurrencies = setOf(ethereum),
)
} returns ethereumFetcherResult
// Act
val actual = fetcher(params)
// Assert
val expected = Either.Right(Unit)
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
userWalletsStore.getSyncStrict(key = userWalletId)
cardTypesResolver.isMultiwalletAllowed()
cardTypesResolver.isSingleWalletWithToken()
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
commonNetworkStatusFetcher.fetch(
userWalletId = params.userWalletId,
network = ethereum.network,
networkCurrencies = setOf(ethereum),
)
}
coVerify(inverse = true) {
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(any(), any())
}
}
@Test
fun `fetch failure if one of them fails`() = runTest {
// Arrange
val networks = setOf(ethereum.network, cardano.network)
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
val userWallet = MockUserWalletFactory.create()
val cardTypesResolver = mockk<CardTypesResolver>()
val params = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = setOf(ethereum.network, cardano.network),
)
val networksCurrencies = mapOf(
ethereum.network to listOf(ethereum),
cardano.network to listOf(cardano),
@ -190,16 +115,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
val ethereumFetcherResult = Either.Left(IllegalStateException())
val cardanoFetcherResult = Either.Right(Unit)
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
mockkStatic(UserWallet.Cold::cardTypesResolver)
every { userWallet.cardTypesResolver } returns cardTypesResolver
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns true
coEvery {
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
} returns networksCurrencies
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } returns networksCurrencies
coEvery {
commonNetworkStatusFetcher.fetch(
@ -222,15 +138,11 @@ internal class DefaultMultiNetworkStatusFetcherTest {
// Assert
val expected = Either.Left(IllegalStateException("Failed to fetch network statuses"))
Truth.assertThat(actual.isLeft()).isTrue()
Truth.assertThat(actual.leftOrNull()).isInstanceOf(expected.leftOrNull()!!::class.java)
Truth.assertThat(actual.leftOrNull()).hasMessageThat().isEqualTo(expected.leftOrNull()!!.message)
assertEither(actual, expected)
coVerifyOrder {
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
userWalletsStore.getSyncStrict(key = userWalletId)
cardTypesResolver.isMultiwalletAllowed()
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
cardCryptoCurrencyFactory.create(params.userWalletId, params.networks)
commonNetworkStatusFetcher.fetch(
userWalletId = params.userWalletId,
network = ethereum.network,
@ -245,18 +157,17 @@ internal class DefaultMultiNetworkStatusFetcherTest {
coVerify(inverse = true) {
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
cardTypesResolver.isSingleWalletWithToken()
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
}
}
@Test
fun `fetch failure if all of them fails`() = runTest {
// Arrange
val networks = setOf(ethereum.network, cardano.network)
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
val userWallet = MockUserWalletFactory.create()
val cardTypesResolver = mockk<CardTypesResolver>()
val params = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = setOf(ethereum.network, cardano.network),
)
val networksCurrencies = mapOf(
ethereum.network to listOf(ethereum),
cardano.network to listOf(cardano),
@ -264,16 +175,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
val ethereumFetcherResult = Either.Left(IllegalStateException("ethereum"))
val cardanoFetcherResult = Either.Left(IllegalStateException("cardano"))
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
mockkStatic(UserWallet.Cold::cardTypesResolver)
every { userWallet.cardTypesResolver } returns cardTypesResolver
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns true
coEvery {
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
} returns networksCurrencies
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } returns networksCurrencies
coEvery {
commonNetworkStatusFetcher.fetch(
@ -296,15 +198,11 @@ internal class DefaultMultiNetworkStatusFetcherTest {
// Assert
val expected = Either.Left(IllegalStateException("Failed to fetch network statuses"))
Truth.assertThat(actual.isLeft()).isTrue()
Truth.assertThat(actual.leftOrNull()).isInstanceOf(expected.leftOrNull()!!::class.java)
Truth.assertThat(actual.leftOrNull()).hasMessageThat().isEqualTo(expected.leftOrNull()!!.message)
assertEither(actual, expected)
coVerifyOrder {
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
userWalletsStore.getSyncStrict(key = userWalletId)
cardTypesResolver.isMultiwalletAllowed()
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
cardCryptoCurrencyFactory.create(params.userWalletId, params.networks)
commonNetworkStatusFetcher.fetch(
userWalletId = params.userWalletId,
network = ethereum.network,
@ -319,85 +217,35 @@ internal class DefaultMultiNetworkStatusFetcherTest {
coVerify(inverse = true) {
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
cardTypesResolver.isSingleWalletWithToken()
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
}
}
@Test
fun `fetch failure if userWalletsStore throws exception`() = runTest {
fun `fetch failure if cardCryptoCurrencyFactory throws exception`() = runTest {
// Arrange
val networks = setOf(ethereum.network, cardano.network)
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
val userWalletStoreException = IllegalStateException()
val params = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = setOf(ethereum.network, cardano.network),
)
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
every { userWalletsStore.getSyncStrict(key = userWalletId) } throws userWalletStoreException
coEvery { networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks) } returns Unit
val factoryException = IllegalStateException()
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } throws factoryException
// Act
val actual = fetcher(params)
// Assert
val expected = Either.Left(userWalletStoreException)
Truth.assertThat(actual).isEqualTo(expected)
val expected = factoryException.left()
assertEither(actual, expected)
coVerifyOrder {
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
userWalletsStore.getSyncStrict(key = userWalletId)
cardCryptoCurrencyFactory.create(params.userWalletId, params.networks)
networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks)
}
coVerify(inverse = true) {
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(any(), any())
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
commonNetworkStatusFetcher.fetch(any(), any(), any())
}
}
@Test
fun `fetch failure if card is single-currency`() = runTest {
// Arrange
val networks = setOf(ethereum.network, cardano.network)
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
val userWallet = MockUserWalletFactory.create()
val cardTypesResolver = mockk<CardTypesResolver>()
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
mockkStatic(UserWallet.Cold::cardTypesResolver)
every { userWallet.cardTypesResolver } returns cardTypesResolver
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns false
coEvery { cardTypesResolver.isSingleWalletWithToken() } returns false
coEvery { networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks) } returns Unit
// Act
val actual = fetcher(params)
// Assert
val expected = Either.Left(IllegalStateException("User wallet is not multi-currency"))
Truth.assertThat(actual.isLeft()).isTrue()
Truth.assertThat(actual.leftOrNull()).isInstanceOf(expected.leftOrNull()!!::class.java)
Truth.assertThat(actual.leftOrNull()).hasMessageThat().isEqualTo(expected.leftOrNull()!!.message)
coVerifyOrder {
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
userWalletsStore.getSyncStrict(key = userWalletId)
cardTypesResolver.isMultiwalletAllowed()
cardTypesResolver.isSingleWalletWithToken()
networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks)
}
coVerify(inverse = true) {
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(any(), any())
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
commonNetworkStatusFetcher.fetch(any(), any(), any())
}
coVerify(inverse = true) { commonNetworkStatusFetcher.fetch(any(), any(), any()) }
}
private companion object {

View file

@ -2,16 +2,15 @@ package com.tangem.data.networks.single
import arrow.core.Either
import arrow.core.left
import com.google.common.truth.Truth
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
import com.tangem.data.networks.store.NetworksStatusesStore
import com.tangem.data.networks.store.setSourceAsCache
import com.tangem.common.test.utils.assertEither
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@ -23,114 +22,51 @@ import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultSingleNetworkStatusFetcherTest {
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher = mockk()
private val networksStatusesStore: NetworksStatusesStore = mockk(relaxUnitFun = true)
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher = mockk()
private val fetcher = DefaultSingleNetworkStatusFetcher(
commonNetworkStatusFetcher = commonNetworkStatusFetcher,
networksStatusesStore = networksStatusesStore,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
dispatchers = TestingCoroutineDispatcherProvider(),
)
private val fetcher = DefaultSingleNetworkStatusFetcher(multiNetworkStatusFetcher = multiNetworkStatusFetcher)
@BeforeEach
fun resetMocks() {
clearMocks(commonNetworkStatusFetcher, networksStatusesStore, cardCryptoCurrencyFactory)
clearMocks(multiNetworkStatusFetcher)
}
@Test
fun `fetch successfully`() = runTest {
// Arrange
val params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = ethereum.network)
val networkCurrencies = listOf(ethereum)
val commonFetcherResult = Either.Right(Unit)
val multiParams = MultiNetworkStatusFetcher.Params(userWalletId, setOf(params.network))
val multiFetcherResult = Either.Right(Unit)
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.network) } returns Unit
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns networkCurrencies
coEvery {
commonNetworkStatusFetcher.fetch(
userWalletId = params.userWalletId,
network = params.network,
networkCurrencies = networkCurrencies.toSet(),
)
} returns commonFetcherResult
coEvery { multiNetworkStatusFetcher(params = multiParams) } returns multiFetcherResult
// Act
val actual = fetcher(params)
// Assert
val expected = commonFetcherResult
val expected = multiFetcherResult
assertEither(actual, expected)
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
networksStatusesStore.setSourceAsCache(params.userWalletId, params.network)
cardCryptoCurrencyFactory.create(params.userWalletId, params.network)
commonNetworkStatusFetcher.fetch(params.userWalletId, params.network, setOf(ethereum))
}
coVerify(exactly = 1) { multiNetworkStatusFetcher(params = multiParams) }
}
@Test
fun `fetch failure if cardCryptoCurrencyFactory throws exception`() = runTest {
fun `fetch failure`() = runTest {
// Arrange
val params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = ethereum.network)
val factoryException = IllegalStateException()
val multiParams = MultiNetworkStatusFetcher.Params(userWalletId, setOf(params.network))
val multiFetcherResult = IllegalStateException("Error").left()
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.network) } returns Unit
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } throws factoryException
coEvery { multiNetworkStatusFetcher(params = multiParams) } returns multiFetcherResult
// Act
val actual = fetcher(params)
// Arrange
val expected = Either.Left(factoryException)
Truth.assertThat(actual).isEqualTo(expected)
val expected = multiFetcherResult
assertEither(actual, expected)
coVerifyOrder {
networksStatusesStore.setSourceAsCache(userWalletId = params.userWalletId, network = params.network)
cardCryptoCurrencyFactory.create(userWalletId = params.userWalletId, network = params.network)
}
coVerify(inverse = true) {
commonNetworkStatusFetcher.fetch(userWalletId = any(), network = any(), networkCurrencies = any())
}
}
@Test
fun `fetch failure if commonNetworkStatusFetcher returns exception`() = runTest {
// Arrange
val params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = ethereum.network)
val networkCurrencies = listOf(ethereum)
val commonFetcherResult = IllegalStateException().left()
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.network) } returns Unit
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns networkCurrencies
coEvery {
commonNetworkStatusFetcher.fetch(
userWalletId = params.userWalletId,
network = params.network,
networkCurrencies = networkCurrencies.toSet(),
)
} returns commonFetcherResult
// Act
val actual = fetcher(params)
// Arrange
val expected = commonFetcherResult
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
networksStatusesStore.setSourceAsCache(userWalletId = params.userWalletId, network = params.network)
cardCryptoCurrencyFactory.create(userWalletId = params.userWalletId, network = params.network)
commonNetworkStatusFetcher.fetch(
userWalletId = params.userWalletId,
network = params.network,
networkCurrencies = setOf(ethereum),
)
}
coVerify(exactly = 1) { multiNetworkStatusFetcher(params = multiParams) }
}
private companion object {