Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-02 16:49:46 +04:00
parent c3da795b23
commit 4104aa82f8
5 changed files with 725 additions and 1 deletions

View file

@ -22,6 +22,7 @@ import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -432,4 +433,26 @@ internal object TokensDomainModule {
fun provideGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
return GetCryptoCurrenciesUseCase(currenciesRepository)
}
@Provides
@Singleton
fun provideWalletBalanceFetcher(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesFetcher: MultiWalletCryptoCurrenciesFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
dispatchers: CoroutineDispatcherProvider,
): WalletBalanceFetcher {
return WalletBalanceFetcher(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesFetcher = multiWalletCryptoCurrenciesFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
dispatchers = dispatchers,
)
}
}

View file

@ -10,5 +10,6 @@ fun <B> assertEither(actual: Either<Throwable, B>, expected: Either<Throwable, B
val expectedError = expected.leftOrNull() ?: error("Actual is Either.Left: $it")
Truth.assertThat(it::class.java).isEqualTo(expectedError::class.java)
Truth.assertThat(it).hasMessageThat().isEqualTo(expectedError.message)
}
}

View file

@ -86,7 +86,9 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
val actual = fetcher(params)
// Assert
val expected = IllegalStateException("${this::class.simpleName} supports only multi-currency wallet").left()
val expected = IllegalStateException(
"${DefaultMultiWalletCryptoCurrenciesFetcher::class.simpleName} supports only multi-currency wallet",
).left()
assertEither(actual, expected)
verifyOrder { userWalletsStore.getSyncStrict(key = params.userWalletId) }

View file

@ -0,0 +1,163 @@
package com.tangem.domain.tokens.wallet
import arrow.core.Either
import com.tangem.domain.core.flow.FlowFetcher
import com.tangem.domain.core.utils.catchOn
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.wallet.implementor.MultiWalletBalanceFetcher
import com.tangem.domain.tokens.wallet.implementor.SingleWalletBalanceFetcher
import com.tangem.domain.tokens.wallet.implementor.SingleWalletWithTokenBalanceFetcher
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import timber.log.Timber
/**
* Fetcher of wallet balance by [UserWalletId]
*
* @property currenciesRepository currencies repository
* @property multiWalletBalanceFetcher balance fetcher of multi-currency wallet
* @property singleWalletWithTokenBalanceFetcher balance fetcher of single-currency wallet with token
* @property singleWalletBalanceFetcher balance fetcher of single-currency wallet
* @property multiNetworkStatusFetcher networks statuses fetcher
* @property multiQuoteStatusFetcher quotes statuses fetcher
* @property multiYieldBalanceFetcher yields balances fetcher
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
@Suppress("LongParameterList")
class WalletBalanceFetcher internal constructor(
private val currenciesRepository: CurrenciesRepository,
private val multiWalletBalanceFetcher: BaseWalletBalanceFetcher,
private val singleWalletWithTokenBalanceFetcher: BaseWalletBalanceFetcher,
private val singleWalletBalanceFetcher: BaseWalletBalanceFetcher,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
private val dispatchers: CoroutineDispatcherProvider,
) : FlowFetcher<WalletBalanceFetcher.Params> {
/** Additional constructor without internal dependencies */
constructor(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesFetcher: MultiWalletCryptoCurrenciesFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
dispatchers: CoroutineDispatcherProvider,
) : this(
currenciesRepository = currenciesRepository,
multiWalletBalanceFetcher = MultiWalletBalanceFetcher(
multiWalletCryptoCurrenciesFetcher = multiWalletCryptoCurrenciesFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
),
singleWalletWithTokenBalanceFetcher = SingleWalletWithTokenBalanceFetcher(
currenciesRepository = currenciesRepository,
),
singleWalletBalanceFetcher = SingleWalletBalanceFetcher(currenciesRepository = currenciesRepository),
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
dispatchers = dispatchers,
)
override suspend fun invoke(params: Params) = Either.catchOn(dispatchers.default) {
val userWalletId = params.userWalletId
val cardTypesResolver = currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
val fetcher = when {
cardTypesResolver.isMultiwalletAllowed() -> multiWalletBalanceFetcher
cardTypesResolver.isSingleWalletWithToken() -> singleWalletWithTokenBalanceFetcher
cardTypesResolver.isSingleWallet() -> singleWalletBalanceFetcher
else -> error("Unknown type of wallet: $userWalletId")
}
val currencies = fetcher.getCryptoCurrencies(userWalletId = userWalletId).ifEmpty {
error("UserWallet doesn't contain crypto-currencies: $userWalletId")
}
fetcher.fetch(userWalletId = userWalletId, currencies = currencies)
}
private suspend fun BaseWalletBalanceFetcher.fetch(userWalletId: UserWalletId, currencies: Set<CryptoCurrency>) {
coroutineScope {
val results = fetchingSources.map { source ->
async {
val maybeResult = when (source) {
FetchingSource.NETWORK -> fetchNetworks(userWalletId = userWalletId, currencies = currencies)
FetchingSource.QUOTE -> fetchQuotes(currencies = currencies)
FetchingSource.STAKING -> fetchStaking(userWalletId = userWalletId, currencies = currencies)
}
source to maybeResult
}
}
.awaitAll()
val errors = results.mapNotNull { (source, maybeResult) ->
val error = maybeResult.leftOrNull() ?: return@mapNotNull null
source to error
}
check(errors.isEmpty()) {
val message = "Failed to fetch next sources for $userWalletId:\n" +
errors.joinToString(separator = "\n") { "${it.first.name} ${it.second}" }
Timber.e(message)
message
}
}
}
private suspend fun fetchNetworks(
userWalletId: UserWalletId,
currencies: Set<CryptoCurrency>,
): Either<Throwable, Unit> {
return multiNetworkStatusFetcher(
params = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network),
),
)
}
private suspend fun fetchQuotes(currencies: Set<CryptoCurrency>): Either<Throwable, Unit> {
return multiQuoteStatusFetcher(
params = MultiQuoteStatusFetcher.Params(
currenciesIds = currencies.mapNotNullTo(destination = hashSetOf(), transform = { it.id.rawCurrencyId }),
appCurrencyId = null,
),
)
}
private suspend fun fetchStaking(
userWalletId: UserWalletId,
currencies: Set<CryptoCurrency>,
): Either<Throwable, Unit> {
return multiYieldBalanceFetcher(
params = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
),
)
}
/**
* Params of [WalletBalanceFetcher]
*
* @property userWalletId user wallet id
*/
data class Params(val userWalletId: UserWalletId)
}

View file

@ -0,0 +1,535 @@
package com.tangem.domain.tokens.wallet
import arrow.core.left
import arrow.core.right
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.common.test.utils.assertEither
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.wallet.FetchingSource.*
import com.tangem.domain.tokens.wallet.implementor.MultiWalletBalanceFetcher
import com.tangem.domain.tokens.wallet.implementor.SingleWalletBalanceFetcher
import com.tangem.domain.tokens.wallet.implementor.SingleWalletWithTokenBalanceFetcher
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
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 WalletBalanceFetcherTest {
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
private val currenciesRepository: CurrenciesRepository = mockk()
private val multiWalletBalanceFetcher: MultiWalletBalanceFetcher = mockk()
private val singleWalletWithTokenBalanceFetcher: SingleWalletWithTokenBalanceFetcher = mockk()
private val singleWalletBalanceFetcher: SingleWalletBalanceFetcher = mockk()
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher = mockk()
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher = mockk()
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher = mockk()
private val fetcher = WalletBalanceFetcher(
currenciesRepository = currenciesRepository,
multiWalletBalanceFetcher = multiWalletBalanceFetcher,
singleWalletWithTokenBalanceFetcher = singleWalletWithTokenBalanceFetcher,
singleWalletBalanceFetcher = singleWalletBalanceFetcher,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@BeforeEach
fun resetMocks() {
clearMocks(
currenciesRepository,
multiWalletBalanceFetcher,
singleWalletWithTokenBalanceFetcher,
singleWalletBalanceFetcher,
multiNetworkStatusFetcher,
multiQuoteStatusFetcher,
multiYieldBalanceFetcher,
)
}
@Test
fun `fetch failure if getCardTypesResolver THROWS EXCEPTION`() = runTest {
// Arrange
val exception = IllegalStateException("Error")
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } throws exception
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = exception.left()
assertEither(actual, expected)
verifyOrder { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) }
coVerify(inverse = true) {
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
multiNetworkStatusFetcher(params = any())
multiQuoteStatusFetcher(params = any())
multiYieldBalanceFetcher(params = any())
}
}
@Test
fun `fetch failure if getCardTypesResolver cannot resolve wallet type`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns false
every { isSingleWalletWithToken() } returns false
every { isSingleWallet() } returns false
}
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = IllegalStateException("Unknown type of wallet: $userWalletId").left()
assertEither(actual, expected)
verifyOrder { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) }
coVerify(inverse = true) {
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
multiNetworkStatusFetcher(params = any())
multiQuoteStatusFetcher(params = any())
multiYieldBalanceFetcher(params = any())
}
}
@Test
fun `fetch failure if getCryptoCurrencies THROWS EXCEPTION`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns true
}
val exception = IllegalStateException("Error")
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
coEvery { multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } throws exception
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = exception.left()
assertEither(actual, expected)
coVerifyOrder {
currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
}
coVerify(inverse = true) {
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
multiNetworkStatusFetcher(params = any())
multiQuoteStatusFetcher(params = any())
multiYieldBalanceFetcher(params = any())
}
}
@Test
fun `fetch failure if getCryptoCurrencies RETURNS EMPTY SET`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns true
}
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
coEvery { multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } returns emptySet()
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = IllegalStateException("UserWallet doesn't contain crypto-currencies: $userWalletId").left()
assertEither(actual, expected)
coVerifyOrder {
currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
}
coVerify(inverse = true) {
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
multiNetworkStatusFetcher(params = any())
multiQuoteStatusFetcher(params = any())
multiYieldBalanceFetcher(params = any())
}
}
@Test
fun `fetch failure if fetchNetworks RETURNS LEFT`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns true
}
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
val networkStatusFetcherParams = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network),
)
val exception = IllegalStateException("Error")
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
coEvery { multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } returns currencies
every { multiWalletBalanceFetcher.fetchingSources } returns setOf(NETWORK)
coEvery { multiNetworkStatusFetcher(params = networkStatusFetcherParams) } returns exception.left()
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = IllegalStateException(
"Failed to fetch next sources for UserWalletId(011...011):\n" +
"NETWORK java.lang.IllegalStateException: Error",
).left()
assertEither(actual, expected)
coVerifyOrder {
currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
multiWalletBalanceFetcher.fetchingSources
multiNetworkStatusFetcher(params = networkStatusFetcherParams)
}
coVerify(inverse = true) {
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
multiQuoteStatusFetcher(params = any())
multiYieldBalanceFetcher(params = any())
}
}
@Test
fun `fetch failure if fetchQuotes RETURNS LEFT`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns true
}
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
val quoteStatusFetcherParams = MultiQuoteStatusFetcher.Params(
currenciesIds = currencies.mapNotNullTo(destination = hashSetOf(), transform = { it.id.rawCurrencyId }),
appCurrencyId = null,
)
val exception = IllegalStateException("Error")
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
coEvery { multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } returns currencies
every { multiWalletBalanceFetcher.fetchingSources } returns setOf(QUOTE)
coEvery { multiQuoteStatusFetcher(params = quoteStatusFetcherParams) } returns exception.left()
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = IllegalStateException(
"Failed to fetch next sources for UserWalletId(011...011):\n" +
"QUOTE java.lang.IllegalStateException: Error",
).left()
assertEither(actual, expected)
coVerifyOrder {
currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
multiWalletBalanceFetcher.fetchingSources
multiQuoteStatusFetcher(params = quoteStatusFetcherParams)
}
coVerify(inverse = true) {
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
multiNetworkStatusFetcher(params = any())
multiYieldBalanceFetcher(params = any())
}
}
@Test
fun `fetch failure if fetchStaking RETURNS LEFT`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns true
}
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
val yieldBalanceFetcherParams = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
)
val exception = IllegalStateException("Error")
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
coEvery { multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } returns currencies
every { multiWalletBalanceFetcher.fetchingSources } returns setOf(STAKING)
coEvery { multiYieldBalanceFetcher(params = yieldBalanceFetcherParams) } returns exception.left()
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = IllegalStateException(
"Failed to fetch next sources for UserWalletId(011...011):\n" +
"STAKING java.lang.IllegalStateException: Error",
).left()
assertEither(actual, expected)
coVerifyOrder {
currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
multiWalletBalanceFetcher.fetchingSources
multiYieldBalanceFetcher(params = yieldBalanceFetcherParams)
}
coVerify(inverse = true) {
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
multiNetworkStatusFetcher(params = any())
multiQuoteStatusFetcher(params = any())
}
}
@Test
fun `fetch failure if all fetching sources RETURNS LEFT`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns true
}
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
val networkStatusFetcherParams = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network),
)
val quoteStatusFetcherParams = MultiQuoteStatusFetcher.Params(
currenciesIds = currencies.mapNotNullTo(destination = hashSetOf(), transform = { it.id.rawCurrencyId }),
appCurrencyId = null,
)
val yieldBalanceFetcherParams = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
)
val exception = IllegalStateException("Error")
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
coEvery { multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } returns currencies
every { multiWalletBalanceFetcher.fetchingSources } returns setOf(NETWORK, QUOTE, STAKING)
coEvery { multiNetworkStatusFetcher(params = networkStatusFetcherParams) } returns exception.left()
coEvery { multiQuoteStatusFetcher(params = quoteStatusFetcherParams) } returns exception.left()
coEvery { multiYieldBalanceFetcher(params = yieldBalanceFetcherParams) } returns exception.left()
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = IllegalStateException(
"Failed to fetch next sources for UserWalletId(011...011):\n" +
"NETWORK java.lang.IllegalStateException: Error\n" +
"QUOTE java.lang.IllegalStateException: Error\n" +
"STAKING java.lang.IllegalStateException: Error",
).left()
assertEither(actual, expected)
coVerifyOrder {
currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
multiWalletBalanceFetcher.fetchingSources
multiNetworkStatusFetcher(params = networkStatusFetcherParams)
multiQuoteStatusFetcher(params = quoteStatusFetcherParams)
multiYieldBalanceFetcher(params = yieldBalanceFetcherParams)
}
coVerify(inverse = true) {
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
}
}
@Test
fun `fetch successfully for multi-currency wallet`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns true
}
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
val networkStatusFetcherParams = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network),
)
val quoteStatusFetcherParams = MultiQuoteStatusFetcher.Params(
currenciesIds = currencies.mapNotNullTo(destination = hashSetOf(), transform = { it.id.rawCurrencyId }),
appCurrencyId = null,
)
val yieldBalanceFetcherParams = MultiYieldBalanceFetcher.Params(
userWalletId = userWalletId,
currencyIdWithNetworkMap = currencies.associateTo(hashMapOf()) { it.id to it.network },
)
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
coEvery { multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } returns currencies
every { multiWalletBalanceFetcher.fetchingSources } returns setOf(NETWORK, QUOTE, STAKING)
coEvery { multiNetworkStatusFetcher(params = networkStatusFetcherParams) } returns Unit.right()
coEvery { multiQuoteStatusFetcher(params = quoteStatusFetcherParams) } returns Unit.right()
coEvery { multiYieldBalanceFetcher(params = yieldBalanceFetcherParams) } returns Unit.right()
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = Unit.right()
assertEither(actual, expected)
coVerifyOrder {
currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
multiWalletBalanceFetcher.fetchingSources
multiNetworkStatusFetcher(params = networkStatusFetcherParams)
multiQuoteStatusFetcher(params = quoteStatusFetcherParams)
multiYieldBalanceFetcher(params = yieldBalanceFetcherParams)
}
coVerify(inverse = true) {
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
}
}
@Test
fun `fetch successfully for single-currency with token wallet`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns false
every { isSingleWalletWithToken() } returns true
}
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
val networkStatusFetcherParams = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network),
)
val quoteStatusFetcherParams = MultiQuoteStatusFetcher.Params(
currenciesIds = currencies.mapNotNullTo(destination = hashSetOf(), transform = { it.id.rawCurrencyId }),
appCurrencyId = null,
)
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
coEvery {
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
} returns currencies
every { singleWalletWithTokenBalanceFetcher.fetchingSources } returns setOf(NETWORK, QUOTE)
coEvery { multiNetworkStatusFetcher(params = networkStatusFetcherParams) } returns Unit.right()
coEvery { multiQuoteStatusFetcher(params = quoteStatusFetcherParams) } returns Unit.right()
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = Unit.right()
assertEither(actual, expected)
coVerifyOrder {
currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
singleWalletWithTokenBalanceFetcher.fetchingSources
multiNetworkStatusFetcher(params = networkStatusFetcherParams)
multiQuoteStatusFetcher(params = quoteStatusFetcherParams)
}
coVerify(inverse = true) {
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
multiYieldBalanceFetcher(params = any())
}
}
@Test
fun `fetch successfully for single-currency wallet`() = runTest {
// Arrange
val cardTypesResolver = mockk<CardTypesResolver> {
every { isMultiwalletAllowed() } returns false
every { isSingleWalletWithToken() } returns false
every { isSingleWallet() } returns true
}
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
val networkStatusFetcherParams = MultiNetworkStatusFetcher.Params(
userWalletId = userWalletId,
networks = currencies.mapTo(destination = hashSetOf(), transform = CryptoCurrency::network),
)
val quoteStatusFetcherParams = MultiQuoteStatusFetcher.Params(
currenciesIds = currencies.mapNotNullTo(destination = hashSetOf(), transform = { it.id.rawCurrencyId }),
appCurrencyId = null,
)
every { currenciesRepository.getCardTypesResolver(userWalletId = userWalletId) } returns cardTypesResolver
coEvery { singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId) } returns currencies
every { singleWalletBalanceFetcher.fetchingSources } returns setOf(NETWORK, QUOTE)
coEvery { multiNetworkStatusFetcher(params = networkStatusFetcherParams) } returns Unit.right()
coEvery { multiQuoteStatusFetcher(params = quoteStatusFetcherParams) } returns Unit.right()
// Act
val actual = fetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
// Assert
val expected = Unit.right()
assertEither(actual, expected)
coVerifyOrder {
currenciesRepository.getCardTypesResolver(userWalletId = userWalletId)
singleWalletBalanceFetcher.getCryptoCurrencies(userWalletId = userWalletId)
singleWalletBalanceFetcher.fetchingSources
multiNetworkStatusFetcher(params = networkStatusFetcherParams)
multiQuoteStatusFetcher(params = quoteStatusFetcherParams)
}
coVerify(inverse = true) {
multiWalletBalanceFetcher.getCryptoCurrencies(userWalletId = any())
singleWalletWithTokenBalanceFetcher.getCryptoCurrencies(userWalletId = any())
multiYieldBalanceFetcher(params = any())
}
}
private companion object {
val userWalletId = UserWalletId("011")
}
}