Updated on 2026-08-14
This commit is contained in:
parent
c56b64bc36
commit
4bb8d20301
7 changed files with 1021 additions and 27 deletions
|
|
@ -5,23 +5,28 @@ import arrow.core.some
|
|||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Produces a flow of [AccountStatusList] for multiple user wallets.
|
||||
*
|
||||
* @property params Parameters for the producer (currently unused).
|
||||
* @property userWalletsListRepository Repository to get the list of user wallets.
|
||||
* @property singleAccountStatusListSupplier Supplier to get the account status list for a single user wallet.
|
||||
* @property dispatchers Coroutine dispatcher provider for managing threading.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO: Finalize [REDACTED_JIRA]
|
||||
internal class DefaultMultiAccountStatusListProducer @AssistedInject constructor(
|
||||
@Assisted val params: Unit,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MultiAccountStatusListProducer {
|
||||
|
||||
override val fallback: Option<List<AccountStatusList>> = emptyList<AccountStatusList>().some()
|
||||
|
|
@ -39,6 +44,7 @@ internal class DefaultMultiAccountStatusListProducer @AssistedInject constructor
|
|||
|
||||
combine(flows) { it.toList() }
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -3,45 +3,121 @@ package com.tangem.domain.account.status.producer
|
|||
import arrow.core.Option
|
||||
import arrow.core.none
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.quote.PriceChange
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.tokens.operations.PriceChangeCalculator
|
||||
import com.tangem.domain.tokens.operations.TokenListFactory
|
||||
import com.tangem.domain.tokens.operations.TotalFiatBalanceCalculator
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Produces a flow of [AccountStatusList] for a single user wallet.
|
||||
*
|
||||
* @property params Parameters containing the user wallet ID.
|
||||
* @property singleAccountListSupplier Supplier to get the list of accounts for the user wallet.
|
||||
* @property cryptoCurrencyStatusesFlowFactory Factory to create flows of cryptocurrency statuses.
|
||||
* @property dispatchers Coroutine dispatcher provider for managing threading.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO: Implement [REDACTED_JIRA]
|
||||
@Suppress("UnusedPrivateProperty", "UnusedPrivateClass")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class DefaultSingleAccountStatusListProducer @AssistedInject constructor(
|
||||
@Assisted private val params: SingleAccountStatusListProducer.Params,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||
private val singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SingleAccountStatusListProducer {
|
||||
|
||||
override val fallback: Option<AccountStatusList> = none()
|
||||
|
||||
override fun produce(): Flow<AccountStatusList> = emptyFlow()
|
||||
override fun produce(): Flow<AccountStatusList> {
|
||||
val accountListFlow = singleAccountListSupplier(
|
||||
params = SingleAccountListProducer.Params(params.userWalletId),
|
||||
)
|
||||
|
||||
private data class CryptoCurrencyStatusSources(
|
||||
val networkStatus: NetworkStatus,
|
||||
val yieldBalance: YieldBalance?,
|
||||
val quoteStatus: QuoteStatus?,
|
||||
)
|
||||
return accountListFlow.flatMapLatest { accountList ->
|
||||
val accountStatusFlows = accountList.accounts
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
.map { account ->
|
||||
if (account.cryptoCurrencies.isEmpty()) {
|
||||
createEmptyAccountStatusFlow(account)
|
||||
} else {
|
||||
getAccountStatusFlow(
|
||||
userWallet = accountList.userWallet,
|
||||
account = account,
|
||||
groupType = accountList.groupType,
|
||||
sortType = accountList.sortType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
combine(accountStatusFlows) { accountStatuses ->
|
||||
val balances = accountStatuses.map { it.tokenList.totalFiatBalance }
|
||||
|
||||
AccountStatusList(
|
||||
userWallet = accountList.userWallet,
|
||||
accountStatuses = accountStatuses.toSet(),
|
||||
totalAccounts = accountList.totalAccounts,
|
||||
totalFiatBalance = TotalFiatBalanceCalculator.calculate(balances),
|
||||
)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
}
|
||||
|
||||
private fun createEmptyAccountStatusFlow(account: Account.CryptoPortfolio): Flow<AccountStatus.CryptoPortfolio> {
|
||||
return flowOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = account,
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = PriceChange(
|
||||
value = BigDecimal.ZERO.movePointLeft(2),
|
||||
source = StatusSource.ACTUAL,
|
||||
).lceContent(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAccountStatusFlow(
|
||||
userWallet: UserWallet,
|
||||
account: Account.CryptoPortfolio,
|
||||
groupType: TokensGroupType,
|
||||
sortType: TokensSortType,
|
||||
): Flow<AccountStatus.CryptoPortfolio> {
|
||||
val statusesFlows = account.cryptoCurrencies.map { currency ->
|
||||
cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = currency)
|
||||
}
|
||||
|
||||
return combine(statusesFlows) { statuses ->
|
||||
val statusList = statuses.toList()
|
||||
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = account,
|
||||
tokenList = TokenListFactory.create(
|
||||
statuses = statusList,
|
||||
groupType = groupType,
|
||||
sortType = sortType,
|
||||
),
|
||||
priceChangeLce = PriceChangeCalculator.calculate(statuses = statusList),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : SingleAccountStatusListProducer.Factory {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import arrow.core.some
|
||||
import arrow.core.toOption
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.network.getAddress
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceProducer
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||
import com.tangem.domain.tokens.operations.CryptoCurrencyStatusFactory
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Factory that creates a flow of [CryptoCurrencyStatus] for a given [CryptoCurrency] in a [UserWallet].
|
||||
*
|
||||
* @property singleNetworkStatusSupplier Supplier for obtaining network status.
|
||||
* @property singleQuoteStatusSupplier Supplier for obtaining quote status.
|
||||
* @property singleYieldBalanceSupplier Supplier for obtaining yield balance.
|
||||
* @property stakingIdFactory Factory for creating staking IDs.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class CryptoCurrencyStatusesFlowFactory @Inject constructor(
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||
private val singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Creates a flow of [CryptoCurrencyStatus] for the specified [userWallet] and [currency].
|
||||
*
|
||||
* @param userWallet The user wallet containing the currency.
|
||||
|
||||
*/
|
||||
fun create(userWallet: UserWallet, currency: CryptoCurrency): Flow<CryptoCurrencyStatus> {
|
||||
return getCryptoCurrencyStatusSourcesFlow(userWallet = userWallet, currency = currency)
|
||||
.map { statusSources ->
|
||||
CryptoCurrencyStatusFactory.create(
|
||||
currency = currency,
|
||||
maybeNetworkStatus = statusSources.networkStatus.some(),
|
||||
maybeQuoteStatus = statusSources.quoteStatus.toOption(),
|
||||
maybeYieldBalance = statusSources.yieldBalance.toOption(),
|
||||
)
|
||||
}
|
||||
.onEmpty {
|
||||
emit(
|
||||
CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading),
|
||||
)
|
||||
}
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun getCryptoCurrencyStatusSourcesFlow(
|
||||
userWallet: UserWallet,
|
||||
currency: CryptoCurrency,
|
||||
): Flow<CryptoCurrencyStatusSources> {
|
||||
val networkStatusFlow = getNetworkStatusFlow(userWalletId = userWallet.walletId, network = currency.network)
|
||||
|
||||
val yieldBalanceFlow = if (userWallet.isMultiCurrency) {
|
||||
networkStatusFlow.flatMapLatest { networkStatus ->
|
||||
getYieldBalanceFlow(
|
||||
userWalletId = userWallet.walletId,
|
||||
currencyId = currency.id,
|
||||
networkStatus = networkStatus,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val quoteStatusFlow = currency.id.rawCurrencyId?.let(::getQuoteStatusFlow)
|
||||
|
||||
return combine(networkStatusFlow, yieldBalanceFlow, quoteStatusFlow)
|
||||
}
|
||||
|
||||
private fun combine(
|
||||
networkStatusFlow: Flow<NetworkStatus>,
|
||||
yieldBalanceFlow: Flow<YieldBalance?>?,
|
||||
quoteStatusFlow: Flow<QuoteStatus>?,
|
||||
): Flow<CryptoCurrencyStatusSources> {
|
||||
return when {
|
||||
yieldBalanceFlow != null && quoteStatusFlow != null -> {
|
||||
combine(
|
||||
flow = networkStatusFlow,
|
||||
flow2 = yieldBalanceFlow,
|
||||
flow3 = quoteStatusFlow,
|
||||
transform = ::CryptoCurrencyStatusSources,
|
||||
)
|
||||
}
|
||||
yieldBalanceFlow != null -> {
|
||||
combine(flow = networkStatusFlow, flow2 = yieldBalanceFlow, transform = ::CryptoCurrencyStatusSources)
|
||||
}
|
||||
quoteStatusFlow != null -> {
|
||||
combine(flow = networkStatusFlow, flow2 = quoteStatusFlow) { networkStatus, quoteStatus ->
|
||||
CryptoCurrencyStatusSources(networkStatus = networkStatus, quoteStatus = quoteStatus)
|
||||
}
|
||||
}
|
||||
else -> networkStatusFlow.map(::CryptoCurrencyStatusSources)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNetworkStatusFlow(userWalletId: UserWalletId, network: Network): Flow<NetworkStatus> {
|
||||
return singleNetworkStatusSupplier(
|
||||
params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network),
|
||||
)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getQuoteStatusFlow(rawCurrencyId: CryptoCurrency.RawID): Flow<QuoteStatus> {
|
||||
return singleQuoteStatusSupplier(
|
||||
params = SingleQuoteStatusProducer.Params(rawCurrencyId = rawCurrencyId),
|
||||
)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun getYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
networkStatus: NetworkStatus,
|
||||
): Flow<YieldBalance?> {
|
||||
val stakingId = stakingIdFactory.create(
|
||||
currencyId = currencyId,
|
||||
defaultAddress = networkStatus.getAddress(),
|
||||
)
|
||||
.getOrNull()
|
||||
|
||||
return if (stakingId != null) {
|
||||
singleYieldBalanceSupplier(
|
||||
params = SingleYieldBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
} else {
|
||||
flowOf(null)
|
||||
}
|
||||
}
|
||||
|
||||
private data class CryptoCurrencyStatusSources(
|
||||
val networkStatus: NetworkStatus,
|
||||
val yieldBalance: YieldBalance? = null,
|
||||
val quoteStatus: QuoteStatus? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
package com.tangem.domain.account.status.producer
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.utils.getEmittedValues
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@Suppress("UnusedFlow")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultMultiAccountStatusListProducerTest {
|
||||
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk()
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier = mockk()
|
||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||
|
||||
private val userWalletId1 = UserWalletId("001")
|
||||
private val userWallet1 = mockk<UserWallet> {
|
||||
every { walletId } returns userWalletId1
|
||||
}
|
||||
|
||||
private val userWalletId2 = UserWalletId("002")
|
||||
private val userWallet2 = mockk<UserWallet> {
|
||||
every { walletId } returns userWalletId2
|
||||
}
|
||||
|
||||
private val producer = DefaultMultiAccountStatusListProducer(
|
||||
params = Unit,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(userWalletsListRepository, singleAccountStatusListSupplier)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `produce returns status lists for all user wallets`() = runTest {
|
||||
// Arrange
|
||||
val wallets = listOf(userWallet1, userWallet2)
|
||||
val walletsFlow = MutableStateFlow(wallets)
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns walletsFlow
|
||||
val accountStatusList1 = mockk<AccountStatusList>()
|
||||
val accountStatusList2 = mockk<AccountStatusList>()
|
||||
|
||||
every {
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId1),
|
||||
)
|
||||
} returns flowOf(accountStatusList1)
|
||||
|
||||
every {
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId2),
|
||||
)
|
||||
} returns flowOf(accountStatusList2)
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = listOf(accountStatusList1, accountStatusList2)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsListRepository.userWallets
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId1),
|
||||
)
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId2),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `produce returns empty flow if userWallets is empty list`() = runTest {
|
||||
// Arrange
|
||||
val walletsFlow = MutableStateFlow<List<UserWallet>>(emptyList())
|
||||
every { userWalletsListRepository.userWallets } returns walletsFlow
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEmpty()
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsListRepository.userWallets
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `produce returns empty flow if userWallets is null`() = runTest {
|
||||
// Arrange
|
||||
val walletsFlow = MutableStateFlow<List<UserWallet>?>(null)
|
||||
every { userWalletsListRepository.userWallets } returns walletsFlow
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEmpty()
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsListRepository.userWallets
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow will updated if userWallets are updated`() = runTest {
|
||||
// Arrange
|
||||
val userWalletId3 = UserWalletId("003")
|
||||
val userWallet3 = mockk<UserWallet> { every { walletId } returns userWalletId3 }
|
||||
|
||||
val walletsFlow = MutableStateFlow(listOf(userWallet1, userWallet2))
|
||||
every { userWalletsListRepository.userWallets } returns walletsFlow
|
||||
|
||||
val accountStatusList1 = mockk<AccountStatusList>()
|
||||
val accountStatusList2 = mockk<AccountStatusList>()
|
||||
val accountStatusList3 = mockk<AccountStatusList>()
|
||||
|
||||
every {
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId1),
|
||||
)
|
||||
} returns flowOf(accountStatusList1)
|
||||
|
||||
every {
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId2),
|
||||
)
|
||||
} returns flowOf(accountStatusList2)
|
||||
|
||||
every {
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId3),
|
||||
)
|
||||
} returns flowOf(accountStatusList3)
|
||||
|
||||
// Act (first emission)
|
||||
val actual1 = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert (first emission)
|
||||
val expected1 = listOf(accountStatusList1, accountStatusList2)
|
||||
Truth.assertThat(actual1).containsExactly(expected1)
|
||||
|
||||
// Act (second emission)
|
||||
walletsFlow.value = listOf(userWallet1, userWallet2, userWallet3)
|
||||
val actual2 = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert (second emission)
|
||||
val expected2 = listOf(accountStatusList1, accountStatusList2, accountStatusList3)
|
||||
Truth.assertThat(actual2).containsExactly(expected2)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsListRepository.userWallets
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId1),
|
||||
)
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId2),
|
||||
)
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId1),
|
||||
)
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId2),
|
||||
)
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId3),
|
||||
)
|
||||
userWalletsListRepository.userWallets
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId1),
|
||||
)
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId2),
|
||||
)
|
||||
singleAccountStatusListSupplier(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId3),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
package com.tangem.domain.account.status.producer
|
||||
|
||||
import arrow.core.nonEmptyListOf
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.utils.getEmittedValues
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.producer.SingleAccountListProducer
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.quote.PriceChange
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("UnusedFlow")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultSingleAccountStatusListProducerTest {
|
||||
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier = mockk()
|
||||
private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory = mockk()
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
}
|
||||
|
||||
private val producer = DefaultSingleAccountStatusListProducer(
|
||||
params = SingleAccountStatusListProducer.Params(userWalletId),
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
cryptoCurrencyStatusesFlowFactory = cryptoCurrencyStatusesFlowFactory,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(singleAccountListSupplier, cryptoCurrencyStatusesFlowFactory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is mapped for user wallet id from params`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
|
||||
every {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
} returns flowOf(accountList)
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = accountList.mainAccount,
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = PriceChange(value = BigDecimal("0.00"), source = StatusSource.ACTUAL).lceContent(),
|
||||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL),
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow will updated if balances are updated`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val updatedAccountList = AccountList.empty(userWallet = userWallet, sortType = TokensSortType.BALANCE)
|
||||
|
||||
val accountListFlow = MutableStateFlow(value = accountList)
|
||||
|
||||
every {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
} returns accountListFlow
|
||||
|
||||
// Act (first emission)
|
||||
val actual1 = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert (first emission)
|
||||
val expected = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = accountList.mainAccount,
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = PriceChange(value = BigDecimal("0.00"), source = StatusSource.ACTUAL).lceContent(),
|
||||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL),
|
||||
)
|
||||
Truth.assertThat(actual1).containsExactly(expected)
|
||||
|
||||
// Act (second emission)
|
||||
accountListFlow.value = updatedAccountList
|
||||
val actual2 = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert (second emission)
|
||||
val expected2 = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = updatedAccountList.mainAccount,
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = PriceChange(value = BigDecimal("0.00"), source = StatusSource.ACTUAL).lceContent(),
|
||||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL),
|
||||
)
|
||||
Truth.assertThat(actual2).containsExactly(expected2)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is filtered the same balance`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountListFlow = MutableStateFlow(value = accountList)
|
||||
|
||||
every {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
} returns accountListFlow
|
||||
|
||||
val expected = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = accountList.mainAccount,
|
||||
tokenList = TokenList.Empty,
|
||||
priceChangeLce = PriceChange(value = BigDecimal("0.00"), source = StatusSource.ACTUAL).lceContent(),
|
||||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal.ZERO, source = StatusSource.ACTUAL),
|
||||
)
|
||||
|
||||
// Act (first emission)
|
||||
val actual1 = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert (first emission)
|
||||
Truth.assertThat(actual1).containsExactly(expected)
|
||||
|
||||
// Act (second emission)
|
||||
accountListFlow.value = accountList
|
||||
val actual2 = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert (second emission)
|
||||
Truth.assertThat(actual2).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is produced for account with non empty crypto currencies`() = runTest {
|
||||
// Arrange
|
||||
val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
val accountList = AccountList.empty(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrencies = cryptoCurrencyFactory.ethereumAndStellar.toSet(),
|
||||
)
|
||||
|
||||
every {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
} returns flowOf(accountList)
|
||||
|
||||
val ethereumStatus = CryptoCurrencyStatus(
|
||||
currency = cryptoCurrencyFactory.ethereum,
|
||||
value = CryptoCurrencyStatus.Loading,
|
||||
)
|
||||
every {
|
||||
cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = cryptoCurrencyFactory.ethereum)
|
||||
} returns flowOf(ethereumStatus)
|
||||
|
||||
val stellarStatus = CryptoCurrencyStatus(
|
||||
currency = cryptoCurrencyFactory.stellar,
|
||||
value = CryptoCurrencyStatus.MissedDerivation(priceChange = null, fiatRate = null),
|
||||
)
|
||||
every {
|
||||
cryptoCurrencyStatusesFlowFactory.create(userWallet = userWallet, currency = cryptoCurrencyFactory.stellar)
|
||||
} returns flowOf(stellarStatus)
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = AccountStatusList(
|
||||
userWallet = userWallet,
|
||||
accountStatuses = setOf(
|
||||
AccountStatus.CryptoPortfolio(
|
||||
account = accountList.mainAccount,
|
||||
tokenList = TokenList.Ungrouped(
|
||||
totalFiatBalance = TotalFiatBalance.Loading,
|
||||
sortedBy = TokensSortType.NONE,
|
||||
currencies = nonEmptyListOf(ethereumStatus, stellarStatus),
|
||||
),
|
||||
priceChangeLce = lceLoading(),
|
||||
),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
totalFiatBalance = TotalFiatBalance.Loading,
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
package com.tangem.domain.account.status.utils
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.utils.getEmittedValues
|
||||
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.quote.QuoteStatus
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceProducer
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("UnusedFlow")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class CryptoCurrencyStatusesFlowFactoryTest {
|
||||
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier = mockk()
|
||||
private val singleQuoteStatusSupplier: SingleQuoteStatusSupplier = mockk()
|
||||
private val singleYieldBalanceSupplier: SingleYieldBalanceSupplier = mockk()
|
||||
private val stakingIdFactory: StakingIdFactory = mockk()
|
||||
|
||||
private val factory = CryptoCurrencyStatusesFlowFactory(
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
|
||||
private val networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(value = "0x1", type = NetworkAddress.Address.Type.Primary),
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(
|
||||
singleNetworkStatusSupplier,
|
||||
singleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier,
|
||||
stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if rawCurrencyId is null, there will be no subscription to the quote status`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns true
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum.copy(
|
||||
id = cryptoCurrencyFactory.ethereum.id.copy(
|
||||
suffix = CryptoCurrency.ID.Suffix.ContractAddress(contractAddress = "0x12345"),
|
||||
),
|
||||
)
|
||||
|
||||
val networkStatus = NetworkStatus(
|
||||
network = currency.network,
|
||||
value = NetworkStatus.Unreachable(address = networkAddress),
|
||||
)
|
||||
val networkStatusFlow = flowOf(networkStatus)
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns networkStatusFlow
|
||||
|
||||
val stakingId = StakingID(integrationId = "id", address = networkAddress.defaultAddress.value)
|
||||
coEvery {
|
||||
stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value)
|
||||
} returns stakingId.right()
|
||||
|
||||
val yieldBalance = YieldBalance.Empty(stakingId = stakingId, source = StatusSource.ACTUAL)
|
||||
val yieldBalanceFlow = flowOf(yieldBalance)
|
||||
every {
|
||||
singleYieldBalanceSupplier(
|
||||
params = SingleYieldBalanceProducer.Params(userWalletId = userWalletId, stakingId = stakingId),
|
||||
)
|
||||
} returns yieldBalanceFlow
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Unreachable(
|
||||
priceChange = null,
|
||||
fiatRate = null,
|
||||
networkAddress = networkAddress,
|
||||
),
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value)
|
||||
singleYieldBalanceSupplier(params = SingleYieldBalanceProducer.Params(userWalletId, stakingId))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if userWallet is not multi-currency, there will be no subscription to the yield balance`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns false
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum
|
||||
|
||||
val networkStatus = NetworkStatus(
|
||||
network = currency.network,
|
||||
value = NetworkStatus.Unreachable(address = networkAddress),
|
||||
)
|
||||
val networkStatusFlow = flowOf(networkStatus)
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns networkStatusFlow
|
||||
|
||||
val quoteStatus = QuoteStatus(
|
||||
rawCurrencyId = currency.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = BigDecimal.ONE,
|
||||
),
|
||||
)
|
||||
val quoteStatusFlow = flowOf(quoteStatus)
|
||||
every {
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
} returns quoteStatusFlow
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Unreachable(
|
||||
priceChange = BigDecimal.ONE,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
networkAddress = networkAddress,
|
||||
),
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no subscription to the quote status and yield balance`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns false
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum.copy(
|
||||
id = cryptoCurrencyFactory.ethereum.id.copy(
|
||||
suffix = CryptoCurrency.ID.Suffix.ContractAddress(contractAddress = "0x12345"),
|
||||
),
|
||||
)
|
||||
|
||||
val networkStatus = NetworkStatus(
|
||||
network = currency.network,
|
||||
value = NetworkStatus.Unreachable(address = networkAddress),
|
||||
)
|
||||
val networkStatusFlow = flowOf(networkStatus)
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns networkStatusFlow
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Unreachable(
|
||||
priceChange = null,
|
||||
fiatRate = null,
|
||||
networkAddress = networkAddress,
|
||||
),
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if stakingId is not supported, yield balance will be null`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns true
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum
|
||||
|
||||
val networkStatus = NetworkStatus(
|
||||
network = currency.network,
|
||||
value = NetworkStatus.Unreachable(address = networkAddress),
|
||||
)
|
||||
val networkStatusFlow = flowOf(networkStatus)
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns networkStatusFlow
|
||||
|
||||
val quoteStatus = QuoteStatus(
|
||||
rawCurrencyId = currency.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = BigDecimal.ONE,
|
||||
),
|
||||
)
|
||||
val quoteStatusFlow = flowOf(quoteStatus)
|
||||
every {
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
} returns quoteStatusFlow
|
||||
|
||||
coEvery {
|
||||
stakingIdFactory.create(currencyId = currency.id, defaultAddress = networkAddress.defaultAddress.value)
|
||||
} returns StakingIdFactory.Error.UnsupportedCurrency.left()
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Unreachable(
|
||||
priceChange = BigDecimal.ONE,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
networkAddress = networkAddress,
|
||||
),
|
||||
)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `all sources are empty`() = runTest {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.isMultiCurrency } returns true
|
||||
}
|
||||
|
||||
val currency = cryptoCurrencyFactory.ethereum
|
||||
|
||||
every {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
} returns emptyFlow()
|
||||
|
||||
every {
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
} returns emptyFlow()
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWallet = userWallet, currency = currency).let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading)
|
||||
Truth.assertThat(actual).containsExactly(expected)
|
||||
|
||||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
singleNetworkStatusSupplier(params = SingleNetworkStatusProducer.Params(userWalletId, currency.network))
|
||||
singleQuoteStatusSupplier(params = SingleQuoteStatusProducer.Params(currency.id.rawCurrencyId!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable
|
|||
/**
|
||||
* Represents the price change of a cryptocurrency asset over a specific time period.
|
||||
*
|
||||
* @param value The amount of price change.
|
||||
* @param value The amount of price change (like `0.00`)
|
||||
* @param source The source of the price change information.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue