From cd572639c7609a21f1b40f1af5f3020825af679e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Jul 2025 11:53:48 +0400 Subject: [PATCH] Updated on 2026-08-14 --- domain/tokens/build.gradle.kts | 9 +- .../tokens/wallet/BaseWalletBalanceFetcher.kt | 18 +++ .../domain/tokens/wallet/FetchingSource.kt | 12 ++ .../implementor/MultiWalletBalanceFetcher.kt | 44 +++++++ .../implementor/SingleWalletBalanceFetcher.kt | 33 +++++ .../SingleWalletWithTokenBalanceFetcher.kt | 31 +++++ .../MultiWalletBalanceFetcherTest.kt | 122 ++++++++++++++++++ .../SingleWalletBalanceFetcherTest.kt | 59 +++++++++ ...SingleWalletWithTokenBalanceFetcherTest.kt | 59 +++++++++ 9 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/BaseWalletBalanceFetcher.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcher.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletBalanceFetcher.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletWithTokenBalanceFetcher.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcherTest.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletBalanceFetcherTest.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletWithTokenBalanceFetcherTest.kt diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index 072542bfb0..3091a9623f 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -8,6 +8,10 @@ android { namespace = "com.tangem.domain.tokens" } +tasks.withType().configureEach { + useJUnitPlatform() +} + dependencies { /** Project - Domain */ @@ -49,9 +53,12 @@ dependencies { implementation(deps.reKotlin) /** Tests */ - testImplementation(deps.test.junit) testImplementation(deps.test.coroutine) + testImplementation(deps.test.junit5) + testRuntimeOnly(deps.test.junit5.engine) testImplementation(deps.test.mockk) + testImplementation(deps.test.truth) + testImplementation(projects.common.test) testImplementation(tangemDeps.blockchain) { exclude(module = "joda-time") } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/BaseWalletBalanceFetcher.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/BaseWalletBalanceFetcher.kt new file mode 100644 index 0000000000..31ac77eb2a --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/BaseWalletBalanceFetcher.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.tokens.wallet + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Base contract for implementation of wallet's balance fetcher + * +[REDACTED_AUTHOR] + */ +internal interface BaseWalletBalanceFetcher { + + /** Fetching sources */ + val fetchingSources: Set + + /** Get crypto currencies of wallet with [userWalletId] */ + suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt new file mode 100644 index 0000000000..efa9097358 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.tokens.wallet + +/** + * Source type that is necessary to load cryptocurrency data + * +[REDACTED_AUTHOR] + */ +internal enum class FetchingSource { + NETWORK, + QUOTE, + STAKING, +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcher.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcher.kt new file mode 100644 index 0000000000..de99232678 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcher.kt @@ -0,0 +1,44 @@ +package com.tangem.domain.tokens.wallet.implementor + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier +import com.tangem.domain.tokens.wallet.BaseWalletBalanceFetcher +import com.tangem.domain.tokens.wallet.FetchingSource +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.firstOrNull +import timber.log.Timber + +/** + * Implementation of [BaseWalletBalanceFetcher] for MULTI-CURRENCY wallet + * + * @property multiWalletCryptoCurrenciesFetcher multi wallet fetcher of crypto currencies + * @property multiWalletCryptoCurrenciesSupplier multi wallet supplier of crypto currencies + * +[REDACTED_AUTHOR] + */ +internal class MultiWalletBalanceFetcher( + private val multiWalletCryptoCurrenciesFetcher: MultiWalletCryptoCurrenciesFetcher, + private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier, +) : BaseWalletBalanceFetcher { + + override val fetchingSources: Set = setOf( + FetchingSource.NETWORK, + FetchingSource.QUOTE, + FetchingSource.STAKING, + ) + + override suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set { + multiWalletCryptoCurrenciesFetcher( + params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId), + ) + .onLeft(Timber::e) + + return multiWalletCryptoCurrenciesSupplier( + params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId), + ) + .firstOrNull() + .orEmpty() + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletBalanceFetcher.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletBalanceFetcher.kt new file mode 100644 index 0000000000..fb99a55dc6 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletBalanceFetcher.kt @@ -0,0 +1,33 @@ +package com.tangem.domain.tokens.wallet.implementor + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.wallet.BaseWalletBalanceFetcher +import com.tangem.domain.tokens.wallet.FetchingSource +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Implementation of [BaseWalletBalanceFetcher] for SINGLE-CURRENCY wallet + * + * @property currenciesRepository currencies repository + * +[REDACTED_AUTHOR] + */ +internal class SingleWalletBalanceFetcher( + private val currenciesRepository: CurrenciesRepository, +) : BaseWalletBalanceFetcher { + + override val fetchingSources: Set = setOf( + FetchingSource.NETWORK, + FetchingSource.QUOTE, + ) + + override suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set { + val primaryCurrency = currenciesRepository.getSingleCurrencyWalletPrimaryCurrency( + userWalletId = userWalletId, + refresh = true, + ) + + return setOf(primaryCurrency) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletWithTokenBalanceFetcher.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletWithTokenBalanceFetcher.kt new file mode 100644 index 0000000000..cf31b198c8 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletWithTokenBalanceFetcher.kt @@ -0,0 +1,31 @@ +package com.tangem.domain.tokens.wallet.implementor + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.wallet.BaseWalletBalanceFetcher +import com.tangem.domain.tokens.wallet.FetchingSource +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Implementation of [BaseWalletBalanceFetcher] for SINGLE-CURRENCY wallet WITH TOKEN (like, NODL) + * + * @property currenciesRepository currencies repository + * +[REDACTED_AUTHOR] + */ +internal class SingleWalletWithTokenBalanceFetcher( + private val currenciesRepository: CurrenciesRepository, +) : BaseWalletBalanceFetcher { + + override val fetchingSources: Set = setOf( + FetchingSource.NETWORK, + FetchingSource.QUOTE, + ) + + override suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set { + return currenciesRepository.getSingleCurrencyWalletWithCardCurrencies( + userWalletId = userWalletId, + refresh = true, + ).toSet() + } +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcherTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcherTest.kt new file mode 100644 index 0000000000..1e6e7d876b --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/MultiWalletBalanceFetcherTest.kt @@ -0,0 +1,122 @@ +package com.tangem.domain.tokens.wallet.implementor + +import arrow.core.left +import arrow.core.right +import com.google.common.truth.Truth +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier +import com.tangem.domain.tokens.wallet.FetchingSource +import com.tangem.domain.wallets.models.UserWalletId +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +/** +[REDACTED_AUTHOR] + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MultiWalletBalanceFetcherTest { + + private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() + + private val multiWalletFetcher: MultiWalletCryptoCurrenciesFetcher = mockk() + private val multiWalletSupplier: MultiWalletCryptoCurrenciesSupplier = mockk() + private val fetcher = MultiWalletBalanceFetcher( + multiWalletCryptoCurrenciesFetcher = multiWalletFetcher, + multiWalletCryptoCurrenciesSupplier = multiWalletSupplier, + ) + + @BeforeEach + fun resetMocks() { + clearMocks(multiWalletFetcher) + } + + @Test + fun getFetchingSources() { + // Act + val actual = fetcher.fetchingSources + + // Assert + val expected = setOf(FetchingSource.NETWORK, FetchingSource.QUOTE, FetchingSource.STAKING) + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `getCryptoCurrencies successfully if multiWalletCryptoCurrenciesFetcher RETURNS SUCCESS`() = runTest { + // Arrange + val currencies: Set = cryptoCurrencyFactory.ethereumAndStellar.toSet() + val supplierFlow = flowOf(currencies) + + coEvery { + multiWalletFetcher(params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)) + } returns Unit.right() + + every { + multiWalletSupplier(params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId)) + } returns supplierFlow + + // Act + val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId) + + // Assert + val expected = currencies.toSet() + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `getCryptoCurrencies successfully if multiWalletCryptoCurrenciesFetcher RETURNS ERROR`() = runTest { + // Arrange + val currencies: Set = cryptoCurrencyFactory.ethereumAndStellar.toSet() + val supplierFlow = flowOf(currencies) + + coEvery { + multiWalletFetcher(params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)) + } returns IllegalStateException().left() + + every { + multiWalletSupplier(params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId)) + } returns supplierFlow + + // Act + val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId) + + // Assert + val expected = currencies.toSet() + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `getCryptoCurrencies successfully if multiWalletCryptoCurrenciesSupplier IS EMPTY FLOW`() = runTest { + // Arrange + val supplierFlow = emptyFlow>() + + coEvery { + multiWalletFetcher(params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)) + } returns Unit.right() + + every { + multiWalletSupplier(params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId)) + } returns supplierFlow + + // Act + val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId) + + // Assert + val expected = emptySet() + Truth.assertThat(actual).isEqualTo(expected) + } + + private companion object { + val userWalletId = UserWalletId("011") + } +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletBalanceFetcherTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletBalanceFetcherTest.kt new file mode 100644 index 0000000000..02bb2caab6 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletBalanceFetcherTest.kt @@ -0,0 +1,59 @@ +package com.tangem.domain.tokens.wallet.implementor + +import com.google.common.truth.Truth +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.wallet.FetchingSource +import com.tangem.domain.wallets.models.UserWalletId +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +/** +[REDACTED_AUTHOR] + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SingleWalletBalanceFetcherTest { + + private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() + + private val currenciesRepository: CurrenciesRepository = mockk() + private val fetcher = SingleWalletBalanceFetcher(currenciesRepository = currenciesRepository) + + @BeforeEach + fun resetMocks() { + clearMocks(currenciesRepository) + } + + @Test + fun getFetchingSources() { + // Act + val actual = fetcher.fetchingSources + + // Assert + val expected = setOf(FetchingSource.NETWORK, FetchingSource.QUOTE) + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun getCryptoCurrencies() = runTest { + // Arrange + val userWalletId = UserWalletId("011") + val currency = cryptoCurrencyFactory.ethereum + + coEvery { + currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId = userWalletId, refresh = true) + } returns currency + + // Act + val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId) + + // Assert + val expected = setOf(currency) + Truth.assertThat(actual).isEqualTo(expected) + } +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletWithTokenBalanceFetcherTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletWithTokenBalanceFetcherTest.kt new file mode 100644 index 0000000000..49a0daf424 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/wallet/implementor/SingleWalletWithTokenBalanceFetcherTest.kt @@ -0,0 +1,59 @@ +package com.tangem.domain.tokens.wallet.implementor + +import com.google.common.truth.Truth +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.tokens.wallet.FetchingSource +import com.tangem.domain.wallets.models.UserWalletId +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +/** +[REDACTED_AUTHOR] + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SingleWalletWithTokenBalanceFetcherTest { + + private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() + + private val currenciesRepository: CurrenciesRepository = mockk() + private val fetcher = SingleWalletWithTokenBalanceFetcher(currenciesRepository = currenciesRepository) + + @BeforeEach + fun resetMocks() { + clearMocks(currenciesRepository) + } + + @Test + fun getFetchingSources() { + // Act + val actual = fetcher.fetchingSources + + // Assert + val expected = setOf(FetchingSource.NETWORK, FetchingSource.QUOTE) + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun getCryptoCurrencies() = runTest { + // Arrange + val userWalletId = UserWalletId("011") + val currencies = cryptoCurrencyFactory.ethereumAndStellar + + coEvery { + currenciesRepository.getSingleCurrencyWalletWithCardCurrencies(userWalletId = userWalletId, refresh = true) + } returns currencies + + // Act + val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId) + + // Assert + val expected = currencies.toSet() + Truth.assertThat(actual).isEqualTo(expected) + } +} \ No newline at end of file