From 043602ebd6518320ddb467ffa5aaf21a26c82ecb Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Sep 2025 18:28:45 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../domain/token/MockCryptoCurrencyFactory.kt | 6 +- .../data/swap/DefaultSwapRepositoryV2.kt | 23 +- .../com/tangem/data/swap/di/SwapDataModule.kt | 2 - .../BaseCurrencyStatusOperations.kt | 2 +- .../CachedCurrenciesStatusesOperations.kt | 15 +- .../operations/CryptoCurrencyStatusFactory.kt | 266 +++++++ .../operations/CurrencyStatusOperations.kt | 161 ---- .../utils/CurrencyStatusProxyCreator.kt | 47 +- .../CryptoCurrencyStatusFactoryTest.kt | 686 ++++++++++++++++++ 9 files changed, 987 insertions(+), 221 deletions(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt delete mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactoryTest.kt diff --git a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt index c56e3718ef..d5f73cbee0 100644 --- a/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt +++ b/common/test/src/main/java/com/tangem/common/test/domain/token/MockCryptoCurrencyFactory.kt @@ -8,12 +8,12 @@ import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.common.test.domain.wallet.MockUserWalletFactory import com.tangem.data.common.currency.CryptoCurrencyFactory -import com.tangem.domain.wallets.derivations.DerivationStyleProvider -import com.tangem.domain.wallets.derivations.derivationStyleProvider import com.tangem.domain.card.configs.GenericCardConfig import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.derivations.DerivationStyleProvider +import com.tangem.domain.wallets.derivations.derivationStyleProvider /** [REDACTED_AUTHOR] @@ -110,7 +110,7 @@ class MockCryptoCurrencyFactory(private val userWallet: UserWallet.Cold = defaul ) } - fun createToken(blockchain: Blockchain): CryptoCurrency { + fun createToken(blockchain: Blockchain): CryptoCurrency.Token { return factory.createToken( sdkToken = Token( name = "NEVER-MIND", diff --git a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt index 277a7125be..1c07c142a5 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/DefaultSwapRepositoryV2.kt @@ -1,6 +1,8 @@ package com.tangem.data.swap -import arrow.core.right +import arrow.core.none +import arrow.core.some +import arrow.core.toOption import com.squareup.moshi.Moshi import com.tangem.data.common.api.safeApiCall import com.tangem.data.swap.converter.SwapDataConverter @@ -28,7 +30,7 @@ import com.tangem.domain.quotes.single.SingleQuoteStatusProducer import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier import com.tangem.domain.swap.SwapRepositoryV2 import com.tangem.domain.swap.models.* -import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator +import com.tangem.domain.tokens.operations.CryptoCurrencyStatusFactory import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async @@ -49,7 +51,6 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( private val dataSignatureVerifier: DataSignatureVerifier, private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier, private val singleQuoteStatusFetcher: SingleQuoteStatusFetcher, - private val currencyStatusProxyCreator: CurrencyStatusProxyCreator, @NetworkMoshi moshi: Moshi, ) : SwapRepositoryV2 { @@ -391,17 +392,19 @@ internal class DefaultSwapRepositoryV2 @Inject constructor( ) } - return currencyStatusProxyCreator.createCurrencyStatus( + val quoteStatus = quote ?: singleQuoteStatusSupplier.getSyncOrNull( + params = SingleQuoteStatusProducer.Params(rawCurrencyId = rawCurrencyId), + ) + + return CryptoCurrencyStatusFactory.create( currency = cryptoCurrency, - maybeQuoteStatus = quote?.right() ?: singleQuoteStatusSupplier.getSyncOrNull( - params = SingleQuoteStatusProducer.Params(rawCurrencyId = rawCurrencyId), - ).right(), maybeNetworkStatus = NetworkStatus( network = cryptoCurrency.network, value = NetworkStatus.MissedDerivation, // Caution!!! Do not change this status - ).right(), - maybeYieldBalance = null, - ).getOrNull() + ).some(), + maybeQuoteStatus = quoteStatus.toOption(), + maybeYieldBalance = none(), + ) } private fun parseTxDetails(txDetailsJson: String): TxDetails? { diff --git a/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt b/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt index 5edf45f002..595d799f2d 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/di/SwapDataModule.kt @@ -17,7 +17,6 @@ import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier import com.tangem.domain.swap.SwapErrorResolver import com.tangem.domain.swap.SwapRepositoryV2 import com.tangem.domain.swap.SwapTransactionRepository -import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -59,7 +58,6 @@ internal object SwapDataModule { moshi = moshi, singleQuoteStatusSupplier = singleQuoteStatusSupplier, singleQuoteStatusFetcher = singleQuoteStatusFetcher, - currencyStatusProxyCreator = CurrencyStatusProxyCreator(), ) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index 55d53e54c8..76c726d9de 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -54,7 +54,7 @@ abstract class BaseCurrencyStatusOperations( private val stakingIdFactory: StakingIdFactory, ) { - protected val currencyStatusProxyCreator = CurrencyStatusProxyCreator() + private val currencyStatusProxyCreator = CurrencyStatusProxyCreator() abstract fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index d261a865d9..fb20d04a34 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -171,29 +171,22 @@ class CachedCurrenciesStatusesOperations( ): Lce> = lce { isLoading.set(isUpdating) - var quotesRetrievingFailed = false - val networksStatuses = maybeNetworkStatuses?.bindEither()?.toNonEmptySetOrNull() val yieldBalances = maybeYieldBalances?.bindEither() val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) { null } - if (quotes == null) { - quotesRetrievingFailed = true - } - currencies.map { currency -> val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId } val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network } val yieldBalance = findYieldBalanceOrNull(yieldBalances, currency, networkStatus) - val currencyStatus = currencyStatusProxyCreator.createCurrencyStatus( + val currencyStatus = CryptoCurrencyStatusFactory.create( currency = currency, - quoteStatus = quote, - networkStatus = networkStatus, - yieldBalance = yieldBalance, - ignoreQuote = quotesRetrievingFailed, + maybeNetworkStatus = networkStatus.toOption(), + maybeQuoteStatus = quote.toOption(), + maybeYieldBalance = yieldBalance.toOption(), ) currencyStatus diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt new file mode 100644 index 0000000000..3ba3d65d4e --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactory.kt @@ -0,0 +1,266 @@ +package com.tangem.domain.tokens.operations + +import arrow.core.Option +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.network.NetworkAddress +import com.tangem.domain.models.network.NetworkStatus +import com.tangem.domain.models.network.TxInfo +import com.tangem.domain.models.quote.QuoteStatus +import com.tangem.domain.models.staking.YieldBalance +import com.tangem.domain.models.yield.supply.YieldSupplyStatus +import java.math.BigDecimal + +/** + * Factory to create [CryptoCurrencyStatus] from [NetworkStatus], [QuoteStatus] and [YieldBalance]. + * +[REDACTED_AUTHOR] + */ +object CryptoCurrencyStatusFactory { + + private val QuoteStatus?.fiatRate: BigDecimal? + get() = (this?.value as? QuoteStatus.Data)?.fiatRate + + private val QuoteStatus?.priceChange: BigDecimal? + get() = (this?.value as? QuoteStatus.Data)?.priceChange + + /** + * Creates [CryptoCurrencyStatus] from [NetworkStatus], [QuoteStatus] and [YieldBalance]. + * + + * @param maybeNetworkStatus An optional network status containing blockchain information. + * @param maybeQuoteStatus An optional quote status containing price information. + * @param maybeYieldBalance An optional yield balance containing staking information. + */ + fun create( + currency: CryptoCurrency, + maybeNetworkStatus: Option, + maybeQuoteStatus: Option, + maybeYieldBalance: Option, + ): CryptoCurrencyStatus { + return CryptoCurrencyStatus( + currency = currency, + value = createStatus( + currency = currency, + maybeNetworkStatus = maybeNetworkStatus, + maybeYieldBalance = maybeYieldBalance, + maybeQuoteStatus = maybeQuoteStatus, + ), + ) + } + + private fun createStatus( + currency: CryptoCurrency, + maybeNetworkStatus: Option, + maybeQuoteStatus: Option, + maybeYieldBalance: Option, + ): CryptoCurrencyStatus.Value { + val quoteStatus = maybeQuoteStatus.getOrNull() + + return when (val status = maybeNetworkStatus.getOrNull()?.value) { + is NetworkStatus.MissedDerivation -> createMissedDerivation(quoteStatus) + is NetworkStatus.Unreachable -> createUnreachable(status, quoteStatus) + is NetworkStatus.NoAccount -> createNoAccount(status, quoteStatus) + is NetworkStatus.Verified -> createStatus(currency, status, quoteStatus, maybeYieldBalance) + null -> CryptoCurrencyStatus.Loading + } + } + + private fun createMissedDerivation(quoteStatus: QuoteStatus?): CryptoCurrencyStatus.MissedDerivation { + return CryptoCurrencyStatus.MissedDerivation( + priceChange = quoteStatus.priceChange, + fiatRate = quoteStatus.fiatRate, + ) + } + + private fun createUnreachable( + status: NetworkStatus.Unreachable, + quoteStatus: QuoteStatus?, + ): CryptoCurrencyStatus.Unreachable { + return CryptoCurrencyStatus.Unreachable( + priceChange = quoteStatus.priceChange, + fiatRate = quoteStatus.fiatRate, + networkAddress = status.address, + ) + } + + private fun createNoAccount( + status: NetworkStatus.NoAccount, + quoteStatus: QuoteStatus?, + ): CryptoCurrencyStatus.NoAccount { + return CryptoCurrencyStatus.NoAccount( + amountToCreateAccount = status.amountToCreateAccount, + fiatAmount = BigDecimal.ZERO, + priceChange = quoteStatus.priceChange, + fiatRate = quoteStatus.fiatRate, + networkAddress = status.address, + sources = CryptoCurrencyStatus.Sources( + networkSource = status.source, + quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, + ), + ) + } + + private fun createStatus( + currency: CryptoCurrency, + status: NetworkStatus.Verified, + quoteStatus: QuoteStatus?, + maybeYieldBalance: Option, + ): CryptoCurrencyStatus.Value { + val amount = when (val amount = status.amounts[currency.id]) { + is NetworkStatus.Amount.Loaded -> amount.value + is NetworkStatus.Amount.NotFound -> { + return createNoAmount(quoteStatus = quoteStatus) + } + null -> { + return CryptoCurrencyStatus.Loading + } + } + + val yieldBalance = maybeYieldBalance.getOrNull(id = currency.id, address = status.address) + + if (currency is CryptoCurrency.Token && currency.isCustom) { + return createCustom( + id = currency.id, + status = status, + amount = amount, + quoteStatus = quoteStatus, + yieldBalance = yieldBalance, + ) + } + + // order is important for correct total balance calculation + return when (val quoteValue = quoteStatus?.value) { + is QuoteStatus.Empty -> { + createNoQuote( + id = currency.id, + status = status, + amount = amount, + quoteStatus = quoteStatus, + yieldBalance = yieldBalance, + ) + } + is QuoteStatus.Data -> { + createLoaded( + id = currency.id, + status = status, + amount = amount, + quoteStatus = quoteValue, + yieldBalance = yieldBalance, + ) + } + null -> CryptoCurrencyStatus.Loading + } + } + + private fun createNoAmount(quoteStatus: QuoteStatus?): CryptoCurrencyStatus.NoAmount { + return CryptoCurrencyStatus.NoAmount( + priceChange = quoteStatus.priceChange, + fiatRate = quoteStatus.fiatRate, + ) + } + + private fun createCustom( + id: CryptoCurrency.ID, + status: NetworkStatus.Verified, + amount: BigDecimal, + quoteStatus: QuoteStatus?, + yieldBalance: YieldBalance.Data?, + ): CryptoCurrencyStatus.Custom { + return CryptoCurrencyStatus.Custom( + amount = amount, + fiatAmount = quoteStatus.fiatRate?.let { calculateFiatAmount(amount, it) }, + fiatRate = quoteStatus.fiatRate, + priceChange = quoteStatus.priceChange, + hasCurrentNetworkTransactions = status.hasCurrentNetworkTransactions(), + pendingTransactions = status.getCurrentTransactions(id), + networkAddress = status.address, + yieldBalance = yieldBalance, + yieldSupplyStatus = status.getYieldSupplyStatus(id), + sources = CryptoCurrencyStatus.Sources( + networkSource = status.source, + yieldBalanceSource = yieldBalance?.source ?: StatusSource.ACTUAL, + quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, + ), + ) + } + + private fun createNoQuote( + id: CryptoCurrency.ID, + status: NetworkStatus.Verified, + amount: BigDecimal, + quoteStatus: QuoteStatus?, + yieldBalance: YieldBalance.Data?, + ): CryptoCurrencyStatus.NoQuote { + return CryptoCurrencyStatus.NoQuote( + amount = amount, + hasCurrentNetworkTransactions = status.hasCurrentNetworkTransactions(), + pendingTransactions = status.getCurrentTransactions(id), + networkAddress = status.address, + yieldBalance = yieldBalance, + yieldSupplyStatus = status.getYieldSupplyStatus(id), + sources = CryptoCurrencyStatus.Sources( + networkSource = status.source, + yieldBalanceSource = yieldBalance?.source ?: StatusSource.ACTUAL, + quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, + ), + ) + } + + private fun createLoaded( + id: CryptoCurrency.ID, + status: NetworkStatus.Verified, + amount: BigDecimal, + quoteStatus: QuoteStatus.Data, + yieldBalance: YieldBalance.Data?, + ): CryptoCurrencyStatus.Loaded { + return CryptoCurrencyStatus.Loaded( + amount = amount, + fiatAmount = calculateFiatAmount(amount, quoteStatus.fiatRate), + fiatRate = quoteStatus.fiatRate, + priceChange = quoteStatus.priceChange, + hasCurrentNetworkTransactions = status.hasCurrentNetworkTransactions(), + pendingTransactions = status.getCurrentTransactions(id), + networkAddress = status.address, + yieldBalance = yieldBalance, + yieldSupplyStatus = status.getYieldSupplyStatus(id), + sources = CryptoCurrencyStatus.Sources( + networkSource = status.source, + yieldBalanceSource = yieldBalance?.source ?: StatusSource.ACTUAL, + quoteSource = quoteStatus.source, + ), + ) + } + + private fun NetworkStatus.Verified.hasCurrentNetworkTransactions() = pendingTransactions.isNotEmpty() + + private fun NetworkStatus.Verified.getCurrentTransactions(id: CryptoCurrency.ID): Set { + return pendingTransactions.getOrElse(key = id, defaultValue = ::emptySet) + } + + private fun NetworkStatus.Verified.getYieldSupplyStatus(id: CryptoCurrency.ID): YieldSupplyStatus? { + return yieldSupplyStatuses[id] + } + + private fun Option.getOrNull(id: CryptoCurrency.ID, address: NetworkAddress): YieldBalance.Data? { + val yieldBalance = this.getOrNull() as? YieldBalance.Data ?: return null + + val isCurrentAddressStaking = yieldBalance.stakingId.address == address.defaultAddress.value + val filteredTokenBalances = yieldBalance.balance.items.filter { + it.token.coinGeckoId == id.rawCurrencyId?.value + } + + return if (isCurrentAddressStaking && filteredTokenBalances.isNotEmpty()) { + yieldBalance.copy( + balance = yieldBalance.balance.copy(items = filteredTokenBalances), + ) + } else { + null + } + } + + private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal { + return amount * fiatRate + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt deleted file mode 100644 index 5a6576e4b3..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.tangem.domain.tokens.operations - -import com.tangem.domain.models.StatusSource -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.network.NetworkStatus -import com.tangem.domain.models.quote.QuoteStatus -import com.tangem.domain.models.staking.YieldBalance -import java.math.BigDecimal - -internal class CurrencyStatusOperations( - private val currency: CryptoCurrency, - private val quoteStatus: QuoteStatus?, - private val networkStatus: NetworkStatus?, - private val yieldBalance: YieldBalance?, - private val ignoreQuote: Boolean, -) { - - private val QuoteStatus?.fiatRate: BigDecimal? - get() = (this?.value as? QuoteStatus.Data)?.fiatRate - - private val QuoteStatus?.priceChange: BigDecimal? - get() = (this?.value as? QuoteStatus.Data)?.priceChange - - fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus()) - - private fun createStatus(): CryptoCurrencyStatus.Value { - return when (val status = networkStatus?.value) { - null -> CryptoCurrencyStatus.Loading - is NetworkStatus.MissedDerivation -> createMissedDerivationStatus() - is NetworkStatus.Unreachable -> createUnreachableStatus(status) - is NetworkStatus.NoAccount -> createNoAccountStatus(status) - is NetworkStatus.Verified -> createStatus(status, yieldBalance) - } - } - - private fun createMissedDerivationStatus(): CryptoCurrencyStatus.MissedDerivation = - CryptoCurrencyStatus.MissedDerivation(priceChange = quoteStatus?.priceChange, fiatRate = quoteStatus?.fiatRate) - - private fun createUnreachableStatus(status: NetworkStatus.Unreachable): CryptoCurrencyStatus.Unreachable { - return CryptoCurrencyStatus.Unreachable( - priceChange = quoteStatus?.priceChange, - fiatRate = quoteStatus?.fiatRate, - networkAddress = status.address, - ) - } - - private fun createNoAccountStatus(status: NetworkStatus.NoAccount): CryptoCurrencyStatus.NoAccount { - return CryptoCurrencyStatus.NoAccount( - amountToCreateAccount = status.amountToCreateAccount, - fiatAmount = if (quoteStatus == null) null else BigDecimal.ZERO, - priceChange = quoteStatus?.priceChange, - fiatRate = quoteStatus?.fiatRate, - networkAddress = status.address, - sources = CryptoCurrencyStatus.Sources( - networkSource = status.source, - quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, - ), - ) - } - - @Suppress("CyclomaticComplexMethod", "LongMethod") - private fun createStatus( - networkStatusValue: NetworkStatus.Verified, - yieldBalance: YieldBalance?, - ): CryptoCurrencyStatus.Value { - val amount = when (val amount = networkStatusValue.amounts[currency.id]) { - null -> { - return CryptoCurrencyStatus.Loading - } - is NetworkStatus.Amount.NotFound -> { - return CryptoCurrencyStatus.NoAmount( - priceChange = quoteStatus?.priceChange, - fiatRate = quoteStatus?.fiatRate, - ) - } - is NetworkStatus.Amount.Loaded -> amount.value - } - - val hasCurrentNetworkTransactions = networkStatusValue.pendingTransactions.isNotEmpty() - val currentTransactions = networkStatusValue.pendingTransactions.getOrElse(currency.id, ::emptySet) - val yieldBalanceData = yieldBalance as? YieldBalance.Data - val isCurrentAddressStaking = - yieldBalanceData?.stakingId?.address == networkStatusValue.address.defaultAddress.value - val filteredTokenBalances = yieldBalanceData?.balance?.items?.filter { - it.token.coinGeckoId == currency.id.rawCurrencyId?.value - } - val currentYieldBalance = if (isCurrentAddressStaking && filteredTokenBalances?.isNotEmpty() == true) { - yieldBalanceData.copy( - balance = yieldBalanceData.balance.copy( - items = filteredTokenBalances, - ), - ) - } else { - null - } - val yieldSupplyStatus = networkStatusValue.yieldSupplyStatuses[currency.id] - - val quoteValue = quoteStatus?.value - - // order is important for correct total balance calculation - return when { - currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom( - amount = amount, - fiatAmount = calculateFiatAmountOrNull(amount, quoteStatus?.fiatRate), - fiatRate = quoteStatus?.fiatRate, - priceChange = quoteStatus?.priceChange, - hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, - pendingTransactions = currentTransactions, - networkAddress = networkStatusValue.address, - yieldBalance = currentYieldBalance, - yieldSupplyStatus = yieldSupplyStatus, - sources = CryptoCurrencyStatus.Sources( - networkSource = networkStatusValue.source, - quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, - yieldBalanceSource = currentYieldBalance?.source ?: StatusSource.ACTUAL, - ), - ) - quoteValue is QuoteStatus.Empty || ignoreQuote -> CryptoCurrencyStatus.NoQuote( - amount = amount, - hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, - pendingTransactions = currentTransactions, - networkAddress = networkStatusValue.address, - yieldBalance = currentYieldBalance, - yieldSupplyStatus = yieldSupplyStatus, - sources = CryptoCurrencyStatus.Sources( - networkSource = networkStatusValue.source, - quoteSource = quoteStatus?.value?.source ?: StatusSource.ACTUAL, - yieldBalanceSource = currentYieldBalance?.source ?: StatusSource.ACTUAL, - ), - ) - quoteValue is QuoteStatus.Data -> CryptoCurrencyStatus.Loaded( - amount = amount, - fiatAmount = calculateFiatAmount(amount, quoteValue.fiatRate), - fiatRate = quoteValue.fiatRate, - priceChange = quoteValue.priceChange, - hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, - pendingTransactions = currentTransactions, - networkAddress = networkStatusValue.address, - yieldBalance = currentYieldBalance, - yieldSupplyStatus = yieldSupplyStatus, - sources = CryptoCurrencyStatus.Sources( - networkSource = networkStatusValue.source, - quoteSource = quoteValue.source, - yieldBalanceSource = currentYieldBalance?.source ?: StatusSource.ACTUAL, - ), - ) - else -> CryptoCurrencyStatus.Loading - } - } - - private fun calculateFiatAmountOrNull(amount: BigDecimal, fiatRate: BigDecimal?): BigDecimal? { - if (fiatRate == null) return null - - return calculateFiatAmount(amount, fiatRate) - } - - private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal { - return amount * fiatRate - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/CurrencyStatusProxyCreator.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/CurrencyStatusProxyCreator.kt index 215e06104f..b917edebde 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/CurrencyStatusProxyCreator.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/CurrencyStatusProxyCreator.kt @@ -3,7 +3,9 @@ package com.tangem.domain.tokens.utils import arrow.core.Either import arrow.core.NonEmptyList import arrow.core.raise.either +import arrow.core.raise.recover import arrow.core.toNonEmptySetOrNull +import arrow.core.toOption import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.NetworkStatus @@ -12,11 +14,11 @@ import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.staking.StakingID import com.tangem.domain.models.staking.YieldBalance import com.tangem.domain.staking.model.StakingIntegrationID +import com.tangem.domain.tokens.operations.CryptoCurrencyStatusFactory import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations.Error -import com.tangem.domain.tokens.operations.CurrencyStatusOperations /** - * Proxy creator of [CryptoCurrencyStatus]. Used [CurrencyStatusOperations] to create statuses. + * Proxy creator of [CryptoCurrencyStatus]. Used [CryptoCurrencyStatusFactory] to create statuses. * [REDACTED_AUTHOR] */ @@ -28,20 +30,17 @@ class CurrencyStatusProxyCreator { maybeNetworkStatus: Either, maybeYieldBalance: Either?, ): Either = either { - var quoteRetrievingFailed = false - val networkStatus = maybeNetworkStatus.bind() - val quote = arrow.core.raise.recover({ maybeQuoteStatus.bind() }) { - quoteRetrievingFailed = true - null - } + val quote = recover( + block = { maybeQuoteStatus.bind() }, + recover = { null }, + ) val yieldBalance = maybeYieldBalance?.getOrNull() createCurrencyStatus( currency = currency, quoteStatus = quote, networkStatus = networkStatus, - ignoreQuote = quoteRetrievingFailed, yieldBalance = yieldBalance, ) } @@ -52,21 +51,8 @@ class CurrencyStatusProxyCreator { maybeNetworkStatuses: Either>, maybeYieldBalances: Either>, ): Either> = either { - var quotesRetrievingFailed = false - val networksStatuses = maybeNetworkStatuses.bind().toNonEmptySetOrNull() - val quoteStatuses: Set? = maybeQuotes?.fold( - ifLeft = { - quotesRetrievingFailed = true - null - }, - ifRight = { - it.ifEmpty { - quotesRetrievingFailed = true - null - } - }, - ) + val quoteStatuses: Set? = maybeQuotes?.getOrNull()?.ifEmpty { null } val yieldBalances = maybeYieldBalances.getOrNull() @@ -90,27 +76,22 @@ class CurrencyStatusProxyCreator { currency = currency, quoteStatus = quote, networkStatus = networkStatus, - ignoreQuote = quotesRetrievingFailed, yieldBalance = yieldBalance, ) } } - fun createCurrencyStatus( + private fun createCurrencyStatus( currency: CryptoCurrency, quoteStatus: QuoteStatus?, networkStatus: NetworkStatus?, - ignoreQuote: Boolean, yieldBalance: YieldBalance?, ): CryptoCurrencyStatus { - val currencyStatusOperations = CurrencyStatusOperations( + return CryptoCurrencyStatusFactory.create( currency = currency, - quoteStatus = quoteStatus, - networkStatus = networkStatus, - ignoreQuote = ignoreQuote, - yieldBalance = yieldBalance, + maybeNetworkStatus = networkStatus.toOption(), + maybeQuoteStatus = quoteStatus.toOption(), + maybeYieldBalance = yieldBalance.toOption(), ) - - return currencyStatusOperations.createTokenStatus() } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactoryTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactoryTest.kt new file mode 100644 index 0000000000..5888b68d94 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactoryTest.kt @@ -0,0 +1,686 @@ +package com.tangem.domain.tokens.operations + +import arrow.core.none +import arrow.core.some +import com.google.common.truth.Truth +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.network.NetworkAddress +import com.tangem.domain.models.network.NetworkStatus +import com.tangem.domain.models.network.NetworkStatus.Amount +import com.tangem.domain.models.network.TxInfo +import com.tangem.domain.models.quote.QuoteStatus +import com.tangem.domain.models.staking.BalanceItem +import com.tangem.domain.models.staking.StakingID +import com.tangem.domain.models.staking.YieldBalance +import com.tangem.domain.models.staking.YieldBalanceItem +import com.tangem.domain.models.yield.supply.YieldSupplyStatus +import com.tangem.domain.staking.model.StakingIntegrationID +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class CryptoCurrencyStatusFactoryTest { + + private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() + private val currency = cryptoCurrencyFactory.cardano + + private val networkAddress = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address( + value = "0x123", + type = NetworkAddress.Address.Type.Primary, + ), + ) + + private val fullQuote = QuoteStatus.Data( + fiatRate = 1800.0.toBigDecimal(), + priceChange = (-2.5).toBigDecimal(), + source = StatusSource.ACTUAL, + ) + private val emptyQuoteStatus = QuoteStatus.Empty.toStatus() + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class MissedDerivation { + + private val networkStatus = NetworkStatus.MissedDerivation.toStatus() + private val maybeYieldBalance = none() // not relevant for this test + + @Test + fun `network is MissedDerivation and QuoteStatus is Data`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = fullQuote.toStatus().some(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.MissedDerivation( + fiatRate = fullQuote.fiatRate, + priceChange = fullQuote.priceChange, + ).toStatus() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is MissedDerivation and QuoteStatus is Empty`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = emptyQuoteStatus.some(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.MissedDerivation(fiatRate = null, priceChange = null).toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is MissedDerivation and QuoteStatus is null`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = none(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.MissedDerivation(fiatRate = null, priceChange = null).toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class Unreachable { + + private val networkStatus = NetworkStatus.Unreachable(address = networkAddress).toStatus() + + private val maybeYieldBalance = none() // not relevant for this test + + @Test + fun `network is Unreachable and QuoteStatus is Data`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = fullQuote.toStatus().some(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.Unreachable( + fiatRate = fullQuote.fiatRate, + priceChange = fullQuote.priceChange, + networkAddress = networkAddress, + ).toStatus() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is Unreachable and QuoteStatus is Empty`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = emptyQuoteStatus.some(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.Unreachable( + fiatRate = null, + priceChange = null, + networkAddress = networkAddress, + ) + .toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is Unreachable and QuoteStatus is null`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = none(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.Unreachable( + fiatRate = null, + priceChange = null, + networkAddress = networkAddress, + ).toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class NoAccount { + + private val networkStatus = NetworkStatus.NoAccount( + address = networkAddress, + amountToCreateAccount = BigDecimal.ONE, + errorMessage = "error message", + source = StatusSource.ACTUAL, + ).toStatus() + + private val maybeYieldBalance = none() // not relevant for this test + + @Test + fun `network is NoAccount and QuoteStatus is Data`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = fullQuote.toStatus().some(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.NoAccount( + amountToCreateAccount = BigDecimal.ONE, + fiatAmount = BigDecimal.ZERO, + priceChange = fullQuote.priceChange, + fiatRate = fullQuote.fiatRate, + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ).toStatus() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is NoAccount and QuoteStatus is Empty`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = emptyQuoteStatus.some(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.NoAccount( + amountToCreateAccount = BigDecimal.ONE, + fiatAmount = BigDecimal.ZERO, + priceChange = null, + fiatRate = null, + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ).toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is NoAccount and QuoteStatus is null`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = none(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.NoAccount( + amountToCreateAccount = BigDecimal.ONE, + fiatAmount = BigDecimal.ZERO, + priceChange = null, + fiatRate = null, + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ).toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class NoAmount { + + private val networkStatus = createVerified(amounts = mapOf(currency.id to Amount.NotFound)).toStatus() + + private val maybeYieldBalance = none() // not relevant for this test + + @Test + fun `network is Verified with Amount is NotFound and QuoteStatus is Data`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = fullQuote.toStatus().some(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.NoAmount( + priceChange = fullQuote.priceChange, + fiatRate = fullQuote.fiatRate, + ).toStatus() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is Verified with Amount is NotFound and QuoteStatus is Empty`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = emptyQuoteStatus.some(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.NoAmount(priceChange = null, fiatRate = null).toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is Verified with Amount is NotFound and QuoteStatus is null`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = none(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.NoAmount(priceChange = null, fiatRate = null).toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class Loading { + + private val maybeYieldBalance = none() // not relevant for this test + + @Test + fun `network is null`() { + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = none(), + maybeQuoteStatus = none(), // not relevant for this test, + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.Loading.toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is Verified with Amount is null`() { + // Arrange + val networkStatus = createVerified().toStatus() + + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = none(), // not relevant for this test, + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.Loading.toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is Verified and QuoteStatus is null`() { + // Arrange + val networkStatus = createVerified( + amounts = mapOf(currency.id to Amount.Loaded(value = BigDecimal.ZERO)), + ).toStatus() + + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = none(), + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.Loading.toStatus() + Truth.assertThat(actual).isEqualTo(expected) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class Custom { + + private val currency = cryptoCurrencyFactory.createToken(blockchain = Blockchain.Cardano).copy(isCustom = true) + + private val networkStatus = createVerified( + amounts = mapOf(currency.id to Amount.Loaded(value = BigDecimal.TEN)), + ).toStatus() + + @Test + fun `network is Verified, QuoteStatus is null, YieldBalance is null`() { + // Arrange + val maybeQuoteStatus = none() + val maybeYieldBalance = none() + + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = maybeQuoteStatus, + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Custom( + amount = BigDecimal.TEN, + fiatAmount = null, + fiatRate = null, + priceChange = null, + yieldBalance = null, + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ), + ) + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is Verified, QuoteStatus is Data, YieldBalance is Data`() { + // Arrange + val yieldBalance = YieldBalance.Data( + stakingId = StakingID( + integrationId = StakingIntegrationID.Coin.Cardano.value, + address = networkAddress.defaultAddress.value, + ), + source = StatusSource.ACTUAL, + balance = YieldBalanceItem( + items = listOf( + mockk { + every { this@mockk.token.coinGeckoId } returns currency.id.rawCurrencyId?.value + }, + mockk { + every { this@mockk.token.coinGeckoId } returns "unknown" + }, + ), + integrationId = StakingIntegrationID.Coin.Cardano.value, + ), + ) + + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = fullQuote.toStatus().some(), + maybeYieldBalance = yieldBalance.some(), + ) + + // Assert + val expected = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Custom( + amount = BigDecimal.TEN, + fiatAmount = BigDecimal.TEN * fullQuote.fiatRate, + fiatRate = fullQuote.fiatRate, + priceChange = fullQuote.priceChange, + yieldBalance = yieldBalance.copy( + balance = yieldBalance.balance.copy( + items = yieldBalance.balance.items.subList(0, 1), + ), + ), + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ), + ) + + Truth.assertThat(actual).isEqualTo(expected) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class NoQuote { + + private val pendingTransactions = mapOf(currency.id to setOf(mockk())) + private val yieldSupplyStatuses = mapOf(currency.id to mockk()) + + private val networkStatus = createVerified( + amounts = mapOf(currency.id to Amount.Loaded(value = BigDecimal.TEN)), + pendingTransactions = pendingTransactions, + yieldSupplyStatuses = yieldSupplyStatuses, + ).toStatus() + + private val maybeQuoteStatus = emptyQuoteStatus.some() + + @Test + fun `network is Verified and YieldBalance is null`() { + // Arrange + val maybeYieldBalance = none() + + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = maybeQuoteStatus, + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.NoQuote( + amount = BigDecimal.TEN, + yieldBalance = null, + yieldSupplyStatus = yieldSupplyStatuses[currency.id]!!, + hasCurrentNetworkTransactions = true, + pendingTransactions = pendingTransactions[currency.id]!!, + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ).toStatus() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is Verified and YieldBalance is Data`() { + // Arrange + val yieldBalance = YieldBalance.Data( + stakingId = StakingID( + integrationId = StakingIntegrationID.Coin.Cardano.value, + address = networkAddress.defaultAddress.value, + ), + source = StatusSource.ACTUAL, + balance = YieldBalanceItem( + items = listOf( + mockk { + every { this@mockk.token.coinGeckoId } returns currency.id.rawCurrencyId?.value + }, + mockk { + every { this@mockk.token.coinGeckoId } returns "unknown" + }, + ), + integrationId = StakingIntegrationID.Coin.Cardano.value, + ), + ) + + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = maybeQuoteStatus, + maybeYieldBalance = yieldBalance.some(), + ) + + // Assert + val expected = CryptoCurrencyStatus.NoQuote( + amount = BigDecimal.TEN, + yieldBalance = yieldBalance.copy( + balance = yieldBalance.balance.copy( + items = yieldBalance.balance.items.subList(0, 1), + ), + ), + yieldSupplyStatus = yieldSupplyStatuses[currency.id]!!, + hasCurrentNetworkTransactions = true, + pendingTransactions = pendingTransactions[currency.id]!!, + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ).toStatus() + + Truth.assertThat(actual).isEqualTo(expected) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class Loaded { + + private val networkStatus = createVerified( + amounts = mapOf(currency.id to Amount.Loaded(value = BigDecimal.TEN)), + ).toStatus() + + private val maybeQuoteStatus = fullQuote.toStatus().some() + + @Test + fun `network is Verified and YieldBalance is null`() { + // Arrange + val maybeYieldBalance = none() + + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = maybeQuoteStatus, + maybeYieldBalance = maybeYieldBalance, + ) + + // Assert + val expected = CryptoCurrencyStatus.Loaded( + amount = BigDecimal.TEN, + fiatAmount = BigDecimal.TEN * fullQuote.fiatRate, + fiatRate = fullQuote.fiatRate, + priceChange = fullQuote.priceChange, + yieldBalance = null, + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ).toStatus() + + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `network is Verified and YieldBalance is Data`() { + // Arrange + val yieldBalance = YieldBalance.Data( + stakingId = StakingID( + integrationId = StakingIntegrationID.Coin.Cardano.value, + address = networkAddress.defaultAddress.value, + ), + source = StatusSource.ACTUAL, + balance = YieldBalanceItem( + items = listOf( + mockk { + every { this@mockk.token.coinGeckoId } returns currency.id.rawCurrencyId?.value + }, + mockk { + every { this@mockk.token.coinGeckoId } returns "unknown" + }, + ), + integrationId = StakingIntegrationID.Coin.Cardano.value, + ), + ) + + // Act + val actual = CryptoCurrencyStatusFactory.create( + currency = currency, + maybeNetworkStatus = networkStatus.some(), + maybeQuoteStatus = fullQuote.toStatus().some(), + maybeYieldBalance = yieldBalance.some(), + ) + + // Assert + val expected = CryptoCurrencyStatus.Loaded( + amount = BigDecimal.TEN, + fiatAmount = BigDecimal.TEN * fullQuote.fiatRate, + fiatRate = fullQuote.fiatRate, + priceChange = fullQuote.priceChange, + yieldBalance = yieldBalance.copy( + balance = yieldBalance.balance.copy( + items = yieldBalance.balance.items.subList(0, 1), + ), + ), + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ).toStatus() + + Truth.assertThat(actual).isEqualTo(expected) + } + } + + private fun createVerified( + amounts: Map = emptyMap(), + pendingTransactions: Map> = emptyMap(), + yieldSupplyStatuses: Map = emptyMap(), + ): NetworkStatus.Verified { + return NetworkStatus.Verified( + address = networkAddress, + amounts = amounts, + pendingTransactions = pendingTransactions, + yieldSupplyStatuses = yieldSupplyStatuses, + source = StatusSource.ACTUAL, + ) + } + + private fun NetworkStatus.Value.toStatus(): NetworkStatus { + return NetworkStatus(network = currency.network, value = this) + } + + private fun CryptoCurrencyStatus.Value.toStatus(): CryptoCurrencyStatus { + return CryptoCurrencyStatus(currency = currency, value = this) + } + + private fun QuoteStatus.Value.toStatus(): QuoteStatus { + return when (this) { + is QuoteStatus.Data -> QuoteStatus(rawCurrencyId = currency.id.rawCurrencyId!!, value = this) + is QuoteStatus.Empty -> QuoteStatus(rawCurrencyId = currency.id.rawCurrencyId!!, value = this) + } + } +} \ No newline at end of file