diff --git a/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducer.kt b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducer.kt new file mode 100644 index 0000000000..a91cf178d7 --- /dev/null +++ b/data/networks/src/main/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducer.kt @@ -0,0 +1,44 @@ +package com.tangem.data.networks.single + +import com.tangem.domain.networks.multi.MultiNetworkStatusProducer +import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier +import com.tangem.domain.networks.single.SingleNetworkStatusProducer +import com.tangem.domain.tokens.model.NetworkStatus +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.mapNotNull + +/** + * Default implementation of [SingleNetworkStatusProducer] + * + * @property params params + * @property multiNetworkStatusSupplier multi network status supplier + * +[REDACTED_AUTHOR] + */ +internal class DefaultSingleNetworkStatusProducer @AssistedInject constructor( + @Assisted val params: SingleNetworkStatusProducer.Params, + private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier, +) : SingleNetworkStatusProducer { + + override val fallback: NetworkStatus + get() = NetworkStatus(network = params.network, value = NetworkStatus.Unreachable(address = null)) + + override fun produce(): Flow { + return multiNetworkStatusSupplier( + params = MultiNetworkStatusProducer.Params(params.userWalletId), + ) + .mapNotNull { statuses -> + statuses.firstOrNull { it.network == params.network } + } + .distinctUntilChanged() + } + + @AssistedFactory + interface Factory : SingleNetworkStatusProducer.Factory { + override fun create(params: SingleNetworkStatusProducer.Params): DefaultSingleNetworkStatusProducer + } +} \ No newline at end of file diff --git a/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducerTest.kt b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducerTest.kt new file mode 100644 index 0000000000..220e8b819f --- /dev/null +++ b/data/networks/src/test/java/com/tangem/data/networks/single/DefaultSingleNetworkStatusProducerTest.kt @@ -0,0 +1,178 @@ +package com.tangem.data.networks.single + +import com.google.common.truth.Truth +import com.tangem.common.test.domain.network.MockNetworkStatusFactory +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.common.test.utils.getEmittedValues +import com.tangem.domain.networks.multi.MultiNetworkStatusProducer +import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier +import com.tangem.domain.networks.single.SingleNetworkStatusProducer +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.wallets.models.UserWalletId +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.test.runTest +import org.junit.Test + +/** +[REDACTED_AUTHOR] + */ +internal class DefaultSingleNetworkStatusProducerTest { + + private val params = SingleNetworkStatusProducer.Params( + userWalletId = UserWalletId(stringValue = "011"), + network = ethereum, + ) + + private val multiNetworkStatusSupplier = mockk() + + private val producer = DefaultSingleNetworkStatusProducer( + params = params, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + ) + + @Test + fun `test that flow is mapped for network from params`() = runTest { + val status = MockNetworkStatusFactory.createMissedDerivation(params.network) + val expected = flowOf( + setOf( + status, + MockNetworkStatusFactory.createMissedDerivation(cardano), + ), + ) + + val multiParams = MultiNetworkStatusProducer.Params(userWalletId = params.userWalletId) + every { multiNetworkStatusSupplier(multiParams) } returns expected + + val actual = producer.produce() + + verify { multiNetworkStatusSupplier(multiParams) } + + val values = backgroundScope.getEmittedValues(testScheduler = testScheduler, actual = actual) + + Truth.assertThat(values.size).isEqualTo(1) + Truth.assertThat(values).isEqualTo(listOf(status)) + } + + @Test + fun `test that flow is updated if network status is updated`() = runTest { + val expected = MutableSharedFlow>(replay = 2, extraBufferCapacity = 1) + + val multiParams = MultiNetworkStatusProducer.Params(userWalletId = params.userWalletId) + every { multiNetworkStatusSupplier(multiParams) } returns expected + + val actual = producer.produceWithFallback() + + verify { multiNetworkStatusSupplier(multiParams) } + + // first emit + val status = MockNetworkStatusFactory.createMissedDerivation(params.network) + expected.emit(value = setOf(status)) + + val values1 = backgroundScope.getEmittedValues(testScheduler = testScheduler, actual = actual) + + Truth.assertThat(values1.size).isEqualTo(1) + Truth.assertThat(values1).isEqualTo(listOf(status)) + + // second emit + val updatedStatus = status.copy(value = NetworkStatus.Unreachable(null)) + expected.emit(value = setOf(updatedStatus)) + + val values2 = backgroundScope.getEmittedValues(testScheduler = testScheduler, actual = actual) + + Truth.assertThat(values2.size).isEqualTo(2) + Truth.assertThat(values2).isEqualTo(listOf(status, updatedStatus)) + } + + @Test + fun `test that flow is filtered the same status`() = runTest { + val expected = MutableSharedFlow>(replay = 2, extraBufferCapacity = 1) + + val multiParams = MultiNetworkStatusProducer.Params(userWalletId = params.userWalletId) + every { multiNetworkStatusSupplier(multiParams) } returns expected + + val actual = producer.produceWithFallback() + + verify { multiNetworkStatusSupplier(multiParams) } + + // first emit + val status = MockNetworkStatusFactory.createMissedDerivation(params.network) + expected.emit(value = setOf(status)) + + val values1 = backgroundScope.getEmittedValues(testScheduler = testScheduler, actual = actual) + + Truth.assertThat(values1.size).isEqualTo(1) + Truth.assertThat(values1).isEqualTo(listOf(status)) + + // second emit + expected.emit(value = setOf(status)) + + val values2 = backgroundScope.getEmittedValues(testScheduler = testScheduler, actual = actual) + + Truth.assertThat(values2.size).isEqualTo(1) + Truth.assertThat(values2).isEqualTo(listOf(status)) + } + + @Test + fun `test if flow throws exception`() = runTest { + val exception = IllegalStateException() + val status = MockNetworkStatusFactory.createMissedDerivation(params.network) + + val innerFlow = MutableStateFlow(value = false) + val expected = flow { + if (innerFlow.value) { + emit(setOf(status)) + } else { + throw exception + } + } + .buffer(capacity = 5) + + val multiParams = MultiNetworkStatusProducer.Params(userWalletId = params.userWalletId) + every { multiNetworkStatusSupplier(multiParams) } returns expected + + val actual = producer.produceWithFallback() + + verify { multiNetworkStatusSupplier(multiParams) } + + val values1 = backgroundScope.getEmittedValues(testScheduler = testScheduler, actual = actual) + + Truth.assertThat(values1.size).isEqualTo(1) + val fallbackStatus = MockNetworkStatusFactory.createUnreachable(params.network) + Truth.assertThat(values1).isEqualTo(listOf(fallbackStatus)) + + innerFlow.emit(value = true) + + val values2 = backgroundScope.getEmittedValues(testScheduler = testScheduler, actual = actual) + Truth.assertThat(values2.size).isEqualTo(1) + Truth.assertThat(values2).isEqualTo(listOf(status)) + } + + @Test + fun `test if flow doesn't contain network from params`() = runTest { + val expected = flowOf( + setOf(MockNetworkStatusFactory.createMissedDerivation(cardano)), + ) + + val multiParams = MultiNetworkStatusProducer.Params(userWalletId = params.userWalletId) + every { multiNetworkStatusSupplier(multiParams) } returns expected + + val actual = producer.produce() + + verify { multiNetworkStatusSupplier(multiParams) } + + val values = backgroundScope.getEmittedValues(testScheduler = testScheduler, actual = actual) + + Truth.assertThat(values.size).isEqualTo(0) + } + + private companion object { + + val mocks = MockCryptoCurrencyFactory() + + val ethereum = mocks.ethereum.network + val cardano = mocks.cardano.network + } +} \ No newline at end of file diff --git a/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusProducer.kt b/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusProducer.kt new file mode 100644 index 0000000000..78262a1049 --- /dev/null +++ b/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusProducer.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.networks.single + +import com.tangem.domain.core.flow.FlowProducer +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Producer of network status [Network] for wallet with [UserWalletId] + * +[REDACTED_AUTHOR] + */ +interface SingleNetworkStatusProducer : FlowProducer { + + data class Params(val userWalletId: UserWalletId, val network: Network) + + interface Factory : FlowProducer.Factory +} \ No newline at end of file diff --git a/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusSupplier.kt b/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusSupplier.kt new file mode 100644 index 0000000000..12afefd145 --- /dev/null +++ b/domain/networks/src/main/java/com/tangem/domain/networks/single/SingleNetworkStatusSupplier.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.networks.single + +import com.tangem.domain.core.flow.FlowCachingSupplier +import com.tangem.domain.tokens.model.NetworkStatus + +/** + * Supplier of network status for selected wallet [SingleNetworkStatusProducer.Params] + * + * @property factory factory for creating [SingleNetworkStatusProducer] + * @property keyCreator key creator + * +[REDACTED_AUTHOR] + */ +abstract class SingleNetworkStatusSupplier( + override val factory: SingleNetworkStatusProducer.Factory, + override val keyCreator: (SingleNetworkStatusProducer.Params) -> String, +) : FlowCachingSupplier() \ No newline at end of file