Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-06 13:08:08 +04:00
parent b2499c1f35
commit c8c4f67018
8 changed files with 269 additions and 1 deletions

View file

@ -13,12 +13,17 @@ dependencies {
implementation(projects.core.datasource)
implementation(projects.core.utils)
implementation(projects.data.common)
implementation(projects.data.tokens)
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.quotes)
implementation(projects.domain.wallets.models)
implementation(deps.androidx.datastore)
implementation(deps.timber)
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)

View file

@ -0,0 +1,62 @@
package com.tangem.data.quotes.multi
import arrow.core.Either
import com.tangem.data.common.api.safeApiCallWithTimeout
import com.tangem.data.quotes.store.QuotesStoreV2
import com.tangem.data.tokens.utils.QuotesUnsupportedCurrenciesIdAdapter
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
import com.tangem.domain.tokens.model.CryptoCurrency
import timber.log.Timber
/**
* Default implementation of [MultiQuoteFetcher]
*
* @property tangemTechApi tangemTech api
* @property appCurrencyResponseStore app currency response store
* @property quotesStore quotes store
*
[REDACTED_AUTHOR]
*/
internal class DefaultMultiQuoteFetcher(
private val tangemTechApi: TangemTechApi,
private val appCurrencyResponseStore: AppCurrencyResponseStore,
private val quotesStore: QuotesStoreV2,
) : MultiQuoteFetcher {
private val quotesUnsupportedCurrenciesAdapter = QuotesUnsupportedCurrenciesIdAdapter()
override suspend fun invoke(params: MultiQuoteFetcher.Params): Either<Throwable, Unit> = Either.catch {
quotesStore.refresh(currenciesIds = params.currenciesIds)
val replacementIdsResult = quotesUnsupportedCurrenciesAdapter.replaceUnsupportedCurrencies(
currenciesIds = params.currenciesIds.mapTo(
destination = hashSetOf(),
transform = CryptoCurrency.RawID::value,
),
)
val appCurrency = appCurrencyResponseStore.getSyncOrNull()
?: error(message = "Unable to get AppCurrency for updating quotes")
safeApiCallWithTimeout(
call = {
val coinIds = replacementIdsResult.idsForRequest.joinToString(separator = ",")
val response = tangemTechApi.getQuotes(currencyId = appCurrency.id, coinIds = coinIds).bind()
val updatedResponse = quotesUnsupportedCurrenciesAdapter.getResponseWithUnsupportedCurrencies(
response = response,
filteredIds = replacementIdsResult.idsFiltered,
)
quotesStore.storeActual(values = updatedResponse.quotes)
},
onError = { error -> throw error },
)
}
.onLeft {
Timber.e(it)
quotesStore.storeError(currenciesIds = params.currenciesIds)
}
}

View file

@ -0,0 +1,143 @@
package com.tangem.data.quotes.multi
import com.google.common.truth.Truth
import com.tangem.common.test.data.quote.MockQuoteResponseFactory
import com.tangem.data.quotes.store.QuotesStoreV2
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
import com.tangem.domain.tokens.model.CryptoCurrency
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifyOrder
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Test
import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
internal class DefaultMultiQuoteFetcherTest {
private val tangemTechApi = mockk<TangemTechApi>(relaxed = true)
private val appCurrencyResponseStore = mockk<AppCurrencyResponseStore>(relaxed = true)
private val quotesStore = mockk<QuotesStoreV2>(relaxed = true)
private val fetcher = DefaultMultiQuoteFetcher(
tangemTechApi = tangemTechApi,
appCurrencyResponseStore = appCurrencyResponseStore,
quotesStore = quotesStore,
)
@Test
fun `fetch quotes successfully`() = runTest {
val params = MultiQuoteFetcher.Params(currenciesIds = currenciesIds)
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency
val coinIds = "BTC,ETH"
coEvery {
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
} returns ApiResponse.Success(successResponse)
val actual = fetcher(params)
coVerifyOrder {
quotesStore.refresh(currenciesIds = params.currenciesIds)
appCurrencyResponseStore.getSyncOrNull()
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
quotesStore.storeActual(values = successResponse.quotes)
}
coVerify(inverse = true) {
quotesStore.storeError(currenciesIds = any())
}
Truth.assertThat(actual.isRight()).isTrue()
}
@Test
fun `fetch quotes failure because api request failed`() = runTest {
val params = MultiQuoteFetcher.Params(currenciesIds = currenciesIds)
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency
val coinIds = "BTC,ETH"
@Suppress("UNCHECKED_CAST")
val errorResponse = ApiResponse.Error(ApiResponseError.NetworkException) as ApiResponse<QuotesResponse>
coEvery { tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds) } returns errorResponse
val actual = fetcher(params)
coVerifyOrder {
quotesStore.refresh(currenciesIds = params.currenciesIds)
appCurrencyResponseStore.getSyncOrNull()
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
quotesStore.storeError(currenciesIds = params.currenciesIds)
}
coVerify(inverse = true) {
quotesStore.storeActual(values = any())
}
Truth.assertThat(actual.isLeft()).isTrue()
}
@Test
fun `fetch quotes failure because app currency not found`() = runTest {
val params = MultiQuoteFetcher.Params(currenciesIds = currenciesIds)
coEvery { appCurrencyResponseStore.getSyncOrNull() } returns null
val actual = fetcher(params)
coVerifyOrder {
quotesStore.refresh(currenciesIds = params.currenciesIds)
appCurrencyResponseStore.getSyncOrNull()
quotesStore.storeError(currenciesIds = params.currenciesIds)
}
coVerify(inverse = true) {
tangemTechApi.getQuotes(currencyId = any(), coinIds = any())
quotesStore.storeActual(values = any())
}
Truth.assertThat(actual.isLeft()).isTrue()
}
private companion object {
val currenciesIds = setOf(
CryptoCurrency.RawID(value = "BTC"),
CryptoCurrency.RawID(value = "ETH"),
)
val usdAppCurrency = CurrenciesResponse.Currency(
id = "USD".lowercase(),
code = "USD",
name = "US Dollar",
unit = "$",
type = "fiat",
rateBTC = "",
)
val successResponse = QuotesResponse(
quotes = mapOf(
"BTC" to MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE),
"ETH" to MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.TEN),
),
)
}
}

View file

@ -8,7 +8,7 @@ import com.tangem.datasource.api.tangemTech.models.QuotesResponse
/**
* Adapter to replace unsupported currencies for quotes request if it necessary
*/
internal class QuotesUnsupportedCurrenciesIdAdapter {
class QuotesUnsupportedCurrenciesIdAdapter {
/**
* Replaces unsupported currencies id to it replacements for request