From 3fd8b110ef77f75a5fc1c62924020032c51f8ffe Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Sep 2025 16:06:15 +0400 Subject: [PATCH] Updated on 2026-08-14 --- data/tokens/build.gradle.kts | 5 +- .../AccountListCryptoCurrenciesFetcher.kt | 35 ++++++ ...ultiWalletCryptoCurrenciesFetcherModule.kt | 41 ++++--- .../AccountListCryptoCurrenciesFetcherTest.kt | 103 ++++++++++++++++++ 4 files changed, 168 insertions(+), 16 deletions(-) create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcher.kt create mode 100644 data/tokens/src/test/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcherTest.kt diff --git a/data/tokens/build.gradle.kts b/data/tokens/build.gradle.kts index f35bd35737..eb3fe93e66 100644 --- a/data/tokens/build.gradle.kts +++ b/data/tokens/build.gradle.kts @@ -24,17 +24,18 @@ dependencies { // endregion // region Project - Domain + implementation(projects.domain.account) + implementation(projects.domain.card) implementation(projects.domain.core) implementation(projects.domain.demo) implementation(projects.domain.legacy) - implementation(projects.domain.walletManager) - implementation(projects.domain.card) implementation(projects.domain.models) implementation(projects.domain.staking) implementation(projects.domain.staking.models) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) implementation(projects.domain.txhistory.models) + implementation(projects.domain.walletManager) implementation(projects.domain.wallets.models) // endregion diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcher.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcher.kt new file mode 100644 index 0000000000..5c8f9d9f23 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcher.kt @@ -0,0 +1,35 @@ +package com.tangem.data.tokens + +import arrow.core.Either +import com.tangem.data.common.account.WalletAccountsFetcher +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.core.utils.catchOn +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.isMultiCurrency +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher.Params +import com.tangem.utils.coroutines.CoroutineDispatcherProvider + +/** + * Implementation of [MultiWalletCryptoCurrenciesFetcher] that fetches crypto currencies of all accounts + * + * @property userWalletsStore [UserWallet]'s store + * @property walletAccountsFetcher instance of [WalletAccountsFetcher] to fetch accounts for a multi wallet + * @property dispatchers dispatchers + * +[REDACTED_AUTHOR] + */ +internal class AccountListCryptoCurrenciesFetcher( + private val userWalletsStore: UserWalletsStore, + private val walletAccountsFetcher: WalletAccountsFetcher, + private val dispatchers: CoroutineDispatcherProvider, +) : MultiWalletCryptoCurrenciesFetcher { + + override suspend fun invoke(params: Params) = Either.catchOn(dispatchers.default) { + val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) + + if (!userWallet.isMultiCurrency) error("${this::class.simpleName} supports only multi-currency wallet") + + walletAccountsFetcher.fetch(userWalletId = params.userWalletId) + } +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt index eea90a597e..c360054a04 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesFetcherModule.kt @@ -1,13 +1,16 @@ package com.tangem.data.tokens.di +import com.tangem.data.common.account.WalletAccountsFetcher import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensSaver +import com.tangem.data.tokens.AccountListCryptoCurrenciesFetcher import com.tangem.data.tokens.DefaultMultiWalletCryptoCurrenciesFetcher import com.tangem.data.tokens.utils.CustomTokensMerger import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -24,28 +27,38 @@ internal class MultiWalletCryptoCurrenciesFetcherModule { @Singleton @Provides fun provideMultiWalletCryptoCurrenciesFetcher( + accountsFeatureToggles: AccountsFeatureToggles, tangemTechApi: TangemTechApi, userWalletsStore: UserWalletsStore, userTokensResponseStore: UserTokensResponseStore, userTokensSaver: UserTokensSaver, cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, expressServiceLoader: ExpressServiceLoader, + walletAccountsFetcher: WalletAccountsFetcher, dispatchers: CoroutineDispatcherProvider, ): MultiWalletCryptoCurrenciesFetcher { - return DefaultMultiWalletCryptoCurrenciesFetcher( - demoConfig = DemoConfig(), - userWalletsStore = userWalletsStore, - tangemTechApi = tangemTechApi, - customTokensMerger = CustomTokensMerger( - tangemTechApi = tangemTechApi, - userTokensSaver = userTokensSaver, + return if (accountsFeatureToggles.isFeatureEnabled) { + AccountListCryptoCurrenciesFetcher( + userWalletsStore = userWalletsStore, + walletAccountsFetcher = walletAccountsFetcher, dispatchers = dispatchers, - ), - userTokensResponseStore = userTokensResponseStore, - userTokensSaver = userTokensSaver, - cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, - expressServiceLoader = expressServiceLoader, - dispatchers = dispatchers, - ) + ) + } else { + DefaultMultiWalletCryptoCurrenciesFetcher( + demoConfig = DemoConfig(), + userWalletsStore = userWalletsStore, + tangemTechApi = tangemTechApi, + customTokensMerger = CustomTokensMerger( + tangemTechApi = tangemTechApi, + userTokensSaver = userTokensSaver, + dispatchers = dispatchers, + ), + userTokensResponseStore = userTokensResponseStore, + userTokensSaver = userTokensSaver, + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, + expressServiceLoader = expressServiceLoader, + dispatchers = dispatchers, + ) + } } } \ No newline at end of file diff --git a/data/tokens/src/test/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcherTest.kt b/data/tokens/src/test/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcherTest.kt new file mode 100644 index 0000000000..79bcd8e6f8 --- /dev/null +++ b/data/tokens/src/test/kotlin/com/tangem/data/tokens/AccountListCryptoCurrenciesFetcherTest.kt @@ -0,0 +1,103 @@ +package com.tangem.data.tokens + +import arrow.core.left +import com.tangem.common.test.utils.assertEither +import com.tangem.common.test.utils.assertEitherRight +import com.tangem.data.common.account.WalletAccountsFetcher +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.isMultiCurrency +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class AccountListCryptoCurrenciesFetcherTest { + + private val userWalletsStore: UserWalletsStore = mockk(relaxUnitFun = true) + private val walletAccountsFetcher: WalletAccountsFetcher = mockk(relaxUnitFun = true) + private val dispatchers = TestingCoroutineDispatcherProvider() + + private val fetcher = AccountListCryptoCurrenciesFetcher( + userWalletsStore = userWalletsStore, + walletAccountsFetcher = walletAccountsFetcher, + dispatchers = dispatchers, + ) + + @BeforeEach + fun resetMocks() { + clearMocks(userWalletsStore, walletAccountsFetcher) + } + + @Test + fun `returns failure if wallet is not multi-currency`() = runTest { + // Arrange + val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId) + val mockUserWallet = mockk { every { isMultiCurrency } returns false } + every { userWalletsStore.getSyncStrict(key = params.userWalletId) } returns mockUserWallet + + // Act + val actual = fetcher(params) + + // Assert + val expected = IllegalStateException( + "${AccountListCryptoCurrenciesFetcher::class.simpleName} supports only multi-currency wallet", + ).left() + assertEither(actual, expected) + + verify { userWalletsStore.getSyncStrict(key = params.userWalletId) } + coVerify(inverse = true) { walletAccountsFetcher.fetch(any()) } + } + + @Test + fun `returns accounts if wallet is multi-currency`() = runTest { + // Arrange + val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId) + val mockUserWallet = mockk { every { isMultiCurrency } returns true } + + every { userWalletsStore.getSyncStrict(key = params.userWalletId) } returns mockUserWallet + + // Act + val actual = fetcher(params) + + // Assert + assertEitherRight(actual) + + coVerify(ordering = Ordering.SEQUENCE) { + userWalletsStore.getSyncStrict(key = params.userWalletId) + walletAccountsFetcher.fetch(userWalletId = params.userWalletId) + } + } + + @Test + fun `returns error if walletAccountsFetcher returns error`() = runTest { + // Arrange + val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId) + val mockUserWallet = mockk { every { isMultiCurrency } returns true } + val error = RuntimeException("fetch error") + + every { userWalletsStore.getSyncStrict(key = params.userWalletId) } returns mockUserWallet + coEvery { walletAccountsFetcher.fetch(userWalletId = params.userWalletId) } throws error + + // Act + val actual = fetcher(params) + + // Assert + val expected = error.left() + assertEither(actual, expected) + + coVerify(ordering = Ordering.SEQUENCE) { + userWalletsStore.getSyncStrict(key = params.userWalletId) + walletAccountsFetcher.fetch(userWalletId = params.userWalletId) + } + } + + private companion object { + val userWalletId = UserWalletId("012") + } +} \ No newline at end of file