Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-27 12:40:31 +04:00
parent d14835df83
commit 44fc5b9610
7 changed files with 260 additions and 122 deletions

View file

@ -1,38 +1,61 @@
package com.tangem.features.swap.v2.impl.amount.analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticsErrorMessages
internal class SwapAmountAnalyticsSender(
private val analyticsEventHandler: AnalyticsEventHandler,
) {
private var lastSentErrorMessage: String? = null
private var lastSentEvent: AnalyticsEvent? = null
fun sendErrorIfNeeded(quotes: List<SwapQuoteUM>, selectedQuote: SwapQuoteUM?) {
val errorMessage = resolveErrorMessage(quotes, selectedQuote)
if (errorMessage == lastSentErrorMessage) return
lastSentErrorMessage = errorMessage
if (errorMessage != null) {
analyticsEventHandler.send(
SendWithSwapAnalyticEvents.SendWithSwapError(
errorScreen = SendWithSwapAnalyticEvents.ErrorScreen.Amount,
message = errorMessage,
),
fun sendErrorIfNeeded(
quotes: List<SwapQuoteUM>,
selectedQuote: SwapQuoteUM?,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
hasInsufficientBalance: Boolean,
) {
val event = resolveEvent(
quotes = quotes,
selectedQuote = selectedQuote,
fromToken = fromToken,
toToken = toToken,
hasInsufficientBalance = hasInsufficientBalance,
)
if (event?.event == lastSentEvent?.event && event?.params == lastSentEvent?.params) return
lastSentEvent = event
if (event != null) {
analyticsEventHandler.send(event)
}
}
private fun resolveEvent(
quotes: List<SwapQuoteUM>,
selectedQuote: SwapQuoteUM?,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
hasInsufficientBalance: Boolean,
): AnalyticsEvent? {
if (hasInsufficientBalance) {
return SendWithSwapAnalyticEvents.ErrorInsufficientBalance(fromToken = fromToken)
}
if (quotes.isEmpty()) return null
val error = (selectedQuote as? SwapQuoteUM.Error)?.expressError ?: return null
return when (error) {
is ExpressError.AmountError.TooSmallError ->
SendWithSwapAnalyticEvents.ErrorMinAmount(fromToken = fromToken)
is ExpressError.AmountError.TooBigError ->
SendWithSwapAnalyticEvents.ErrorMaxAmount(fromToken = fromToken)
else -> SendWithSwapAnalyticEvents.ErrorExpressQuote(
fromToken = fromToken,
toToken = toToken,
errorDescription = "code=${error.code}",
)
}
}
private fun resolveErrorMessage(quotes: List<SwapQuoteUM>, selectedQuote: SwapQuoteUM?): String? {
if (quotes.isEmpty()) return SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE_NO_PROVIDERS
val error = (selectedQuote as? SwapQuoteUM.Error)?.expressError ?: return null
return when (error) {
is ExpressError.AmountError.TooSmallError -> SendWithSwapAnalyticsErrorMessages.MIN_AMOUNT
is ExpressError.AmountError.TooBigError -> SendWithSwapAnalyticsErrorMessages.MAX_AMOUNT
else -> "${SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE}: code=${error.code}"
}
}
}

View file

@ -57,7 +57,6 @@ import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents.NoticeFixedRate.toAnalyticsRateType
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticsErrorMessages
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.Debouncer
import com.tangem.utils.coroutines.PeriodicTask
@ -616,18 +615,35 @@ internal class SwapAmountModel @Inject constructor(
| Secondary -> $secondaryStatus
""".trimIndent(),
)
analyticsEventHandler.send(
SendWithSwapAnalyticEvents.SendWithSwapError(
errorScreen = SendWithSwapAnalyticEvents.ErrorScreen.Amount,
message = "${SendWithSwapAnalyticsErrorMessages.INVALID_CRYPTOCURRENCIES_STATUS}: " +
"primary=$primaryStatus, secondary=$secondaryStatus",
),
)
showErrorAlert(errorMessage = null)
}
}
}
private fun sendAmountErrorAnalyticsIfNeeded(quotes: List<SwapQuoteUM>) {
val content = uiState.value as? SwapAmountUM.Content ?: return
val toCurrency = content.secondaryCryptoCurrencyStatus?.currency ?: return
amountAnalyticsSender.sendErrorIfNeeded(
quotes = quotes,
selectedQuote = content.selectedQuote,
fromToken = content.primaryCryptoCurrencyStatus.currency,
toToken = toCurrency,
hasInsufficientBalance = hasInsufficientBalance(content),
)
}
private fun hasInsufficientBalance(content: SwapAmountUM.Content): Boolean {
val primaryBalance = content.primaryCryptoCurrencyStatus.value.amount ?: return false
val fromAmount = when (content.selectedAmountType) {
SwapAmountType.To -> (content.selectedQuote as? SwapQuoteUM.Content)?.fromAmount
SwapAmountType.From -> {
val field = content.primaryAmount as? SwapAmountFieldUM.Content
(field?.amountField as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
}
} ?: return false
return fromAmount > primaryBalance
}
private fun sendAmountScreenOpenedIfNeeded(secondaryStatus: CryptoCurrencyStatus) {
if (params !is SwapAmountComponentParams.AmountParams) return
if (isAmountScreenOpenedSent) return
@ -711,7 +727,9 @@ internal class SwapAmountModel @Inject constructor(
val isAmountScreen = params is SwapAmountComponentParams.AmountParams
val isAmountError = amountField?.amountTextField?.isError == true || amountValue.isNullOrZero()
if (isAmountScreen && isAmountError) {
uiState.transformerUpdate(SwapQuoteEmptyStateTransformer); return
uiState.transformerUpdate(SwapQuoteEmptyStateTransformer)
sendAmountErrorAnalyticsIfNeeded(quotes = emptyList())
return
}
val rateType = when (state.selectedAmountType) {
@ -783,8 +801,7 @@ internal class SwapAmountModel @Inject constructor(
),
)
if (params is SwapAmountComponentParams.AmountParams) {
val selectedQuote = (uiState.value as? SwapAmountUM.Content)?.selectedQuote
amountAnalyticsSender.sendErrorIfNeeded(quotes, selectedQuote)
sendAmountErrorAnalyticsIfNeeded(quotes)
}
feeSelectorReloadTrigger.triggerUpdate()
}

View file

@ -18,7 +18,6 @@ import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsUpdateListener
import com.tangem.features.swap.v2.impl.notifications.entity.SwapNotificationUM
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticsErrorMessages
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -46,7 +45,7 @@ internal class SwapNotificationsModel @Inject constructor(
private val params: SwapNotificationsComponent.Params = paramsContainer.require()
private var notificationData = params.swapNotificationData
private var lastSentErrorMessages: Set<String> = emptySet()
private var lastSentErrorKeys: Set<Pair<String, Map<String, String>>> = emptySet()
val uiState: StateFlow<ImmutableList<NotificationUM>>
field = MutableStateFlow<ImmutableList<NotificationUM>>(persistentListOf())
@ -203,32 +202,32 @@ internal class SwapNotificationsModel @Inject constructor(
}
private fun sendErrorAnalyticsIfNeeded(notifications: List<NotificationUM>) {
val currentErrors = notifications.mapNotNull { notification ->
val fromToken = notificationData.fromCryptoCurrency ?: return
val toToken = notificationData.toCryptoCurrencyStatus?.currency
val events = notifications.mapNotNull { notification ->
when (notification) {
is SwapNotificationUM.Error.InsufficientFunds ->
SendWithSwapAnalyticsErrorMessages.INSUFFICIENT_BALANCE
SendWithSwapAnalyticEvents.ErrorInsufficientBalance(fromToken = fromToken)
is SwapNotificationUM.Error.MinimalAmountError ->
SendWithSwapAnalyticsErrorMessages.MIN_AMOUNT
SendWithSwapAnalyticEvents.ErrorMinAmount(fromToken = fromToken)
is SwapNotificationUM.Error.MaximumAmountError ->
SendWithSwapAnalyticsErrorMessages.MAX_AMOUNT
is SwapNotificationUM.Warning.ExpressGeneralError ->
"${SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE}: code=${notification.expressError.code}"
is NotificationUM.Error.DestinationTagRequired ->
SendWithSwapAnalyticsErrorMessages.DESTINATION_TAG_REQUIRED
SendWithSwapAnalyticEvents.ErrorMaxAmount(fromToken = fromToken)
is SwapNotificationUM.Warning.ExpressGeneralError -> toToken?.let { receiveToken ->
SendWithSwapAnalyticEvents.ErrorExpressQuote(
fromToken = fromToken,
toToken = receiveToken,
errorDescription = "code=${notification.expressError.code}",
)
}
else -> null
}
}.toSet()
val newErrors = currentErrors - lastSentErrorMessages
lastSentErrorMessages = currentErrors
newErrors.forEach { errorMessage ->
analyticsEventHandler.send(
SendWithSwapAnalyticEvents.SendWithSwapError(
errorScreen = SendWithSwapAnalyticEvents.ErrorScreen.Confirm,
message = errorMessage,
),
)
}
val currentKeys = events.map { it.event to it.params }.toSet()
val newEvents = events.filter { it.event to it.params !in lastSentErrorKeys }
lastSentErrorKeys = currentKeys
newEvents.forEach(analyticsEventHandler::send)
}
}

View file

@ -4,7 +4,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_FROM
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_TO
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_DESCRIPTION
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER
import com.tangem.core.analytics.models.AnalyticsParam.Key.RATE_TYPE
@ -120,19 +120,51 @@ internal sealed class SendWithSwapAnalyticEvents(
params = emptyMap(),
)
data class SendWithSwapError(
val errorScreen: ErrorScreen,
val message: String,
data class ErrorInsufficientBalance(
val fromToken: CryptoCurrency,
) : SendWithSwapAnalyticEvents(
event = when (errorScreen) {
ErrorScreen.Amount -> "Send With Swap Amount Screen Error"
ErrorScreen.Confirm -> "Send With Swap Confirm Screen Error"
},
event = "Error - Insufficient balance",
params = mapOf(
ERROR_MESSAGE to message,
SEND_TOKEN to fromToken.symbol,
SEND_BLOCKCHAIN to fromToken.network.name,
),
)
data class ErrorMinAmount(
val fromToken: CryptoCurrency,
) : SendWithSwapAnalyticEvents(
event = "Error - Min amount",
params = mapOf(
SEND_TOKEN to fromToken.symbol,
SEND_BLOCKCHAIN to fromToken.network.name,
),
)
data class ErrorMaxAmount(
val fromToken: CryptoCurrency,
) : SendWithSwapAnalyticEvents(
event = "Error - Max amount",
params = mapOf(
SEND_TOKEN to fromToken.symbol,
SEND_BLOCKCHAIN to fromToken.network.name,
),
)
data class ErrorExpressQuote(
val fromToken: CryptoCurrency,
val toToken: CryptoCurrency,
val errorDescription: String? = null,
) : SendWithSwapAnalyticEvents(
event = "Error - Express quote",
params = buildMap {
put(SEND_TOKEN, fromToken.symbol)
put(SEND_BLOCKCHAIN, fromToken.network.name)
put(RECEIVE_TOKEN, toToken.symbol)
put(RECEIVE_BLOCKCHAIN, toToken.network.name)
if (errorDescription != null) put(ERROR_DESCRIPTION, errorDescription)
},
)
class HighPriceImpact(
val sendToken: String,
val receiveToken: String,
@ -168,11 +200,6 @@ internal sealed class SendWithSwapAnalyticEvents(
),
)
enum class ErrorScreen {
Amount,
Confirm,
}
enum class RateType {
Float,
Fixed,

View file

@ -1,11 +0,0 @@
package com.tangem.features.swap.v2.impl.sendviaswap.analytics
internal object SendWithSwapAnalyticsErrorMessages {
const val INSUFFICIENT_BALANCE = "Error - Insufficient balance"
const val MIN_AMOUNT = "Error - Min amount"
const val MAX_AMOUNT = "Error - Max amount"
const val EXPRESS_QUOTE_NO_PROVIDERS = "Error - Express quote no providers found"
const val EXPRESS_QUOTE = "Error - Express quote"
const val DESTINATION_TAG_REQUIRED = "Error - Destination tag required"
const val INVALID_CRYPTOCURRENCIES_STATUS = "Error - Invalid cryptocurrencies status"
}

View file

@ -322,12 +322,17 @@ internal class SendWithSwapConfirmModel @Inject constructor(
isAmountSubtractAvailable = isAmountSubtractAvailable,
onExpressError = { expressError ->
uiState.transformerUpdate(SendWithSwapConfirmSendingStateTransformer(false))
analyticsEventHandler.send(
SendWithSwapAnalyticEvents.SendWithSwapError(
errorScreen = SendWithSwapAnalyticEvents.ErrorScreen.Confirm,
message = "Express error: $expressError",
),
)
val fromCurrency = confirmData.fromCryptoCurrencyStatus?.currency
val toCurrency = confirmData.toCryptoCurrencyStatus?.currency
if (fromCurrency != null && toCurrency != null) {
analyticsEventHandler.send(
SendWithSwapAnalyticEvents.ErrorExpressQuote(
fromToken = fromCurrency,
toToken = toCurrency,
errorDescription = "code=${expressError.code}",
),
)
}
swapAlertFactory.getGenericErrorState(
expressError = expressError,
onFailedTxEmailClick = {
@ -345,12 +350,6 @@ internal class SendWithSwapConfirmModel @Inject constructor(
},
onSendError = { error ->
uiState.transformerUpdate(SendWithSwapConfirmSendingStateTransformer(false))
analyticsEventHandler.send(
SendWithSwapAnalyticEvents.SendWithSwapError(
errorScreen = SendWithSwapAnalyticEvents.ErrorScreen.Confirm,
message = "Send error: ${error?.toString().orEmpty()}",
),
)
swapAlertFactory.getSendTransactionErrorState(
error = error,
onFailedTxEmailClick = { _ ->

View file

@ -6,9 +6,10 @@ import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticsErrorMessages
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
@ -21,6 +22,21 @@ class SwapAmountAnalyticsSenderTest {
private val analyticsEventHandler = mockk<AnalyticsEventHandler>(relaxed = true)
private val sender = SwapAmountAnalyticsSender(analyticsEventHandler)
private val fromNetwork = mockk<Network>(relaxed = true).also {
every { it.name } returns "Ethereum"
}
private val toNetwork = mockk<Network>(relaxed = true).also {
every { it.name } returns "Bitcoin"
}
private val fromToken = mockk<CryptoCurrency>(relaxed = true).also {
every { it.symbol } returns "ETH"
every { it.network } returns fromNetwork
}
private val toToken = mockk<CryptoCurrency>(relaxed = true).also {
every { it.symbol } returns "BTC"
every { it.network } returns toNetwork
}
private val testProvider = ExpressProvider(
providerId = "test",
name = "Test Provider",
@ -31,21 +47,41 @@ class SwapAmountAnalyticsSenderTest {
slippage = null,
)
private fun send(
quotes: List<SwapQuoteUM> = emptyList(),
selectedQuote: SwapQuoteUM? = null,
hasInsufficientBalance: Boolean = false,
) = sender.sendErrorIfNeeded(
quotes = quotes,
selectedQuote = selectedQuote,
fromToken = fromToken,
toToken = toToken,
hasInsufficientBalance = hasInsufficientBalance,
)
@Test
fun `GIVEN empty quotes WHEN sendErrorIfNeeded THEN send no providers error`() {
val eventSlot = slot<AnalyticsEvent>()
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
fun `GIVEN empty quotes WHEN sendErrorIfNeeded THEN do not send analytics`() {
send(quotes = emptyList(), selectedQuote = null)
sender.sendErrorIfNeeded(quotes = emptyList(), selectedQuote = null)
verify(exactly = 1) { analyticsEventHandler.send(any()) }
val event = eventSlot.captured as SendWithSwapAnalyticEvents.SendWithSwapError
assertThat(event.errorScreen).isEqualTo(SendWithSwapAnalyticEvents.ErrorScreen.Amount)
assertThat(event.message).isEqualTo(SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE_NO_PROVIDERS)
verify(exactly = 0) { analyticsEventHandler.send(any()) }
}
@Test
fun `GIVEN too small error quote WHEN sendErrorIfNeeded THEN send min amount error`() {
fun `GIVEN insufficient balance WHEN sendErrorIfNeeded THEN send ErrorInsufficientBalance with from token params`() {
val eventSlot = slot<AnalyticsEvent>()
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
send(hasInsufficientBalance = true)
verify(exactly = 1) { analyticsEventHandler.send(any()) }
val event = eventSlot.captured
assertThat(event).isInstanceOf(SendWithSwapAnalyticEvents.ErrorInsufficientBalance::class.java)
assertThat(event.event).isEqualTo("Error - Insufficient balance")
assertThat(event.params).containsExactly("Send Token", "ETH", "Send Blockchain", "Ethereum")
}
@Test
fun `GIVEN insufficient balance and express error WHEN sendErrorIfNeeded THEN insufficient balance takes priority`() {
val errorQuote = SwapQuoteUM.Error(
provider = testProvider,
expressError = ExpressError.AmountError.TooSmallError(code = 1001, amount = BigDecimal("0.01")),
@ -53,15 +89,32 @@ class SwapAmountAnalyticsSenderTest {
val eventSlot = slot<AnalyticsEvent>()
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
send(quotes = listOf(errorQuote), selectedQuote = errorQuote, hasInsufficientBalance = true)
verify(exactly = 1) { analyticsEventHandler.send(any()) }
val event = eventSlot.captured as SendWithSwapAnalyticEvents.SendWithSwapError
assertThat(event.message).isEqualTo(SendWithSwapAnalyticsErrorMessages.MIN_AMOUNT)
assertThat(eventSlot.captured).isInstanceOf(SendWithSwapAnalyticEvents.ErrorInsufficientBalance::class.java)
}
@Test
fun `GIVEN too big error quote WHEN sendErrorIfNeeded THEN send max amount error`() {
fun `GIVEN too small error quote WHEN sendErrorIfNeeded THEN send ErrorMinAmount with from token params`() {
val errorQuote = SwapQuoteUM.Error(
provider = testProvider,
expressError = ExpressError.AmountError.TooSmallError(code = 1001, amount = BigDecimal("0.01")),
)
val eventSlot = slot<AnalyticsEvent>()
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
send(quotes = listOf(errorQuote), selectedQuote = errorQuote)
verify(exactly = 1) { analyticsEventHandler.send(any()) }
val event = eventSlot.captured
assertThat(event).isInstanceOf(SendWithSwapAnalyticEvents.ErrorMinAmount::class.java)
assertThat(event.event).isEqualTo("Error - Min amount")
assertThat(event.params).containsExactly("Send Token", "ETH", "Send Blockchain", "Ethereum")
}
@Test
fun `GIVEN too big error quote WHEN sendErrorIfNeeded THEN send ErrorMaxAmount with from token params`() {
val errorQuote = SwapQuoteUM.Error(
provider = testProvider,
expressError = ExpressError.AmountError.TooBigError(code = 1002, amount = BigDecimal("1000")),
@ -69,15 +122,17 @@ class SwapAmountAnalyticsSenderTest {
val eventSlot = slot<AnalyticsEvent>()
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
send(quotes = listOf(errorQuote), selectedQuote = errorQuote)
verify(exactly = 1) { analyticsEventHandler.send(any()) }
val event = eventSlot.captured as SendWithSwapAnalyticEvents.SendWithSwapError
assertThat(event.message).isEqualTo(SendWithSwapAnalyticsErrorMessages.MAX_AMOUNT)
val event = eventSlot.captured
assertThat(event).isInstanceOf(SendWithSwapAnalyticEvents.ErrorMaxAmount::class.java)
assertThat(event.event).isEqualTo("Error - Max amount")
assertThat(event.params).containsExactly("Send Token", "ETH", "Send Blockchain", "Ethereum")
}
@Test
fun `GIVEN unknown express error WHEN sendErrorIfNeeded THEN send express quote error with code`() {
fun `GIVEN unknown express error WHEN sendErrorIfNeeded THEN send ErrorExpressQuote with both tokens and code`() {
val errorQuote = SwapQuoteUM.Error(
provider = testProvider,
expressError = ExpressError.InternalError(code = 500),
@ -85,18 +140,26 @@ class SwapAmountAnalyticsSenderTest {
val eventSlot = slot<AnalyticsEvent>()
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
send(quotes = listOf(errorQuote), selectedQuote = errorQuote)
verify(exactly = 1) { analyticsEventHandler.send(any()) }
val event = eventSlot.captured as SendWithSwapAnalyticEvents.SendWithSwapError
assertThat(event.message).isEqualTo("${SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE}: code=500")
val event = eventSlot.captured
assertThat(event).isInstanceOf(SendWithSwapAnalyticEvents.ErrorExpressQuote::class.java)
assertThat(event.event).isEqualTo("Error - Express quote")
assertThat(event.params).containsExactly(
"Send Token", "ETH",
"Send Blockchain", "Ethereum",
"Receive Token", "BTC",
"Receive Blockchain", "Bitcoin",
"Error Description", "code=500",
)
}
@Test
fun `GIVEN content quote WHEN sendErrorIfNeeded THEN do not send analytics`() {
val contentQuote = mockk<SwapQuoteUM.Content>()
sender.sendErrorIfNeeded(quotes = listOf(contentQuote), selectedQuote = contentQuote)
send(quotes = listOf(contentQuote), selectedQuote = contentQuote)
verify(exactly = 0) { analyticsEventHandler.send(any()) }
}
@ -108,8 +171,16 @@ class SwapAmountAnalyticsSenderTest {
expressError = ExpressError.AmountError.TooSmallError(code = 1001, amount = BigDecimal("0.01")),
)
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
send(quotes = listOf(errorQuote), selectedQuote = errorQuote)
send(quotes = listOf(errorQuote), selectedQuote = errorQuote)
verify(exactly = 1) { analyticsEventHandler.send(any()) }
}
@Test
fun `GIVEN insufficient balance twice WHEN sendErrorIfNeeded THEN send analytics only once`() {
send(hasInsufficientBalance = true)
send(hasInsufficientBalance = true)
verify(exactly = 1) { analyticsEventHandler.send(any()) }
}
@ -125,8 +196,21 @@ class SwapAmountAnalyticsSenderTest {
expressError = ExpressError.AmountError.TooBigError(code = 1002, amount = BigDecimal("1000")),
)
sender.sendErrorIfNeeded(quotes = listOf(smallError), selectedQuote = smallError)
sender.sendErrorIfNeeded(quotes = listOf(bigError), selectedQuote = bigError)
send(quotes = listOf(smallError), selectedQuote = smallError)
send(quotes = listOf(bigError), selectedQuote = bigError)
verify(exactly = 2) { analyticsEventHandler.send(any()) }
}
@Test
fun `GIVEN express error then insufficient balance WHEN sendErrorIfNeeded THEN send analytics twice`() {
val smallError = SwapQuoteUM.Error(
provider = testProvider,
expressError = ExpressError.AmountError.TooSmallError(code = 1001, amount = BigDecimal("0.01")),
)
send(quotes = listOf(smallError), selectedQuote = smallError)
send(quotes = listOf(smallError), selectedQuote = smallError, hasInsufficientBalance = true)
verify(exactly = 2) { analyticsEventHandler.send(any()) }
}
@ -139,9 +223,9 @@ class SwapAmountAnalyticsSenderTest {
)
val contentQuote = mockk<SwapQuoteUM.Content>()
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
sender.sendErrorIfNeeded(quotes = listOf(contentQuote), selectedQuote = contentQuote)
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
send(quotes = listOf(errorQuote), selectedQuote = errorQuote)
send(quotes = listOf(contentQuote), selectedQuote = contentQuote)
send(quotes = listOf(errorQuote), selectedQuote = errorQuote)
verify(exactly = 2) { analyticsEventHandler.send(any()) }
}