Updated on 2026-08-14
This commit is contained in:
parent
ab6072e12d
commit
d34a83ced5
8 changed files with 814 additions and 2 deletions
|
|
@ -4,6 +4,10 @@ plugins {
|
|||
id("configuration")
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(projects.core.analytics.models)
|
||||
|
|
@ -15,4 +19,11 @@ dependencies {
|
|||
api(projects.domain.core)
|
||||
api(projects.domain.settings)
|
||||
implementation(deps.kotlin.serialization)
|
||||
|
||||
/** Tests */
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit5)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
data class OnrampOffersBlock(
|
||||
val category: OnrampOfferCategory,
|
||||
val offers: List<OnrampOffer>,
|
||||
val isVisible: Boolean = offers.isNotEmpty(),
|
||||
)
|
||||
|
||||
data class OnrampOffer(
|
||||
val quote: OnrampQuote,
|
||||
val advantages: OnrampOfferAdvantages = OnrampOfferAdvantages.Default,
|
||||
)
|
||||
|
||||
enum class OnrampOfferAdvantages {
|
||||
Default, BestRate, Fastest,
|
||||
}
|
||||
|
||||
enum class OnrampOfferCategory {
|
||||
Recent, Recommended,
|
||||
}
|
||||
|
|
@ -13,27 +13,59 @@ data class OnrampPaymentMethod(
|
|||
enum class PaymentMethodType(val id: String?) {
|
||||
GOOGLE_PAY(id = "google-pay"),
|
||||
CARD(id = "card"),
|
||||
REVOLUT_PAY(id = "invoice-revolut-pay"),
|
||||
SEPA(id = "sepa"),
|
||||
OTHER(id = null),
|
||||
;
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun getPriority(isGooglePayEnabled: Boolean): Int = if (isGooglePayEnabled) {
|
||||
when (this) {
|
||||
GOOGLE_PAY -> 0
|
||||
CARD -> 1
|
||||
OTHER -> 2
|
||||
SEPA -> 2
|
||||
REVOLUT_PAY -> 3
|
||||
OTHER -> 4
|
||||
}
|
||||
} else {
|
||||
when (this) {
|
||||
CARD -> 0
|
||||
GOOGLE_PAY -> 1
|
||||
OTHER -> 2
|
||||
SEPA -> 2
|
||||
REVOLUT_PAY -> 3
|
||||
OTHER -> 4
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BE AWARE. HARDCODED. Returns the speed of transaction for payment method type.
|
||||
*/
|
||||
fun getProcessingSpeed(): PaymentSpeed = when (this) {
|
||||
REVOLUT_PAY,
|
||||
GOOGLE_PAY,
|
||||
-> PaymentSpeed.Instant
|
||||
CARD -> PaymentSpeed.FewMin
|
||||
SEPA -> PaymentSpeed.FewDays
|
||||
OTHER -> PaymentSpeed.PlentyDays
|
||||
}
|
||||
|
||||
/**
|
||||
* @param speed - the lower the value, the faster the speed.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
enum class PaymentSpeed(val speed: Int) {
|
||||
Instant(0), FewMin(1), FewDays(2), PlentyDays(3), Unknown(4)
|
||||
}
|
||||
|
||||
fun isInstant(): Boolean = getProcessingSpeed() == PaymentSpeed.Instant
|
||||
|
||||
companion object {
|
||||
|
||||
fun getType(id: String): PaymentMethodType = when (id) {
|
||||
GOOGLE_PAY.id -> GOOGLE_PAY
|
||||
CARD.id -> CARD
|
||||
REVOLUT_PAY.id -> REVOLUT_PAY
|
||||
SEPA.id -> SEPA
|
||||
else -> OTHER
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class OnrampPaymentMethodGroup(
|
||||
val paymentMethod: OnrampPaymentMethod,
|
||||
val offers: List<OnrampOffer>,
|
||||
val bestRateOffer: OnrampOffer?,
|
||||
val providerCount: Int,
|
||||
val isBestPaymentMethod: Boolean,
|
||||
) {
|
||||
|
||||
val bestRateAmount: BigDecimal? = bestRateOffer?.let { offer ->
|
||||
when (val quote = offer.quote) {
|
||||
is OnrampQuote.Data -> quote.toAmount.value
|
||||
else -> BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.onramp.model.OnrampOffer
|
||||
import com.tangem.domain.onramp.model.OnrampOfferAdvantages
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethodGroup
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigDecimal
|
||||
|
||||
class GetOnrampAllOffersUseCase(
|
||||
private val onrampRepository: OnrampRepository,
|
||||
private val errorResolver: OnrampErrorResolver,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
): EitherFlow<OnrampError, List<OnrampPaymentMethodGroup>> {
|
||||
return onrampRepository.getQuotes()
|
||||
.map { quotes -> processAllOffers(quotes).right() }
|
||||
.catch { throwable -> errorResolver.resolve(throwable).left() }
|
||||
}
|
||||
|
||||
private suspend fun processAllOffers(quotes: List<OnrampQuote>): List<OnrampPaymentMethodGroup> {
|
||||
val validQuotes = quotes.filterIsInstance<OnrampQuote.Data>()
|
||||
if (validQuotes.isEmpty()) return emptyList()
|
||||
val isGooglePayAvailable = settingsRepository.isGooglePayAvailability()
|
||||
|
||||
val overallBestRateQuote = validQuotes.maxByOrNull { it.toAmount.value }
|
||||
|
||||
val offersByPaymentMethod = validQuotes.groupBy { it.paymentMethod }
|
||||
|
||||
return offersByPaymentMethod.map { (paymentMethod, methodQuotes) ->
|
||||
val methodOffers = methodQuotes.map { quote ->
|
||||
val advantages = if (quote == overallBestRateQuote) {
|
||||
OnrampOfferAdvantages.BestRate
|
||||
} else {
|
||||
OnrampOfferAdvantages.Default
|
||||
}
|
||||
OnrampOffer(quote = quote, advantages = advantages)
|
||||
}
|
||||
|
||||
val groupBestRateOfferData = methodQuotes.maxByOrNull { it.toAmount.value }
|
||||
val groupBestRateOffer = methodOffers.find {
|
||||
when (val quote = it.quote) {
|
||||
is OnrampQuote.Data -> quote == groupBestRateOfferData
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
OnrampPaymentMethodGroup(
|
||||
paymentMethod = paymentMethod,
|
||||
offers = methodOffers.sortedByDescending { offer ->
|
||||
when (val quote = offer.quote) {
|
||||
is OnrampQuote.Data -> quote.toAmount.value
|
||||
else -> BigDecimal.ZERO
|
||||
}
|
||||
},
|
||||
providerCount = methodOffers.map { it.quote.provider.id }.distinct().size,
|
||||
bestRateOffer = groupBestRateOffer,
|
||||
isBestPaymentMethod = overallBestRateQuote?.paymentMethod == paymentMethod,
|
||||
)
|
||||
}.sortedBy { it.paymentMethod.type.getPriority(isGooglePayAvailable) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.onramp.model.*
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigDecimal
|
||||
|
||||
class GetOnrampOffersUseCase(
|
||||
private val onrampRepository: OnrampRepository,
|
||||
private val onrampTransactionRepository: OnrampTransactionRepository,
|
||||
private val errorResolver: OnrampErrorResolver,
|
||||
) {
|
||||
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
): EitherFlow<OnrampError, List<OnrampOffersBlock>> {
|
||||
return combine(
|
||||
onrampRepository.getQuotes(),
|
||||
onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId),
|
||||
) { quotes, transactions ->
|
||||
processOffers(quotes, transactions)
|
||||
}
|
||||
.map { offers -> offers.right() }
|
||||
.catch { throwable -> errorResolver.resolve(throwable).left() }
|
||||
}
|
||||
|
||||
private fun processOffers(
|
||||
quotes: List<OnrampQuote>,
|
||||
transactions: List<OnrampTransaction>,
|
||||
): List<OnrampOffersBlock> {
|
||||
val validQuotes = quotes.filterIsInstance<OnrampQuote.Data>()
|
||||
if (validQuotes.isEmpty()) return emptyList()
|
||||
|
||||
val offers = validQuotes.map { quote ->
|
||||
OnrampOffer(quote = quote)
|
||||
}
|
||||
|
||||
val recentOffer = findRecentOffer(offers, transactions)
|
||||
val bestRateOffer = findBestRateOffer(offers)
|
||||
val fastestOffer = findFastestOffer(offers)
|
||||
|
||||
return buildOffersBlocks(
|
||||
recentOffer = recentOffer,
|
||||
bestRateOffer = bestRateOffer,
|
||||
fastestOffer = fastestOffer,
|
||||
allOffers = offers,
|
||||
)
|
||||
}
|
||||
|
||||
private fun findRecentOffer(offers: List<OnrampOffer>, transactions: List<OnrampTransaction>): OnrampOffer? {
|
||||
val lastTransaction = transactions.maxByOrNull { it.timestamp } ?: return null
|
||||
|
||||
return offers.find { offer ->
|
||||
offer.quote.provider.id == lastTransaction.providerType &&
|
||||
offer.quote.paymentMethod.id == lastTransaction.paymentMethod
|
||||
}
|
||||
}
|
||||
|
||||
private fun findBestRateOffer(offers: List<OnrampOffer>): OnrampOffer? {
|
||||
return offers.maxByOrNull { offer ->
|
||||
when (val quote = offer.quote) {
|
||||
is OnrampQuote.Data -> quote.toAmount.value
|
||||
else -> BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findFastestOffer(offers: List<OnrampOffer>): OnrampOffer? {
|
||||
val instantOffers = offers.filter { it.quote.paymentMethod.type.isInstant() }
|
||||
return if (instantOffers.isNotEmpty()) {
|
||||
instantOffers.maxByOrNull { offer ->
|
||||
when (val quote = offer.quote) {
|
||||
is OnrampQuote.Data -> quote.toAmount.value
|
||||
else -> BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val offersBySpeed = offers.groupBy { offer ->
|
||||
offer.quote.paymentMethod.type.getProcessingSpeed().speed
|
||||
}
|
||||
val fastestSpeed = offersBySpeed.keys.minOrNull() ?: return null
|
||||
val fastestOffers = offersBySpeed[fastestSpeed] ?: return null
|
||||
fastestOffers.maxByOrNull { offer ->
|
||||
when (val quote = offer.quote) {
|
||||
is OnrampQuote.Data -> quote.toAmount.value
|
||||
else -> BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildOffersBlocks(
|
||||
recentOffer: OnrampOffer?,
|
||||
bestRateOffer: OnrampOffer?,
|
||||
fastestOffer: OnrampOffer?,
|
||||
allOffers: List<OnrampOffer>,
|
||||
): List<OnrampOffersBlock> {
|
||||
return buildList {
|
||||
if (recentOffer != null) {
|
||||
add(
|
||||
OnrampOffersBlock(
|
||||
category = OnrampOfferCategory.Recent,
|
||||
offers = listOf(
|
||||
recentOffer.copy(
|
||||
advantages = determineAdvantages(
|
||||
recentOffer,
|
||||
bestRateOffer,
|
||||
fastestOffer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val recommendedOffers = buildRecommendedOffers(
|
||||
recentOffer = recentOffer,
|
||||
bestRateOffer = bestRateOffer,
|
||||
fastestOffer = fastestOffer,
|
||||
)
|
||||
|
||||
if (recommendedOffers.isNotEmpty() && hasOnlyOneMethodAndProvider(allOffers).not()) {
|
||||
add(
|
||||
OnrampOffersBlock(
|
||||
category = OnrampOfferCategory.Recommended,
|
||||
offers = recommendedOffers,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun determineAdvantages(
|
||||
recentOffer: OnrampOffer,
|
||||
bestRateOffer: OnrampOffer?,
|
||||
fastestOffer: OnrampOffer?,
|
||||
): OnrampOfferAdvantages {
|
||||
if (isSameOffer(recentOffer, bestRateOffer) && isSameOffer(recentOffer, fastestOffer)) {
|
||||
return OnrampOfferAdvantages.BestRate
|
||||
}
|
||||
if (isSameOffer(recentOffer, bestRateOffer)) {
|
||||
return OnrampOfferAdvantages.BestRate
|
||||
}
|
||||
if (isSameOffer(recentOffer, fastestOffer)) {
|
||||
return OnrampOfferAdvantages.Fastest
|
||||
}
|
||||
return OnrampOfferAdvantages.Default
|
||||
}
|
||||
|
||||
private fun buildRecommendedOffers(
|
||||
recentOffer: OnrampOffer?,
|
||||
bestRateOffer: OnrampOffer?,
|
||||
fastestOffer: OnrampOffer?,
|
||||
): List<OnrampOffer> {
|
||||
return buildList {
|
||||
if (isSameOffer(bestRateOffer, fastestOffer)) {
|
||||
bestRateOffer?.let { offer ->
|
||||
add(offer.copy(advantages = OnrampOfferAdvantages.BestRate))
|
||||
}
|
||||
} else {
|
||||
if (bestRateOffer != null && !isSameOffer(bestRateOffer, recentOffer)) {
|
||||
add(bestRateOffer.copy(advantages = OnrampOfferAdvantages.BestRate))
|
||||
}
|
||||
|
||||
if (fastestOffer != null && !isSameOffer(fastestOffer, recentOffer) &&
|
||||
!isSameOffer(fastestOffer, bestRateOffer)
|
||||
) {
|
||||
add(fastestOffer.copy(advantages = OnrampOfferAdvantages.Fastest))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasOnlyOneMethodAndProvider(offers: List<OnrampOffer>): Boolean {
|
||||
val uniquePaymentMethods = offers.map { it.quote.paymentMethod.id }.distinct()
|
||||
val uniqueProviders = offers.map { it.quote.provider.id }.distinct()
|
||||
return uniquePaymentMethods.size == 1 && uniqueProviders.size == 1
|
||||
}
|
||||
|
||||
private fun isSameOffer(offer1: OnrampOffer?, offer2: OnrampOffer?): Boolean {
|
||||
if (offer1 == null || offer2 == null) return false
|
||||
return offer1.quote.provider.id == offer2.quote.provider.id &&
|
||||
offer1.quote.paymentMethod.id == offer2.quote.paymentMethod.id
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.onramp.model.OnrampOfferAdvantages
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.OnrampProvider
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class GetOnrampAllOffersUseCaseTest {
|
||||
|
||||
private val onrampRepository: OnrampRepository = mockk(relaxUnitFun = true)
|
||||
private val errorResolver: OnrampErrorResolver = mockk(relaxUnitFun = true)
|
||||
private val settingsRepository: SettingsRepository = mockk(relaxUnitFun = true)
|
||||
private val cryptoCurrencyId: CryptoCurrency.ID = mockk(relaxUnitFun = true)
|
||||
private val userWalletId: UserWalletId = mockk(relaxUnitFun = true)
|
||||
|
||||
private lateinit var useCase: GetOnrampAllOffersUseCase
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
clearMocks(onrampRepository, errorResolver, settingsRepository, cryptoCurrencyId)
|
||||
useCase = GetOnrampAllOffersUseCase(
|
||||
onrampRepository = onrampRepository,
|
||||
errorResolver = errorResolver,
|
||||
settingsRepository = settingsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return empty list when no valid quotes`() = runTest {
|
||||
val emptyQuotes = listOf<OnrampQuote>()
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(emptyQuotes)
|
||||
|
||||
val result = useCase(userWalletId, cryptoCurrencyId)
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers -> Truth.assertThat(offers).isEmpty() },
|
||||
)
|
||||
}
|
||||
coVerify { onrampRepository.getQuotes() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return grouped offers with best rate marked`() = runTest {
|
||||
val paymentMethod1 = createMockPaymentMethod("card", "Card")
|
||||
val paymentMethod2 = createMockPaymentMethod("bank", "Bank Transfer")
|
||||
val provider1 = createMockProvider("provider1", "Provider 1")
|
||||
val provider2 = createMockProvider("provider2", "Provider 2")
|
||||
|
||||
val quotes = listOf(
|
||||
createMockQuote(paymentMethod1, provider1, BigDecimal("100.0")),
|
||||
createMockQuote(paymentMethod1, provider2, BigDecimal("95.0")),
|
||||
createMockQuote(paymentMethod2, provider1, BigDecimal("98.0")),
|
||||
)
|
||||
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
|
||||
coEvery { settingsRepository.isGooglePayAvailability() } returns false
|
||||
|
||||
val result = useCase(userWalletId, cryptoCurrencyId)
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers ->
|
||||
Truth.assertThat(offers).hasSize(2)
|
||||
|
||||
val cardGroup = offers.find { it.paymentMethod.id == "card" }
|
||||
Truth.assertThat(cardGroup).isNotNull()
|
||||
Truth.assertThat(cardGroup?.offers).hasSize(2)
|
||||
Truth.assertThat(cardGroup?.providerCount).isEqualTo(2)
|
||||
Truth.assertThat(cardGroup?.isBestPaymentMethod).isTrue()
|
||||
|
||||
val bestRateOffer = cardGroup?.offers?.find { it.advantages == OnrampOfferAdvantages.BestRate }
|
||||
Truth.assertThat(bestRateOffer).isNotNull()
|
||||
|
||||
val bankGroup = offers.find { it.paymentMethod.id == "bank" }
|
||||
Truth.assertThat(bankGroup).isNotNull()
|
||||
Truth.assertThat(bankGroup?.offers).hasSize(1)
|
||||
Truth.assertThat(bankGroup?.providerCount).isEqualTo(1)
|
||||
Truth.assertThat(bankGroup?.isBestPaymentMethod).isFalse()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
coVerify { onrampRepository.getQuotes() }
|
||||
coVerify { settingsRepository.isGooglePayAvailability() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should sort offers by toAmount descending`() = runTest {
|
||||
val paymentMethod = createMockPaymentMethod("card", "Card")
|
||||
val provider = createMockProvider("provider1", "Provider 1")
|
||||
|
||||
val quotes = listOf(
|
||||
createMockQuote(paymentMethod, provider, BigDecimal("90.0")),
|
||||
createMockQuote(paymentMethod, provider, BigDecimal("100.0")),
|
||||
createMockQuote(paymentMethod, provider, BigDecimal("95.0")),
|
||||
)
|
||||
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
|
||||
coEvery { settingsRepository.isGooglePayAvailability() } returns false
|
||||
|
||||
val result = useCase(userWalletId, cryptoCurrencyId)
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers ->
|
||||
Truth.assertThat(offers).hasSize(1)
|
||||
val group = offers.first()
|
||||
Truth.assertThat(group.offers).hasSize(3)
|
||||
|
||||
val amounts = group.offers.map { offer ->
|
||||
when (val quote = offer.quote) {
|
||||
is OnrampQuote.Data -> quote.toAmount.value
|
||||
else -> BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
Truth.assertThat(amounts).containsExactly(
|
||||
BigDecimal("100.0"),
|
||||
BigDecimal("95.0"),
|
||||
BigDecimal("90.0"),
|
||||
).inOrder()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockPaymentMethod(id: String, name: String): OnrampPaymentMethod {
|
||||
return mockk<OnrampPaymentMethod> {
|
||||
every { this@mockk.id } returns id
|
||||
every { this@mockk.name } returns name
|
||||
every { this@mockk.type } returns mockk {
|
||||
every { getPriority(any()) } returns 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockProvider(id: String, name: String): OnrampProvider {
|
||||
return mockk<OnrampProvider> {
|
||||
every { this@mockk.id } returns id
|
||||
every { this@mockk.info.name } returns name
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockQuote(
|
||||
paymentMethod: OnrampPaymentMethod,
|
||||
provider: OnrampProvider,
|
||||
toAmount: BigDecimal,
|
||||
): OnrampQuote.Data {
|
||||
return mockk<OnrampQuote.Data> {
|
||||
every { this@mockk.paymentMethod } returns paymentMethod
|
||||
every { this@mockk.provider } returns provider
|
||||
every { this@mockk.toAmount } returns mockk {
|
||||
every { value } returns toAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.onramp.model.*
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class GetOnrampOffersUseCaseTest {
|
||||
|
||||
private val onrampRepository: OnrampRepository = mockk(relaxUnitFun = true)
|
||||
private val onrampTransactionRepository: OnrampTransactionRepository = mockk(relaxUnitFun = true)
|
||||
private val errorResolver: OnrampErrorResolver = mockk(relaxUnitFun = true)
|
||||
private val cryptoCurrencyId: CryptoCurrency.ID = mockk(relaxUnitFun = true)
|
||||
private val userWalletId: UserWalletId = mockk(relaxUnitFun = true)
|
||||
|
||||
private lateinit var useCase: GetOnrampOffersUseCase
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
clearMocks(onrampRepository, onrampTransactionRepository, errorResolver, cryptoCurrencyId)
|
||||
useCase = GetOnrampOffersUseCase(
|
||||
onrampRepository = onrampRepository,
|
||||
onrampTransactionRepository = onrampTransactionRepository,
|
||||
errorResolver = errorResolver,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return empty list when no valid quotes`() = runTest {
|
||||
val emptyQuotes = listOf<OnrampQuote>()
|
||||
val emptyTransactions = listOf<OnrampTransaction>()
|
||||
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(emptyQuotes)
|
||||
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
|
||||
emptyTransactions,
|
||||
)
|
||||
|
||||
val result = useCase(userWalletId, cryptoCurrencyId)
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers -> Truth.assertThat(offers).isEmpty() },
|
||||
)
|
||||
}
|
||||
|
||||
coVerify { onrampRepository.getQuotes() }
|
||||
coVerify { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return offers blocks with recent and recommended categories`() = runTest {
|
||||
val paymentMethod1 = createMockPaymentMethod("card", "Card", isInstant = true)
|
||||
val paymentMethod2 = createMockPaymentMethod("bank", "Bank Transfer", isInstant = false)
|
||||
val provider1 = createMockProvider("provider1", "Provider 1")
|
||||
val provider2 = createMockProvider("provider2", "Provider 2")
|
||||
|
||||
val quotes = listOf(
|
||||
createMockQuote(paymentMethod1, provider1, BigDecimal("90.0")),
|
||||
createMockQuote(paymentMethod2, provider2, BigDecimal("100.0")),
|
||||
)
|
||||
|
||||
val transactions = listOf(
|
||||
createMockTransaction("provider1", "card", 1000L),
|
||||
)
|
||||
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
|
||||
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
|
||||
transactions,
|
||||
)
|
||||
|
||||
val result = useCase(userWalletId, cryptoCurrencyId)
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers ->
|
||||
Truth.assertThat(offers).hasSize(2)
|
||||
|
||||
val recentBlock = offers.find { it.category == OnrampOfferCategory.Recent }
|
||||
Truth.assertThat(recentBlock).isNotNull()
|
||||
Truth.assertThat(recentBlock?.offers).hasSize(1)
|
||||
Truth.assertThat(recentBlock?.offers?.first()?.advantages).isEqualTo(OnrampOfferAdvantages.Fastest)
|
||||
|
||||
val recommendedBlock = offers.find { it.category == OnrampOfferCategory.Recommended }
|
||||
Truth.assertThat(recommendedBlock).isNotNull()
|
||||
Truth.assertThat(recommendedBlock?.offers).hasSize(1)
|
||||
Truth.assertThat(recommendedBlock?.offers?.first()?.advantages)
|
||||
.isEqualTo(OnrampOfferAdvantages.BestRate)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should find best rate offer correctly`() = runTest {
|
||||
val paymentMethod1 = createMockPaymentMethod("card", "Card", isInstant = false)
|
||||
val paymentMethod2 = createMockPaymentMethod("bank", "Bank Transfer", isInstant = false)
|
||||
val provider1 = createMockProvider("provider1", "Provider 1")
|
||||
val provider2 = createMockProvider("provider2", "Provider 2")
|
||||
|
||||
val quotes = listOf(
|
||||
createMockQuote(paymentMethod1, provider1, BigDecimal("90.0")),
|
||||
createMockQuote(paymentMethod2, provider2, BigDecimal("100.0")),
|
||||
createMockQuote(paymentMethod1, provider1, BigDecimal("95.0")),
|
||||
)
|
||||
|
||||
val transactions = emptyList<OnrampTransaction>()
|
||||
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
|
||||
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
|
||||
transactions,
|
||||
)
|
||||
|
||||
val result = useCase(userWalletId, cryptoCurrencyId)
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers ->
|
||||
Truth.assertThat(offers).hasSize(1)
|
||||
|
||||
val recommendedBlock = offers.find { it.category == OnrampOfferCategory.Recommended }
|
||||
Truth.assertThat(recommendedBlock).isNotNull()
|
||||
Truth.assertThat(recommendedBlock?.offers).hasSize(1)
|
||||
|
||||
val bestRateOffer = recommendedBlock?.offers?.first()
|
||||
Truth.assertThat(bestRateOffer?.advantages).isEqualTo(OnrampOfferAdvantages.BestRate)
|
||||
|
||||
when (val quote = bestRateOffer?.quote) {
|
||||
is OnrampQuote.Data -> Truth.assertThat(quote.toAmount.value).isEqualTo(BigDecimal("100.0"))
|
||||
else -> Truth.assertThat(false).isTrue()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should find fastest offer correctly`() = runTest {
|
||||
val instantPaymentMethod = createMockPaymentMethod("card", "Card", isInstant = true)
|
||||
val slowPaymentMethod = createMockPaymentMethod("bank", "Bank Transfer", isInstant = false)
|
||||
val provider1 = createMockProvider("provider1", "Provider 1")
|
||||
val provider2 = createMockProvider("provider2", "Provider 2")
|
||||
|
||||
val quotes = listOf(
|
||||
createMockQuote(instantPaymentMethod, provider1, BigDecimal("90.0")),
|
||||
createMockQuote(slowPaymentMethod, provider2, BigDecimal("100.0")),
|
||||
)
|
||||
|
||||
val transactions = emptyList<OnrampTransaction>()
|
||||
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
|
||||
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
|
||||
transactions,
|
||||
)
|
||||
|
||||
val result = useCase(userWalletId, cryptoCurrencyId)
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers ->
|
||||
Truth.assertThat(offers).hasSize(1)
|
||||
|
||||
val recommendedBlock = offers.find { it.category == OnrampOfferCategory.Recommended }
|
||||
Truth.assertThat(recommendedBlock).isNotNull()
|
||||
Truth.assertThat(recommendedBlock?.offers).hasSize(2)
|
||||
|
||||
val bestRateOffer = recommendedBlock
|
||||
?.offers
|
||||
?.find { it.advantages == OnrampOfferAdvantages.BestRate }
|
||||
val fastestOffer = recommendedBlock
|
||||
?.offers
|
||||
?.find { it.advantages == OnrampOfferAdvantages.Fastest }
|
||||
|
||||
Truth.assertThat(bestRateOffer).isNotNull()
|
||||
Truth.assertThat(fastestOffer).isNotNull()
|
||||
|
||||
when (val quote = bestRateOffer?.quote) {
|
||||
is OnrampQuote.Data -> Truth.assertThat(quote.toAmount.value).isEqualTo(BigDecimal("100.0"))
|
||||
else -> Truth.assertThat(false).isTrue()
|
||||
}
|
||||
|
||||
when (val quote = fastestOffer?.quote) {
|
||||
is OnrampQuote.Data -> Truth.assertThat(quote.toAmount.value).isEqualTo(BigDecimal("90.0"))
|
||||
else -> Truth.assertThat(false).isTrue()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should not show recommended block when only one method and provider`() = runTest {
|
||||
val paymentMethod = createMockPaymentMethod("card", "Card", isInstant = false)
|
||||
val provider = createMockProvider("provider1", "Provider 1")
|
||||
|
||||
val quotes = listOf(
|
||||
createMockQuote(paymentMethod, provider, BigDecimal("100.0")),
|
||||
)
|
||||
|
||||
val transactions = emptyList<OnrampTransaction>()
|
||||
|
||||
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
|
||||
coEvery { onrampTransactionRepository.getTransactions(userWalletId, cryptoCurrencyId) } returns flowOf(
|
||||
transactions,
|
||||
)
|
||||
|
||||
val result = useCase(userWalletId, cryptoCurrencyId)
|
||||
|
||||
result.collect { either ->
|
||||
Truth.assertThat(either.isRight()).isTrue()
|
||||
either.fold(
|
||||
ifLeft = { error -> Truth.assertThat(error).isNull() },
|
||||
ifRight = { offers ->
|
||||
Truth.assertThat(offers).isEmpty()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockPaymentMethod(id: String, name: String, isInstant: Boolean): OnrampPaymentMethod {
|
||||
return mockk<OnrampPaymentMethod> {
|
||||
every { this@mockk.id } returns id
|
||||
every { this@mockk.name } returns name
|
||||
every { this@mockk.type } returns mockk {
|
||||
every { isInstant() } returns isInstant
|
||||
every { getProcessingSpeed() } returns mockk {
|
||||
every { speed } returns if (isInstant) 1 else 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockProvider(id: String, name: String): OnrampProvider {
|
||||
return mockk<OnrampProvider> {
|
||||
every { this@mockk.id } returns id
|
||||
every { this@mockk.info.name } returns name
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockQuote(
|
||||
paymentMethod: OnrampPaymentMethod,
|
||||
provider: OnrampProvider,
|
||||
toAmount: BigDecimal,
|
||||
): OnrampQuote.Data {
|
||||
return mockk<OnrampQuote.Data> {
|
||||
every { this@mockk.paymentMethod } returns paymentMethod
|
||||
every { this@mockk.provider } returns provider
|
||||
every { this@mockk.toAmount } returns mockk {
|
||||
every { value } returns toAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockTransaction(providerType: String, paymentMethod: String, timestamp: Long): OnrampTransaction {
|
||||
return mockk<OnrampTransaction> {
|
||||
every { this@mockk.providerType } returns providerType
|
||||
every { this@mockk.paymentMethod } returns paymentMethod
|
||||
every { this@mockk.timestamp } returns timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue