Updated on 2026-08-14
This commit is contained in:
commit
bc47de97b7
997 changed files with 45685 additions and 12520 deletions
|
|
@ -17,6 +17,7 @@ dependencies {
|
|||
implementation(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.account.status)
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ dependencies {
|
|||
|
||||
// region Other libraries
|
||||
implementation(deps.kotlin.serialization)
|
||||
api(deps.kotlin.datetime)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.domain.yield.supply.models
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
sealed interface YieldBoostPromo {
|
||||
|
||||
data object None : YieldBoostPromo
|
||||
|
||||
data class Active(
|
||||
val tokens: List<PromoToken>,
|
||||
val timeline: Timeline,
|
||||
val link: String?,
|
||||
) : YieldBoostPromo {
|
||||
|
||||
data class PromoToken(
|
||||
val contractAddress: String,
|
||||
val tokenSymbol: String,
|
||||
val tokenName: String,
|
||||
val networkId: String,
|
||||
)
|
||||
|
||||
data class Timeline(val start: Instant, val end: Instant)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.domain.yield.supply.models
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
sealed interface YieldBoostStatus {
|
||||
|
||||
data object NotStarted : YieldBoostStatus
|
||||
|
||||
/**
|
||||
* User is enrolled in the boost (backend `active` or `completed`).
|
||||
*
|
||||
* The boost block on the active screen is driven entirely by [qualificationEndDate], which the backend
|
||||
* computes as the end of the bonus-accrual period:
|
||||
* - `null` — nothing is shown;
|
||||
* - in the future — days left until the date;
|
||||
* - reached / passed — awaiting payout.
|
||||
*/
|
||||
data class Enrolled(
|
||||
val tokenName: String,
|
||||
val networkId: String,
|
||||
val moduleAddress: String,
|
||||
val userAddress: String,
|
||||
val contractAddress: String,
|
||||
val qualificationEndDate: Instant?,
|
||||
) : YieldBoostStatus
|
||||
|
||||
data class Disqualified(val reason: Reason) : YieldBoostStatus {
|
||||
|
||||
enum class Reason { FROD, LESS_THAN_1_USD, CLOSED, UNKNOWN }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.yield.supply.promo
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostPromo
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostStatus
|
||||
|
||||
/**
|
||||
* Backend yield-boost promo plumbing.
|
||||
*
|
||||
* Implementations keep an in-memory cache keyed by [UserWalletId]. On a refresh failure the cached
|
||||
* value is returned. With an empty cache the call throws — use cases swallow that to "hide UI".
|
||||
*/
|
||||
interface YieldPromoRepository {
|
||||
|
||||
@Throws
|
||||
suspend fun getYieldBoostPromo(userWalletId: UserWalletId, forceRefresh: Boolean = false): YieldBoostPromo
|
||||
|
||||
@Throws
|
||||
suspend fun getYieldBoostStatus(userWalletId: UserWalletId, forceRefresh: Boolean = false): YieldBoostStatus
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.yield.supply.promo.usecase
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Pure boosted APY calculation. Hard-coded x3 coefficient — single place to swap when the backend
|
||||
* starts returning the coefficient explicitly.
|
||||
*/
|
||||
class GetBoostedApyUseCase {
|
||||
|
||||
operator fun invoke(baseApy: BigDecimal): BigDecimal = baseApy.multiply(BOOST_MULTIPLIER)
|
||||
|
||||
private companion object {
|
||||
val BOOST_MULTIPLIER: BigDecimal = BigDecimal(3)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.yield.supply.promo.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostStatus
|
||||
import com.tangem.domain.yield.supply.promo.YieldPromoRepository
|
||||
|
||||
class GetYieldBoostStatusUseCase(
|
||||
private val repository: YieldPromoRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
forceRefresh: Boolean = false,
|
||||
): Either<Throwable, YieldBoostStatus> = Either.catch {
|
||||
repository.getYieldBoostStatus(userWalletId, forceRefresh)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.domain.yield.supply.promo.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostPromo
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostStatus
|
||||
import com.tangem.domain.yield.supply.promo.YieldPromoRepository
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
|
||||
/**
|
||||
* Returns `true` iff the given token is in the active promo list AND the user has not started boost yet.
|
||||
*
|
||||
* Short-circuits to `false` on:
|
||||
* - non-Token currency
|
||||
* - promo `None` (no active promo)
|
||||
* - status not `NotStarted` (already Active / Completed / Disqualified)
|
||||
*
|
||||
* Any underlying repository failure surfaces as `Either.Left`.
|
||||
*
|
||||
* Feature-toggle and redesign-flag gating is the caller's responsibility — keep this use case
|
||||
* decoupled from feature-layer toggles to avoid the cyclic dependency `domain -> features`.
|
||||
*/
|
||||
class IsYieldBoostPromoEnabledForTokenUseCase(
|
||||
private val repository: YieldPromoRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Either<Throwable, Boolean> = Either.catch {
|
||||
val token = cryptoCurrency as? CryptoCurrency.Token ?: return@catch false
|
||||
|
||||
val promo = repository.getYieldBoostPromo(userWalletId)
|
||||
if (promo !is YieldBoostPromo.Active) return@catch false
|
||||
|
||||
val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId)
|
||||
val isTokenMatched = promo.tokens.any { promoToken ->
|
||||
promoToken.contractAddress.equals(token.contractAddress, ignoreCase = shouldIgnoreCase) &&
|
||||
promoToken.networkId == token.network.rawId
|
||||
}
|
||||
if (!isTokenMatched) return@catch false
|
||||
|
||||
val status = repository.getYieldBoostStatus(userWalletId)
|
||||
status is YieldBoostStatus.NotStarted
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.domain.yield.supply.promo.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostPromo
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostStatus
|
||||
import com.tangem.domain.yield.supply.promo.YieldPromoRepository
|
||||
|
||||
/**
|
||||
* Returns `true` iff the main wallet boost banner should be shown:
|
||||
* - promo is `Active` server-side
|
||||
* - status is `NotStarted`
|
||||
*
|
||||
* Token ownership is intentionally NOT checked — the banner is shown to every eligible wallet
|
||||
* regardless of whether it currently holds a promo token.
|
||||
*
|
||||
* Any repository failure surfaces as `Either.Left` — never assume eligibility on uncertainty.
|
||||
* Feature-toggle / redesign / "user dismissed" gating is the caller's responsibility.
|
||||
*/
|
||||
class ShouldShowYieldBoostMainBannerUseCase(
|
||||
private val repository: YieldPromoRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<Throwable, Boolean> = Either.catch {
|
||||
val promo = repository.getYieldBoostPromo(userWalletId)
|
||||
if (promo !is YieldBoostPromo.Active) return@catch false
|
||||
|
||||
val status = repository.getYieldBoostStatus(userWalletId)
|
||||
status is YieldBoostStatus.NotStarted
|
||||
}
|
||||
}
|
||||
|
|
@ -316,9 +316,9 @@ class YieldSupplyEstimateEnterFeeUseCaseTest {
|
|||
val deployFee = txs.first().fee as Fee.Ethereum.EIP1559
|
||||
val approveFee = txs[1].fee as Fee.Ethereum.EIP1559
|
||||
val enterFee = txs.last().fee as Fee.Ethereum.EIP1559
|
||||
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(1_200))
|
||||
Truth.assertThat(approveFee.gasLimit).isEqualTo(BigInteger.valueOf(2_400))
|
||||
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(3_600))
|
||||
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(1_400))
|
||||
Truth.assertThat(approveFee.gasLimit).isEqualTo(BigInteger.valueOf(2_800))
|
||||
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(4_200))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -355,9 +355,9 @@ class YieldSupplyEstimateEnterFeeUseCaseTest {
|
|||
val deployFee = txs.first().fee as Fee.Ethereum.Legacy
|
||||
val approveFee = txs[1].fee as Fee.Ethereum.Legacy
|
||||
val enterFee = txs.last().fee as Fee.Ethereum.Legacy
|
||||
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(1_200))
|
||||
Truth.assertThat(approveFee.gasLimit).isEqualTo(BigInteger.valueOf(2_400))
|
||||
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(3_600))
|
||||
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(1_400))
|
||||
Truth.assertThat(approveFee.gasLimit).isEqualTo(BigInteger.valueOf(2_800))
|
||||
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(4_200))
|
||||
}
|
||||
|
||||
private fun getDeployTx() = uncompiled(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.domain.yield.supply.promo.usecase
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class GetBoostedApyUseCaseTest {
|
||||
|
||||
private val useCase = GetBoostedApyUseCase()
|
||||
|
||||
@Test
|
||||
fun `GIVEN base apy 5_1 WHEN invoke THEN returns 15_3`() {
|
||||
val result = useCase(BigDecimal("5.1"))
|
||||
|
||||
assertThat(result).isEqualTo(BigDecimal("15.3"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN base apy 0 WHEN invoke THEN returns 0`() {
|
||||
val result = useCase(BigDecimal.ZERO)
|
||||
|
||||
assertThat(result).isEqualTo(BigDecimal.ZERO.multiply(BigDecimal(3)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN base apy 4_99 WHEN invoke THEN returns 14_97`() {
|
||||
val result = useCase(BigDecimal("4.99"))
|
||||
|
||||
assertThat(result).isEqualTo(BigDecimal("14.97"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN base apy 100 WHEN invoke THEN returns 300`() {
|
||||
val result = useCase(BigDecimal("100"))
|
||||
|
||||
assertThat(result).isEqualTo(BigDecimal("300"))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
package com.tangem.domain.yield.supply.promo.usecase
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostPromo
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostStatus
|
||||
import com.tangem.domain.yield.supply.promo.YieldPromoRepository
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.datetime.Instant
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class IsYieldBoostPromoEnabledForTokenUseCaseTest {
|
||||
|
||||
private val repository: YieldPromoRepository = mockk()
|
||||
private lateinit var useCase: IsYieldBoostPromoEnabledForTokenUseCase
|
||||
|
||||
private val userWalletId = UserWalletId("abcdef012345")
|
||||
private val contractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
||||
private val networkRawId = "ethereum"
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
useCase = IsYieldBoostPromoEnabledForTokenUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN currency is coin WHEN invoke THEN returns Right(false)`() = runTest {
|
||||
val coin = createCoin()
|
||||
|
||||
val result = useCase(userWalletId, coin)
|
||||
|
||||
assertThat(result.getOrNull()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN promo is None WHEN invoke THEN returns Right(false)`() = runTest {
|
||||
val token = createToken()
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns YieldBoostPromo.None
|
||||
|
||||
val result = useCase(userWalletId, token)
|
||||
|
||||
assertThat(result.getOrNull()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN promo repository throws WHEN invoke THEN returns Left`() = runTest {
|
||||
val token = createToken()
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } throws RuntimeException("net")
|
||||
|
||||
val result = useCase(userWalletId, token)
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN token not in promo list WHEN invoke THEN returns Right(false)`() = runTest {
|
||||
val token = createToken(contractAddress = "0xdifferent")
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
|
||||
val result = useCase(userWalletId, token)
|
||||
|
||||
assertThat(result.getOrNull()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN network mismatch WHEN invoke THEN returns Right(false)`() = runTest {
|
||||
val token = createToken(networkRawId = "polygon")
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
|
||||
val result = useCase(userWalletId, token)
|
||||
|
||||
assertThat(result.getOrNull()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN status repository throws WHEN invoke THEN returns Left`() = runTest {
|
||||
val token = createToken()
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } throws RuntimeException("net")
|
||||
|
||||
val result = useCase(userWalletId, token)
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN status is Enrolled WHEN invoke THEN returns Right(false)`() = runTest {
|
||||
val token = createToken()
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns enrolledStatus()
|
||||
|
||||
val result = useCase(userWalletId, token)
|
||||
|
||||
assertThat(result.getOrNull()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN status is Disqualified WHEN invoke THEN returns Right(false)`() = runTest {
|
||||
val token = createToken()
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns
|
||||
YieldBoostStatus.Disqualified(YieldBoostStatus.Disqualified.Reason.FROD)
|
||||
|
||||
val result = useCase(userWalletId, token)
|
||||
|
||||
assertThat(result.getOrNull()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN status is NotStarted and token matches WHEN invoke THEN returns Right(true)`() = runTest {
|
||||
val token = createToken()
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns YieldBoostStatus.NotStarted
|
||||
|
||||
val result = useCase(userWalletId, token)
|
||||
|
||||
assertThat(result.getOrNull()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN contract address differs only in case on EVM WHEN invoke THEN returns Right(true)`() = runTest {
|
||||
val token = createToken(contractAddress = contractAddress.uppercase())
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns YieldBoostStatus.NotStarted
|
||||
|
||||
val result = useCase(userWalletId, token)
|
||||
|
||||
assertThat(result.getOrNull()).isTrue()
|
||||
}
|
||||
|
||||
private fun activePromo() = YieldBoostPromo.Active(
|
||||
tokens = listOf(
|
||||
YieldBoostPromo.Active.PromoToken(
|
||||
contractAddress = contractAddress,
|
||||
tokenSymbol = "USDC",
|
||||
tokenName = "USD Coin",
|
||||
networkId = networkRawId,
|
||||
),
|
||||
),
|
||||
timeline = YieldBoostPromo.Active.Timeline(
|
||||
start = Instant.parse("2026-01-01T00:00:00Z"),
|
||||
end = Instant.parse("2027-01-01T00:00:00Z"),
|
||||
),
|
||||
link = null,
|
||||
)
|
||||
|
||||
private fun enrolledStatus() = YieldBoostStatus.Enrolled(
|
||||
tokenName = "USD Coin",
|
||||
networkId = networkRawId,
|
||||
moduleAddress = "0xmodule",
|
||||
userAddress = "0xuser",
|
||||
contractAddress = contractAddress,
|
||||
qualificationEndDate = Instant.parse("2026-06-01T00:00:00Z"),
|
||||
)
|
||||
|
||||
private fun createToken(
|
||||
contractAddress: String = this.contractAddress,
|
||||
networkRawId: String = this.networkRawId,
|
||||
): CryptoCurrency.Token {
|
||||
val derivationPath = Network.DerivationPath.None
|
||||
val network = Network(
|
||||
id = Network.ID(value = networkRawId, derivationPath = derivationPath),
|
||||
name = networkRawId,
|
||||
currencySymbol = networkRawId.take(3).uppercase(),
|
||||
derivationPath = derivationPath,
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.Unspecified("UNSPECIFIED"),
|
||||
hasFiatFeeRate = true,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
return CryptoCurrency.Token(
|
||||
id = CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkId(networkRawId),
|
||||
suffix = CryptoCurrency.ID.Suffix.RawID(networkRawId),
|
||||
),
|
||||
network = network,
|
||||
name = "USDC",
|
||||
symbol = "USDC",
|
||||
decimals = 6,
|
||||
iconUrl = null,
|
||||
isCustom = false,
|
||||
contractAddress = contractAddress,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createCoin(): CryptoCurrency.Coin {
|
||||
val derivationPath = Network.DerivationPath.None
|
||||
val network = Network(
|
||||
id = Network.ID(value = networkRawId, derivationPath = derivationPath),
|
||||
name = networkRawId,
|
||||
currencySymbol = "ETH",
|
||||
derivationPath = derivationPath,
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.Unspecified("UNSPECIFIED"),
|
||||
hasFiatFeeRate = true,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
return CryptoCurrency.Coin(
|
||||
id = CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkId(networkRawId),
|
||||
suffix = CryptoCurrency.ID.Suffix.RawID(networkRawId),
|
||||
),
|
||||
network = network,
|
||||
name = "Ethereum",
|
||||
symbol = "ETH",
|
||||
decimals = 18,
|
||||
iconUrl = null,
|
||||
isCustom = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package com.tangem.domain.yield.supply.promo.usecase
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostPromo
|
||||
import com.tangem.domain.yield.supply.models.YieldBoostStatus
|
||||
import com.tangem.domain.yield.supply.promo.YieldPromoRepository
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.datetime.Instant
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ShouldShowYieldBoostMainBannerUseCaseTest {
|
||||
|
||||
private val repository: YieldPromoRepository = mockk()
|
||||
private lateinit var useCase: ShouldShowYieldBoostMainBannerUseCase
|
||||
|
||||
private val userWalletId = UserWalletId("abcdef012345")
|
||||
private val contractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
||||
private val networkRawId = "ethereum"
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
useCase = ShouldShowYieldBoostMainBannerUseCase(repository = repository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN promo repository throws WHEN invoke THEN returns Left`() = runTest {
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } throws RuntimeException("net")
|
||||
|
||||
val result = useCase(userWalletId)
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN promo is None WHEN invoke THEN returns Right(false)`() = runTest {
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns YieldBoostPromo.None
|
||||
|
||||
val result = useCase(userWalletId)
|
||||
|
||||
assertThat(result.getOrNull()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN status repository throws WHEN invoke THEN returns Left`() = runTest {
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } throws RuntimeException("net")
|
||||
|
||||
val result = useCase(userWalletId)
|
||||
|
||||
assertThat(result.isLeft()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN status is Enrolled WHEN invoke THEN returns Right(false)`() = runTest {
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns enrolledStatus()
|
||||
|
||||
val result = useCase(userWalletId)
|
||||
|
||||
assertThat(result.getOrNull()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN promo Active and status NotStarted WHEN invoke THEN returns Right(true)`() = runTest {
|
||||
coEvery { repository.getYieldBoostPromo(userWalletId, false) } returns activePromo()
|
||||
coEvery { repository.getYieldBoostStatus(userWalletId, false) } returns YieldBoostStatus.NotStarted
|
||||
|
||||
val result = useCase(userWalletId)
|
||||
|
||||
assertThat(result.getOrNull()).isTrue()
|
||||
}
|
||||
|
||||
private fun activePromo() = YieldBoostPromo.Active(
|
||||
tokens = listOf(
|
||||
YieldBoostPromo.Active.PromoToken(
|
||||
contractAddress = contractAddress,
|
||||
tokenSymbol = "USDC",
|
||||
tokenName = "USD Coin",
|
||||
networkId = networkRawId,
|
||||
),
|
||||
),
|
||||
timeline = YieldBoostPromo.Active.Timeline(
|
||||
start = Instant.parse("2026-01-01T00:00:00Z"),
|
||||
end = Instant.parse("2027-01-01T00:00:00Z"),
|
||||
),
|
||||
link = null,
|
||||
)
|
||||
|
||||
private fun enrolledStatus() = YieldBoostStatus.Enrolled(
|
||||
tokenName = "USD Coin",
|
||||
networkId = networkRawId,
|
||||
moduleAddress = "0xmodule",
|
||||
userAddress = "0xuser",
|
||||
contractAddress = contractAddress,
|
||||
qualificationEndDate = Instant.parse("2026-06-01T00:00:00Z"),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue