Updated on 2026-08-14
This commit is contained in:
parent
6ad1b0fe5b
commit
bfd371bc33
20 changed files with 869 additions and 417 deletions
|
|
@ -80,7 +80,7 @@ internal class DefaultNetworksStatusesStore(
|
|||
ifNotFound: (Network.ID) -> SimpleNetworkStatus?,
|
||||
) {
|
||||
if (networks.isEmpty()) {
|
||||
Timber.d("Nothing to update: networks is empty")
|
||||
Timber.d("Nothing to update: networks are empty")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ android {
|
|||
namespace = "com.tangem.data.quotes"
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
|
@ -30,7 +34,8 @@ dependencies {
|
|||
kapt(deps.hilt.kapt)
|
||||
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.junit5)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(projects.common.test)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.data.quotes
|
||||
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.quotes.QuotesRepositoryV2
|
||||
import com.tangem.domain.tokens.model.QuoteStatus
|
||||
|
|
@ -14,7 +14,7 @@ import javax.inject.Inject
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultQuotesRepositoryV2 @Inject constructor(
|
||||
private val quotesStore: QuotesStoreV2,
|
||||
private val quotesStore: QuotesStatusesStore,
|
||||
) : QuotesRepositoryV2 {
|
||||
|
||||
override suspend fun getMultiQuoteSyncOrNull(currenciesIds: Set<CryptoCurrency.RawID>): Set<QuoteStatus>? {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import androidx.datastore.core.DataStoreFactory
|
|||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.data.quotes.DefaultQuotesRepositoryV2
|
||||
import com.tangem.data.quotes.store.DefaultQuotesStoreV2
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.data.quotes.store.DefaultQuotesStatusesStore
|
||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
|
|
@ -33,8 +33,8 @@ internal object QuotesDataModule {
|
|||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): QuotesStoreV2 {
|
||||
return DefaultQuotesStoreV2(
|
||||
): QuotesStatusesStore {
|
||||
return DefaultQuotesStatusesStore(
|
||||
runtimeStore = RuntimeSharedStore(),
|
||||
persistenceDataStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package com.tangem.data.quotes.multi
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.data.common.api.safeApiCallWithTimeout
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||
import com.tangem.data.quotes.store.setSourceAsCache
|
||||
import com.tangem.data.quotes.store.setSourceAsOnlyCache
|
||||
import com.tangem.data.tokens.utils.QuotesUnsupportedCurrenciesIdAdapter
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
|
||||
|
|
@ -29,7 +31,7 @@ import javax.inject.Singleton
|
|||
internal class DefaultMultiQuoteFetcher @Inject constructor(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val appCurrencyResponseStore: AppCurrencyResponseStore,
|
||||
private val quotesStore: QuotesStoreV2,
|
||||
private val quotesStore: QuotesStatusesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MultiQuoteFetcher {
|
||||
|
||||
|
|
@ -41,7 +43,7 @@ internal class DefaultMultiQuoteFetcher @Inject constructor(
|
|||
return@catchOn
|
||||
}
|
||||
|
||||
quotesStore.refresh(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
|
||||
|
||||
val replacementIdsResult = quotesUnsupportedCurrenciesAdapter.replaceUnsupportedCurrencies(
|
||||
currenciesIds = params.currenciesIds.mapTo(
|
||||
|
|
@ -67,11 +69,11 @@ internal class DefaultMultiQuoteFetcher @Inject constructor(
|
|||
filteredIds = replacementIdsResult.idsFiltered,
|
||||
)
|
||||
|
||||
quotesStore.storeActual(values = updatedResponse.quotes)
|
||||
quotesStore.store(values = updatedResponse.quotes)
|
||||
}
|
||||
.onLeft {
|
||||
Timber.e(it)
|
||||
quotesStore.storeError(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds)
|
||||
}
|
||||
|
||||
private suspend fun getAppCurrencyId(params: MultiQuoteFetcher.Params): String {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.data.quotes.multi
|
|||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import arrow.core.left
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
|
|
@ -33,7 +33,7 @@ import javax.inject.Singleton
|
|||
@Singleton
|
||||
internal class DefaultMultiQuoteUpdater @Inject constructor(
|
||||
private val appCurrencyResponseStore: AppCurrencyResponseStore,
|
||||
private val quotesStore: QuotesStoreV2,
|
||||
private val quotesStore: QuotesStatusesStore,
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) : MultiQuoteUpdater {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.data.quotes.single
|
||||
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||
import com.tangem.domain.quotes.single.SingleQuoteProducer
|
||||
import com.tangem.domain.tokens.model.QuoteStatus
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -20,7 +20,7 @@ import kotlinx.coroutines.flow.mapNotNull
|
|||
*/
|
||||
internal class DefaultSingleQuoteProducer @AssistedInject constructor(
|
||||
@Assisted val params: SingleQuoteProducer.Params,
|
||||
private val quotesStore: QuotesStoreV2,
|
||||
private val quotesStore: QuotesStatusesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SingleQuoteProducer {
|
||||
|
||||
|
|
|
|||
|
|
@ -15,21 +15,22 @@ import kotlinx.coroutines.coroutineScope
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
internal typealias CurrencyIdWithQuote = Map<String, QuotesResponse.Quote>
|
||||
|
||||
/**
|
||||
* Default implementation of [QuotesStoreV2]
|
||||
* Default implementation of [QuotesStatusesStore]
|
||||
*
|
||||
* @property runtimeStore runtime store
|
||||
* @property persistenceDataStore persistence store
|
||||
* @param dispatchers dispatchers
|
||||
*/
|
||||
internal class DefaultQuotesStoreV2(
|
||||
internal class DefaultQuotesStatusesStore(
|
||||
private val runtimeStore: RuntimeSharedStore<Set<QuoteStatus>>,
|
||||
private val persistenceDataStore: DataStore<CurrencyIdWithQuote>,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) : QuotesStoreV2 {
|
||||
) : QuotesStatusesStore {
|
||||
|
||||
private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io)
|
||||
|
||||
|
|
@ -49,33 +50,28 @@ internal class DefaultQuotesStoreV2(
|
|||
|
||||
override suspend fun getAllSyncOrNull(): Set<QuoteStatus>? = runtimeStore.getSyncOrNull()
|
||||
|
||||
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 = QuoteStatusConverter(isCached = false).convertSet(input = values.entries)
|
||||
storeInRuntimeStore(values = quotes)
|
||||
}
|
||||
launch { storeInPersistenceStore(values = values) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun storeError(currenciesIds: Set<CryptoCurrency.RawID>) {
|
||||
updateStatusSourceInRuntime(
|
||||
currenciesIds = currenciesIds,
|
||||
ifNotFound = ::QuoteStatus,
|
||||
source = StatusSource.ONLY_CACHE,
|
||||
override suspend fun updateStatusSource(
|
||||
currencyId: CryptoCurrency.RawID,
|
||||
source: StatusSource,
|
||||
ifNotFound: (CryptoCurrency.RawID) -> QuoteStatus?,
|
||||
) {
|
||||
updateStatusSource(
|
||||
currenciesIds = setOf(currencyId),
|
||||
source = source,
|
||||
ifNotFound = ifNotFound,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateStatusSourceInRuntime(
|
||||
override suspend fun updateStatusSource(
|
||||
currenciesIds: Set<CryptoCurrency.RawID>,
|
||||
ifNotFound: (CryptoCurrency.RawID) -> QuoteStatus? = { null },
|
||||
source: StatusSource,
|
||||
ifNotFound: (CryptoCurrency.RawID) -> QuoteStatus?,
|
||||
) {
|
||||
if (currenciesIds.isEmpty()) {
|
||||
Timber.d("Nothing to update: currencies ids are empty")
|
||||
return
|
||||
}
|
||||
|
||||
runtimeStore.update(default = emptySet()) { stored ->
|
||||
val updatedQuotes = currenciesIds.mapNotNullTo(hashSetOf()) { id ->
|
||||
val quote = stored.firstOrNull { it.rawCurrencyId == id }
|
||||
|
|
@ -89,13 +85,24 @@ internal class DefaultQuotesStoreV2(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInRuntimeStore(values: Set<QuoteStatus>) {
|
||||
runtimeStore.update(default = emptySet()) { saved ->
|
||||
saved.addOrReplace(items = values) { prev, new -> prev.rawCurrencyId == new.rawCurrencyId }
|
||||
override suspend fun store(values: CurrencyIdWithQuote) {
|
||||
if (values.isEmpty()) return
|
||||
|
||||
coroutineScope {
|
||||
launch { storeInRuntime(values = values) }
|
||||
launch { storeInPersistence(values = values) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInPersistenceStore(values: Map<String, QuotesResponse.Quote>) {
|
||||
private suspend fun storeInRuntime(values: CurrencyIdWithQuote) {
|
||||
val quotes = QuoteStatusConverter(isCached = false).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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
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.tokens.model.QuoteStatus
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Store of [QuoteStatus]'es set */
|
||||
internal interface QuotesStatusesStore {
|
||||
|
||||
/** Get flow of quotes */
|
||||
fun get(): Flow<Set<QuoteStatus>>
|
||||
|
||||
/** Get all quotes synchronously or null */
|
||||
suspend fun getAllSyncOrNull(): Set<QuoteStatus>?
|
||||
|
||||
/**
|
||||
* Update [source] of [QuoteStatus] by [currencyId].
|
||||
* If the status is not found, create a new one by [ifNotFound].
|
||||
*/
|
||||
suspend fun updateStatusSource(
|
||||
currencyId: CryptoCurrency.RawID,
|
||||
source: StatusSource,
|
||||
ifNotFound: (CryptoCurrency.RawID) -> QuoteStatus? = { null },
|
||||
)
|
||||
|
||||
/**
|
||||
* Update [source] of [QuoteStatus]es by [currenciesIds].
|
||||
* If the status is not found, create a new one by [ifNotFound].
|
||||
*/
|
||||
suspend fun updateStatusSource(
|
||||
currenciesIds: Set<CryptoCurrency.RawID>,
|
||||
source: StatusSource,
|
||||
ifNotFound: (CryptoCurrency.RawID) -> QuoteStatus? = { null },
|
||||
)
|
||||
|
||||
/**
|
||||
* Store quotes statuses
|
||||
*
|
||||
* @param values map of currency ids and quotes
|
||||
*
|
||||
* See complex methods in `QuotesStatusesStoreExt`.
|
||||
*/
|
||||
suspend fun store(values: Map<String, QuotesResponse.Quote>)
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.data.quotes.store
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.QuoteStatus
|
||||
|
||||
/** Set [StatusSource] of quotes statuses as [StatusSource.CACHE] for [currenciesIds] */
|
||||
internal suspend fun QuotesStatusesStore.setSourceAsCache(currenciesIds: Set<CryptoCurrency.RawID>) {
|
||||
updateStatusSource(currenciesIds = currenciesIds, source = StatusSource.CACHE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set [StatusSource] of quotes statuses as [StatusSource.ONLY_CACHE] for [currenciesIds].
|
||||
* If the stored status is not found, store a default [QuoteStatus.Empty] status.
|
||||
*/
|
||||
internal suspend fun QuotesStatusesStore.setSourceAsOnlyCache(currenciesIds: Set<CryptoCurrency.RawID>) {
|
||||
updateStatusSource(
|
||||
currenciesIds = currenciesIds,
|
||||
source = StatusSource.ONLY_CACHE,
|
||||
ifNotFound = ::QuoteStatus,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.data.quotes.store
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.QuoteStatus
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Store of [QuoteStatus]'es set */
|
||||
internal interface QuotesStoreV2 {
|
||||
|
||||
/** Get flow of quotes */
|
||||
fun get(): Flow<Set<QuoteStatus>>
|
||||
|
||||
/** Get all quotes synchronously or null */
|
||||
suspend fun getAllSyncOrNull(): Set<QuoteStatus>?
|
||||
|
||||
/** 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>)
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@ package com.tangem.data.quotes.multi
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.data.quote.MockQuoteResponseFactory
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||
import com.tangem.data.quotes.store.setSourceAsCache
|
||||
import com.tangem.data.quotes.store.setSourceAsOnlyCache
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
|
|
@ -27,7 +29,7 @@ internal class DefaultMultiQuoteStatusFetcherTest {
|
|||
|
||||
private val tangemTechApi = mockk<TangemTechApi>(relaxed = true)
|
||||
private val appCurrencyResponseStore = mockk<AppCurrencyResponseStore>(relaxed = true)
|
||||
private val quotesStore = mockk<QuotesStoreV2>(relaxed = true)
|
||||
private val quotesStore = mockk<QuotesStatusesStore>(relaxed = true)
|
||||
|
||||
private val fetcher = DefaultMultiQuoteFetcher(
|
||||
tangemTechApi = tangemTechApi,
|
||||
|
|
@ -50,17 +52,17 @@ internal class DefaultMultiQuoteStatusFetcherTest {
|
|||
val actual = fetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
|
||||
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
|
||||
quotesStore.storeActual(values = successResponse.quotes)
|
||||
quotesStore.store(values = successResponse.quotes)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
quotesStore.storeError(currenciesIds = any())
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isRight()).isTrue()
|
||||
|
|
@ -73,11 +75,11 @@ internal class DefaultMultiQuoteStatusFetcherTest {
|
|||
val actual = fetcher(params)
|
||||
|
||||
coVerify(inverse = true) {
|
||||
quotesStore.refresh(currenciesIds = any())
|
||||
quotesStore.setSourceAsCache(currenciesIds = any())
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
tangemTechApi.getQuotes(currencyId = any(), coinIds = any())
|
||||
quotesStore.storeActual(values = any())
|
||||
quotesStore.storeError(currenciesIds = any())
|
||||
quotesStore.store(values = any())
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isRight()).isTrue()
|
||||
|
|
@ -96,16 +98,16 @@ internal class DefaultMultiQuoteStatusFetcherTest {
|
|||
val actual = fetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
|
||||
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
|
||||
quotesStore.storeActual(values = successResponse.quotes)
|
||||
quotesStore.store(values = successResponse.quotes)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
quotesStore.storeError(currenciesIds = any())
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isRight()).isTrue()
|
||||
|
|
@ -119,14 +121,14 @@ internal class DefaultMultiQuoteStatusFetcherTest {
|
|||
val actual = fetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = params.currenciesIds)
|
||||
quotesStore.storeError(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
tangemTechApi.getQuotes(currencyId = any(), coinIds = any())
|
||||
quotesStore.storeActual(values = any())
|
||||
quotesStore.store(values = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
|
|
@ -150,17 +152,17 @@ internal class DefaultMultiQuoteStatusFetcherTest {
|
|||
val actual = fetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
|
||||
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
|
||||
quotesStore.storeError(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
quotesStore.storeActual(values = any())
|
||||
quotesStore.store(values = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
|
|
@ -175,14 +177,14 @@ internal class DefaultMultiQuoteStatusFetcherTest {
|
|||
val actual = fetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsCache(currenciesIds = params.currenciesIds)
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
quotesStore.storeError(currenciesIds = params.currenciesIds)
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = params.currenciesIds)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
tangemTechApi.getQuotes(currencyId = any(), coinIds = any())
|
||||
quotesStore.storeActual(values = any())
|
||||
quotesStore.store(values = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.data.quotes.multi
|
|||
import arrow.core.right
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.utils.getEmittedValues
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
|
|
@ -19,7 +19,7 @@ import org.junit.Test
|
|||
internal class DefaultMultiQuoteStatusUpdaterTest {
|
||||
|
||||
private val appCurrencyResponseStore: AppCurrencyResponseStore = mockk()
|
||||
private val quotesStore: QuotesStoreV2 = mockk()
|
||||
private val quotesStore: QuotesStatusesStore = mockk()
|
||||
private val multiQuoteFetcher: MultiQuoteFetcher = mockk()
|
||||
|
||||
private val multiQuoteUpdater = DefaultMultiQuoteUpdater(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.tangem.data.quotes.single
|
|||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.data.quote.MockQuoteResponseFactory
|
||||
import com.tangem.data.quotes.multi.DefaultMultiQuoteFetcher
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||
import com.tangem.data.quotes.store.setSourceAsCache
|
||||
import com.tangem.data.quotes.store.setSourceAsOnlyCache
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
|
|
@ -18,14 +20,14 @@ import io.mockk.coVerify
|
|||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultSingleQuoteStatusFetcherTest {
|
||||
|
||||
private val tangemTechApi = mockk<TangemTechApi>(relaxed = true)
|
||||
private val appCurrencyResponseStore = mockk<AppCurrencyResponseStore>(relaxed = true)
|
||||
private val quotesStore = mockk<QuotesStoreV2>(relaxed = true)
|
||||
private val quotesStore = mockk<QuotesStatusesStore>(relaxed = true)
|
||||
|
||||
private val multiFetcher = DefaultMultiQuoteFetcher(
|
||||
tangemTechApi = tangemTechApi,
|
||||
|
|
@ -50,14 +52,14 @@ internal class DefaultSingleQuoteStatusFetcherTest {
|
|||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(params.rawCurrencyId))
|
||||
quotesStore.setSourceAsCache(currenciesIds = setOf(params.rawCurrencyId))
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
quotesStore.storeActual(values = successResponse.quotes)
|
||||
quotesStore.store(values = successResponse.quotes)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
quotesStore.storeError(currenciesIds = any())
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isRight()).isTrue()
|
||||
|
|
@ -76,9 +78,9 @@ internal class DefaultSingleQuoteStatusFetcherTest {
|
|||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.setSourceAsCache(currenciesIds = setOf(currenciesId))
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
quotesStore.storeActual(values = successResponse.quotes)
|
||||
quotesStore.store(values = successResponse.quotes)
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isRight()).isTrue()
|
||||
|
|
@ -92,8 +94,8 @@ internal class DefaultSingleQuoteStatusFetcherTest {
|
|||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.storeError(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.setSourceAsCache(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = setOf(currenciesId))
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
|
|
@ -117,14 +119,14 @@ internal class DefaultSingleQuoteStatusFetcherTest {
|
|||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.setSourceAsCache(currenciesIds = setOf(currenciesId))
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
tangemTechApi.getQuotes(currencyId = "usd", coinIds = coinIds)
|
||||
quotesStore.storeError(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = setOf(currenciesId))
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
quotesStore.storeActual(values = any())
|
||||
quotesStore.store(values = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
|
|
@ -139,14 +141,14 @@ internal class DefaultSingleQuoteStatusFetcherTest {
|
|||
val actual = singleFetcher(params)
|
||||
|
||||
coVerifyOrder {
|
||||
quotesStore.refresh(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.setSourceAsCache(currenciesIds = setOf(currenciesId))
|
||||
appCurrencyResponseStore.getSyncOrNull()
|
||||
quotesStore.storeError(currenciesIds = setOf(currenciesId))
|
||||
quotesStore.setSourceAsOnlyCache(currenciesIds = setOf(currenciesId))
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
tangemTechApi.getQuotes(currencyId = any(), coinIds = any())
|
||||
quotesStore.storeActual(values = any())
|
||||
quotesStore.store(values = any())
|
||||
}
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.data.quotes.single
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.utils.getEmittedValues
|
||||
import com.tangem.data.quotes.store.QuotesStoreV2
|
||||
import com.tangem.data.quotes.store.QuotesStatusesStore
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.quotes.single.SingleQuoteProducer
|
||||
|
|
@ -25,7 +25,7 @@ internal class DefaultSingleQuoteStatusProducerTest {
|
|||
rawCurrencyId = CryptoCurrency.RawID(value = "BTC"),
|
||||
)
|
||||
|
||||
private val quotesStore = mockk<QuotesStoreV2>()
|
||||
private val quotesStore = mockk<QuotesStatusesStore>()
|
||||
|
||||
private val producer = DefaultSingleQuoteProducer(
|
||||
params = params,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
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.ProvideTestModels
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.QuoteStatus
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import java.math.BigDecimal
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class QuotesStatusesStoreExtTest {
|
||||
|
||||
private var runtimeStore: RuntimeSharedStore<Set<QuoteStatus>> by Delegates.notNull()
|
||||
private var persistenceStore: MockStateDataStore<CurrencyIdWithQuote> by Delegates.notNull()
|
||||
private var store: DefaultQuotesStatusesStore by Delegates.notNull()
|
||||
|
||||
private val btcQuoteDM = "BTC" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ZERO)
|
||||
private val btcQuote = btcQuoteDM.toDomain()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
runtimeStore = RuntimeSharedStore()
|
||||
persistenceStore = MockStateDataStore(default = emptyMap())
|
||||
|
||||
store = DefaultQuotesStatusesStore(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class SetSourceAsCache {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun setSourceAsCache(model: SetSourceTestModel) = runTest {
|
||||
// Arrange
|
||||
if (model.initialRuntime != null) {
|
||||
runtimeStore.store(value = model.initialRuntime)
|
||||
}
|
||||
|
||||
// Act
|
||||
store.setSourceAsCache(model.currenciesIds)
|
||||
val actual = store.getAllSyncOrNull()
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
SetSourceTestModel(
|
||||
initialRuntime = null,
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
expected = emptySet(),
|
||||
),
|
||||
SetSourceTestModel(
|
||||
initialRuntime = setOf(btcQuote),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
expected = setOf(btcQuoteDM.toDomain(source = StatusSource.CACHE)),
|
||||
),
|
||||
SetSourceTestModel(
|
||||
initialRuntime = setOf(btcQuoteDM.toDomain(source = StatusSource.CACHE)),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
expected = setOf(btcQuoteDM.toDomain(source = StatusSource.CACHE)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class SetSourceAsOnlyCache {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun setSourceAsOnlyCache(model: SetSourceTestModel) = runTest {
|
||||
// Arrange
|
||||
if (model.initialRuntime != null) {
|
||||
runtimeStore.store(value = model.initialRuntime)
|
||||
}
|
||||
|
||||
// Act
|
||||
store.setSourceAsOnlyCache(model.currenciesIds)
|
||||
val actual = store.getAllSyncOrNull()
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
SetSourceTestModel(
|
||||
initialRuntime = null,
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
expected = setOf(QuoteStatus(btcQuote.rawCurrencyId)),
|
||||
),
|
||||
SetSourceTestModel(
|
||||
initialRuntime = setOf(btcQuote),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
expected = setOf(btcQuoteDM.toDomain(source = StatusSource.ONLY_CACHE)),
|
||||
),
|
||||
SetSourceTestModel(
|
||||
initialRuntime = setOf(btcQuoteDM.toDomain(source = StatusSource.ONLY_CACHE)),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
expected = setOf(btcQuoteDM.toDomain(source = StatusSource.ONLY_CACHE)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class SetSourceTestModel(
|
||||
val initialRuntime: Set<QuoteStatus>?,
|
||||
val currenciesIds: Set<CryptoCurrency.RawID>,
|
||||
val expected: Set<QuoteStatus>?,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,565 @@
|
|||
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.common.test.utils.ProvideTestModels
|
||||
import com.tangem.common.test.utils.getEmittedValues
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.QuoteStatus
|
||||
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.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import java.math.BigDecimal
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class QuotesStatusesStoreTest {
|
||||
|
||||
private var runtimeStore: RuntimeSharedStore<Set<QuoteStatus>> by Delegates.notNull()
|
||||
private var persistenceStore: MockStateDataStore<CurrencyIdWithQuote> by Delegates.notNull()
|
||||
private var store: DefaultQuotesStatusesStore by Delegates.notNull()
|
||||
|
||||
// region Data models
|
||||
private val btcQuoteDM = "BTC" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ZERO)
|
||||
private val ethQuoteDM = "ETH" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ONE)
|
||||
// endregion
|
||||
|
||||
// region Domain models
|
||||
private val btcQuote = btcQuoteDM.toDomain()
|
||||
private val ethQuote = ethQuoteDM.toDomain()
|
||||
private val adaEmptyQuote = QuoteStatus(rawCurrencyId = CryptoCurrency.RawID(value = "ADA"))
|
||||
// endregion
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
runtimeStore = RuntimeSharedStore()
|
||||
persistenceStore = MockStateDataStore(default = emptyMap())
|
||||
|
||||
store = DefaultQuotesStatusesStore(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Initialization {
|
||||
|
||||
@Test
|
||||
fun `initialization if cache store is empty`() = runTest {
|
||||
// Arrange
|
||||
val runtimeStore = RuntimeSharedStore<Set<QuoteStatus>>()
|
||||
val persistenceStore: DataStore<CurrencyIdWithQuote> = mockk()
|
||||
|
||||
every { persistenceStore.data } returns emptyFlow()
|
||||
|
||||
// Act
|
||||
DefaultQuotesStatusesStore(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
val actual = runtimeStore.getSyncOrNull()
|
||||
|
||||
// Assert
|
||||
val expected = null
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initialization if cache store contains empty map`() = runTest {
|
||||
// Arrange
|
||||
val runtimeStore = RuntimeSharedStore<Set<QuoteStatus>>()
|
||||
val persistenceStore = MockStateDataStore<CurrencyIdWithQuote>(default = emptyMap())
|
||||
|
||||
// Act
|
||||
DefaultQuotesStatusesStore(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
val actual = runtimeStore.getSyncOrNull()
|
||||
|
||||
// Assert
|
||||
val expected = null
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initialization if cache store is not empty`() = runTest {
|
||||
// Arrange
|
||||
val runtimeStore = RuntimeSharedStore<Set<QuoteStatus>>()
|
||||
val persistenceStore = MockStateDataStore<CurrencyIdWithQuote>(default = emptyMap())
|
||||
|
||||
persistenceStore.updateData {
|
||||
it.toMutableMap().apply {
|
||||
this += btcQuoteDM
|
||||
this += ethQuoteDM
|
||||
}
|
||||
}
|
||||
|
||||
// Act
|
||||
DefaultQuotesStatusesStore(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
val actual = runtimeStore.getSyncOrNull()
|
||||
|
||||
// Assert
|
||||
val expected = setOf(
|
||||
btcQuoteDM.toDomain(source = StatusSource.CACHE),
|
||||
ethQuoteDM.toDomain(source = StatusSource.CACHE),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Get {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun get(model: GetTestModel) = runTest {
|
||||
// Arrange
|
||||
if (model.initialRuntime != null) {
|
||||
runtimeStore.store(value = model.initialRuntime)
|
||||
}
|
||||
|
||||
// Act
|
||||
val actual = getEmittedValues(flow = store.get())
|
||||
|
||||
// Assert
|
||||
val expected = listOfNotNull(model.expected)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
GetTestModel(initialRuntime = null, expected = null),
|
||||
GetTestModel(initialRuntime = emptySet(), expected = emptySet()),
|
||||
GetTestModel(initialRuntime = setOf(btcQuote, ethQuote), expected = setOf(btcQuote, ethQuote)),
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class GetAllSyncOrNull {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun getAllSyncOrNull(model: GetTestModel) = runTest {
|
||||
// Arrange
|
||||
if (model.initialRuntime != null) {
|
||||
runtimeStore.store(value = model.initialRuntime)
|
||||
}
|
||||
|
||||
// Act
|
||||
val actual = store.getAllSyncOrNull()
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
GetTestModel(initialRuntime = null, expected = null),
|
||||
GetTestModel(initialRuntime = emptySet(), expected = emptySet()),
|
||||
GetTestModel(initialRuntime = setOf(btcQuote, ethQuote), expected = setOf(btcQuote, ethQuote)),
|
||||
)
|
||||
}
|
||||
|
||||
data class GetTestModel(val initialRuntime: Set<QuoteStatus>?, val expected: Set<QuoteStatus>?)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class SingleUpdateStatusSource {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun updateStatusSource(model: UpdateStatusSourceModel.Single) = runTest {
|
||||
// Arrange
|
||||
if (model.initialRuntime != null) {
|
||||
runtimeStore.store(value = model.initialRuntime)
|
||||
}
|
||||
|
||||
// Act
|
||||
store.updateStatusSource(
|
||||
currencyId = model.currencyId,
|
||||
source = model.source,
|
||||
ifNotFound = model.ifNotFound,
|
||||
)
|
||||
|
||||
val actual = store.getAllSyncOrNull()
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
// region runtime store is null
|
||||
UpdateStatusSourceModel.Single(
|
||||
initialRuntime = null,
|
||||
currencyId = btcQuote.rawCurrencyId,
|
||||
source = StatusSource.CACHE, // never-mind
|
||||
expected = emptySet(),
|
||||
),
|
||||
UpdateStatusSourceModel.Single(
|
||||
initialRuntime = null,
|
||||
currencyId = btcQuote.rawCurrencyId,
|
||||
source = StatusSource.ONLY_CACHE, // never-mind
|
||||
ifNotFound = ::QuoteStatus,
|
||||
expected = setOf(
|
||||
QuoteStatus(rawCurrencyId = btcQuote.rawCurrencyId),
|
||||
),
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region runtime store is empty
|
||||
UpdateStatusSourceModel.Single(
|
||||
initialRuntime = emptySet(),
|
||||
currencyId = btcQuote.rawCurrencyId,
|
||||
source = StatusSource.CACHE, // never-mind
|
||||
expected = emptySet(),
|
||||
),
|
||||
UpdateStatusSourceModel.Single(
|
||||
initialRuntime = emptySet(),
|
||||
currencyId = btcQuote.rawCurrencyId,
|
||||
source = StatusSource.ONLY_CACHE, // never-mind
|
||||
ifNotFound = ::QuoteStatus,
|
||||
expected = setOf(
|
||||
QuoteStatus(rawCurrencyId = btcQuote.rawCurrencyId),
|
||||
),
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region runtime store contains statuses
|
||||
UpdateStatusSourceModel.Single(
|
||||
initialRuntime = setOf(btcQuote, ethQuote, adaEmptyQuote),
|
||||
currencyId = btcQuote.rawCurrencyId,
|
||||
source = StatusSource.CACHE,
|
||||
expected = setOf(
|
||||
btcQuote.copy(value = btcQuote.value.copySealed(source = StatusSource.CACHE)),
|
||||
ethQuote,
|
||||
adaEmptyQuote,
|
||||
),
|
||||
),
|
||||
UpdateStatusSourceModel.Single(
|
||||
initialRuntime = setOf(btcQuote, ethQuote),
|
||||
currencyId = btcQuote.rawCurrencyId,
|
||||
source = StatusSource.ONLY_CACHE,
|
||||
expected = setOf(
|
||||
btcQuote.copy(value = btcQuote.value.copySealed(source = StatusSource.ONLY_CACHE)),
|
||||
ethQuote,
|
||||
),
|
||||
),
|
||||
UpdateStatusSourceModel.Single(
|
||||
initialRuntime = setOf(btcQuote, ethQuote),
|
||||
currencyId = btcQuote.rawCurrencyId,
|
||||
source = StatusSource.ACTUAL,
|
||||
expected = setOf(btcQuote, ethQuote),
|
||||
),
|
||||
UpdateStatusSourceModel.Single(
|
||||
initialRuntime = setOf(btcQuote),
|
||||
currencyId = adaEmptyQuote.rawCurrencyId,
|
||||
source = StatusSource.ACTUAL,
|
||||
ifNotFound = ::QuoteStatus,
|
||||
expected = setOf(btcQuote, adaEmptyQuote),
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class MultiUpdateStatusSource {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun updateStatusSource(model: UpdateStatusSourceModel.Multi) = runTest {
|
||||
// Arrange
|
||||
if (model.initialRuntime != null) {
|
||||
runtimeStore.store(value = model.initialRuntime)
|
||||
}
|
||||
|
||||
// Act
|
||||
store.updateStatusSource(
|
||||
currenciesIds = model.currenciesIds,
|
||||
source = model.source,
|
||||
ifNotFound = model.ifNotFound,
|
||||
)
|
||||
|
||||
val actual = store.getAllSyncOrNull()
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
// region runtime store is null
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = null,
|
||||
currenciesIds = setOf(),
|
||||
source = StatusSource.CACHE, // never-mind
|
||||
expected = null,
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = null,
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
source = StatusSource.ONLY_CACHE, // never-mind
|
||||
expected = emptySet(),
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = null,
|
||||
currenciesIds = setOf(
|
||||
btcQuote.rawCurrencyId,
|
||||
CryptoCurrency.RawID(value = "ETH"),
|
||||
),
|
||||
source = StatusSource.ACTUAL, // never-mind
|
||||
expected = emptySet(),
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = null,
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
source = StatusSource.ONLY_CACHE, // never-mind
|
||||
ifNotFound = ::QuoteStatus,
|
||||
expected = setOf(
|
||||
QuoteStatus(rawCurrencyId = btcQuote.rawCurrencyId),
|
||||
),
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region runtime store is empty
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = emptySet(),
|
||||
currenciesIds = setOf(),
|
||||
source = StatusSource.CACHE, // never-mind
|
||||
expected = emptySet(),
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = emptySet(),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
source = StatusSource.ONLY_CACHE, // never-mind
|
||||
expected = emptySet(),
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = emptySet(),
|
||||
currenciesIds = setOf(
|
||||
btcQuote.rawCurrencyId,
|
||||
CryptoCurrency.RawID(value = "ETH"),
|
||||
),
|
||||
source = StatusSource.ACTUAL, // never-mind
|
||||
expected = emptySet(),
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = emptySet(),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId),
|
||||
source = StatusSource.ONLY_CACHE, // never-mind
|
||||
ifNotFound = ::QuoteStatus,
|
||||
expected = setOf(
|
||||
QuoteStatus(rawCurrencyId = btcQuote.rawCurrencyId),
|
||||
),
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region runtime store contains statuses
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = setOf(btcQuote, adaEmptyQuote),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId, adaEmptyQuote.rawCurrencyId),
|
||||
source = StatusSource.CACHE,
|
||||
expected = setOf(
|
||||
btcQuote.copy(value = btcQuote.value.copySealed(source = StatusSource.CACHE)),
|
||||
adaEmptyQuote,
|
||||
),
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = setOf(btcQuote, adaEmptyQuote),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId, adaEmptyQuote.rawCurrencyId),
|
||||
source = StatusSource.ONLY_CACHE,
|
||||
expected = setOf(
|
||||
btcQuote.copy(value = btcQuote.value.copySealed(source = StatusSource.ONLY_CACHE)),
|
||||
adaEmptyQuote,
|
||||
),
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = setOf(btcQuote, adaEmptyQuote),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId, adaEmptyQuote.rawCurrencyId),
|
||||
source = StatusSource.ACTUAL,
|
||||
expected = setOf(btcQuote, adaEmptyQuote),
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = setOf(btcQuote),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId, adaEmptyQuote.rawCurrencyId),
|
||||
source = StatusSource.ACTUAL,
|
||||
expected = setOf(btcQuote),
|
||||
),
|
||||
UpdateStatusSourceModel.Multi(
|
||||
initialRuntime = setOf(btcQuote),
|
||||
currenciesIds = setOf(btcQuote.rawCurrencyId, adaEmptyQuote.rawCurrencyId),
|
||||
source = StatusSource.ACTUAL,
|
||||
ifNotFound = ::QuoteStatus,
|
||||
expected = setOf(btcQuote, adaEmptyQuote),
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface UpdateStatusSourceModel {
|
||||
|
||||
val initialRuntime: Set<QuoteStatus>?
|
||||
val source: StatusSource
|
||||
val ifNotFound: (CryptoCurrency.RawID) -> QuoteStatus?
|
||||
val expected: Set<QuoteStatus>?
|
||||
|
||||
data class Single(
|
||||
override val initialRuntime: Set<QuoteStatus>?,
|
||||
val currencyId: CryptoCurrency.RawID,
|
||||
override val source: StatusSource,
|
||||
override val ifNotFound: (CryptoCurrency.RawID) -> QuoteStatus? = { null },
|
||||
override val expected: Set<QuoteStatus>?,
|
||||
) : UpdateStatusSourceModel
|
||||
|
||||
data class Multi(
|
||||
override val initialRuntime: Set<QuoteStatus>?,
|
||||
val currenciesIds: Set<CryptoCurrency.RawID>,
|
||||
override val source: StatusSource,
|
||||
override val ifNotFound: (CryptoCurrency.RawID) -> QuoteStatus? = { null },
|
||||
override val expected: Set<QuoteStatus>?,
|
||||
) : UpdateStatusSourceModel
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Store {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun store(model: StoreTestModel) = runTest {
|
||||
// Arrange
|
||||
if (model.initialRuntime != null) {
|
||||
runtimeStore.store(value = model.initialRuntime)
|
||||
}
|
||||
|
||||
if (model.initialPersistence != null) {
|
||||
persistenceStore.updateData { model.initialPersistence }
|
||||
}
|
||||
|
||||
// Act
|
||||
store.store(values = model.values)
|
||||
|
||||
val runtimeActual = runtimeStore.getSyncOrNull()
|
||||
val persistenceActual = getEmittedValues(persistenceStore.data)
|
||||
|
||||
// Assert
|
||||
val runtimeExpected = model.runtimeExpected
|
||||
val persistenceExpected = listOf(model.persistenceExpected)
|
||||
|
||||
Truth.assertThat(runtimeActual).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceActual).isEqualTo(persistenceExpected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
// region stores are null
|
||||
StoreTestModel(
|
||||
initialRuntime = null,
|
||||
initialPersistence = null,
|
||||
values = emptyMap(),
|
||||
runtimeExpected = null,
|
||||
persistenceExpected = emptyMap(),
|
||||
),
|
||||
StoreTestModel(
|
||||
initialRuntime = null,
|
||||
initialPersistence = null,
|
||||
values = mapOf(btcQuoteDM),
|
||||
runtimeExpected = setOf(btcQuote),
|
||||
persistenceExpected = mapOf(btcQuoteDM),
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region persistence store is null
|
||||
StoreTestModel(
|
||||
initialRuntime = setOf(btcQuote),
|
||||
initialPersistence = null,
|
||||
values = emptyMap(),
|
||||
runtimeExpected = setOf(btcQuote),
|
||||
persistenceExpected = emptyMap(),
|
||||
),
|
||||
StoreTestModel(
|
||||
initialRuntime = setOf(ethQuote),
|
||||
initialPersistence = null,
|
||||
values = mapOf(btcQuoteDM),
|
||||
runtimeExpected = setOf(ethQuote, btcQuote),
|
||||
persistenceExpected = mapOf(btcQuoteDM),
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region runtime store is null
|
||||
StoreTestModel(
|
||||
initialRuntime = null,
|
||||
initialPersistence = mapOf(btcQuoteDM),
|
||||
values = emptyMap(),
|
||||
runtimeExpected = null,
|
||||
persistenceExpected = mapOf(btcQuoteDM),
|
||||
),
|
||||
StoreTestModel(
|
||||
initialRuntime = null,
|
||||
initialPersistence = mapOf(ethQuoteDM),
|
||||
values = mapOf(btcQuoteDM),
|
||||
runtimeExpected = setOf(btcQuote),
|
||||
persistenceExpected = mapOf(ethQuoteDM, btcQuoteDM),
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region stores contain data
|
||||
StoreTestModel(
|
||||
initialRuntime = setOf(btcQuote),
|
||||
initialPersistence = mapOf(btcQuoteDM),
|
||||
values = emptyMap(),
|
||||
runtimeExpected = setOf(btcQuote),
|
||||
persistenceExpected = mapOf(btcQuoteDM),
|
||||
),
|
||||
StoreTestModel(
|
||||
initialRuntime = setOf(btcQuote),
|
||||
initialPersistence = mapOf(btcQuoteDM),
|
||||
values = mapOf(ethQuoteDM),
|
||||
runtimeExpected = setOf(ethQuote, btcQuote),
|
||||
persistenceExpected = mapOf(ethQuoteDM, btcQuoteDM),
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
}
|
||||
|
||||
data class StoreTestModel(
|
||||
val initialRuntime: Set<QuoteStatus>?,
|
||||
val initialPersistence: CurrencyIdWithQuote?,
|
||||
val values: CurrencyIdWithQuote,
|
||||
val persistenceExpected: CurrencyIdWithQuote?,
|
||||
val runtimeExpected: Set<QuoteStatus>?,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
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.QuoteStatus
|
||||
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<QuoteStatus>>()
|
||||
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 = getEmittedValues(flow = actual)
|
||||
|
||||
Truth.assertThat(values).isEqualTo(emptyList<Set<QuoteStatus>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test get if runtime store contains empty set`() = runTest {
|
||||
runtimeStore.store(value = emptySet())
|
||||
|
||||
val actual = store.get()
|
||||
|
||||
val values = getEmittedValues(flow = actual)
|
||||
|
||||
Truth.assertThat(values).isEqualTo(listOf(emptySet<QuoteStatus>()))
|
||||
}
|
||||
|
||||
@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 = getEmittedValues(flow = actual)
|
||||
|
||||
Truth.assertThat(values.size).isEqualTo(1)
|
||||
Truth.assertThat(values).isEqualTo(listOf(setOf(btcQuote.toDomain(), ethQuote.toDomain())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test getAllSyncOrNull if runtime store is empty`() = runTest {
|
||||
val actual = store.getAllSyncOrNull()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test getAllSyncOrNull if runtime store contains empty set`() = runTest {
|
||||
runtimeStore.store(value = emptySet())
|
||||
|
||||
val actual = store.getAllSyncOrNull()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(emptySet<QuoteStatus>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test getAllSyncOrNull 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.getAllSyncOrNull()
|
||||
|
||||
val expected = setOf(btcQuote.toDomain(), ethQuote.toDomain())
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
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.QuoteStatus
|
||||
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<QuoteStatus>>()
|
||||
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<QuoteStatus>>()
|
||||
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<QuoteStatus>>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
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.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.QuoteStatus
|
||||
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<QuoteStatus>>()
|
||||
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)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(emptySet<QuoteStatus>())
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<QuoteStatus>>())
|
||||
}
|
||||
|
||||
@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.copy(value = quote.value.copySealed(source = StatusSource.CACHE)),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<QuoteStatus>>())
|
||||
}
|
||||
|
||||
@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 { QuoteStatus(rawCurrencyId = it, value = QuoteStatus.Empty) }
|
||||
.toSet()
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<QuoteStatus>>())
|
||||
}
|
||||
|
||||
@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),
|
||||
QuoteStatus(rawCurrencyId = CryptoCurrency.RawID(value = "ETH"), value = QuoteStatus.Empty),
|
||||
),
|
||||
)
|
||||
|
||||
store.storeError(
|
||||
currenciesIds = setOf(
|
||||
CryptoCurrency.RawID(value = "BTC"),
|
||||
CryptoCurrency.RawID(value = "ETH"),
|
||||
),
|
||||
)
|
||||
|
||||
val runtimeExpected = setOf(
|
||||
status.toDomain(rawCurrencyId = "BTC", source = StatusSource.ONLY_CACHE),
|
||||
QuoteStatus(rawCurrencyId = CryptoCurrency.RawID(value = "ETH"), value = QuoteStatus.Empty),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<QuoteStatus>>())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue