Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-23 11:42:55 +03:00
parent 78a173a140
commit 2a2e0b3323
6 changed files with 123 additions and 45 deletions

View file

@ -19,6 +19,7 @@ import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -76,7 +77,16 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
currencies = networkCurrencies.toSet(),
)
networksStatusesStore.storeActual(userWalletId = params.userWalletId, value = status)
val statusValue = status.value
if (statusValue is NetworkStatus.Unreachable) {
networksStatusesStore.storeError(
userWalletId = params.userWalletId,
network = params.network,
value = statusValue,
)
} else {
networksStatusesStore.storeSuccess(userWalletId = params.userWalletId, value = status)
}
}
.onLeft {
Timber.e("Failed to fetch network status for $params: $it")

View file

@ -70,9 +70,19 @@ internal class DefaultNetworksStatusesStoreV2(
}
}
override suspend fun storeActual(userWalletId: UserWalletId, value: NetworkStatus) {
override suspend fun storeSuccess(userWalletId: UserWalletId, value: NetworkStatus) {
if (value.value is NetworkStatus.Unreachable) {
val message = "Use storeError method to save unreachable status"
Timber.d(message)
error(message)
}
if (value.value.source != StatusSource.ACTUAL) {
error("Method storeActual can be called only with StatusSource.ACTUAL")
val message = "Method storeActual can be called only with StatusSource.ACTUAL"
Timber.d(message)
error(message)
}
coroutineScope {
@ -81,11 +91,14 @@ internal class DefaultNetworksStatusesStoreV2(
}
}
override suspend fun storeError(userWalletId: UserWalletId, network: Network) {
override suspend fun storeError(userWalletId: UserWalletId, network: Network, value: NetworkStatus.Unreachable?) {
updateInRuntime(
userWalletId = userWalletId,
networks = setOf(network),
ifNotFound = ::createUnreachableStatus,
ifNotFound = { id ->
value?.let { SimpleNetworkStatus(id = id, value = value) }
?: createUnreachableStatus(id = id)
},
update = { status ->
status.copy(value = status.value.copySealed(source = StatusSource.ONLY_CACHE))
},

View file

@ -18,9 +18,13 @@ internal interface NetworksStatusesStoreV2 {
/** Refresh statuses of [networks] by [userWalletId] */
suspend fun refresh(userWalletId: UserWalletId, networks: Set<Network>)
/** Store actual [NetworkStatus] by [userWalletId] */
suspend fun storeActual(userWalletId: UserWalletId, value: NetworkStatus)
/** Store success [value] by [userWalletId]. If status's value is unreachable, throws exception */
@Throws
suspend fun storeSuccess(userWalletId: UserWalletId, value: NetworkStatus)
/** Store error by [userWalletId] and [network] */
suspend fun storeError(userWalletId: UserWalletId, network: Network)
/**
* Store error [value] by [userWalletId] and [network].
* If [value] is null, default unreachable status will be stored.
*/
suspend fun storeError(userWalletId: UserWalletId, network: Network, value: NetworkStatus.Unreachable? = null)
}

View file

@ -52,7 +52,7 @@ internal class DefaultSingleNetworkStatusFetcherTest {
networksStatusesStore.refresh(userWalletId = userWalletId, network = network)
userWalletsStore.getSyncStrict(key = userWalletId)
walletManagersFacade.update(userWalletId, network, emptySet())
networksStatusesStore.storeActual(
networksStatusesStore.storeSuccess(
userWalletId = userWalletId,
value = NetworkStatus(network, NetworkStatus.MissedDerivation),
)
@ -82,7 +82,7 @@ internal class DefaultSingleNetworkStatusFetcherTest {
coVerify(inverse = true) {
walletManagersFacade.update(userWalletId = any(), network = any(), extraTokens = any())
networksStatusesStore.storeActual(userWalletId = any(), value = any())
networksStatusesStore.storeSuccess(userWalletId = any(), value = any())
}
Truth.assertThat(actual.isLeft()).isTrue()
@ -105,7 +105,7 @@ internal class DefaultSingleNetworkStatusFetcherTest {
coVerifyOrder {
userWalletsStore.getSyncStrict(key = userWalletId)
walletManagersFacade.update(userWalletId, network, emptySet())
networksStatusesStore.storeActual(
networksStatusesStore.storeSuccess(
userWalletId = userWalletId,
value = NetworkStatus(network, NetworkStatus.MissedDerivation),
)

View file

@ -43,7 +43,7 @@ internal class NetworkStatusesStoreUpdateMethodsTest {
@Test
fun `refresh the single network if runtime store contains status with this network`() = runTest {
val status = MockNetworkStatusFactory.createVerified().toSimple()
val status = MockNetworkStatusFactory.createVerified(network).toSimple()
runtimeStore.store(
value = mapOf(userWalletId.stringValue to setOf(status)),
@ -98,41 +98,50 @@ internal class NetworkStatusesStoreUpdateMethodsTest {
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 status = MockNetworkStatusFactory.createVerified(network).copy(
value = MockNetworkStatusFactory.createVerified(network).value.copySealed(source = StatusSource.ACTUAL),
)
val actual = runCatching { store.storeActual(userWalletId, status) }
val actual = runCatching { store.storeSuccess(userWalletId, status) }
val runtimeExpected = mapOf(userWalletId.stringValue to setOf(status.toSimple()))
val persistenceExpected = mapOf(userWalletId.stringValue to setOf(status.toDataModel()!!))
Truth.assertThat(actual.isSuccess).isTrue()
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(persistenceExpected)
// #2: StatusSource.CACHE
val cacheStatus = MockNetworkStatusFactory.createVerified().copy(
value = MockNetworkStatusFactory.createVerified().value.copySealed(source = StatusSource.CACHE),
val cacheStatus = MockNetworkStatusFactory.createVerified(network).copy(
value = MockNetworkStatusFactory.createVerified(network).value.copySealed(source = StatusSource.CACHE),
)
val cacheActual = runCatching { store.storeActual(userWalletId, cacheStatus) }
val cacheActual = runCatching { store.storeSuccess(userWalletId, cacheStatus) }
Truth.assertThat(cacheActual.isFailure).isTrue()
Truth.assertThat(cacheActual.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java)
Truth.assertThat(cacheActual.exceptionOrNull()).hasMessageThat().isEqualTo(expectedErrorMessage)
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(persistenceExpected)
// #3: StatusSource.ONLY_CACHE
val onlyCacheStatus = MockNetworkStatusFactory.createVerified().copy(
value = MockNetworkStatusFactory.createVerified().value.copySealed(source = StatusSource.ONLY_CACHE),
val onlyCacheStatus = MockNetworkStatusFactory.createVerified(network).copy(
value = MockNetworkStatusFactory.createVerified(network).value.copySealed(source = StatusSource.ONLY_CACHE),
)
val onlyCacheActual = runCatching { store.storeActual(userWalletId, onlyCacheStatus) }
val onlyCacheActual = runCatching { store.storeSuccess(userWalletId, onlyCacheStatus) }
Truth.assertThat(onlyCacheActual.isFailure).isTrue()
Truth.assertThat(onlyCacheActual.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java)
Truth.assertThat(onlyCacheActual.exceptionOrNull()).hasMessageThat().isEqualTo(expectedErrorMessage)
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(persistenceExpected)
}
@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),
val prevStatus = MockNetworkStatusFactory.createVerified(network).copy(
value = MockNetworkStatusFactory.createVerified(network).value.copySealed(source = StatusSource.ONLY_CACHE),
)
runtimeStore.store(
@ -145,11 +154,11 @@ internal class NetworkStatusesStoreUpdateMethodsTest {
}
}
val newStatus = MockNetworkStatusFactory.createVerified().copy(
value = MockNetworkStatusFactory.createVerified().value.copySealed(source = StatusSource.ACTUAL),
val newStatus = MockNetworkStatusFactory.createVerified(network).copy(
value = MockNetworkStatusFactory.createVerified(network).value.copySealed(source = StatusSource.ACTUAL),
)
store.storeActual(userWalletId, newStatus)
store.storeSuccess(userWalletId, newStatus)
val runtimeExpected = mapOf(
userWalletId.stringValue to setOf(newStatus.toSimple()),
@ -165,7 +174,7 @@ internal class NetworkStatusesStoreUpdateMethodsTest {
@Test
fun `store actual unreachable status if cache store contains verified status`() = runTest {
val prevStatus = MockNetworkStatusFactory.createVerified()
val prevStatus = MockNetworkStatusFactory.createVerified(network)
runtimeStore.store(
value = mapOf(userWalletId.stringValue to setOf(prevStatus.toSimple())),
@ -177,18 +186,17 @@ internal class NetworkStatusesStoreUpdateMethodsTest {
}
}
val newStatus = MockNetworkStatusFactory.createUnreachable()
val newStatus = MockNetworkStatusFactory.createUnreachable(network)
store.storeActual(userWalletId, newStatus)
val actual = runCatching { store.storeSuccess(userWalletId, newStatus) }
val runtimeExpected = mapOf(
userWalletId.stringValue to setOf(newStatus.toSimple()),
)
val persistenceExpected = mapOf(
userWalletId.stringValue to setOf(prevStatus.toDataModel()!!),
)
val expectedErrorMessage = "Use storeError method to save unreachable status"
val runtimeExpected = mapOf(userWalletId.stringValue to setOf(prevStatus.toSimple()))
val persistenceExpected = mapOf(userWalletId.stringValue to setOf(prevStatus.toDataModel()!!))
Truth.assertThat(actual.isFailure).isTrue()
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java)
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(expectedErrorMessage)
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(persistenceExpected)
}
@ -212,7 +220,7 @@ internal class NetworkStatusesStoreUpdateMethodsTest {
@Test
fun `store error if runtime store contains status with this network`() = runTest {
val status = MockNetworkStatusFactory.createVerified().toSimple()
val status = MockNetworkStatusFactory.createVerified(network).toSimple()
runtimeStore.store(
value = mapOf(userWalletId.stringValue to setOf(status)),
@ -230,6 +238,48 @@ internal class NetworkStatusesStoreUpdateMethodsTest {
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<NetworkStatusDM>>())
}
@Test
fun `store error if runtime store is empty and unreachable status was passed`() = runTest {
val status = MockNetworkStatusFactory.createUnreachable(network)
store.storeError(
userWalletId = userWalletId,
network = network,
value = status.value as NetworkStatus.Unreachable,
)
val runtimeExpected = mapOf(
userWalletId.stringValue to setOf(status.toSimple()),
)
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 and unreachable status was passed`() = runTest {
val status = MockNetworkStatusFactory.createVerified(network)
runtimeStore.store(
value = mapOf(userWalletId.stringValue to setOf(status.toSimple())),
)
store.storeError(
userWalletId = userWalletId,
network = network,
value = MockNetworkStatusFactory.createUnreachable(network).value as NetworkStatus.Unreachable,
)
val runtimeExpected = mapOf(
userWalletId.stringValue to setOf(
status.copy(value = status.value.copySealed(source = StatusSource.ONLY_CACHE)).toSimple(),
),
)
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap<String, Set<NetworkStatusDM>>())
}
private companion object {
val userWalletId = UserWalletId(stringValue = "011")

View file

@ -33,15 +33,16 @@ internal class StoreActualNetworkStatusTest(private val model: Model) {
@Test
fun `store actual`() = runTest {
store.storeActual(userWalletId = userWalletId, value = model.status)
val actual = runCatching { store.storeSuccess(userWalletId = userWalletId, value = model.status) }
Truth.assertThat(actual.isSuccess).isEqualTo(model.isSuccess)
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(model.runtimeExpected)
Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(model.persistenceExpected)
}
data class Model(
val status: NetworkStatus,
val isSuccess: Boolean,
val runtimeExpected: Map<String, Set<SimpleNetworkStatus>>,
val persistenceExpected: WalletIdWithStatusDM,
)
@ -56,6 +57,7 @@ internal class StoreActualNetworkStatusTest(private val model: Model) {
return listOf(
Model(
status = MockNetworkStatusFactory.createVerified(),
isSuccess = true,
runtimeExpected = mapOf(
userWalletId.stringValue to setOf(
MockNetworkStatusFactory.createVerified().toSimple(),
@ -69,6 +71,7 @@ internal class StoreActualNetworkStatusTest(private val model: Model) {
),
Model(
status = MockNetworkStatusFactory.createNoAccount(),
isSuccess = true,
runtimeExpected = mapOf(
userWalletId.stringValue to setOf(
MockNetworkStatusFactory.createNoAccount().toSimple(),
@ -82,6 +85,7 @@ internal class StoreActualNetworkStatusTest(private val model: Model) {
),
Model(
status = MockNetworkStatusFactory.createMissedDerivation(),
isSuccess = true,
runtimeExpected = mapOf(
userWalletId.stringValue to setOf(
MockNetworkStatusFactory.createMissedDerivation().toSimple(),
@ -91,11 +95,8 @@ internal class StoreActualNetworkStatusTest(private val model: Model) {
),
Model(
status = MockNetworkStatusFactory.createUnreachable(),
runtimeExpected = mapOf(
userWalletId.stringValue to setOf(
MockNetworkStatusFactory.createUnreachable().toSimple(),
),
),
isSuccess = false,
runtimeExpected = mapOf(),
persistenceExpected = mapOf(),
),
)