Updated on 2026-08-14
This commit is contained in:
parent
9ee1319e40
commit
c6b2e89890
20 changed files with 142 additions and 4 deletions
|
|
@ -14,6 +14,7 @@ object MockQuoteResponseFactory {
|
|||
priceChange24h = value,
|
||||
priceChange1w = value,
|
||||
priceChange30d = value,
|
||||
priceUsd = value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource =
|
|||
source = source,
|
||||
fiatRate = price.orZero(),
|
||||
priceChange = priceChange24h.orZero().movePointLeft(2),
|
||||
fiatRateUSD = priceUsd.orZero(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ data class QuotesResponse(
|
|||
val priceChange1w: BigDecimal?,
|
||||
@Json(name = "priceChange30d")
|
||||
val priceChange30d: BigDecimal?,
|
||||
@Json(name = "priceUsd")
|
||||
val priceUsd: BigDecimal?,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
|
@ -29,6 +31,7 @@ data class QuotesResponse(
|
|||
priceChange24h = null,
|
||||
priceChange1w = null,
|
||||
priceChange30d = null,
|
||||
priceUsd = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,8 +43,9 @@ interface QuotesFetcher {
|
|||
PRICE_CHANGE_24H(value = "priceChange24h"),
|
||||
PRICE_CHANGE_1W(value = "priceChange1w"),
|
||||
PRICE_CHANGE_30D(value = "priceChange30d"),
|
||||
PRICE_USD(value = "priceUsd"),
|
||||
ALL_PRICES(
|
||||
value = setOf(PRICE, PRICE_CHANGE_24H, PRICE_CHANGE_1W, PRICE_CHANGE_30D).combine(),
|
||||
value = setOf(PRICE, PRICE_CHANGE_24H, PRICE_CHANGE_1W, PRICE_CHANGE_30D, PRICE_USD).combine(),
|
||||
),
|
||||
LAST_UPDATED_AT(value = "lastUpdatedAt"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ internal class HotCryptoCurrencyConverter(
|
|||
rawCurrencyId = rawCurrencyId,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = fiatRate,
|
||||
fiatRateUSD = BigDecimal.ZERO,
|
||||
priceChange = priceChange.movePointLeft(2),
|
||||
source = StatusSource.ACTUAL, // It doesn't matter
|
||||
),
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ internal class QuoteStatusConverter(
|
|||
source = source,
|
||||
fiatRate = quote.price.orZero(),
|
||||
priceChange = quote.priceChange24h.orZero().movePointLeft(2),
|
||||
fiatRateUSD = quote.priceUsd.orZero(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,11 @@ import com.tangem.datasource.di.NetworkMoshi
|
|||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.domain.quotes.GetCurrencyUSDQuoteUseCase
|
||||
import com.tangem.domain.quotes.QuotesRepository
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteUpdater
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -72,4 +73,10 @@ internal object QuotesDataModule {
|
|||
coroutineScope = coroutineScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideGetCurrencyUSDQuoteUseCase(quotesRepository: QuotesRepository): GetCurrencyUSDQuoteUseCase {
|
||||
return GetCurrencyUSDQuoteUseCase(quotesRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ internal class DefaultMultiQuoteStatusFetcher @Inject constructor(
|
|||
val response = quotesFetcher.fetch(
|
||||
fiatCurrencyId = appCurrencyId,
|
||||
currenciesIds = replacementIdsResult.idsForRequest,
|
||||
fields = setOf(Field.PRICE, Field.PRICE_CHANGE_24H),
|
||||
fields = setOf(Field.PRICE, Field.PRICE_CHANGE_24H, Field.PRICE_USD),
|
||||
)
|
||||
.getOrElse { error("Cause: $it") }
|
||||
|
||||
|
|
|
|||
|
|
@ -31,4 +31,8 @@ internal class DefaultQuotesRepository(
|
|||
?: QuoteStatus(rawCurrencyId = currencyId)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getCurrencyUSDQuote(currencyId: CryptoCurrency.RawID): QuoteStatus? {
|
||||
return getMultiQuoteSyncOrNull(currenciesIds = setOf(currencyId)).firstOrNull()
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,7 @@ internal class QuoteStatusConverterTest {
|
|||
priceChange24h = null,
|
||||
priceChange1w = null,
|
||||
priceChange30d = null,
|
||||
priceUsd = null,
|
||||
),
|
||||
),
|
||||
expected = QuoteStatus(
|
||||
|
|
@ -62,6 +63,7 @@ internal class QuoteStatusConverterTest {
|
|||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ZERO,
|
||||
fiatRateUSD = BigDecimal.ZERO,
|
||||
priceChange = BigDecimal("0.00"),
|
||||
),
|
||||
),
|
||||
|
|
@ -74,6 +76,7 @@ internal class QuoteStatusConverterTest {
|
|||
priceChange24h = null,
|
||||
priceChange1w = BigDecimal.ZERO,
|
||||
priceChange30d = BigDecimal.ZERO,
|
||||
priceUsd = null,
|
||||
),
|
||||
),
|
||||
expected = QuoteStatus(
|
||||
|
|
@ -81,6 +84,7 @@ internal class QuoteStatusConverterTest {
|
|||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ZERO,
|
||||
fiatRateUSD = BigDecimal.ZERO,
|
||||
priceChange = BigDecimal("0.00"),
|
||||
),
|
||||
),
|
||||
|
|
@ -93,6 +97,7 @@ internal class QuoteStatusConverterTest {
|
|||
priceChange24h = BigDecimal.ONE,
|
||||
priceChange1w = null,
|
||||
priceChange30d = null,
|
||||
priceUsd = BigDecimal.ONE
|
||||
),
|
||||
),
|
||||
expected = QuoteStatus(
|
||||
|
|
@ -100,6 +105,7 @@ internal class QuoteStatusConverterTest {
|
|||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
fiatRateUSD = BigDecimal.ONE,
|
||||
priceChange = BigDecimal("0.01"),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -240,6 +240,6 @@ internal class DefaultMultiQuoteStatusFetcherTest {
|
|||
),
|
||||
)
|
||||
|
||||
val fields = setOf(QuotesFetcher.Field.PRICE, QuotesFetcher.Field.PRICE_CHANGE_24H)
|
||||
val fields = setOf(QuotesFetcher.Field.PRICE, QuotesFetcher.Field.PRICE_CHANGE_24H, QuotesFetcher.Field.PRICE_USD)
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ internal class DefaultQuotesRepositoryTest {
|
|||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ZERO,
|
||||
fiatRateUSD = BigDecimal.ZERO,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
),
|
||||
)
|
||||
|
|
@ -100,9 +101,75 @@ internal class DefaultQuotesRepositoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class GetCurrencyUSDQuote {
|
||||
|
||||
private val btcRawId = CryptoCurrency.RawID(value = "BTC")
|
||||
private val ethRawId = CryptoCurrency.RawID(value = "ETH")
|
||||
|
||||
private val ethQuote = QuoteStatus(
|
||||
rawCurrencyId = ethRawId,
|
||||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ZERO,
|
||||
fiatRateUSD = BigDecimal.ZERO,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
),
|
||||
)
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun getCurrencyUSDQuote(model: GetCurrencyUSDQuoteModel) = runTest {
|
||||
// Arrange
|
||||
coEvery { quotesStatusesStore.getAllSyncOrNull() } returns model.initialStore
|
||||
|
||||
// Act
|
||||
val actual = repository.getCurrencyUSDQuote(currencyId = model.currencyId)
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
GetCurrencyUSDQuoteModel(
|
||||
initialStore = null,
|
||||
currencyId = ethRawId,
|
||||
expected = QuoteStatus(rawCurrencyId = ethRawId),
|
||||
),
|
||||
GetCurrencyUSDQuoteModel(
|
||||
initialStore = emptySet(),
|
||||
currencyId = ethRawId,
|
||||
expected = QuoteStatus(rawCurrencyId = ethRawId),
|
||||
),
|
||||
GetCurrencyUSDQuoteModel(
|
||||
initialStore = setOf(ethQuote),
|
||||
currencyId = ethRawId,
|
||||
expected = ethQuote,
|
||||
),
|
||||
GetCurrencyUSDQuoteModel(
|
||||
initialStore = setOf(QuoteStatus(rawCurrencyId = btcRawId)),
|
||||
currencyId = ethRawId,
|
||||
expected = QuoteStatus(rawCurrencyId = ethRawId),
|
||||
),
|
||||
GetCurrencyUSDQuoteModel(
|
||||
initialStore = setOf(ethQuote, QuoteStatus(rawCurrencyId = btcRawId)),
|
||||
currencyId = ethRawId,
|
||||
expected = ethQuote,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class GetMultiQuoteSyncOrNullModel(
|
||||
val initialStore: Set<QuoteStatus>?,
|
||||
val currencyIds: Set<CryptoCurrency.RawID>,
|
||||
val expected: Set<QuoteStatus>?,
|
||||
)
|
||||
|
||||
data class GetCurrencyUSDQuoteModel(
|
||||
val initialStore: Set<QuoteStatus>?,
|
||||
val currencyId: CryptoCurrency.RawID,
|
||||
val expected: QuoteStatus?,
|
||||
)
|
||||
}
|
||||
|
|
@ -83,6 +83,7 @@ internal class DefaultSingleQuoteStatusProducerTest {
|
|||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
fiatRateUSD = BigDecimal.ZERO,
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
)
|
||||
|
|
@ -129,6 +130,7 @@ internal class DefaultSingleQuoteStatusProducerTest {
|
|||
rawCurrencyId = params.rawCurrencyId,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal.ONE,
|
||||
fiatRateUSD = BigDecimal.ZERO,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -40,11 +40,13 @@ data class QuoteStatus(val rawCurrencyId: CryptoCurrency.RawID, val value: Value
|
|||
*
|
||||
* @property source status source
|
||||
* @property fiatRate the current fiat exchange rate for the cryptocurrency
|
||||
* @property fiatRateUSD the current fiat exchange rate in USD for the cryptocurrency
|
||||
* @property priceChange the price change for the cryptocurrency
|
||||
*/
|
||||
data class Data(
|
||||
override val source: StatusSource,
|
||||
val fiatRate: BigDecimal,
|
||||
val fiatRateUSD: BigDecimal,
|
||||
val priceChange: BigDecimal,
|
||||
) : Value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.domain.quotes
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Get currency USD quote use case
|
||||
*/
|
||||
class GetCurrencyUSDQuoteUseCase(
|
||||
private val quotesRepository: QuotesRepository,
|
||||
) {
|
||||
|
||||
/** Get quote by [currencyId] synchronously or null */
|
||||
suspend operator fun invoke(currencyId: CryptoCurrency.RawID): BigDecimal? {
|
||||
val value = quotesRepository.getCurrencyUSDQuote(currencyId)?.value
|
||||
|
||||
return if (value is QuoteStatus.Data) {
|
||||
value.fiatRateUSD
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,4 +12,7 @@ interface QuotesRepository {
|
|||
|
||||
/** Get quotes by [currenciesIds] synchronously or null */
|
||||
suspend fun getMultiQuoteSyncOrNull(currenciesIds: Set<CryptoCurrency.RawID>): Set<QuoteStatus>?
|
||||
|
||||
/** Get quote by [currencyId] synchronously or null */
|
||||
suspend fun getCurrencyUSDQuote(currencyId: CryptoCurrency.RawID): QuoteStatus?
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token1.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("1.23"),
|
||||
fiatRateUSD = BigDecimal("1.23"),
|
||||
priceChange = BigDecimal("0.01"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
@ -22,6 +23,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token2.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("2.34"),
|
||||
fiatRateUSD = BigDecimal("2.34"),
|
||||
priceChange = BigDecimal("-0.02"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
@ -31,6 +33,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token3.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("3.45"),
|
||||
fiatRateUSD = BigDecimal("3.45"),
|
||||
priceChange = BigDecimal("0.03"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
@ -40,6 +43,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token4.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("4.56"),
|
||||
fiatRateUSD = BigDecimal("4.56"),
|
||||
priceChange = BigDecimal("-0.04"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
@ -49,6 +53,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token5.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("5.67"),
|
||||
fiatRateUSD = BigDecimal("5.67"),
|
||||
priceChange = BigDecimal("0.05"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
@ -58,6 +63,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token6.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("6.78"),
|
||||
fiatRateUSD = BigDecimal("6.78"),
|
||||
priceChange = BigDecimal("-0.06"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
@ -67,6 +73,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token7.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("7.89"),
|
||||
fiatRateUSD = BigDecimal("7.89"),
|
||||
priceChange = BigDecimal("0.07"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
@ -76,6 +83,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token8.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("8.90"),
|
||||
fiatRateUSD = BigDecimal("8.90"),
|
||||
priceChange = BigDecimal("-0.08"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
@ -85,6 +93,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token9.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("9.01"),
|
||||
fiatRateUSD = BigDecimal("9.01"),
|
||||
priceChange = BigDecimal("0.09"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
@ -94,6 +103,7 @@ internal object MockQuotes {
|
|||
rawCurrencyId = MockTokens.token10.id.rawCurrencyId!!,
|
||||
value = QuoteStatus.Data(
|
||||
fiatRate = BigDecimal("10.12"),
|
||||
fiatRateUSD = BigDecimal("10.12"),
|
||||
priceChange = BigDecimal("-0.10"),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class CryptoCurrencyStatusFactoryTest {
|
|||
|
||||
private val fullQuote = QuoteStatus.Data(
|
||||
fiatRate = 1800.0.toBigDecimal(),
|
||||
fiatRateUSD = 1800.0.toBigDecimal(),
|
||||
priceChange = (-2.5).toBigDecimal(),
|
||||
source = StatusSource.ACTUAL,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ class YieldSupplyMinAmountUseCaseTest {
|
|||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = nativeFiatRate,
|
||||
fiatRateUSD = nativeFiatRate,
|
||||
priceChange = BigDecimal("0.09000000000000007"),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = nativeFiatRate,
|
||||
fiatRateUSD = nativeFiatRate,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
),
|
||||
),
|
||||
|
|
@ -130,6 +131,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = nativeFiatRate,
|
||||
fiatRateUSD = nativeFiatRate,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
),
|
||||
),
|
||||
|
|
@ -256,6 +258,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest {
|
|||
value = QuoteStatus.Data(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatRate = BigDecimal.ZERO, // non-positive
|
||||
fiatRateUSD = BigDecimal.ZERO,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue