From 762caca32e367d7f7f5138ba7ac792530e41fee0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 2 Dec 2025 16:36:03 +0700 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/AccountDomainModule.kt | 2 + .../usecase/RecoverCryptoPortfolioUseCase.kt | 12 +++++ .../RecoverCryptoPortfolioUseCaseTest.kt | 49 +++++++++++++++++-- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt index 2afd20f31a..80d510d122 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt @@ -67,11 +67,13 @@ internal object AccountDomainModule { accountsCRUDRepository: AccountsCRUDRepository, mainAccountTokensMigration: MainAccountTokensMigration, cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher, + singleAccountListFetcher: SingleAccountListFetcher, ): RecoverCryptoPortfolioUseCase { return RecoverCryptoPortfolioUseCase( crudRepository = accountsCRUDRepository, mainAccountTokensMigration = mainAccountTokensMigration, cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher, + singleAccountListFetcher = singleAccountListFetcher, ) } diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt index d2eb7a9b1c..c972f97b80 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt @@ -7,6 +7,7 @@ import arrow.core.raise.Raise import arrow.core.raise.catch import arrow.core.raise.either import arrow.core.raise.ensure +import com.tangem.domain.account.fetcher.SingleAccountListFetcher import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.models.ArchivedAccount import com.tangem.domain.account.repository.AccountsCRUDRepository @@ -23,6 +24,8 @@ import com.tangem.domain.models.wallet.UserWalletId * * @property crudRepository repository for performing CRUD operations on accounts * @property mainAccountTokensMigration handles the migration of tokens from the main account to the recovered account + * @property cryptoCurrencyBalanceFetcher Fetcher for updating crypto currency balances. + * @property singleAccountListFetcher fetches the list of accounts for a single user wallet * [REDACTED_AUTHOR] */ @@ -30,6 +33,7 @@ class RecoverCryptoPortfolioUseCase( private val crudRepository: AccountsCRUDRepository, private val mainAccountTokensMigration: MainAccountTokensMigration, private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher, + private val singleAccountListFetcher: SingleAccountListFetcher, ) { /** @@ -38,6 +42,8 @@ class RecoverCryptoPortfolioUseCase( * @param accountId the unique identifier of the account to recover */ suspend operator fun invoke(accountId: AccountId): Either = either { + fetchAccountList(userWalletId = accountId.userWalletId) + val accountList = getAccountList(userWalletId = accountId.userWalletId) ensure(accountList.canAddMoreAccounts) { @@ -62,6 +68,12 @@ class RecoverCryptoPortfolioUseCase( recoveredAccount } + private suspend fun Raise.fetchAccountList(userWalletId: UserWalletId) { + singleAccountListFetcher(params = SingleAccountListFetcher.Params(userWalletId)).onLeft { + raise(Error.DataOperationFailed(cause = it)) + } + } + private suspend fun Raise.getAccountList(userWalletId: UserWalletId): AccountList { return catch( block = { crudRepository.getAccountListSync(userWalletId = userWalletId) }, diff --git a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCaseTest.kt b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCaseTest.kt index f6fe05e41d..8bf3eb7e56 100644 --- a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCaseTest.kt +++ b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCaseTest.kt @@ -5,6 +5,7 @@ import arrow.core.left import arrow.core.right import arrow.core.toOption import com.google.common.truth.Truth +import com.tangem.domain.account.fetcher.SingleAccountListFetcher import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.models.ArchivedAccount import com.tangem.domain.account.repository.AccountsCRUDRepository @@ -27,17 +28,19 @@ import kotlin.random.Random class RecoverCryptoPortfolioUseCaseTest { private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true) + private val singleAccountListFetcher: SingleAccountListFetcher = mockk() private val mainAccountTokensMigration: MainAccountTokensMigration = mockk() private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher = mockk(relaxUnitFun = true) private val useCase = RecoverCryptoPortfolioUseCase( crudRepository = crudRepository, mainAccountTokensMigration = mainAccountTokensMigration, cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher, + singleAccountListFetcher = singleAccountListFetcher, ) @BeforeEach fun resetMocks() { - clearMocks(crudRepository, mainAccountTokensMigration, cryptoCurrencyBalanceFetcher) + clearMocks(crudRepository, mainAccountTokensMigration, cryptoCurrencyBalanceFetcher, singleAccountListFetcher) } @Test @@ -56,6 +59,7 @@ class RecoverCryptoPortfolioUseCaseTest { val updatedAccountList = (accountList + account).getOrNull()!! + coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right() coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption() coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns archivedAccount.toOption() coEvery { mainAccountTokensMigration.migrate(userWalletId, account.derivationIndex) } returns Unit.right() @@ -69,6 +73,7 @@ class RecoverCryptoPortfolioUseCaseTest { Truth.assertThat(actual).isEqualTo(expected) coVerifySequence { + singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) crudRepository.getAccountListSync(userWalletId) crudRepository.getArchivedAccountSync(account.accountId) crudRepository.saveAccounts(updatedAccountList) @@ -85,6 +90,7 @@ class RecoverCryptoPortfolioUseCaseTest { derivationIndex = DerivationIndex.Companion.Main, ) + coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right() coEvery { crudRepository.getAccountListSync(userWalletId) } returns None // Act @@ -95,7 +101,8 @@ class RecoverCryptoPortfolioUseCaseTest { Truth.assertThat(actual.cause).isInstanceOf(expected::class.java) Truth.assertThat(actual.cause).hasMessageThat().isEqualTo(expected.message) - coVerifySequence { crudRepository.getAccountListSync(userWalletId) } + coVerifySequence { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) + crudRepository.getAccountListSync(userWalletId) } coVerify(inverse = true) { crudRepository.getArchivedAccountSync(any()) crudRepository.saveAccounts(any()) @@ -111,6 +118,7 @@ class RecoverCryptoPortfolioUseCaseTest { ) val exception = IllegalStateException("Test error") + coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right() coEvery { crudRepository.getAccountListSync(userWalletId) } throws exception // Act @@ -120,7 +128,8 @@ class RecoverCryptoPortfolioUseCaseTest { val expected = DataOperationFailed(exception).left() Truth.assertThat(actual).isEqualTo(expected) - coVerifySequence { crudRepository.getAccountListSync(userWalletId) } + coVerifySequence { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) + crudRepository.getAccountListSync(userWalletId) } coVerify(inverse = true) { crudRepository.getArchivedAccountSync(any()) crudRepository.saveAccounts(any()) @@ -134,6 +143,7 @@ class RecoverCryptoPortfolioUseCaseTest { val accountList = AccountList.Companion.empty(userWalletId) val exception = IllegalStateException("Test error") + coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right() coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption() coEvery { crudRepository.getArchivedAccountSync(account.accountId) } throws exception @@ -145,6 +155,7 @@ class RecoverCryptoPortfolioUseCaseTest { Truth.assertThat(actual).isEqualTo(expected) coVerifySequence { + singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) crudRepository.getAccountListSync(userWalletId) crudRepository.getArchivedAccountSync(account.accountId) } @@ -157,6 +168,7 @@ class RecoverCryptoPortfolioUseCaseTest { val account = createAccount(userWalletId) val accountList = AccountList.Companion.empty(userWalletId) + coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right() coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption() coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns None @@ -169,6 +181,7 @@ class RecoverCryptoPortfolioUseCaseTest { Truth.assertThat(actual.cause).hasMessageThat().isEqualTo(expected.message) coVerifySequence { + singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) crudRepository.getAccountListSync(userWalletId) crudRepository.getArchivedAccountSync(account.accountId) } @@ -192,6 +205,7 @@ class RecoverCryptoPortfolioUseCaseTest { val updatedAccountList = (accountList + account).getOrNull()!! val exception = IllegalStateException("Save failed") + coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns Unit.right() coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption() coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns archivedAccount.toOption() coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception @@ -204,12 +218,41 @@ class RecoverCryptoPortfolioUseCaseTest { Truth.assertThat(actual).isEqualTo(expected) coVerifySequence { + singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) crudRepository.getAccountListSync(userWalletId) crudRepository.getArchivedAccountSync(account.accountId) crudRepository.saveAccounts(updatedAccountList) } } + @Test + fun `invoke should return error if fetch is failed`() = runTest { + // Arrange + val accountId = AccountId.Companion.forCryptoPortfolio( + userWalletId = userWalletId, + derivationIndex = DerivationIndex.Companion.Main, + ) + val exception = IllegalStateException("Test error") + + coEvery { singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) } returns exception.left() + + // Act + val actual = useCase(accountId) + + // Assert + val expected = DataOperationFailed(exception).left() + Truth.assertThat(actual).isEqualTo(expected) + + coVerifySequence { + singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId)) + } + coVerify(inverse = true) { + crudRepository.getAccountListSync(any()) + crudRepository.getArchivedAccountSync(any()) + crudRepository.saveAccounts(any()) + } + } + private fun createAccount( userWalletId: UserWalletId, name: String = "Test Account",