Updated on 2026-08-14
This commit is contained in:
parent
5ecc4c6775
commit
c38c185a2d
19 changed files with 858 additions and 5 deletions
1
data/networks/.gitignore
vendored
Normal file
1
data/networks/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
43
data/networks/build.gradle.kts
Normal file
43
data/networks/build.gradle.kts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.networks"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
implementation(projects.data.common)
|
||||
implementation(projects.data.tokens)
|
||||
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.timber)
|
||||
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(tangemDeps.card.core)
|
||||
testImplementation(projects.common.test)
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.data.networks.converters
|
||||
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.datasource.local.network.converter.NetworkAddressConverter
|
||||
import com.tangem.datasource.local.network.converter.NetworkAmountsConverter
|
||||
import com.tangem.datasource.local.network.converter.NetworkDerivationPathConverter
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converter from [NetworkStatusDM] to [SimpleNetworkStatus]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object SimpleNetworkStatusConverter : Converter<NetworkStatusDM, SimpleNetworkStatus> {
|
||||
|
||||
override fun convert(value: NetworkStatusDM): SimpleNetworkStatus {
|
||||
val address = NetworkAddressConverter(selectedAddress = value.selectedAddress)
|
||||
.convert(value = value.availableAddresses)
|
||||
|
||||
val status = when (value) {
|
||||
is NetworkStatusDM.Verified -> {
|
||||
NetworkStatus.Verified(
|
||||
address = address,
|
||||
amounts = NetworkAmountsConverter.convert(value = value.amounts),
|
||||
pendingTransactions = mapOf(),
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
is NetworkStatusDM.NoAccount -> {
|
||||
NetworkStatus.NoAccount(
|
||||
address = address,
|
||||
amountToCreateAccount = value.amountToCreateAccount,
|
||||
errorMessage = value.errorMessage,
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return SimpleNetworkStatus(
|
||||
id = SimpleNetworkStatus.Id(
|
||||
networkId = value.networkId,
|
||||
derivationPath = NetworkDerivationPathConverter.convert(value = value.derivationPath),
|
||||
),
|
||||
value = status,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.data.networks.models
|
||||
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
|
||||
/**
|
||||
* Simplified model of network status. Like [NetworkStatus] but without metadata.
|
||||
*
|
||||
* @property id identifier
|
||||
* @property value value
|
||||
*/
|
||||
internal data class SimpleNetworkStatus(val id: Id, val value: NetworkStatus.Value) {
|
||||
|
||||
/**
|
||||
* Identifier of [SimpleNetworkStatus]
|
||||
*
|
||||
* @property networkId network id
|
||||
* @property derivationPath derivation path
|
||||
*/
|
||||
data class Id(val networkId: Network.ID, val derivationPath: Network.DerivationPath) {
|
||||
|
||||
constructor(network: Network) : this(
|
||||
networkId = network.id,
|
||||
derivationPath = network.derivationPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package com.tangem.data.networks.store
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.data.networks.converters.SimpleNetworkStatusConverter
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.network.converter.NetworkStatusDataModelConverter
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
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.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal typealias WalletIdWithSimpleStatus = Map<String, Set<SimpleNetworkStatus>>
|
||||
internal typealias WalletIdWithStatusDM = Map<String, Set<NetworkStatusDM>>
|
||||
|
||||
/**
|
||||
* Default implementation of [NetworksStatusesStoreV2]
|
||||
*
|
||||
* @property runtimeStore runtime store
|
||||
* @property persistenceDataStore persistence store
|
||||
* @param dispatchers dispatchers
|
||||
*/
|
||||
internal class DefaultNetworksStatusesStoreV2(
|
||||
private val runtimeStore: RuntimeSharedStore<WalletIdWithSimpleStatus>,
|
||||
private val persistenceDataStore: DataStore<WalletIdWithStatusDM>,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) : NetworksStatusesStoreV2 {
|
||||
|
||||
private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io)
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
val cachedStatuses = persistenceDataStore.data.firstOrNull() ?: return@launch
|
||||
|
||||
runtimeStore.store(
|
||||
value = cachedStatuses.mapValues { (_, statuses) ->
|
||||
SimpleNetworkStatusConverter.convertSet(statuses)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(userWalletId: UserWalletId): Flow<Set<SimpleNetworkStatus>> {
|
||||
return runtimeStore.get().mapNotNull { it[userWalletId.stringValue] }
|
||||
}
|
||||
|
||||
override suspend fun refresh(userWalletId: UserWalletId, network: Network) {
|
||||
updateStatusSourceInRuntime(userWalletId = userWalletId, network = network, source = StatusSource.CACHE)
|
||||
}
|
||||
|
||||
override suspend fun storeActual(userWalletId: UserWalletId, value: NetworkStatus) {
|
||||
if (value.value.source != StatusSource.ACTUAL) {
|
||||
error("Method storeActual can be called only with StatusSource.ACTUAL")
|
||||
}
|
||||
|
||||
coroutineScope {
|
||||
launch { storeInRuntime(userWalletId = userWalletId, status = value) }
|
||||
launch { storeInPersistence(userWalletId = userWalletId, status = value) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun storeError(userWalletId: UserWalletId, network: Network) {
|
||||
updateStatusSourceInRuntime(userWalletId = userWalletId, network = network, source = StatusSource.ONLY_CACHE)
|
||||
}
|
||||
|
||||
private suspend fun updateStatusSourceInRuntime(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
source: StatusSource,
|
||||
) {
|
||||
val simpleStatusId = SimpleNetworkStatus.Id(network)
|
||||
|
||||
runtimeStore.update(default = emptyMap()) { stored ->
|
||||
stored.toMutableMap().apply {
|
||||
val status = this[userWalletId.stringValue].orEmpty()
|
||||
.firstOrNull { it.id == simpleStatusId }
|
||||
?: SimpleNetworkStatus(
|
||||
id = simpleStatusId,
|
||||
value = NetworkStatus.Unreachable(address = null),
|
||||
)
|
||||
|
||||
val updatedStatus = status.copy(
|
||||
value = status.value.copySealed(source = source),
|
||||
)
|
||||
|
||||
val updatedStatuses = this[userWalletId.stringValue].orEmpty()
|
||||
.addOrReplace(updatedStatus) { it.id == simpleStatusId }
|
||||
|
||||
put(key = userWalletId.stringValue, value = updatedStatuses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInRuntime(userWalletId: UserWalletId, status: NetworkStatus) {
|
||||
val simpleStatusId = SimpleNetworkStatus.Id(network = status.network)
|
||||
|
||||
runtimeStore.update(default = emptyMap()) { stored ->
|
||||
stored.toMutableMap().apply {
|
||||
val simpleStatus = SimpleNetworkStatus(id = simpleStatusId, value = status.value)
|
||||
|
||||
val updatedStatuses = this[userWalletId.stringValue].orEmpty()
|
||||
.addOrReplace(simpleStatus) { it.id == simpleStatusId }
|
||||
|
||||
put(key = userWalletId.stringValue, value = updatedStatuses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInPersistence(userWalletId: UserWalletId, status: NetworkStatus) {
|
||||
// Converter will return null if the network status is not supported
|
||||
val statusDM = NetworkStatusDataModelConverter.convert(value = status) ?: return
|
||||
|
||||
persistenceDataStore.updateData { storedStatuses ->
|
||||
storedStatuses.toMutableMap().apply {
|
||||
val updatedValues = this[userWalletId.stringValue].orEmpty().addOrReplace(statusDM) {
|
||||
it.networkId == statusDM.networkId && it.derivationPath == statusDM.derivationPath
|
||||
}
|
||||
|
||||
this[userWalletId.stringValue] = updatedValues
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.data.networks.store
|
||||
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Store of [NetworkStatus]'es set */
|
||||
internal interface NetworksStatusesStoreV2 {
|
||||
|
||||
/** Get statuses by [userWalletId] */
|
||||
fun get(userWalletId: UserWalletId): Flow<Set<SimpleNetworkStatus>>
|
||||
|
||||
/** Refresh status of [network] by [userWalletId] */
|
||||
suspend fun refresh(userWalletId: UserWalletId, network: Network)
|
||||
|
||||
/** Store actual [NetworkStatus] by [userWalletId] */
|
||||
suspend fun storeActual(userWalletId: UserWalletId, value: NetworkStatus)
|
||||
|
||||
/** Store error by [userWalletId] and [network] */
|
||||
suspend fun storeError(userWalletId: UserWalletId, network: Network)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.data.networks
|
||||
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.datasource.local.network.converter.NetworkStatusDataModelConverter
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
|
||||
internal fun NetworkStatus.toSimple(): SimpleNetworkStatus {
|
||||
return SimpleNetworkStatus(
|
||||
id = SimpleNetworkStatus.Id(network = network),
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun NetworkStatus.toDataModel(): NetworkStatusDM? {
|
||||
return NetworkStatusDataModelConverter.convert(value = this)
|
||||
}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
package com.tangem.data.networks.store
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.datastore.MockStateDataStore
|
||||
import com.tangem.common.test.domain.network.MockNetworkStatusFactory
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.data.networks.toDataModel
|
||||
import com.tangem.data.networks.toSimple
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class NetworkStatusesStoreUpdateMethodsTest {
|
||||
|
||||
private val runtimeStore = RuntimeSharedStore<WalletIdWithSimpleStatus>()
|
||||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `refresh if runtime store is empty`() = runTest {
|
||||
store.refresh(userWalletId = userWalletId, network = network)
|
||||
|
||||
val runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
SimpleNetworkStatus(
|
||||
id = SimpleNetworkStatus.Id(network),
|
||||
value = NetworkStatus.Unreachable(address = null),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<NetworkStatusDM>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `refresh if runtime store contains status with this network`() = runTest {
|
||||
val status = MockNetworkStatusFactory.createVerified().toSimple()
|
||||
|
||||
runtimeStore.store(
|
||||
value = mapOf(userWalletId.stringValue to setOf(status)),
|
||||
)
|
||||
|
||||
store.refresh(userWalletId = userWalletId, network = network)
|
||||
|
||||
val runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
status.copy(value = status.value.copySealed(source = StatusSource.CACHE)),
|
||||
),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<NetworkStatusDM>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `store actual with any status sources`() = runTest {
|
||||
val expectedErrorMessage = "Method storeActual can be called only with StatusSource.ACTUAL"
|
||||
|
||||
// #1: StatusSource.ACTUAL
|
||||
val status = MockNetworkStatusFactory.createVerified().copy(
|
||||
value = MockNetworkStatusFactory.createVerified().value.copySealed(source = StatusSource.ACTUAL),
|
||||
)
|
||||
|
||||
val actual = runCatching { store.storeActual(userWalletId, status) }
|
||||
|
||||
Truth.assertThat(actual.isSuccess).isTrue()
|
||||
|
||||
// #2: StatusSource.CACHE
|
||||
val cacheStatus = MockNetworkStatusFactory.createVerified().copy(
|
||||
value = MockNetworkStatusFactory.createVerified().value.copySealed(source = StatusSource.CACHE),
|
||||
)
|
||||
|
||||
val cacheActual = runCatching { store.storeActual(userWalletId, cacheStatus) }
|
||||
|
||||
Truth.assertThat(cacheActual.isFailure).isTrue()
|
||||
Truth.assertThat(cacheActual.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java)
|
||||
Truth.assertThat(cacheActual.exceptionOrNull()).hasMessageThat().isEqualTo(expectedErrorMessage)
|
||||
|
||||
// #3: StatusSource.ONLY_CACHE
|
||||
val onlyCacheStatus = MockNetworkStatusFactory.createVerified().copy(
|
||||
value = MockNetworkStatusFactory.createVerified().value.copySealed(source = StatusSource.ONLY_CACHE),
|
||||
)
|
||||
|
||||
val onlyCacheActual = runCatching { store.storeActual(userWalletId, onlyCacheStatus) }
|
||||
|
||||
Truth.assertThat(onlyCacheActual.isFailure).isTrue()
|
||||
Truth.assertThat(onlyCacheActual.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java)
|
||||
Truth.assertThat(onlyCacheActual.exceptionOrNull()).hasMessageThat().isEqualTo(expectedErrorMessage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `store actual if runtime and cache stores contain status with this network`() = runTest {
|
||||
val prevStatus = MockNetworkStatusFactory.createVerified().copy(
|
||||
value = MockNetworkStatusFactory.createVerified().value.copySealed(source = StatusSource.ONLY_CACHE),
|
||||
)
|
||||
|
||||
runtimeStore.store(
|
||||
value = mapOf(userWalletId.stringValue to setOf(prevStatus.toSimple())),
|
||||
)
|
||||
|
||||
persistenceStore.updateData {
|
||||
it.toMutableMap().apply {
|
||||
put(userWalletId.stringValue, setOf(prevStatus.toDataModel()!!))
|
||||
}
|
||||
}
|
||||
|
||||
val newStatus = MockNetworkStatusFactory.createVerified().copy(
|
||||
value = MockNetworkStatusFactory.createVerified().value.copySealed(source = StatusSource.ACTUAL),
|
||||
)
|
||||
|
||||
store.storeActual(userWalletId, newStatus)
|
||||
|
||||
val runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(newStatus.toSimple()),
|
||||
)
|
||||
|
||||
val persistenceExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(newStatus.toDataModel()!!),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(persistenceExpected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `store actual unreachable status if cache store contains verified status`() = runTest {
|
||||
val prevStatus = MockNetworkStatusFactory.createVerified()
|
||||
|
||||
runtimeStore.store(
|
||||
value = mapOf(userWalletId.stringValue to setOf(prevStatus.toSimple())),
|
||||
)
|
||||
|
||||
persistenceStore.updateData {
|
||||
it.toMutableMap().apply {
|
||||
put(userWalletId.stringValue, setOf(prevStatus.toDataModel()!!))
|
||||
}
|
||||
}
|
||||
|
||||
val newStatus = MockNetworkStatusFactory.createUnreachable()
|
||||
|
||||
store.storeActual(userWalletId, newStatus)
|
||||
|
||||
val runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(newStatus.toSimple()),
|
||||
)
|
||||
|
||||
val persistenceExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(prevStatus.toDataModel()!!),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(persistenceExpected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `store error if runtime store is empty`() = runTest {
|
||||
store.storeError(userWalletId = userWalletId, network = network)
|
||||
|
||||
val runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
SimpleNetworkStatus(
|
||||
id = SimpleNetworkStatus.Id(network),
|
||||
value = NetworkStatus.Unreachable(address = null),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<NetworkStatusDM>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `store error if runtime store contains status with this network`() = runTest {
|
||||
val status = MockNetworkStatusFactory.createVerified().toSimple()
|
||||
|
||||
runtimeStore.store(
|
||||
value = mapOf(userWalletId.stringValue to setOf(status)),
|
||||
)
|
||||
|
||||
store.storeError(userWalletId = userWalletId, network = network)
|
||||
|
||||
val runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
status.copy(value = status.value.copySealed(source = StatusSource.ONLY_CACHE)),
|
||||
),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<NetworkStatusDM>>())
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val userWalletId = UserWalletId(stringValue = "011")
|
||||
|
||||
val network = MockCryptoCurrencyFactory().ethereum.network
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.data.networks.store
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.datastore.MockStateDataStore
|
||||
import com.tangem.common.test.domain.network.MockNetworkStatusFactory
|
||||
import com.tangem.common.test.utils.getEmittedValues
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.data.networks.toSimple
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class NetworksStatusesStoreGetMethodTest {
|
||||
|
||||
private val runtimeStore = RuntimeSharedStore<WalletIdWithSimpleStatus>()
|
||||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `test get if runtime store is empty`() = runTest {
|
||||
val actual = store.get(userWalletId = userWalletId)
|
||||
|
||||
val values = backgroundScope.getEmittedValues(testScheduler, actual)
|
||||
|
||||
Truth.assertThat(values).isEqualTo(emptyList<Set<SimpleNetworkStatus>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test get if runtime store contains empty map`() = runTest {
|
||||
runtimeStore.store(value = emptyMap())
|
||||
|
||||
val actual = store.get(userWalletId = userWalletId)
|
||||
|
||||
val values = backgroundScope.getEmittedValues(testScheduler, actual)
|
||||
|
||||
Truth.assertThat(values).isEqualTo(emptyList<Set<SimpleNetworkStatus>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test get if runtime store contains portfolio with empty statuses`() = runTest {
|
||||
runtimeStore.store(
|
||||
value = mapOf(userWalletId.stringValue to emptySet()),
|
||||
)
|
||||
|
||||
val actual = store.get(userWalletId = userWalletId)
|
||||
|
||||
val values = backgroundScope.getEmittedValues(testScheduler, actual)
|
||||
|
||||
Truth.assertThat(values.size).isEqualTo(1)
|
||||
Truth.assertThat(values).isEqualTo(listOf(emptySet<SimpleNetworkStatus>()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test get if runtime store is not empty`() = runTest {
|
||||
val status = MockNetworkStatusFactory.createVerified()
|
||||
|
||||
runtimeStore.store(
|
||||
value = mapOf(userWalletId.stringValue to setOf(status.toSimple())),
|
||||
)
|
||||
|
||||
val actual = store.get(userWalletId = userWalletId)
|
||||
|
||||
val values = backgroundScope.getEmittedValues(testScheduler, actual)
|
||||
|
||||
Truth.assertThat(values.size).isEqualTo(1)
|
||||
Truth.assertThat(values).isEqualTo(listOf(setOf(status.toSimple())))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val userWalletId = UserWalletId(stringValue = "011")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.data.networks.store
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.datastore.MockStateDataStore
|
||||
import com.tangem.common.test.domain.network.MockNetworkStatusFactory
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.data.networks.toDataModel
|
||||
import com.tangem.data.networks.toSimple
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
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
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class NetworksStatusesStoreInitializationTest {
|
||||
|
||||
@Test
|
||||
fun `test initialization if cache store is empty`() = runTest {
|
||||
val runtimeStore = RuntimeSharedStore<WalletIdWithSimpleStatus>()
|
||||
val persistenceStore: DataStore<WalletIdWithStatusDM> = mockk()
|
||||
|
||||
every { persistenceStore.data } returns emptyFlow()
|
||||
|
||||
DefaultNetworksStatusesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore, // local mock
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test initialization if cache store contains empty map`() = runTest {
|
||||
val runtimeStore = RuntimeSharedStore<WalletIdWithSimpleStatus>()
|
||||
val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
DefaultNetworksStatusesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(emptyMap<String, Set<SimpleNetworkStatus>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test initialization if cache store is not empty`() = runTest {
|
||||
val runtimeStore = RuntimeSharedStore<WalletIdWithSimpleStatus>()
|
||||
val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
val status = MockNetworkStatusFactory.createVerified()
|
||||
|
||||
persistenceStore.updateData {
|
||||
it.toMutableMap().apply {
|
||||
put(userWalletId.stringValue, setOf(status.toDataModel()!!))
|
||||
}
|
||||
}
|
||||
|
||||
DefaultNetworksStatusesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
val expectedStatus = status.toSimple().copy(value = status.value.copySealed(source = StatusSource.CACHE))
|
||||
val expected = mapOf(userWalletId.stringValue to setOf(expectedStatus))
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val userWalletId = UserWalletId(stringValue = "011")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package com.tangem.data.networks.store
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.datastore.MockStateDataStore
|
||||
import com.tangem.common.test.domain.network.MockNetworkStatusFactory
|
||||
import com.tangem.data.networks.models.SimpleNetworkStatus
|
||||
import com.tangem.data.networks.toDataModel
|
||||
import com.tangem.data.networks.toSimple
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@RunWith(Parameterized::class)
|
||||
internal class StoreActualNetworkStatusTest(private val model: Model) {
|
||||
|
||||
private val runtimeStore = RuntimeSharedStore<WalletIdWithSimpleStatus>()
|
||||
private val persistenceStore = MockStateDataStore<WalletIdWithStatusDM>(default = emptyMap())
|
||||
|
||||
private val store = DefaultNetworksStatusesStoreV2(
|
||||
runtimeStore = runtimeStore,
|
||||
persistenceDataStore = persistenceStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `store actual`() = runTest {
|
||||
store.storeActual(userWalletId = userWalletId, value = model.status)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(model.runtimeExpected)
|
||||
|
||||
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(model.persistenceExpected)
|
||||
}
|
||||
|
||||
data class Model(
|
||||
val status: NetworkStatus,
|
||||
val runtimeExpected: Map<String, Set<SimpleNetworkStatus>>,
|
||||
val persistenceExpected: WalletIdWithStatusDM,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
|
||||
val userWalletId = UserWalletId(stringValue = "011")
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun data(): Collection<Model> {
|
||||
return listOf(
|
||||
Model(
|
||||
status = MockNetworkStatusFactory.createVerified(),
|
||||
runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
MockNetworkStatusFactory.createVerified().toSimple(),
|
||||
),
|
||||
),
|
||||
persistenceExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
MockNetworkStatusFactory.createVerified().toDataModel()!!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Model(
|
||||
status = MockNetworkStatusFactory.createNoAccount(),
|
||||
runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
MockNetworkStatusFactory.createNoAccount().toSimple(),
|
||||
),
|
||||
),
|
||||
persistenceExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
MockNetworkStatusFactory.createNoAccount().toDataModel()!!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Model(
|
||||
status = MockNetworkStatusFactory.createMissedDerivation(),
|
||||
runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
MockNetworkStatusFactory.createMissedDerivation().toSimple(),
|
||||
),
|
||||
),
|
||||
persistenceExpected = mapOf(),
|
||||
),
|
||||
Model(
|
||||
status = MockNetworkStatusFactory.createUnreachable(),
|
||||
runtimeExpected = mapOf(
|
||||
userWalletId.stringValue to setOf(
|
||||
MockNetworkStatusFactory.createUnreachable().toSimple(),
|
||||
),
|
||||
),
|
||||
persistenceExpected = mapOf(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue