Updated on 2026-08-14
This commit is contained in:
parent
95ba8be221
commit
48f6177936
2 changed files with 166 additions and 10 deletions
|
|
@ -45,25 +45,25 @@ internal class GetCurrencyWarningsUseCase @Inject constructor(
|
||||||
derivationPath: Network.DerivationPath,
|
derivationPath: Network.DerivationPath,
|
||||||
): Flow<Set<CryptoCurrencyWarning>> {
|
): Flow<Set<CryptoCurrencyWarning>> {
|
||||||
val currency = currencyStatus.currency
|
val currency = currencyStatus.currency
|
||||||
|
val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)
|
||||||
|
|
||||||
// don't add here notifications that require async requests
|
// don't add here notifications that require async requests
|
||||||
return combine(
|
return combine(
|
||||||
flow = getCoinRelatedWarnings(
|
flow = getCoinRelatedWarnings(
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
currency = currency,
|
currency = currency,
|
||||||
|
existentialDeposit = existentialDeposit,
|
||||||
),
|
),
|
||||||
flow2 = flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus)),
|
flow2 = flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus)),
|
||||||
flow3 = flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)),
|
flow3 = flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)),
|
||||||
flow4 = flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)),
|
flow4 = if (currency is CryptoCurrency.Coin) {
|
||||||
flow5 = if (currency is CryptoCurrency.Coin) {
|
|
||||||
dynamicAddressesRepository.hasFundsOnAdditionalAddresses(userWalletId, currency.network)
|
dynamicAddressesRepository.hasFundsOnAdditionalAddresses(userWalletId, currency.network)
|
||||||
} else {
|
} else {
|
||||||
flowOf(false)
|
flowOf(false)
|
||||||
},
|
},
|
||||||
) { coinRelatedWarnings, maybeRentWarning, maybeEdWarning, maybeFeeResource, hasExtraFunds ->
|
) { coinRelatedWarnings, maybeRentWarning, maybeFeeResource, hasExtraFunds ->
|
||||||
setOfNotNull(
|
setOfNotNull(
|
||||||
maybeRentWarning,
|
maybeRentWarning,
|
||||||
maybeEdWarning?.let { getExistentialDepositWarning(currency, it) },
|
|
||||||
maybeFeeResource?.let { getFeeResourceWarning(it) },
|
maybeFeeResource?.let { getFeeResourceWarning(it) },
|
||||||
*coinRelatedWarnings.toTypedArray(),
|
*coinRelatedWarnings.toTypedArray(),
|
||||||
getNetworkUnavailableWarning(currencyStatus),
|
getNetworkUnavailableWarning(currencyStatus),
|
||||||
|
|
@ -80,6 +80,7 @@ internal class GetCurrencyWarningsUseCase @Inject constructor(
|
||||||
private suspend fun getCoinRelatedWarnings(
|
private suspend fun getCoinRelatedWarnings(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
currency: CryptoCurrency,
|
currency: CryptoCurrency,
|
||||||
|
existentialDeposit: BigDecimal?,
|
||||||
): Flow<List<CryptoCurrencyWarning>> {
|
): Flow<List<CryptoCurrencyWarning>> {
|
||||||
return singleAccountStatusListSupplier(userWalletId)
|
return singleAccountStatusListSupplier(userWalletId)
|
||||||
.map { accountStatusList ->
|
.map { accountStatusList ->
|
||||||
|
|
@ -100,6 +101,10 @@ internal class GetCurrencyWarningsUseCase @Inject constructor(
|
||||||
coinStatus = coinStatus,
|
coinStatus = coinStatus,
|
||||||
tokenStatus = tokenStatus,
|
tokenStatus = tokenStatus,
|
||||||
),
|
),
|
||||||
|
getExistentialDepositWarning(
|
||||||
|
coinStatus = coinStatus,
|
||||||
|
existentialDeposit = existentialDeposit,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
listOf(CryptoCurrencyWarning.SomeNetworksUnreachable)
|
listOf(CryptoCurrencyWarning.SomeNetworksUnreachable)
|
||||||
|
|
@ -209,12 +214,19 @@ internal class GetCurrencyWarningsUseCase @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getExistentialDepositWarning(
|
private fun getExistentialDepositWarning(
|
||||||
currency: CryptoCurrency,
|
coinStatus: CryptoCurrencyStatus,
|
||||||
amount: BigDecimal,
|
existentialDeposit: BigDecimal?,
|
||||||
): CryptoCurrencyWarning.ExistentialDeposit {
|
): CryptoCurrencyWarning.ExistentialDeposit? {
|
||||||
|
if (existentialDeposit == null) return null
|
||||||
|
|
||||||
|
val coinAmount = coinStatus.value.amount ?: return null
|
||||||
|
if (coinAmount > existentialDeposit) return null
|
||||||
|
|
||||||
|
val coin = coinStatus.currency
|
||||||
|
|
||||||
return CryptoCurrencyWarning.ExistentialDeposit(
|
return CryptoCurrencyWarning.ExistentialDeposit(
|
||||||
currencyName = currency.name,
|
currencyName = coin.name,
|
||||||
edStringValueWithSymbol = "${amount.toPlainString()} ${currency.symbol}",
|
edStringValueWithSymbol = "${existentialDeposit.toPlainString()} ${coin.symbol}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.feature.tokendetails.domain
|
package com.tangem.feature.tokendetails.domain
|
||||||
|
|
||||||
import arrow.core.none
|
import arrow.core.none
|
||||||
|
import arrow.core.some
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import com.tangem.domain.account.models.AccountStatusList
|
import com.tangem.domain.account.models.AccountStatusList
|
||||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||||
|
|
@ -11,6 +12,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.network.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||||
|
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||||
import com.tangem.domain.tokens.model.warnings.DynamicAddressesWarnings
|
import com.tangem.domain.tokens.model.warnings.DynamicAddressesWarnings
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||||
|
|
@ -31,6 +33,7 @@ import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class GetCurrencyWarningsUseCaseTest {
|
class GetCurrencyWarningsUseCaseTest {
|
||||||
|
|
@ -73,6 +76,7 @@ class GetCurrencyWarningsUseCaseTest {
|
||||||
coEvery { currencyChecksRepository.getExistentialDeposit(any(), any()) } returns null
|
coEvery { currencyChecksRepository.getExistentialDeposit(any(), any()) } returns null
|
||||||
coEvery { currencyChecksRepository.getFeeResourceAmount(any(), any()) } returns null
|
coEvery { currencyChecksRepository.getFeeResourceAmount(any(), any()) } returns null
|
||||||
coEvery { walletManagersFacade.getAssetRequirements(any(), any()) } returns null
|
coEvery { walletManagersFacade.getAssetRequirements(any(), any()) } returns null
|
||||||
|
every { dynamicAddressesRepository.hasFundsOnAdditionalAddresses(any(), any()) } returns flowOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
|
|
@ -130,12 +134,152 @@ class GetCurrencyWarningsUseCaseTest {
|
||||||
assertThat(result).doesNotContain(DynamicAddressesWarnings.FundsFound)
|
assertThat(result).doesNotContain(DynamicAddressesWarnings.FundsFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN coin balance above existential deposit WHEN invoke THEN ExistentialDeposit warning is absent`() =
|
||||||
|
runTest {
|
||||||
|
// GIVEN
|
||||||
|
val coin = coin()
|
||||||
|
val coinStatus = statusFor(currency = coin, amount = BigDecimal("0.98910916"))
|
||||||
|
givenCoinStatus(coinStatus)
|
||||||
|
coEvery { currencyChecksRepository.getExistentialDeposit(any(), any()) } returns BigDecimal("0.01")
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = useCase.invoke(userWalletId, coinStatus, derivationPath).first()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertThat(result.filterIsInstance<CryptoCurrencyWarning.ExistentialDeposit>()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN coin balance equal to existential deposit WHEN invoke THEN ExistentialDeposit warning is present`() =
|
||||||
|
runTest {
|
||||||
|
// GIVEN
|
||||||
|
val coin = coin(name = "Polkadot Asset Hub", symbol = "DOT")
|
||||||
|
val coinStatus = statusFor(currency = coin, amount = BigDecimal("0.01"))
|
||||||
|
givenCoinStatus(coinStatus)
|
||||||
|
coEvery { currencyChecksRepository.getExistentialDeposit(any(), any()) } returns BigDecimal("0.01")
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = useCase.invoke(userWalletId, coinStatus, derivationPath).first()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertThat(result).contains(
|
||||||
|
CryptoCurrencyWarning.ExistentialDeposit(
|
||||||
|
currencyName = "Polkadot Asset Hub",
|
||||||
|
edStringValueWithSymbol = "0.01 DOT",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN coin balance below existential deposit WHEN invoke THEN ExistentialDeposit warning is present`() =
|
||||||
|
runTest {
|
||||||
|
// GIVEN
|
||||||
|
val coin = coin()
|
||||||
|
val coinStatus = statusFor(currency = coin, amount = BigDecimal("0.005"))
|
||||||
|
givenCoinStatus(coinStatus)
|
||||||
|
coEvery { currencyChecksRepository.getExistentialDeposit(any(), any()) } returns BigDecimal("0.01")
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = useCase.invoke(userWalletId, coinStatus, derivationPath).first()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertThat(result.filterIsInstance<CryptoCurrencyWarning.ExistentialDeposit>()).hasSize(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN unknown coin balance WHEN invoke THEN ExistentialDeposit warning is absent`() = runTest {
|
||||||
|
// GIVEN
|
||||||
|
val coin = coin()
|
||||||
|
val coinStatus = statusFor(currency = coin, amount = null)
|
||||||
|
givenCoinStatus(coinStatus)
|
||||||
|
coEvery { currencyChecksRepository.getExistentialDeposit(any(), any()) } returns BigDecimal("0.01")
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = useCase.invoke(userWalletId, coinStatus, derivationPath).first()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertThat(result.filterIsInstance<CryptoCurrencyWarning.ExistentialDeposit>()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN network without existential deposit WHEN invoke THEN ExistentialDeposit warning is absent`() = runTest {
|
||||||
|
// GIVEN
|
||||||
|
val coin = coin()
|
||||||
|
val coinStatus = statusFor(currency = coin, amount = BigDecimal.ZERO)
|
||||||
|
givenCoinStatus(coinStatus)
|
||||||
|
coEvery { currencyChecksRepository.getExistentialDeposit(any(), any()) } returns null
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = useCase.invoke(userWalletId, coinStatus, derivationPath).first()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertThat(result.filterIsInstance<CryptoCurrencyWarning.ExistentialDeposit>()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN token with balance AND coin below deposit WHEN invoke THEN warning is built from coin`() = runTest {
|
||||||
|
// GIVEN
|
||||||
|
val coin = coin(name = "Polkadot Asset Hub", symbol = "DOT")
|
||||||
|
val token: CryptoCurrency.Token = mockk(relaxed = true) {
|
||||||
|
every { this@mockk.network } returns this@GetCurrencyWarningsUseCaseTest.network
|
||||||
|
every { this@mockk.name } returns "Tether"
|
||||||
|
every { this@mockk.symbol } returns "USDT"
|
||||||
|
}
|
||||||
|
val coinStatus = statusFor(currency = coin, amount = BigDecimal("0.005"))
|
||||||
|
val tokenStatus = statusFor(currency = token, amount = BigDecimal("100"))
|
||||||
|
givenStatuses(coinStatus = coinStatus, currencyStatus = tokenStatus)
|
||||||
|
coEvery { currencyChecksRepository.getExistentialDeposit(any(), any()) } returns BigDecimal("0.01")
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = useCase.invoke(userWalletId, tokenStatus, derivationPath).first()
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertThat(result).contains(
|
||||||
|
CryptoCurrencyWarning.ExistentialDeposit(
|
||||||
|
currencyName = "Polkadot Asset Hub",
|
||||||
|
edStringValueWithSymbol = "0.01 DOT",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun coin(name: String = "Polkadot", symbol: String = "DOT"): CryptoCurrency.Coin {
|
||||||
|
return mockk(relaxed = true) {
|
||||||
|
every { this@mockk.network } returns this@GetCurrencyWarningsUseCaseTest.network
|
||||||
|
every { this@mockk.name } returns name
|
||||||
|
every { this@mockk.symbol } returns symbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Makes both coin and token lookups resolve to [status], so coin-related warnings are actually built. */
|
||||||
|
private fun givenCoinStatus(status: CryptoCurrencyStatus) {
|
||||||
|
givenStatuses(coinStatus = status, currencyStatus = status)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenStatuses(coinStatus: CryptoCurrencyStatus, currencyStatus: CryptoCurrencyStatus) {
|
||||||
|
with(CryptoCurrencyStatusOperations) {
|
||||||
|
every { accountStatusList.getCoinStatus(any<CryptoCurrency>()) } returns coinStatus.some()
|
||||||
|
every { accountStatusList.getCryptoCurrencyStatus(any<CryptoCurrency>()) } returns currencyStatus.some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun statusFor(currency: CryptoCurrency): CryptoCurrencyStatus {
|
private fun statusFor(currency: CryptoCurrency): CryptoCurrencyStatus {
|
||||||
return mockk(relaxed = true) {
|
return mockk(relaxed = true) {
|
||||||
every { this@mockk.currency } returns currency
|
every { this@mockk.currency } returns currency
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun statusFor(currency: CryptoCurrency, amount: BigDecimal?): CryptoCurrencyStatus {
|
||||||
|
val statusValue: CryptoCurrencyStatus.Value = mockk(relaxed = true) {
|
||||||
|
every { this@mockk.amount } returns amount
|
||||||
|
}
|
||||||
|
|
||||||
|
return mockk(relaxed = true) {
|
||||||
|
every { this@mockk.currency } returns currency
|
||||||
|
every { this@mockk.value } returns statusValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class TestDispatchers(dispatcher: CoroutineDispatcher) : CoroutineDispatcherProvider {
|
private class TestDispatchers(dispatcher: CoroutineDispatcher) : CoroutineDispatcherProvider {
|
||||||
override val main: CoroutineDispatcher = dispatcher
|
override val main: CoroutineDispatcher = dispatcher
|
||||||
override val mainImmediate: CoroutineDispatcher = dispatcher
|
override val mainImmediate: CoroutineDispatcher = dispatcher
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue