Updated on 2026-08-14
This commit is contained in:
parent
b5f0c9e097
commit
14c007d93a
4 changed files with 190 additions and 34 deletions
|
|
@ -14,29 +14,45 @@ tasks.withType<Test>().configureEach {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// region Project - Core
|
||||||
implementation(projects.core.datasource)
|
implementation(projects.core.datasource)
|
||||||
implementation(projects.core.utils)
|
api(projects.core.utils)
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Project - Data
|
||||||
implementation(projects.data.common)
|
implementation(projects.data.common)
|
||||||
implementation(projects.data.tokens)
|
// endregion
|
||||||
|
|
||||||
implementation(projects.domain.appCurrency.models)
|
// region Project - Domain
|
||||||
implementation(projects.domain.models)
|
api(projects.domain.models)
|
||||||
implementation(projects.domain.tokens.models)
|
api(projects.domain.quotes)
|
||||||
implementation(projects.domain.quotes)
|
// endregion
|
||||||
implementation(projects.domain.wallets.models)
|
|
||||||
|
|
||||||
|
// region Project - Libs
|
||||||
|
implementation(projects.libs.blockchainSdk)
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region DI
|
||||||
|
implementation(deps.hilt.android)
|
||||||
|
kapt(deps.hilt.kapt)
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Tangem SDKs
|
||||||
|
implementation(tangemDeps.blockchain)
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Other libraries
|
||||||
implementation(deps.androidx.datastore)
|
implementation(deps.androidx.datastore)
|
||||||
implementation(deps.moshi.kotlin)
|
implementation(deps.moshi.kotlin)
|
||||||
implementation(deps.timber)
|
implementation(deps.timber)
|
||||||
|
// endregion
|
||||||
|
|
||||||
implementation(deps.hilt.android)
|
// region Tests
|
||||||
kapt(deps.hilt.kapt)
|
|
||||||
|
|
||||||
testImplementation(deps.test.coroutine)
|
testImplementation(deps.test.coroutine)
|
||||||
testImplementation(deps.test.junit5)
|
testImplementation(deps.test.junit5)
|
||||||
testRuntimeOnly(deps.test.junit5.engine)
|
testRuntimeOnly(deps.test.junit5.engine)
|
||||||
testImplementation(deps.test.mockk)
|
testImplementation(deps.test.mockk)
|
||||||
testImplementation(deps.test.truth)
|
testImplementation(deps.test.truth)
|
||||||
testImplementation(projects.common.test)
|
testImplementation(projects.common.test)
|
||||||
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import com.tangem.data.common.api.safeApiCallWithTimeout
|
||||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||||
import com.tangem.data.quotes.store.setSourceAsCache
|
import com.tangem.data.quotes.store.setSourceAsCache
|
||||||
import com.tangem.data.quotes.store.setSourceAsOnlyCache
|
import com.tangem.data.quotes.store.setSourceAsOnlyCache
|
||||||
import com.tangem.data.tokens.utils.QuotesUnsupportedCurrenciesIdAdapter
|
import com.tangem.data.quotes.utils.QuotesUnsupportedCurrenciesIdAdapter
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
|
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
|
||||||
import com.tangem.domain.core.utils.catchOn
|
import com.tangem.domain.core.utils.catchOn
|
||||||
|
|
@ -35,8 +35,6 @@ internal class DefaultMultiQuoteFetcher @Inject constructor(
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : MultiQuoteFetcher {
|
) : MultiQuoteFetcher {
|
||||||
|
|
||||||
private val quotesUnsupportedCurrenciesAdapter = QuotesUnsupportedCurrenciesIdAdapter()
|
|
||||||
|
|
||||||
override suspend fun invoke(params: MultiQuoteFetcher.Params) = Either.catchOn(dispatchers.default) {
|
override suspend fun invoke(params: MultiQuoteFetcher.Params) = Either.catchOn(dispatchers.default) {
|
||||||
if (params.currenciesIds.isEmpty()) {
|
if (params.currenciesIds.isEmpty()) {
|
||||||
Timber.d("No currencies to fetch quotes for")
|
Timber.d("No currencies to fetch quotes for")
|
||||||
|
|
@ -45,7 +43,7 @@ internal class DefaultMultiQuoteFetcher @Inject constructor(
|
||||||
|
|
||||||
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
|
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
|
||||||
|
|
||||||
val replacementIdsResult = quotesUnsupportedCurrenciesAdapter.replaceUnsupportedCurrencies(
|
val replacementIdsResult = QuotesUnsupportedCurrenciesIdAdapter.replaceUnsupportedCurrencies(
|
||||||
currenciesIds = params.currenciesIds.mapTo(
|
currenciesIds = params.currenciesIds.mapTo(
|
||||||
destination = hashSetOf(),
|
destination = hashSetOf(),
|
||||||
transform = CryptoCurrency.RawID::value,
|
transform = CryptoCurrency.RawID::value,
|
||||||
|
|
@ -64,7 +62,7 @@ internal class DefaultMultiQuoteFetcher @Inject constructor(
|
||||||
onError = { error -> throw error },
|
onError = { error -> throw error },
|
||||||
)
|
)
|
||||||
|
|
||||||
val updatedResponse = quotesUnsupportedCurrenciesAdapter.getResponseWithUnsupportedCurrencies(
|
val updatedResponse = QuotesUnsupportedCurrenciesIdAdapter.getResponseWithUnsupportedCurrencies(
|
||||||
response = response,
|
response = response,
|
||||||
filteredIds = replacementIdsResult.idsFiltered,
|
filteredIds = replacementIdsResult.idsFiltered,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,27 @@
|
||||||
package com.tangem.data.tokens.utils
|
package com.tangem.data.quotes.utils
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchainsdk.compatibility.l2BlockchainsCoinIds
|
import com.tangem.blockchainsdk.compatibility.l2BlockchainsCoinIds
|
||||||
import com.tangem.blockchainsdk.utils.toCoinId
|
import com.tangem.blockchainsdk.utils.toCoinId
|
||||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||||
|
|
||||||
/**
|
/** Adapter to replace unsupported currencies for quotes request if it necessary */
|
||||||
* Adapter to replace unsupported currencies for quotes request if it necessary
|
internal object QuotesUnsupportedCurrenciesIdAdapter {
|
||||||
*/
|
|
||||||
class QuotesUnsupportedCurrenciesIdAdapter {
|
|
||||||
|
|
||||||
/**
|
/** Ethereum coin ID */
|
||||||
* Replaces unsupported currencies id to it replacements for request
|
private val ethCoinId = Blockchain.Ethereum.toCoinId()
|
||||||
*/
|
|
||||||
|
/** Map that contains unsupported currencies and their replacement for request */
|
||||||
|
private val UNSUPPORTED_IDS_WITH_REPLACEMENTS = l2BlockchainsCoinIds.associateWith { ethCoinId }
|
||||||
|
|
||||||
|
/** Replaces unsupported currencies id [currenciesIds] to it replacements for request */
|
||||||
fun replaceUnsupportedCurrencies(currenciesIds: Set<String>): ReplacementResult {
|
fun replaceUnsupportedCurrencies(currenciesIds: Set<String>): ReplacementResult {
|
||||||
val idsForRequest = mutableSetOf<String>()
|
val idsForRequest = mutableSetOf<String>()
|
||||||
val idsFiltered = mutableSetOf<String>()
|
val idsFiltered = mutableSetOf<String>()
|
||||||
|
|
||||||
currenciesIds.forEach { currencyId ->
|
currenciesIds.forEach { currencyId ->
|
||||||
val replacementId = UNSUPPORTED_IDS_WITH_REPLACEMENTS[currencyId]
|
val replacementId = UNSUPPORTED_IDS_WITH_REPLACEMENTS[currencyId]
|
||||||
|
|
||||||
if (replacementId != null) {
|
if (replacementId != null) {
|
||||||
idsFiltered.add(currencyId)
|
idsFiltered.add(currencyId)
|
||||||
idsForRequest.add(replacementId)
|
idsForRequest.add(replacementId)
|
||||||
|
|
@ -25,16 +29,17 @@ class QuotesUnsupportedCurrenciesIdAdapter {
|
||||||
idsForRequest.add(currencyId)
|
idsForRequest.add(currencyId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ReplacementResult(idsForRequest = idsForRequest, idsFiltered = idsFiltered)
|
return ReplacementResult(idsForRequest = idsForRequest, idsFiltered = idsFiltered)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Recover previously replaced currencies [filteredIds] to [response] that uses in application */
|
||||||
* Recover previously replaced currencies to response that uses in application
|
|
||||||
*/
|
|
||||||
fun getResponseWithUnsupportedCurrencies(response: QuotesResponse, filteredIds: Set<String>): QuotesResponse {
|
fun getResponseWithUnsupportedCurrencies(response: QuotesResponse, filteredIds: Set<String>): QuotesResponse {
|
||||||
val updatedQuotes = mutableMapOf<String, QuotesResponse.Quote>()
|
val updatedQuotes = mutableMapOf<String, QuotesResponse.Quote>()
|
||||||
|
|
||||||
filteredIds.forEach { filteredId ->
|
filteredIds.forEach { filteredId ->
|
||||||
val replacementId = UNSUPPORTED_IDS_WITH_REPLACEMENTS[filteredId]
|
val replacementId = UNSUPPORTED_IDS_WITH_REPLACEMENTS[filteredId]
|
||||||
|
|
||||||
if (replacementId != null) {
|
if (replacementId != null) {
|
||||||
val quoteReplacement = response.quotes[replacementId]
|
val quoteReplacement = response.quotes[replacementId]
|
||||||
if (quoteReplacement != null) {
|
if (quoteReplacement != null) {
|
||||||
|
|
@ -42,16 +47,9 @@ class QuotesUnsupportedCurrenciesIdAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.copy(quotes = updatedQuotes + response.quotes)
|
return response.copy(quotes = updatedQuotes + response.quotes)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ReplacementResult(val idsForRequest: Set<String>, val idsFiltered: Set<String>)
|
data class ReplacementResult(val idsForRequest: Set<String>, val idsFiltered: Set<String>)
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Map that contains unsupported currencies and their replacement for request
|
|
||||||
*/
|
|
||||||
private val ethCoinId = Blockchain.Ethereum.toCoinId()
|
|
||||||
private val UNSUPPORTED_IDS_WITH_REPLACEMENTS = l2BlockchainsCoinIds.associateWith { ethCoinId }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
package com.tangem.data.quotes.utils
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.blockchainsdk.compatibility.l2BlockchainsCoinIds
|
||||||
|
import com.tangem.blockchainsdk.utils.toCoinId
|
||||||
|
import com.tangem.common.test.data.quote.MockQuoteResponseFactory
|
||||||
|
import com.tangem.common.test.utils.ProvideTestModels
|
||||||
|
import com.tangem.data.quotes.utils.QuotesUnsupportedCurrenciesIdAdapter.ReplacementResult
|
||||||
|
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||||
|
import com.tangem.datasource.api.tangemTech.models.QuotesResponse.Quote
|
||||||
|
import org.junit.jupiter.api.Nested
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
internal class QuotesUnsupportedCurrenciesIdAdapterTest {
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
inner class ReplaceUnsupportedCurrencies {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ProvideTestModels
|
||||||
|
fun replaceUnsupportedCurrencies(model: ReplaceUnsupportedCurrenciesModel) {
|
||||||
|
// Act
|
||||||
|
val actual = QuotesUnsupportedCurrenciesIdAdapter.replaceUnsupportedCurrencies(
|
||||||
|
currenciesIds = model.currenciesIds,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val expected = model.expected
|
||||||
|
Truth.assertThat(actual).isEqualTo(expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun provideTestModels() = listOf(
|
||||||
|
ReplaceUnsupportedCurrenciesModel(
|
||||||
|
currenciesIds = l2BlockchainsCoinIds.toSet(),
|
||||||
|
expected = ReplacementResult(
|
||||||
|
idsForRequest = l2BlockchainsCoinIds.mapTo(hashSetOf()) {
|
||||||
|
Blockchain.Ethereum.toCoinId()
|
||||||
|
},
|
||||||
|
idsFiltered = l2BlockchainsCoinIds.toSet(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ReplaceUnsupportedCurrenciesModel(
|
||||||
|
currenciesIds = notL2BlockchainsCoinIds,
|
||||||
|
expected = ReplacementResult(idsForRequest = notL2BlockchainsCoinIds, idsFiltered = emptySet()),
|
||||||
|
),
|
||||||
|
ReplaceUnsupportedCurrenciesModel(
|
||||||
|
currenciesIds = blockchainCoinIds,
|
||||||
|
expected = ReplacementResult(
|
||||||
|
idsForRequest = Blockchain.entries
|
||||||
|
.filterNot(Blockchain::isTestnet)
|
||||||
|
.mapTo(hashSetOf()) {
|
||||||
|
if (it.isL2EthereumNetwork()) {
|
||||||
|
Blockchain.Ethereum.toCoinId()
|
||||||
|
} else {
|
||||||
|
it.toCoinId()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
idsFiltered = l2BlockchainsCoinIds.toSet(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ReplaceUnsupportedCurrenciesModel(
|
||||||
|
val currenciesIds: Set<String>,
|
||||||
|
val expected: ReplacementResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
inner class GetResponseWithUnsupportedCurrencies {
|
||||||
|
|
||||||
|
private val zeroQuote = MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ZERO)
|
||||||
|
private val ethQuote = "ethereum" to zeroQuote
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ProvideTestModels
|
||||||
|
fun getResponseWithUnsupportedCurrencies(model: GetResponseWithUnsupportedCurrenciesModel) {
|
||||||
|
// Act
|
||||||
|
val actual = QuotesUnsupportedCurrenciesIdAdapter.getResponseWithUnsupportedCurrencies(
|
||||||
|
response = model.response,
|
||||||
|
filteredIds = model.filteredIds,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val expected = model.expected
|
||||||
|
Truth.assertThat(actual).isEqualTo(expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun provideTestModels() = listOf(
|
||||||
|
GetResponseWithUnsupportedCurrenciesModel(
|
||||||
|
response = QuotesResponse(quotes = emptyMap()),
|
||||||
|
filteredIds = emptySet(),
|
||||||
|
expected = QuotesResponse(quotes = emptyMap()),
|
||||||
|
),
|
||||||
|
GetResponseWithUnsupportedCurrenciesModel(
|
||||||
|
response = createQuotesResponse(ethQuote),
|
||||||
|
filteredIds = emptySet(),
|
||||||
|
expected = createQuotesResponse(ethQuote),
|
||||||
|
),
|
||||||
|
GetResponseWithUnsupportedCurrenciesModel(
|
||||||
|
response = createQuotesResponse(
|
||||||
|
"arbitrum-one" to MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE),
|
||||||
|
ethQuote,
|
||||||
|
),
|
||||||
|
filteredIds = blockchainCoinIds,
|
||||||
|
expected = createQuotesResponse(
|
||||||
|
*l2BlockchainsCoinIds.map { it to zeroQuote }.toTypedArray(),
|
||||||
|
"arbitrum-one" to MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE),
|
||||||
|
ethQuote,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createQuotesResponse(vararg quotes: Pair<String, Quote>): QuotesResponse {
|
||||||
|
return QuotesResponse(quotes = quotes.toMap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GetResponseWithUnsupportedCurrenciesModel(
|
||||||
|
val response: QuotesResponse,
|
||||||
|
val filteredIds: Set<String>,
|
||||||
|
val expected: QuotesResponse,
|
||||||
|
)
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
|
||||||
|
val blockchainCoinIds = Blockchain.entries
|
||||||
|
.filterNot(Blockchain::isTestnet)
|
||||||
|
.mapTo(destination = hashSetOf(), transform = Blockchain::toCoinId)
|
||||||
|
|
||||||
|
val notL2BlockchainsCoinIds = Blockchain.entries
|
||||||
|
.filterNot { it.isTestnet() || it.isL2EthereumNetwork() }
|
||||||
|
.mapTo(hashSetOf()) { it.toCoinId() }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue