From 851d82631e4074d4c5cfea7850d2b44b34ad7a7a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 May 2026 14:55:23 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../test/data/quote/QuoteResponseExt.kt | 15 ++++- .../converters/HotCryptoCurrencyConverter.kt | 2 + .../quotes/converter/FiatCurrencyConverter.kt | 20 ++++++ .../quotes/converter/QuoteStatusConverter.kt | 6 +- .../tangem/data/quotes/di/QuotesDataModule.kt | 13 ++-- .../multi/DefaultMultiQuoteStatusFetcher.kt | 29 +++++---- .../store/DefaultQuotesStatusesStore.kt | 57 +++++++++++++---- .../tangem/data/quotes/store/QuoteStatusDM.kt | 28 +++++++++ .../data/quotes/store/QuotesStatusesStore.kt | 6 +- .../converter/QuoteStatusConverterTest.kt | 7 ++- .../DefaultMultiQuoteStatusFetcherTest.kt | 50 ++++----------- .../repository/DefaultQuotesRepositoryTest.kt | 3 + .../DefaultSingleQuoteStatusProducerTest.kt | 3 + .../store/QuotesStatusesStoreExtTest.kt | 5 +- .../quotes/store/QuotesStatusesStoreTest.kt | 63 ++++++++++++------- domain/app-currency/build.gradle.kts | 1 + .../extenstions/UseCaseExtensions.kt | 5 +- .../domain/models/currency/FiatCurrency.kt | 21 +++++++ .../tangem/domain/models/quote/QuoteStatus.kt | 11 ++-- .../tangem/domain/tokens/mock/MockQuotes.kt | 11 ++++ .../CryptoCurrencyStatusFactoryTest.kt | 2 + .../operations/PriceChangeCalculatorTest.kt | 1 + .../TotalFiatBalanceCalculatorTest.kt | 1 + .../supply/YieldSupplyMinAmountUseCaseTest.kt | 2 + .../YieldSupplyEnterStatusUseCaseTest.kt | 1 + .../YieldSupplyGetCurrentFeeUseCaseTest.kt | 4 ++ .../YieldSupplyGetDustMinAmountUseCaseTest.kt | 1 + ...YieldSupplyGetRewardsBalanceUseCaseTest.kt | 1 + .../SwapInteractorImplFindBestQuoteTest.kt | 2 + 29 files changed, 264 insertions(+), 107 deletions(-) create mode 100644 data/quotes/src/main/java/com/tangem/data/quotes/converter/FiatCurrencyConverter.kt create mode 100644 data/quotes/src/main/java/com/tangem/data/quotes/store/QuoteStatusDM.kt create mode 100644 domain/models/src/main/kotlin/com/tangem/domain/models/currency/FiatCurrency.kt diff --git a/common/test/src/main/java/com/tangem/common/test/data/quote/QuoteResponseExt.kt b/common/test/src/main/java/com/tangem/common/test/data/quote/QuoteResponseExt.kt index 1e50e6ae60..bf35371231 100644 --- a/common/test/src/main/java/com/tangem/common/test/data/quote/QuoteResponseExt.kt +++ b/common/test/src/main/java/com/tangem/common/test/data/quote/QuoteResponseExt.kt @@ -3,14 +3,20 @@ package com.tangem.common.test.data.quote import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import com.tangem.utils.extensions.orZero -fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource = StatusSource.ACTUAL): QuoteStatus { +fun QuotesResponse.Quote.toDomain( + rawCurrencyId: String, + source: StatusSource = StatusSource.ACTUAL, + fiatCurrency: FiatCurrency = FiatCurrency.Default, +): QuoteStatus { return QuoteStatus( rawCurrencyId = CryptoCurrency.RawID(rawCurrencyId), value = QuoteStatus.Data( source = source, + fiatCurrency = fiatCurrency, fiatRate = price.orZero(), priceChange = priceChange24h.orZero().movePointLeft(2), fiatRateUSD = priceUsd.orZero(), @@ -18,6 +24,9 @@ fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource = ) } -fun Pair.toDomain(source: StatusSource = StatusSource.ACTUAL): QuoteStatus { - return second.toDomain(rawCurrencyId = first, source = source) +fun Pair.toDomain( + source: StatusSource = StatusSource.ACTUAL, + fiatCurrency: FiatCurrency = FiatCurrency.Default, +): QuoteStatus { + return second.toDomain(rawCurrencyId = first, source = source, fiatCurrency = fiatCurrency) } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt index 8311e2cdce..6b47b53975 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/HotCryptoCurrencyConverter.kt @@ -8,6 +8,7 @@ import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.api.tangemTech.models.HotCryptoResponse import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.wallet.UserWallet @@ -94,6 +95,7 @@ internal class HotCryptoCurrencyConverter( QuoteStatus( rawCurrencyId = rawCurrencyId, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, // hot crypto rates are quoted in USD fiatRate = fiatRate, fiatRateUSD = BigDecimal.ZERO, priceChange = priceChange.movePointLeft(2), diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/converter/FiatCurrencyConverter.kt b/data/quotes/src/main/java/com/tangem/data/quotes/converter/FiatCurrencyConverter.kt new file mode 100644 index 0000000000..936bfa14db --- /dev/null +++ b/data/quotes/src/main/java/com/tangem/data/quotes/converter/FiatCurrencyConverter.kt @@ -0,0 +1,20 @@ +package com.tangem.data.quotes.converter + +import com.tangem.data.quotes.store.QuoteStatusDM +import com.tangem.domain.models.currency.FiatCurrency +import com.tangem.utils.converter.TwoWayConverter + +/** + * Two-way converter between domain [FiatCurrency] and persisted [QuoteStatusDM.FiatCurrency]. + * + * - [convert] — domain → DM (for persistence). + * - [convertBack] — DM → domain (for restore on cold start). + */ +internal object FiatCurrencyConverter : TwoWayConverter { + + override fun convert(value: FiatCurrency): QuoteStatusDM.FiatCurrency = + QuoteStatusDM.FiatCurrency(code = value.code, symbol = value.symbol) + + override fun convertBack(value: QuoteStatusDM.FiatCurrency): FiatCurrency = + FiatCurrency(code = value.code, symbol = value.symbol) +} \ No newline at end of file diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/converter/QuoteStatusConverter.kt b/data/quotes/src/main/java/com/tangem/data/quotes/converter/QuoteStatusConverter.kt index 163618af88..87165420e2 100644 --- a/data/quotes/src/main/java/com/tangem/data/quotes/converter/QuoteStatusConverter.kt +++ b/data/quotes/src/main/java/com/tangem/data/quotes/converter/QuoteStatusConverter.kt @@ -3,6 +3,7 @@ package com.tangem.data.quotes.converter import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.orZero @@ -10,12 +11,14 @@ import com.tangem.utils.extensions.orZero /** * Converter from [QuotesResponse.Quote] to [QuoteStatus] * - * @property source status source + * @property source status source + * @property fiatCurrency fiat currency in which the quote is expressed * [REDACTED_AUTHOR] */ internal class QuoteStatusConverter( private val source: StatusSource, + private val fiatCurrency: FiatCurrency, ) : Converter, QuoteStatus> { override fun convert(value: Map.Entry): QuoteStatus { @@ -25,6 +28,7 @@ internal class QuoteStatusConverter( rawCurrencyId = CryptoCurrency.RawID(currencyId), value = QuoteStatus.Data( source = source, + fiatCurrency = fiatCurrency, fiatRate = quote.price.orZero(), priceChange = quote.priceChange24h.orZero().movePointLeft(2), fiatRateUSD = quote.priceUsd.orZero(), diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/di/QuotesDataModule.kt b/data/quotes/src/main/java/com/tangem/data/quotes/di/QuotesDataModule.kt index ffd7ce636e..bca467d1f0 100644 --- a/data/quotes/src/main/java/com/tangem/data/quotes/di/QuotesDataModule.kt +++ b/data/quotes/src/main/java/com/tangem/data/quotes/di/QuotesDataModule.kt @@ -4,16 +4,16 @@ import android.content.Context import androidx.datastore.core.DataStoreFactory import androidx.datastore.dataStoreFile import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter import com.tangem.data.quotes.multi.DefaultMultiQuoteUpdater import com.tangem.data.quotes.repository.DefaultQuotesRepository import com.tangem.data.quotes.store.DefaultQuotesStatusesStore +import com.tangem.data.quotes.store.QuoteStatusDM import com.tangem.data.quotes.store.QuotesStatusesStore -import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.datasource.appcurrency.AppCurrencyResponseStore 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.domain.quotes.GetCurrencyUSDQuoteUseCase import com.tangem.domain.quotes.QuotesRepository import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher @@ -30,6 +30,7 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal object QuotesDataModule { + @OptIn(ExperimentalStdlibApi::class) @Singleton @Provides fun provideQuotesStoreV2( @@ -41,13 +42,13 @@ internal object QuotesDataModule { runtimeStore = RuntimeSharedStore(), persistenceDataStore = DataStoreFactory.create( serializer = MoshiDataStoreSerializer( - moshi = moshi, - types = mapWithStringKeyTypes(), - defaultValue = emptyMap(), + defaultValue = QuoteStatusDM.Empty, + adapter = moshi.adapter(), ), - produceFile = { context.dataStoreFile(fileName = "quotes") }, + produceFile = { context.dataStoreFile(fileName = "quotes_v2") }, scope = appScope, ), + legacyCacheFile = context.dataStoreFile(fileName = "quotes"), scope = appScope, ) } diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/multi/DefaultMultiQuoteStatusFetcher.kt b/data/quotes/src/main/java/com/tangem/data/quotes/multi/DefaultMultiQuoteStatusFetcher.kt index 768a1fba48..e9b3cc6e7e 100644 --- a/data/quotes/src/main/java/com/tangem/data/quotes/multi/DefaultMultiQuoteStatusFetcher.kt +++ b/data/quotes/src/main/java/com/tangem/data/quotes/multi/DefaultMultiQuoteStatusFetcher.kt @@ -8,9 +8,11 @@ import com.tangem.data.quotes.store.QuotesStatusesStore import com.tangem.data.quotes.store.setSourceAsCache import com.tangem.data.quotes.store.setSourceAsOnlyCache import com.tangem.data.quotes.utils.QuotesUnsupportedCurrenciesIdAdapter +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.datasource.appcurrency.AppCurrencyResponseStore import com.tangem.domain.core.utils.catchOn import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.logging.TangemLogger @@ -50,10 +52,10 @@ internal class DefaultMultiQuoteStatusFetcher @Inject constructor( ), ) - val appCurrencyId = getAppCurrencyId(params = params) + val (fiatCurrencyId, fiatCurrency) = resolveFiatCurrency() val response = quotesFetcher.fetch( - fiatCurrencyId = appCurrencyId, + fiatCurrencyId = fiatCurrencyId, currenciesIds = replacementIdsResult.idsForRequest, fields = setOf(Field.PRICE, Field.PRICE_CHANGE_24H, Field.PRICE_USD), ) @@ -64,24 +66,27 @@ internal class DefaultMultiQuoteStatusFetcher @Inject constructor( filteredIds = replacementIdsResult.idsFiltered, ) - quotesStatusesStore.store(values = updatedResponse.quotes) + quotesStatusesStore.store(values = updatedResponse.quotes, fiatCurrency = fiatCurrency) } .onLeft { throwable -> TangemLogger.e("Error", throwable) quotesStatusesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds) } - private suspend fun getAppCurrencyId(params: MultiQuoteStatusFetcher.Params): String { - val appCurrencyId = params.appCurrencyId - ?: appCurrencyResponseStore.getSyncOrNull()?.id + private suspend fun resolveFiatCurrency(): Pair { + val stored = appCurrencyResponseStore.getSyncOrNull() ?: failOnMissingAppCurrency() + if (stored.id.isBlank()) failOnMissingAppCurrency() - if (appCurrencyId.isNullOrBlank()) { - val exception = IllegalStateException("Unable to get AppCurrency for updating quotes") - TangemLogger.e("Error", exception) + return stored.id to stored.toFiatCurrency() + } - throw exception - } + private fun failOnMissingAppCurrency(): Nothing { + val exception = IllegalStateException("Unable to get AppCurrency for updating quotes") + TangemLogger.e("Error", exception) + throw exception + } - return appCurrencyId + private fun CurrenciesResponse.Currency.toFiatCurrency(): FiatCurrency { + return FiatCurrency(code = code, symbol = unit) } } \ No newline at end of file diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/store/DefaultQuotesStatusesStore.kt b/data/quotes/src/main/java/com/tangem/data/quotes/store/DefaultQuotesStatusesStore.kt index 9c4bd521e7..ee341241a2 100644 --- a/data/quotes/src/main/java/com/tangem/data/quotes/store/DefaultQuotesStatusesStore.kt +++ b/data/quotes/src/main/java/com/tangem/data/quotes/store/DefaultQuotesStatusesStore.kt @@ -1,11 +1,13 @@ package com.tangem.data.quotes.store import androidx.datastore.core.DataStore +import com.tangem.data.quotes.converter.FiatCurrencyConverter import com.tangem.data.quotes.converter.QuoteStatusConverter import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import com.tangem.utils.coroutines.AppCoroutineScope import com.tangem.utils.extensions.addOrReplace @@ -14,6 +16,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch +import java.io.File internal typealias CurrencyIdWithQuote = Map @@ -21,27 +24,46 @@ internal typealias CurrencyIdWithQuote = Map * Default implementation of [QuotesStatusesStore] * * @property runtimeStore runtime store - * @property persistenceDataStore persistence store - * @param dispatchers dispatchers + * @property persistenceDataStore persistence store (keeps quotes together with the fiat currency they're expressed in) + * @property legacyCacheFile pre-v2 cache file kept on disk; deleted once on init + * @param scope app coroutine scope */ internal class DefaultQuotesStatusesStore( private val runtimeStore: RuntimeSharedStore>, - private val persistenceDataStore: DataStore, + private val persistenceDataStore: DataStore, + private val legacyCacheFile: File, private val scope: AppCoroutineScope, ) : QuotesStatusesStore { init { scope.launch { - val cachedStatuses = persistenceDataStore.data.firstOrNull() + deleteLegacyCacheFile() - if (cachedStatuses.isNullOrEmpty()) return@launch + val cached = persistenceDataStore.data.firstOrNull() ?: return@launch + val fiatCurrency = cached.fiatCurrency?.let(FiatCurrencyConverter::convertBack) ?: return@launch + + if (cached.quotes.isEmpty()) return@launch runtimeStore.store( - value = QuoteStatusConverter(source = StatusSource.CACHE).convertSet(input = cachedStatuses.entries), + value = QuoteStatusConverter(source = StatusSource.CACHE, fiatCurrency = fiatCurrency) + .convertSet(input = cached.quotes.entries), ) } } + private fun deleteLegacyCacheFile() { + if (!legacyCacheFile.exists()) return + runCatching { legacyCacheFile.delete() } + .onSuccess { deleted -> + if (deleted) { + TangemLogger.i("Deleted legacy quotes cache file: ${legacyCacheFile.name}") + } else { + TangemLogger.e("Could not delete legacy quotes cache file: ${legacyCacheFile.name}") + } + } + .onFailure { TangemLogger.e("Failed to delete legacy quotes cache file", it) } + } + override fun get(): Flow> = runtimeStore.get() override suspend fun getAllSyncOrNull(): Set? = runtimeStore.getSyncOrNull() @@ -81,24 +103,33 @@ internal class DefaultQuotesStatusesStore( } } - override suspend fun store(values: CurrencyIdWithQuote) { + override suspend fun store(values: CurrencyIdWithQuote, fiatCurrency: FiatCurrency) { if (values.isEmpty()) return coroutineScope { - launch { storeInRuntime(values = values) } - launch { storeInPersistence(values = values) } + launch { storeInRuntime(values = values, fiatCurrency = fiatCurrency) } + launch { storeInPersistence(values = values, fiatCurrency = fiatCurrency) } } } - private suspend fun storeInRuntime(values: CurrencyIdWithQuote) { - val quotes = QuoteStatusConverter(source = StatusSource.ACTUAL).convertSet(input = values.entries) + private suspend fun storeInRuntime(values: CurrencyIdWithQuote, fiatCurrency: FiatCurrency) { + val quotes = QuoteStatusConverter(source = StatusSource.ACTUAL, fiatCurrency = fiatCurrency) + .convertSet(input = values.entries) runtimeStore.update(default = emptySet()) { saved -> saved.addOrReplace(items = quotes) { prev, new -> prev.rawCurrencyId == new.rawCurrencyId } } } - private suspend fun storeInPersistence(values: CurrencyIdWithQuote) { - persistenceDataStore.updateData { storedQuotes -> storedQuotes + values } + private suspend fun storeInPersistence(values: CurrencyIdWithQuote, fiatCurrency: FiatCurrency) { + persistenceDataStore.updateData { stored -> + val isSameCurrency = stored.fiatCurrency?.code == fiatCurrency.code + val mergedQuotes = if (isSameCurrency) stored.quotes + values else values + + QuoteStatusDM( + fiatCurrency = FiatCurrencyConverter.convert(fiatCurrency), + quotes = mergedQuotes, + ) + } } } \ No newline at end of file diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/store/QuoteStatusDM.kt b/data/quotes/src/main/java/com/tangem/data/quotes/store/QuoteStatusDM.kt new file mode 100644 index 0000000000..50a7a02a6f --- /dev/null +++ b/data/quotes/src/main/java/com/tangem/data/quotes/store/QuoteStatusDM.kt @@ -0,0 +1,28 @@ +package com.tangem.data.quotes.store + +import com.squareup.moshi.JsonClass +import com.tangem.datasource.api.tangemTech.models.QuotesResponse + +/** + * Persisted form of [com.tangem.domain.models.quote.QuoteStatus] cache. Carries the fiat currency + * the quotes are expressed in, so it can be restored on cold start. + * + * @property fiatCurrency fiat currency the [quotes] are expressed in; `null` for an empty default cache + * @property quotes map of currency id to its quote + */ +@JsonClass(generateAdapter = true) +internal data class QuoteStatusDM( + val fiatCurrency: FiatCurrency?, + val quotes: Map, +) { + + @JsonClass(generateAdapter = true) + internal data class FiatCurrency( + val code: String, + val symbol: String, + ) + + companion object { + val Empty = QuoteStatusDM(fiatCurrency = null, quotes = emptyMap()) + } +} \ No newline at end of file diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/store/QuotesStatusesStore.kt b/data/quotes/src/main/java/com/tangem/data/quotes/store/QuotesStatusesStore.kt index a9dfe1fac2..4ef830ac73 100644 --- a/data/quotes/src/main/java/com/tangem/data/quotes/store/QuotesStatusesStore.kt +++ b/data/quotes/src/main/java/com/tangem/data/quotes/store/QuotesStatusesStore.kt @@ -3,6 +3,7 @@ package com.tangem.data.quotes.store import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import kotlinx.coroutines.flow.Flow @@ -38,9 +39,10 @@ internal interface QuotesStatusesStore { /** * Store quotes statuses * - * @param values map of currency ids and quotes + * @param values map of currency ids and quotes + * @param fiatCurrency fiat currency the [values] are expressed in * * See complex methods in `QuotesStatusesStoreExt`. */ - suspend fun store(values: Map) + suspend fun store(values: Map, fiatCurrency: FiatCurrency) } \ No newline at end of file diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/converter/QuoteStatusConverterTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/converter/QuoteStatusConverterTest.kt index a22d7c9513..e06122bad7 100644 --- a/data/quotes/src/test/java/com/tangem/data/quotes/converter/QuoteStatusConverterTest.kt +++ b/data/quotes/src/test/java/com/tangem/data/quotes/converter/QuoteStatusConverterTest.kt @@ -6,6 +6,7 @@ import com.tangem.common.test.data.quote.toDomain import com.tangem.datasource.api.tangemTech.models.QuotesResponse import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import com.tangem.test.core.ProvideTestModels import org.junit.jupiter.api.TestInstance @@ -24,7 +25,8 @@ internal class QuoteStatusConverterTest { @ProvideTestModels fun convert(model: ConvertTestModel) { // Act - val actual = QuoteStatusConverter(source = model.source).convert(value = model.value) + val actual = QuoteStatusConverter(source = model.source, fiatCurrency = FiatCurrency.Default) + .convert(value = model.value) // Assert val expected = model.expected @@ -62,6 +64,7 @@ internal class QuoteStatusConverterTest { rawCurrencyId = CryptoCurrency.RawID("ETH"), value = QuoteStatus.Data( source = StatusSource.ACTUAL, + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO, priceChange = BigDecimal("0.00"), @@ -83,6 +86,7 @@ internal class QuoteStatusConverterTest { rawCurrencyId = CryptoCurrency.RawID("ETH"), value = QuoteStatus.Data( source = StatusSource.ACTUAL, + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO, priceChange = BigDecimal("0.00"), @@ -104,6 +108,7 @@ internal class QuoteStatusConverterTest { rawCurrencyId = CryptoCurrency.RawID("ETH"), value = QuoteStatus.Data( source = StatusSource.ACTUAL, + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal.ONE, fiatRateUSD = BigDecimal.ONE, priceChange = BigDecimal("0.01"), diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/multi/DefaultMultiQuoteStatusFetcherTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/multi/DefaultMultiQuoteStatusFetcherTest.kt index bc4e500fc0..d90505dcc4 100644 --- a/data/quotes/src/test/java/com/tangem/data/quotes/multi/DefaultMultiQuoteStatusFetcherTest.kt +++ b/data/quotes/src/test/java/com/tangem/data/quotes/multi/DefaultMultiQuoteStatusFetcherTest.kt @@ -69,7 +69,7 @@ internal class DefaultMultiQuoteStatusFetcherTest { quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds) appCurrencyResponseStore.getSyncOrNull() quotesFetcher.fetch(fiatCurrencyId = "usd", currenciesIds = currenciesIds, fields = fields) - quotesStore.store(values = successResponse.quotes) + quotesStore.store(values = successResponse.quotes, fiatCurrency = any()) } coVerify(inverse = true) { @@ -93,21 +93,21 @@ internal class DefaultMultiQuoteStatusFetcherTest { quotesStore.setSourceAsCache(currenciesIds = any()) appCurrencyResponseStore.getSyncOrNull() quotesFetcher.fetch(fiatCurrencyId = any(), currenciesIds = any(), fields = any()) - quotesStore.store(values = any()) + quotesStore.store(values = any(), fiatCurrency = any()) quotesStore.setSourceAsOnlyCache(currenciesIds = any()) } } @Test - fun `fetch successfully if appCurrencyId from params is not null`() = runTest { - // Arrange - val appCurrencyId = "usd" - val params = MultiQuoteStatusFetcher.Params(currenciesIds = currenciesIds, appCurrencyId = appCurrencyId) + fun `fetch ignores params appCurrencyId and uses stored app currency`() = runTest { + // Arrange: params.appCurrencyId is set but different from stored — fetcher must still use stored + val params = MultiQuoteStatusFetcher.Params(currenciesIds = currenciesIds, appCurrencyId = "eur") val currenciesIds = setOf("BTC", "ETH") + coEvery { appCurrencyResponseStore.getSyncOrNull() } returns usdAppCurrency coEvery { - quotesFetcher.fetch(fiatCurrencyId = appCurrencyId, currenciesIds = currenciesIds, fields = fields) + quotesFetcher.fetch(fiatCurrencyId = "usd", currenciesIds = currenciesIds, fields = fields) } returns successResponse.right() // Act @@ -119,42 +119,16 @@ internal class DefaultMultiQuoteStatusFetcherTest { coVerifyOrder { quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds) - quotesFetcher.fetch(fiatCurrencyId = appCurrencyId, currenciesIds = currenciesIds, fields = fields) - quotesStore.store(values = successResponse.quotes) + appCurrencyResponseStore.getSyncOrNull() + quotesFetcher.fetch(fiatCurrencyId = "usd", currenciesIds = currenciesIds, fields = fields) + quotesStore.store(values = successResponse.quotes, fiatCurrency = any()) } coVerify(inverse = true) { - appCurrencyResponseStore.getSyncOrNull() quotesStore.setSourceAsOnlyCache(currenciesIds = any()) } } - @Test - fun `fetch failure because appCurrencyId from params is blank`() = runTest { - // Arrange - val appCurrencyId = "" - val params = MultiQuoteStatusFetcher.Params(currenciesIds = currenciesIds, appCurrencyId = appCurrencyId) - - // Act - val actual = fetcher(params) - - // Assert - val expected = IllegalStateException("Unable to get AppCurrency for updating quotes").left() - - assertEither(actual, expected) - - coVerifyOrder { - quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds) - quotesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds) - } - - coVerify(inverse = true) { - appCurrencyResponseStore.getSyncOrNull() - quotesFetcher.fetch(fiatCurrencyId = any(), currenciesIds = any(), fields = any()) - quotesStore.store(values = any()) - } - } - @Test fun `fetch failure because api request failed`() = runTest { // Arrange @@ -186,7 +160,7 @@ internal class DefaultMultiQuoteStatusFetcherTest { } coVerify(inverse = true) { - quotesStore.store(values = any()) + quotesStore.store(values = any(), fiatCurrency = any()) } } @@ -213,7 +187,7 @@ internal class DefaultMultiQuoteStatusFetcherTest { coVerify(inverse = true) { quotesFetcher.fetch(fiatCurrencyId = any(), currenciesIds = any(), fields = any()) - quotesStore.store(values = any()) + quotesStore.store(values = any(), fiatCurrency = any()) } } diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/repository/DefaultQuotesRepositoryTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/repository/DefaultQuotesRepositoryTest.kt index ea9203adf3..0b6aadd5b3 100644 --- a/data/quotes/src/test/java/com/tangem/data/quotes/repository/DefaultQuotesRepositoryTest.kt +++ b/data/quotes/src/test/java/com/tangem/data/quotes/repository/DefaultQuotesRepositoryTest.kt @@ -4,6 +4,7 @@ import com.google.common.truth.Truth import com.tangem.data.quotes.store.QuotesStatusesStore import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.quotes.QuotesRepository import com.tangem.test.core.ProvideTestModels @@ -41,6 +42,7 @@ internal class DefaultQuotesRepositoryTest { private val ethQuote = QuoteStatus( rawCurrencyId = ethRawId, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, source = StatusSource.ACTUAL, fiatRate = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO, @@ -111,6 +113,7 @@ internal class DefaultQuotesRepositoryTest { private val ethQuote = QuoteStatus( rawCurrencyId = ethRawId, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, source = StatusSource.ACTUAL, fiatRate = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO, diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/single/DefaultSingleQuoteStatusProducerTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/single/DefaultSingleQuoteStatusProducerTest.kt index 486788d947..f1936025b4 100644 --- a/data/quotes/src/test/java/com/tangem/data/quotes/single/DefaultSingleQuoteStatusProducerTest.kt +++ b/data/quotes/src/test/java/com/tangem/data/quotes/single/DefaultSingleQuoteStatusProducerTest.kt @@ -5,6 +5,7 @@ import com.tangem.data.quotes.store.QuotesStatusesStore import com.tangem.domain.core.flow.FlowProducerTools import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.quotes.single.SingleQuoteStatusProducer import com.tangem.test.core.getEmittedValues @@ -81,6 +82,7 @@ internal class DefaultSingleQuoteStatusProducerTest { val updatedStatus = QuoteStatus( rawCurrencyId = params.rawCurrencyId, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal.ONE, priceChange = BigDecimal.ZERO, fiatRateUSD = BigDecimal.ZERO, @@ -129,6 +131,7 @@ internal class DefaultSingleQuoteStatusProducerTest { val status = QuoteStatus( rawCurrencyId = params.rawCurrencyId, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal.ONE, fiatRateUSD = BigDecimal.ZERO, priceChange = BigDecimal.ZERO, diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStatusesStoreExtTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStatusesStoreExtTest.kt index 1d187c765b..46416c5e76 100644 --- a/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStatusesStoreExtTest.kt +++ b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStatusesStoreExtTest.kt @@ -25,7 +25,7 @@ import kotlin.properties.Delegates internal class QuotesStatusesStoreExtTest { private var runtimeStore: RuntimeSharedStore> by Delegates.notNull() - private var persistenceStore: MockStateDataStore by Delegates.notNull() + private var persistenceStore: MockStateDataStore by Delegates.notNull() private var store: DefaultQuotesStatusesStore by Delegates.notNull() private val btcQuoteDM = "BTC" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ZERO) @@ -34,11 +34,12 @@ internal class QuotesStatusesStoreExtTest { @BeforeEach fun resetMocks() { runtimeStore = RuntimeSharedStore() - persistenceStore = MockStateDataStore(default = emptyMap()) + persistenceStore = MockStateDataStore(default = QuoteStatusDM.Empty) store = DefaultQuotesStatusesStore( runtimeStore = runtimeStore, persistenceDataStore = persistenceStore, + legacyCacheFile = java.io.File("legacy-quotes-cache-noop"), scope = TestAppCoroutineScope(), ) } diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStatusesStoreTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStatusesStoreTest.kt index 67df2cab9e..329046d5e5 100644 --- a/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStatusesStoreTest.kt +++ b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStatusesStoreTest.kt @@ -6,9 +6,11 @@ import com.tangem.common.test.TestAppCoroutineScope import com.tangem.common.test.data.quote.MockQuoteResponseFactory import com.tangem.common.test.data.quote.toDomain import com.tangem.common.test.datastore.MockStateDataStore +import com.tangem.data.quotes.converter.FiatCurrencyConverter import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import com.tangem.test.core.ProvideTestModels import com.tangem.test.core.getEmittedValues @@ -31,7 +33,7 @@ import kotlin.properties.Delegates internal class QuotesStatusesStoreTest { private var runtimeStore: RuntimeSharedStore> by Delegates.notNull() - private var persistenceStore: MockStateDataStore by Delegates.notNull() + private var persistenceStore: MockStateDataStore by Delegates.notNull() private var store: DefaultQuotesStatusesStore by Delegates.notNull() // region Data models @@ -48,11 +50,12 @@ internal class QuotesStatusesStoreTest { @BeforeEach fun resetMocks() { runtimeStore = RuntimeSharedStore() - persistenceStore = MockStateDataStore(default = emptyMap()) + persistenceStore = MockStateDataStore(default = QuoteStatusDM.Empty) store = DefaultQuotesStatusesStore( runtimeStore = runtimeStore, persistenceDataStore = persistenceStore, + legacyCacheFile = java.io.File("legacy-quotes-cache-noop"), scope = TestAppCoroutineScope(), ) } @@ -65,7 +68,7 @@ internal class QuotesStatusesStoreTest { fun `initialization if cache store is empty`() = runTest { // Arrange val runtimeStore = RuntimeSharedStore>() - val persistenceStore: DataStore = mockk() + val persistenceStore: DataStore = mockk() every { persistenceStore.data } returns emptyFlow() @@ -73,6 +76,7 @@ internal class QuotesStatusesStoreTest { DefaultQuotesStatusesStore( runtimeStore = runtimeStore, persistenceDataStore = persistenceStore, + legacyCacheFile = java.io.File("legacy-quotes-cache-noop"), scope = TestAppCoroutineScope(), ) @@ -87,12 +91,13 @@ internal class QuotesStatusesStoreTest { fun `initialization if cache store contains empty map`() = runTest { // Arrange val runtimeStore = RuntimeSharedStore>() - val persistenceStore = MockStateDataStore(default = emptyMap()) + val persistenceStore = MockStateDataStore(default = QuoteStatusDM.Empty) // Act DefaultQuotesStatusesStore( runtimeStore = runtimeStore, persistenceDataStore = persistenceStore, + legacyCacheFile = java.io.File("legacy-quotes-cache-noop"), scope = TestAppCoroutineScope(), ) @@ -107,19 +112,20 @@ internal class QuotesStatusesStoreTest { fun `initialization if cache store is not empty`() = runTest { // Arrange val runtimeStore = RuntimeSharedStore>() - val persistenceStore = MockStateDataStore(default = emptyMap()) + val persistenceStore = MockStateDataStore(default = QuoteStatusDM.Empty) persistenceStore.updateData { - it.toMutableMap().apply { - this += btcQuoteDM - this += ethQuoteDM - } + QuoteStatusDM( + fiatCurrency = FiatCurrencyConverter.convert(FiatCurrency.Default), + quotes = mapOf(btcQuoteDM, ethQuoteDM), + ) } // Act DefaultQuotesStatusesStore( runtimeStore = runtimeStore, persistenceDataStore = persistenceStore, + legacyCacheFile = java.io.File("legacy-quotes-cache-noop"), scope = TestAppCoroutineScope(), ) @@ -471,7 +477,7 @@ internal class QuotesStatusesStoreTest { } // Act - store.store(values = model.values) + store.store(values = model.values, fiatCurrency = FiatCurrency.Default) val runtimeActual = runtimeStore.getSyncOrNull() val persistenceActual = getEmittedValues(persistenceStore.data) @@ -491,14 +497,14 @@ internal class QuotesStatusesStoreTest { initialPersistence = null, values = emptyMap(), runtimeExpected = null, - persistenceExpected = emptyMap(), + persistenceExpected = QuoteStatusDM.Empty, ), StoreTestModel( initialRuntime = null, initialPersistence = null, values = mapOf(btcQuoteDM), runtimeExpected = setOf(btcQuote), - persistenceExpected = mapOf(btcQuoteDM), + persistenceExpected = persistedDefault(btcQuoteDM), ), // endregion @@ -508,48 +514,48 @@ internal class QuotesStatusesStoreTest { initialPersistence = null, values = emptyMap(), runtimeExpected = setOf(btcQuote), - persistenceExpected = emptyMap(), + persistenceExpected = QuoteStatusDM.Empty, ), StoreTestModel( initialRuntime = setOf(ethQuote), initialPersistence = null, values = mapOf(btcQuoteDM), runtimeExpected = setOf(ethQuote, btcQuote), - persistenceExpected = mapOf(btcQuoteDM), + persistenceExpected = persistedDefault(btcQuoteDM), ), // endregion // region runtime store is null StoreTestModel( initialRuntime = null, - initialPersistence = mapOf(btcQuoteDM), + initialPersistence = persistedDefault(btcQuoteDM), values = emptyMap(), runtimeExpected = null, - persistenceExpected = mapOf(btcQuoteDM), + persistenceExpected = persistedDefault(btcQuoteDM), ), StoreTestModel( initialRuntime = null, - initialPersistence = mapOf(ethQuoteDM), + initialPersistence = persistedDefault(ethQuoteDM), values = mapOf(btcQuoteDM), runtimeExpected = setOf(btcQuote), - persistenceExpected = mapOf(ethQuoteDM, btcQuoteDM), + persistenceExpected = persistedDefault(ethQuoteDM, btcQuoteDM), ), // endregion // region stores contain data StoreTestModel( initialRuntime = setOf(btcQuote), - initialPersistence = mapOf(btcQuoteDM), + initialPersistence = persistedDefault(btcQuoteDM), values = emptyMap(), runtimeExpected = setOf(btcQuote), - persistenceExpected = mapOf(btcQuoteDM), + persistenceExpected = persistedDefault(btcQuoteDM), ), StoreTestModel( initialRuntime = setOf(btcQuote), - initialPersistence = mapOf(btcQuoteDM), + initialPersistence = persistedDefault(btcQuoteDM), values = mapOf(ethQuoteDM), runtimeExpected = setOf(ethQuote, btcQuote), - persistenceExpected = mapOf(ethQuoteDM, btcQuoteDM), + persistenceExpected = persistedDefault(ethQuoteDM, btcQuoteDM), ), // endregion ) @@ -557,9 +563,18 @@ internal class QuotesStatusesStoreTest { data class StoreTestModel( val initialRuntime: Set?, - val initialPersistence: CurrencyIdWithQuote?, + val initialPersistence: QuoteStatusDM?, val values: CurrencyIdWithQuote, - val persistenceExpected: CurrencyIdWithQuote?, + val persistenceExpected: QuoteStatusDM?, val runtimeExpected: Set?, ) + + companion object { + private fun persistedDefault( + vararg quotes: Pair, + ): QuoteStatusDM = QuoteStatusDM( + fiatCurrency = FiatCurrencyConverter.convert(FiatCurrency.Default), + quotes = mapOf(*quotes), + ) + } } \ No newline at end of file diff --git a/domain/app-currency/build.gradle.kts b/domain/app-currency/build.gradle.kts index 45af2fd004..629a373edf 100644 --- a/domain/app-currency/build.gradle.kts +++ b/domain/app-currency/build.gradle.kts @@ -8,5 +8,6 @@ dependencies { /** Project - Domain */ implementation(projects.core.utils) implementation(projects.domain.core) + implementation(projects.domain.models) implementation(projects.domain.appCurrency.models) } \ No newline at end of file diff --git a/domain/app-currency/src/main/kotlin/com/tangem/domain/appcurrency/extenstions/UseCaseExtensions.kt b/domain/app-currency/src/main/kotlin/com/tangem/domain/appcurrency/extenstions/UseCaseExtensions.kt index 724bddd8bc..e209161604 100644 --- a/domain/app-currency/src/main/kotlin/com/tangem/domain/appcurrency/extenstions/UseCaseExtensions.kt +++ b/domain/app-currency/src/main/kotlin/com/tangem/domain/appcurrency/extenstions/UseCaseExtensions.kt @@ -3,6 +3,7 @@ package com.tangem.domain.appcurrency.extenstions import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.currency.FiatCurrency import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map @@ -13,4 +14,6 @@ suspend fun GetSelectedAppCurrencyUseCase.unwrap(): AppCurrency { } .firstOrNull() ?: AppCurrency.Default -} \ No newline at end of file +} + +fun AppCurrency.toFiatCurrency(): FiatCurrency = FiatCurrency(code = code, symbol = symbol) \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/currency/FiatCurrency.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/FiatCurrency.kt new file mode 100644 index 0000000000..42ca2defd2 --- /dev/null +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/FiatCurrency.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.models.currency + +import kotlinx.serialization.Serializable + +/** + * Fiat currency in which a [com.tangem.domain.models.quote.QuoteStatus] is expressed. Plain business + * entity without UI metadata (icons, localized name) — those live in `AppCurrency`. + * + * @property code ISO code, e.g. "USD" + * @property symbol display symbol, e.g. "$" + */ +@Serializable +data class FiatCurrency( + val code: String, + val symbol: String, +) { + + companion object { + val Default = FiatCurrency(code = "USD", symbol = "$") + } +} \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/quote/QuoteStatus.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/quote/QuoteStatus.kt index 74f40ab8c6..0871227a73 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/quote/QuoteStatus.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/quote/QuoteStatus.kt @@ -2,6 +2,7 @@ package com.tangem.domain.models.quote import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import java.math.BigDecimal /** @@ -38,13 +39,15 @@ data class QuoteStatus(val rawCurrencyId: CryptoCurrency.RawID, val value: Value * Represents financial information for a specific cryptocurrency, including its fiat exchange rate and * price change. * - * @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 + * @property source status source + * @property fiatCurrency fiat currency in which [fiatRate] is expressed + * @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 fiatCurrency: FiatCurrency, val fiatRate: BigDecimal, val fiatRateUSD: BigDecimal, val priceChange: BigDecimal, diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt index 081ad0fbf2..18a6c6d77a 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt @@ -3,6 +3,7 @@ package com.tangem.domain.tokens.mock import arrow.core.nonEmptySetOf import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import java.math.BigDecimal @@ -12,6 +13,7 @@ internal object MockQuotes { val quote1 = QuoteStatus( rawCurrencyId = MockTokens.token1.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("1.23"), fiatRateUSD = BigDecimal("1.23"), priceChange = BigDecimal("0.01"), @@ -22,6 +24,7 @@ internal object MockQuotes { val quote2 = QuoteStatus( rawCurrencyId = MockTokens.token2.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("2.34"), fiatRateUSD = BigDecimal("2.34"), priceChange = BigDecimal("-0.02"), @@ -32,6 +35,7 @@ internal object MockQuotes { val quote3 = QuoteStatus( rawCurrencyId = MockTokens.token3.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("3.45"), fiatRateUSD = BigDecimal("3.45"), priceChange = BigDecimal("0.03"), @@ -42,6 +46,7 @@ internal object MockQuotes { val quote4 = QuoteStatus( rawCurrencyId = MockTokens.token4.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("4.56"), fiatRateUSD = BigDecimal("4.56"), priceChange = BigDecimal("-0.04"), @@ -52,6 +57,7 @@ internal object MockQuotes { val quote5 = QuoteStatus( rawCurrencyId = MockTokens.token5.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("5.67"), fiatRateUSD = BigDecimal("5.67"), priceChange = BigDecimal("0.05"), @@ -62,6 +68,7 @@ internal object MockQuotes { val quote6 = QuoteStatus( rawCurrencyId = MockTokens.token6.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("6.78"), fiatRateUSD = BigDecimal("6.78"), priceChange = BigDecimal("-0.06"), @@ -72,6 +79,7 @@ internal object MockQuotes { val quote7 = QuoteStatus( rawCurrencyId = MockTokens.token7.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("7.89"), fiatRateUSD = BigDecimal("7.89"), priceChange = BigDecimal("0.07"), @@ -82,6 +90,7 @@ internal object MockQuotes { val quote8 = QuoteStatus( rawCurrencyId = MockTokens.token8.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("8.90"), fiatRateUSD = BigDecimal("8.90"), priceChange = BigDecimal("-0.08"), @@ -92,6 +101,7 @@ internal object MockQuotes { val quote9 = QuoteStatus( rawCurrencyId = MockTokens.token9.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("9.01"), fiatRateUSD = BigDecimal("9.01"), priceChange = BigDecimal("0.09"), @@ -102,6 +112,7 @@ internal object MockQuotes { val quote10 = QuoteStatus( rawCurrencyId = MockTokens.token10.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = BigDecimal("10.12"), fiatRateUSD = BigDecimal("10.12"), priceChange = BigDecimal("-0.10"), diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactoryTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactoryTest.kt index 966dada81e..a76cf699c6 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactoryTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/CryptoCurrencyStatusFactoryTest.kt @@ -7,6 +7,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkStatus @@ -43,6 +44,7 @@ class CryptoCurrencyStatusFactoryTest { ) private val fullQuote = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, fiatRate = 1800.0.toBigDecimal(), fiatRateUSD = 1800.0.toBigDecimal(), priceChange = (-2.5).toBigDecimal(), diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/PriceChangeCalculatorTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/PriceChangeCalculatorTest.kt index 93087cadfc..caf32150eb 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/PriceChangeCalculatorTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/PriceChangeCalculatorTest.kt @@ -7,6 +7,7 @@ import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.quote.PriceChange import com.tangem.domain.tokens.mock.MockTokens diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/TotalFiatBalanceCalculatorTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/TotalFiatBalanceCalculatorTest.kt index 197949310f..f49fd39402 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/TotalFiatBalanceCalculatorTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/TotalFiatBalanceCalculatorTest.kt @@ -8,6 +8,7 @@ import com.tangem.domain.models.StatusSource import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.staking.BalanceItem import com.tangem.domain.models.staking.BalanceType diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/YieldSupplyMinAmountUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/YieldSupplyMinAmountUseCaseTest.kt index 03680be520..577d49c03b 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/YieldSupplyMinAmountUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/YieldSupplyMinAmountUseCaseTest.kt @@ -8,6 +8,7 @@ import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress @@ -81,6 +82,7 @@ class YieldSupplyMinAmountUseCaseTest { QuoteStatus( rawCurrencyId = CryptoCurrency.RawID("polygon-ecosystem-token"), value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, source = StatusSource.ACTUAL, fiatRate = nativeFiatRate, fiatRateUSD = nativeFiatRate, diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt index e8f69ad3c5..3dd9e82d0f 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt @@ -4,6 +4,7 @@ import arrow.core.Either import com.google.common.truth.Truth.assertThat import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.wallet.UserWalletId diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetCurrentFeeUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetCurrentFeeUseCaseTest.kt index 39ab76e2a6..34293cf646 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetCurrentFeeUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetCurrentFeeUseCaseTest.kt @@ -8,6 +8,7 @@ import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress @@ -77,6 +78,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest { QuoteStatus( rawCurrencyId = nativeCoin.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, source = StatusSource.ACTUAL, fiatRate = nativeFiatRate, fiatRateUSD = nativeFiatRate, @@ -129,6 +131,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest { QuoteStatus( rawCurrencyId = nativeCoin.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, source = StatusSource.ACTUAL, fiatRate = nativeFiatRate, fiatRateUSD = nativeFiatRate, @@ -256,6 +259,7 @@ class YieldSupplyGetCurrentFeeUseCaseTest { QuoteStatus( rawCurrencyId = nativeCoin.id.rawCurrencyId!!, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, source = StatusSource.ACTUAL, fiatRate = BigDecimal.ZERO, // non-positive fiatRateUSD = BigDecimal.ZERO, diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt index d8defa5bea..089b806d91 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt @@ -4,6 +4,7 @@ import com.google.common.truth.Truth.assertThat import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import org.junit.jupiter.api.Test diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt index 01534f3b92..a491d98ef4 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.yield.supply.YieldSupplyRepository diff --git a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplFindBestQuoteTest.kt b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplFindBestQuoteTest.kt index b0241f7f9e..0578355fb6 100644 --- a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplFindBestQuoteTest.kt +++ b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplFindBestQuoteTest.kt @@ -11,6 +11,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.FiatCurrency import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.swap.models.SwapCurrencyStatus @@ -86,6 +87,7 @@ internal class SwapInteractorImplFindBestQuoteTest : SwapInteractorImplTestBase( QuoteStatus( rawCurrencyId = rawId, value = QuoteStatus.Data( + fiatCurrency = FiatCurrency.Default, source = StatusSource.ACTUAL, fiatRate = BigDecimal.ONE, fiatRateUSD = BigDecimal.ONE,