Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-08 09:35:23 +03:00
parent 9bcc68222d
commit 03f702640e
3 changed files with 149 additions and 0 deletions

View file

@ -27,6 +27,7 @@ import com.tangem.features.onramp.main.entity.factory.OnrampAmountStateFactory
import com.tangem.features.onramp.main.entity.factory.OnrampOffersStateFactory
import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
import com.tangem.features.onramp.utils.sendProviderCalculatedEvent
import com.tangem.features.onramp.utils.showDemoModeWarningIfNeeded
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -330,6 +331,10 @@ internal class OnrampMainComponentModel @Inject constructor(
state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
}
else -> {
analyticsEventHandler.sendProviderCalculatedEvent(
quotes = quotes,
tokenSymbol = params.cryptoCurrency.symbol,
)
state.update { prevState ->
val resetState = amountStateFactory.getAmountSecondaryFieldResetState()
if (prevState is OnrampMainComponentUM.Content &&

View file

@ -0,0 +1,21 @@
package com.tangem.features.onramp.utils
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
import com.tangem.domain.onramp.model.OnrampQuote
internal fun AnalyticsEventHandler.sendProviderCalculatedEvent(quotes: List<OnrampQuote>, tokenSymbol: String) {
val quote = quotes.findBestRateQuote() ?: return
send(
OnrampAnalyticsEvent.ProviderCalculated(
providerName = quote.provider.info.name,
tokenSymbol = tokenSymbol,
paymentMethod = quote.paymentMethod.name,
),
)
}
private fun List<OnrampQuote>.findBestRateQuote(): OnrampQuote.Data? {
return filterIsInstance<OnrampQuote.Data>().maxByOrNull { it.toAmount.value }
}

View file

@ -0,0 +1,123 @@
package com.tangem.features.onramp.utils
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
import com.tangem.domain.onramp.model.OnrampQuote
import com.tangem.test.core.ProvideTestModels
import io.mockk.Called
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import java.math.BigDecimal
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class OnrampProviderCalculatedAnalyticsSenderTest {
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxUnitFun = true)
@BeforeEach
fun resetMocks() {
clearMocks(analyticsEventHandler)
}
@ParameterizedTest
@ProvideTestModels
fun `GIVEN quotes WHEN send THEN provider calculated sent for best-rate provider`(model: SelectionModel) {
// Act
analyticsEventHandler.sendProviderCalculatedEvent(quotes = model.quotes, tokenSymbol = TOKEN_SYMBOL)
// Assert
verify(exactly = 1) {
analyticsEventHandler.send(
OnrampAnalyticsEvent.ProviderCalculated(
providerName = model.expectedProviderName,
tokenSymbol = TOKEN_SYMBOL,
paymentMethod = PAYMENT_METHOD,
),
)
}
}
@Test
fun `GIVEN no quotes WHEN send THEN no event sent`() {
// Act
analyticsEventHandler.sendProviderCalculatedEvent(quotes = emptyList(), tokenSymbol = TOKEN_SYMBOL)
// Assert
verify { analyticsEventHandler wasNot Called }
}
@Test
fun `GIVEN only non-loaded quotes WHEN send THEN no event sent`() {
// Arrange
val quotes = listOf(mockk<OnrampQuote.Error>(), mockk<OnrampQuote.AmountError>())
// Act
analyticsEventHandler.sendProviderCalculatedEvent(quotes = quotes, tokenSymbol = TOKEN_SYMBOL)
// Assert
verify { analyticsEventHandler wasNot Called }
}
private fun provideTestModels() = listOf(
SelectionModel(
name = "highest-rate quote among several is selected",
quotes = listOf(
createQuote(providerName = "Low", rate = BigDecimal("100")),
createQuote(providerName = "High", rate = BigDecimal("120")),
createQuote(providerName = "Mid", rate = BigDecimal("90")),
),
expectedProviderName = "High",
),
SelectionModel(
name = "SEPA quote with lower rate is NOT prioritized, higher-rate quote wins",
quotes = listOf(
createQuote(providerName = "SepaLowerRate", rate = BigDecimal("100")),
createQuote(providerName = "CardHigherRate", rate = BigDecimal("105")),
),
expectedProviderName = "CardHigherRate",
),
SelectionModel(
name = "single loaded quote is selected",
quotes = listOf(
createQuote(providerName = "Single", rate = BigDecimal("100")),
),
expectedProviderName = "Single",
),
SelectionModel(
name = "best-rate loaded quote is selected even when error quotes are present",
quotes = listOf(
mockk<OnrampQuote.Error>(),
createQuote(providerName = "Loaded", rate = BigDecimal("100")),
mockk<OnrampQuote.AmountError>(),
),
expectedProviderName = "Loaded",
),
)
private fun createQuote(providerName: String, rate: BigDecimal): OnrampQuote.Data {
return mockk<OnrampQuote.Data> {
every { provider.info.name } returns providerName
every { paymentMethod.name } returns PAYMENT_METHOD
every { toAmount.value } returns rate
}
}
internal data class SelectionModel(
val name: String,
val quotes: List<OnrampQuote>,
val expectedProviderName: String,
) {
override fun toString(): String = name
}
private companion object {
const val TOKEN_SYMBOL = "BTC"
const val PAYMENT_METHOD = "Card"
}
}