Updated on 2026-08-14
This commit is contained in:
parent
14c20117eb
commit
a398620b0d
14 changed files with 477 additions and 4 deletions
|
|
@ -9,6 +9,9 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
implementation(projects.data.common)
|
||||
|
||||
implementation(projects.domain.legacy)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.common.test.data.quote
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object MockQuoteResponseFactory {
|
||||
|
||||
fun createSinglePrice(value: BigDecimal): QuotesResponse.Quote {
|
||||
return QuotesResponse.Quote(
|
||||
price = value,
|
||||
priceChange24h = value,
|
||||
priceChange1w = value,
|
||||
priceChange30d = value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.common.test.data.quote
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.local.quote.converter.QuoteConverter
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
|
||||
fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource = StatusSource.ACTUAL): Quote {
|
||||
return QuoteConverter(source = source).convert(value = mapOf(rawCurrencyId to this).entries.first())
|
||||
}
|
||||
|
||||
fun Pair<String, QuotesResponse.Quote>.toDomain(source: StatusSource = StatusSource.ACTUAL): Quote {
|
||||
return QuoteConverter(source = source).convert(value = mapOf(this).entries.first())
|
||||
}
|
||||
|
|
@ -10,13 +10,24 @@ import com.tangem.utils.extensions.orZero
|
|||
/**
|
||||
* Converter from [QuotesResponse.Quote] to [Quote.Value]
|
||||
*
|
||||
* @property isCached flag that determines whether the quote is a cache
|
||||
* @property source status source
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class QuoteConverter(private val isCached: Boolean) :
|
||||
class QuoteConverter(
|
||||
private val source: StatusSource,
|
||||
) :
|
||||
Converter<Map.Entry<String, QuotesResponse.Quote>, Quote.Value> {
|
||||
|
||||
/**
|
||||
* Secondary constructor
|
||||
*
|
||||
* @param isCached flag that determines whether the quote is a cache
|
||||
*/
|
||||
constructor(isCached: Boolean) : this(
|
||||
source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL,
|
||||
)
|
||||
|
||||
override fun convert(value: Map.Entry<String, QuotesResponse.Quote>): Quote.Value {
|
||||
val (currencyId, quote) = value
|
||||
|
||||
|
|
@ -24,7 +35,7 @@ internal class QuoteConverter(private val isCached: Boolean) :
|
|||
rawCurrencyId = CryptoCurrency.RawID(currencyId),
|
||||
fiatRate = quote.price.orZero(),
|
||||
priceChange = quote.priceChange24h.orZero().movePointLeft(2),
|
||||
source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ internal class NetworksStatusesStoreInitializationTest {
|
|||
|
||||
DefaultNetworksStatusesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore, // local mock
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
|
|
|
|||
1
data/quotes/.gitignore
vendored
Normal file
1
data/quotes/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
30
data/quotes/build.gradle.kts
Normal file
30
data/quotes/build.gradle.kts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.quotes"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
implementation(deps.androidx.datastore)
|
||||
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(projects.common.test)
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.data.quotes.store
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.quote.converter.QuoteConverter
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal typealias CurrencyIdWithQuote = Map<String, QuotesResponse.Quote>
|
||||
|
||||
/**
|
||||
* Default implementation of [QuotesStoreV2]
|
||||
*
|
||||
* @property runtimeStore runtime store
|
||||
* @property persistenceDataStore persistence store
|
||||
* @param dispatchers dispatchers
|
||||
*/
|
||||
internal class DefaultQuotesStoreV2(
|
||||
private val runtimeStore: RuntimeSharedStore<Set<Quote>>,
|
||||
private val persistenceDataStore: DataStore<CurrencyIdWithQuote>,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) : QuotesStoreV2 {
|
||||
|
||||
private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io)
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
val cachedStatuses = persistenceDataStore.data.firstOrNull()
|
||||
|
||||
if (cachedStatuses.isNullOrEmpty()) return@launch
|
||||
|
||||
runtimeStore.store(
|
||||
value = QuoteConverter(isCached = true).convertSet(input = cachedStatuses.entries),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(): Flow<Set<Quote>> = runtimeStore.get()
|
||||
|
||||
override suspend fun refresh(currenciesIds: Set<CryptoCurrency.RawID>) {
|
||||
updateStatusSourceInRuntime(currenciesIds = currenciesIds, source = StatusSource.CACHE)
|
||||
}
|
||||
|
||||
override suspend fun storeActual(values: Map<String, QuotesResponse.Quote>) {
|
||||
coroutineScope {
|
||||
launch {
|
||||
val quotes = QuoteConverter(isCached = false).convertSet(input = values.entries)
|
||||
storeInRuntimeStore(values = quotes)
|
||||
}
|
||||
launch { storeInPersistenceStore(values = values) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun storeError(currenciesIds: Set<CryptoCurrency.RawID>) {
|
||||
updateStatusSourceInRuntime(currenciesIds = currenciesIds, source = StatusSource.ONLY_CACHE)
|
||||
}
|
||||
|
||||
private suspend fun updateStatusSourceInRuntime(currenciesIds: Set<CryptoCurrency.RawID>, source: StatusSource) {
|
||||
runtimeStore.update(default = emptySet()) { stored ->
|
||||
val updatedQuotes = currenciesIds.mapTo(hashSetOf()) { id ->
|
||||
val quote = stored.firstOrNull { it.rawCurrencyId == id } ?: Quote.Empty(id)
|
||||
|
||||
quote.copySealed(source = source)
|
||||
}
|
||||
|
||||
stored.addOrReplace(items = updatedQuotes) { old, new -> old.rawCurrencyId == new.rawCurrencyId }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInRuntimeStore(values: Set<Quote>) {
|
||||
runtimeStore.update(default = emptySet()) { saved ->
|
||||
saved.addOrReplace(items = values) { prev, new -> prev.rawCurrencyId == new.rawCurrencyId }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInPersistenceStore(values: Map<String, QuotesResponse.Quote>) {
|
||||
persistenceDataStore.updateData { storedQuotes -> storedQuotes + values }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.data.quotes.store
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Store of [Quote]'es set */
|
||||
internal interface QuotesStoreV2 {
|
||||
|
||||
/** Get flow of quotes */
|
||||
fun get(): Flow<Set<Quote>>
|
||||
|
||||
/** Refresh status of [currenciesIds] */
|
||||
suspend fun refresh(currenciesIds: Set<CryptoCurrency.RawID>)
|
||||
|
||||
/** Store actual map of currency ids and quotes [values] */
|
||||
suspend fun storeActual(values: Map<String, QuotesResponse.Quote>)
|
||||
|
||||
/** Store error for [currenciesIds] */
|
||||
suspend fun storeError(currenciesIds: Set<CryptoCurrency.RawID>)
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.data.quotes.store
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
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.common.test.utils.getEmittedValues
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class QuotesStoreGetMethodTest {
|
||||
|
||||
private val runtimeStore = RuntimeSharedStore<Set<Quote>>()
|
||||
private val persistenceStore = MockStateDataStore<CurrencyIdWithQuote>(default = emptyMap())
|
||||
|
||||
private val store = DefaultQuotesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `test get if runtime store is empty`() = runTest {
|
||||
val actual = store.get()
|
||||
|
||||
val values = backgroundScope.getEmittedValues(testScheduler, actual)
|
||||
|
||||
Truth.assertThat(values).isEqualTo(emptyList<Set<Quote>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test get if runtime store contains empty set`() = runTest {
|
||||
runtimeStore.store(value = emptySet())
|
||||
|
||||
val actual = store.get()
|
||||
|
||||
val values = backgroundScope.getEmittedValues(testScheduler, actual)
|
||||
|
||||
Truth.assertThat(values).isEqualTo(listOf(emptySet<Quote>()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test get if runtime store is not empty`() = runTest {
|
||||
val btcQuote = "BTC" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ZERO)
|
||||
val ethQuote = "ETH" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ONE)
|
||||
|
||||
runtimeStore.store(value = setOf(btcQuote.toDomain(), ethQuote.toDomain()))
|
||||
|
||||
val actual = store.get()
|
||||
|
||||
val values = backgroundScope.getEmittedValues(testScheduler, actual)
|
||||
|
||||
Truth.assertThat(values.size).isEqualTo(1)
|
||||
Truth.assertThat(values).isEqualTo(listOf(setOf(btcQuote.toDomain(), ethQuote.toDomain())))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.tangem.data.quotes.store
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.google.common.truth.Truth
|
||||
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.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class QuotesStoreInitializationTest {
|
||||
|
||||
@Test
|
||||
fun `test initialization if cache store is empty`() = runTest {
|
||||
val runtimeStore = RuntimeSharedStore<Set<Quote>>()
|
||||
val persistenceStore: DataStore<CurrencyIdWithQuote> = mockk()
|
||||
|
||||
every { persistenceStore.data } returns emptyFlow()
|
||||
|
||||
DefaultQuotesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test initialization if cache store contains empty map`() = runTest {
|
||||
val runtimeStore = RuntimeSharedStore<Set<Quote>>()
|
||||
val persistenceStore = MockStateDataStore<CurrencyIdWithQuote>(default = emptyMap())
|
||||
|
||||
DefaultQuotesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test initialization if cache store is not empty`() = runTest {
|
||||
val runtimeStore = RuntimeSharedStore<Set<Quote>>()
|
||||
val persistenceStore = MockStateDataStore<CurrencyIdWithQuote>(default = emptyMap())
|
||||
|
||||
val btcQuote = "BTC" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ZERO)
|
||||
val ethQuote = "ETH" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ONE)
|
||||
|
||||
persistenceStore.updateData {
|
||||
it.toMutableMap().apply {
|
||||
this += btcQuote
|
||||
this += ethQuote
|
||||
}
|
||||
}
|
||||
|
||||
DefaultQuotesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
val expected = setOf(
|
||||
btcQuote.toDomain(source = StatusSource.CACHE),
|
||||
ethQuote.toDomain(source = StatusSource.CACHE),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package com.tangem.data.quotes.store
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
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.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class QuotesStoreUpdateMethodsTest {
|
||||
|
||||
private val runtimeStore = RuntimeSharedStore<Set<Quote>>()
|
||||
private val persistenceStore = MockStateDataStore<CurrencyIdWithQuote>(default = emptyMap())
|
||||
|
||||
private val store = DefaultQuotesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `refresh if runtime store is empty`() = runTest {
|
||||
val currenciesIds = setOf(
|
||||
CryptoCurrency.RawID(value = "BTC"),
|
||||
CryptoCurrency.RawID(value = "ETH"),
|
||||
)
|
||||
|
||||
store.refresh(currenciesIds = currenciesIds)
|
||||
|
||||
val runtimeExpected = currenciesIds.map(Quote::Empty).toSet()
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<Quote>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `refresh if runtime store contains quote with this id`() = runTest {
|
||||
val quote = MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE)
|
||||
.toDomain(rawCurrencyId = "BTC", source = StatusSource.ACTUAL)
|
||||
|
||||
runtimeStore.store(value = setOf(quote))
|
||||
|
||||
store.refresh(currenciesIds = setOf(quote.rawCurrencyId))
|
||||
|
||||
val runtimeExpected = setOf(quote.copySealed(source = StatusSource.CACHE))
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<Quote>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `store actual if runtime and cache stores contain quotes with this id`() = runTest {
|
||||
val prevStatus = MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE)
|
||||
|
||||
runtimeStore.store(
|
||||
value = setOf(
|
||||
prevStatus.toDomain(rawCurrencyId = "BTC", source = StatusSource.ONLY_CACHE),
|
||||
),
|
||||
)
|
||||
|
||||
persistenceStore.updateData {
|
||||
it.toMutableMap().apply {
|
||||
put("BTC", prevStatus)
|
||||
}
|
||||
}
|
||||
|
||||
val newStatus = MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.TEN)
|
||||
|
||||
store.storeActual(values = mapOf("BTC" to newStatus))
|
||||
|
||||
val runtimeExpected = setOf(
|
||||
newStatus.toDomain(rawCurrencyId = "BTC", source = StatusSource.ACTUAL),
|
||||
)
|
||||
val persistenceExpected = mapOf("BTC" to newStatus)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(persistenceExpected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `store error if runtime store is empty`() = runTest {
|
||||
val currenciesIds = setOf(
|
||||
CryptoCurrency.RawID(value = "BTC"),
|
||||
CryptoCurrency.RawID(value = "ETH"),
|
||||
)
|
||||
|
||||
store.storeError(currenciesIds = currenciesIds)
|
||||
|
||||
val runtimeExpected = currenciesIds.map(Quote::Empty).toSet()
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<Quote>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `store error if runtime store contains status with this network`() = runTest {
|
||||
val status = MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE)
|
||||
|
||||
runtimeStore.store(
|
||||
value = setOf(
|
||||
status.toDomain(rawCurrencyId = "BTC", source = StatusSource.CACHE),
|
||||
Quote.Empty(rawCurrencyId = CryptoCurrency.RawID(value = "ETH")),
|
||||
),
|
||||
)
|
||||
|
||||
store.storeError(
|
||||
currenciesIds = setOf(
|
||||
CryptoCurrency.RawID(value = "BTC"),
|
||||
CryptoCurrency.RawID(value = "ETH"),
|
||||
),
|
||||
)
|
||||
|
||||
val runtimeExpected = setOf(
|
||||
status.toDomain(rawCurrencyId = "BTC", source = StatusSource.ONLY_CACHE),
|
||||
Quote.Empty(rawCurrencyId = CryptoCurrency.RawID(value = "ETH")),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<Quote>>())
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,13 @@ sealed interface Quote {
|
|||
|
||||
val rawCurrencyId: CryptoCurrency.RawID
|
||||
|
||||
fun copySealed(source: StatusSource): Quote {
|
||||
return when (this) {
|
||||
is Empty -> this
|
||||
is Value -> copy(source = source)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents unknown financial information for a specific cryptocurrency.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -314,4 +314,5 @@ include(":data:manage-tokens")
|
|||
include(":data:networks")
|
||||
include(":data:nft")
|
||||
include(":data:onramp")
|
||||
include(":data:quotes")
|
||||
// endregion Data modules
|
||||
Loading…
Add table
Add a link
Reference in a new issue