Updated on 2026-08-14
This commit is contained in:
parent
a1bce4b52c
commit
08521b6c11
12 changed files with 230 additions and 90 deletions
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
||||
import com.tangem.domain.account.usecase.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -15,8 +17,16 @@ internal object AccountDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAddCryptoPortfolioUseCase(accountsCRUDRepository: AccountsCRUDRepository): AddCryptoPortfolioUseCase {
|
||||
return AddCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
||||
fun provideAddCryptoPortfolioUseCase(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
singleAccountListFetcher: SingleAccountListFetcher,
|
||||
mainAccountTokensMigration: MainAccountTokensMigration,
|
||||
): AddCryptoPortfolioUseCase {
|
||||
return AddCryptoPortfolioUseCase(
|
||||
crudRepository = accountsCRUDRepository,
|
||||
singleAccountListFetcher = singleAccountListFetcher,
|
||||
mainAccountTokensMigration = mainAccountTokensMigration,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -7,13 +7,16 @@ import com.tangem.data.account.fetcher.DefaultWalletAccountsFetcher
|
|||
import com.tangem.data.account.repository.DefaultAccountsCRUDRepository
|
||||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.account.store.ArchivedAccountsStoreFactory
|
||||
import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.data.common.account.WalletAccountsSaver
|
||||
import com.tangem.data.common.cache.etag.ETagsStore
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -61,4 +64,16 @@ internal object AccountDataModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletAccountsSaver(impl: DefaultWalletAccountsFetcher): WalletAccountsSaver = impl
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMainAccountTokensMigration(
|
||||
accountsResponseStoreFactory: AccountsResponseStoreFactory,
|
||||
userTokensSaver: UserTokensSaver,
|
||||
): MainAccountTokensMigration {
|
||||
return DefaultMainAccountTokensMigration(
|
||||
accountsResponseStoreFactory = accountsResponseStoreFactory,
|
||||
userTokensSaver = userTokensSaver,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import kotlinx.coroutines.coroutineScope
|
|||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Implementation of [MultiAccountListFetcher]
|
||||
|
|
@ -20,7 +21,7 @@ import java.util.concurrent.ConcurrentHashMap
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultMultiAccountListFetcher(
|
||||
internal class DefaultMultiAccountListFetcher @Inject constructor(
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
) : MultiAccountListFetcher {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.data.account.fetcher
|
|||
import arrow.core.Either
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Implementation of [SingleAccountListFetcher]
|
||||
|
|
@ -11,7 +12,7 @@ import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultSingleAccountListFetcher(
|
||||
internal class DefaultSingleAccountListFetcher @Inject constructor(
|
||||
private val walletAccountsFetcher: WalletAccountsFetcher,
|
||||
) : SingleAccountListFetcher {
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import com.tangem.blockchainsdk.utils.fromNetworkId
|
|||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.account.utils.assignTokens
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.account.WalletAccountsSaver
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
|
|
@ -28,14 +27,12 @@ import timber.log.Timber
|
|||
*
|
||||
* @property accountsResponseStoreFactory Factory for creating stores to access cached account responses.
|
||||
* @property userTokensSaver Saver for updating user tokens in persistent storage.
|
||||
* @property walletAccountsSaver Saver for updating wallet accounts in persistent storage.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultMainAccountTokensMigration(
|
||||
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
private val walletAccountsSaver: WalletAccountsSaver,
|
||||
) : MainAccountTokensMigration {
|
||||
|
||||
override suspend fun migrate(
|
||||
|
|
@ -81,8 +78,6 @@ internal class DefaultMainAccountTokensMigration(
|
|||
},
|
||||
)
|
||||
|
||||
walletAccountsSaver.store(userWalletId = userWalletId, response = updatedResponse)
|
||||
|
||||
userTokensSaver.push(
|
||||
userWalletId = userWalletId,
|
||||
response = updatedResponse.toUserTokensResponse(),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.data.account.store.AccountsResponseStore
|
|||
import com.tangem.data.account.store.AccountsResponseStoreFactory
|
||||
import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration
|
||||
import com.tangem.data.account.utils.toUserTokensResponse
|
||||
import com.tangem.data.common.account.WalletAccountsSaver
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
|
|
@ -32,11 +31,9 @@ class DefaultMainAccountTokensMigrationTest {
|
|||
private val accountsResponseStoreFlow = MutableStateFlow<GetWalletAccountsResponse?>(value = null)
|
||||
|
||||
private val userTokensSaver = mockk<UserTokensSaver>(relaxed = true)
|
||||
private val walletAccountsSaver = mockk<WalletAccountsSaver>(relaxed = true)
|
||||
private val migration = DefaultMainAccountTokensMigration(
|
||||
accountsResponseStoreFactory = accountsResponseStoreFactory,
|
||||
userTokensSaver = userTokensSaver,
|
||||
walletAccountsSaver = walletAccountsSaver,
|
||||
)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
|
|
@ -50,7 +47,7 @@ class DefaultMainAccountTokensMigrationTest {
|
|||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(accountsResponseStoreFactory, accountsResponseStore, userTokensSaver, walletAccountsSaver)
|
||||
clearMocks(accountsResponseStoreFactory, accountsResponseStore, userTokensSaver)
|
||||
accountsResponseStoreFlow.value = null
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +62,6 @@ class DefaultMainAccountTokensMigrationTest {
|
|||
coVerify(inverse = true) {
|
||||
accountsResponseStoreFactory.create(any())
|
||||
accountsResponseStore.data
|
||||
walletAccountsSaver.store(userWalletId = any(), response = any())
|
||||
userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any())
|
||||
}
|
||||
}
|
||||
|
|
@ -85,7 +81,6 @@ class DefaultMainAccountTokensMigrationTest {
|
|||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
walletAccountsSaver.store(userWalletId = any(), response = any())
|
||||
userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any())
|
||||
}
|
||||
}
|
||||
|
|
@ -115,7 +110,6 @@ class DefaultMainAccountTokensMigrationTest {
|
|||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
walletAccountsSaver.store(userWalletId = any(), response = any())
|
||||
userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any())
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +145,6 @@ class DefaultMainAccountTokensMigrationTest {
|
|||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
walletAccountsSaver.store(userWalletId = any(), response = any())
|
||||
userTokensSaver.push(userWalletId = any(), response = any(), onFailSend = any())
|
||||
}
|
||||
}
|
||||
|
|
@ -206,7 +199,6 @@ class DefaultMainAccountTokensMigrationTest {
|
|||
coVerifySequence {
|
||||
accountsResponseStoreFactory.create(userWalletId)
|
||||
accountsResponseStore.data
|
||||
walletAccountsSaver.store(userWalletId = userWalletId, response = migratedResponse)
|
||||
userTokensSaver.push(
|
||||
userWalletId = userWalletId,
|
||||
response = migratedResponse.toUserTokensResponse(),
|
||||
|
|
|
|||
|
|
@ -120,8 +120,8 @@ data class AccountList private constructor(
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data class DuplicateAccountNames(val message: String) : Error {
|
||||
override fun toString(): String = "$tag: Account list contains duplicate account names. $message"
|
||||
data object DuplicateAccountNames : Error {
|
||||
override fun toString(): String = "$tag: Account list contains duplicate account names"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,17 +161,11 @@ data class AccountList private constructor(
|
|||
val uniqueAccountIdsCount = accounts.map { it.accountId.value }.distinct().size
|
||||
ensure(accounts.size == uniqueAccountIdsCount) { Error.DuplicateAccountIds }
|
||||
|
||||
val defaultMainNameCount = accounts.count { it.accountName is AccountName.DefaultMain }
|
||||
|
||||
val customNames = accounts.mapNotNull { (it.accountName as? AccountName.Custom)?.value }
|
||||
val uniqueCustomNameCount = customNames.distinct().size
|
||||
|
||||
ensure(defaultMainNameCount == 0 || defaultMainNameCount == 1) {
|
||||
Error.DuplicateAccountNames("Only one account can have the default main name.")
|
||||
}
|
||||
|
||||
ensure(customNames.size == uniqueCustomNameCount) {
|
||||
Error.DuplicateAccountNames("Custom account names must be unique.")
|
||||
Error.DuplicateAccountNames
|
||||
}
|
||||
|
||||
AccountList(
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
package com.tangem.domain.account.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Option
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
||||
import com.tangem.domain.models.account.*
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
|
|
@ -17,11 +16,15 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
* Use case for adding a new crypto portfolio account
|
||||
*
|
||||
* @property crudRepository the repository used for performing CRUD operations on accounts
|
||||
* @property singleAccountListFetcher fetches the list of accounts for a single user wallet
|
||||
* @property mainAccountTokensMigration handles the migration of tokens from the main account to the new
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddCryptoPortfolioUseCase(
|
||||
private val crudRepository: AccountsCRUDRepository,
|
||||
private val singleAccountListFetcher: SingleAccountListFetcher,
|
||||
private val mainAccountTokensMigration: MainAccountTokensMigration,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -40,21 +43,29 @@ class AddCryptoPortfolioUseCase(
|
|||
icon: CryptoPortfolioIcon,
|
||||
derivationIndex: DerivationIndex,
|
||||
): Either<Error, Account.CryptoPortfolio> = either {
|
||||
val newAccount = createAccount(userWalletId, accountName, icon, derivationIndex)
|
||||
fetchAccountList(userWalletId)
|
||||
|
||||
val accountList = getAccountList(userWalletId = userWalletId).getOrElse {
|
||||
createNewAccountList(userWalletId = userWalletId)
|
||||
}
|
||||
val accountList = getAccountList(userWalletId = userWalletId)
|
||||
|
||||
val newAccount = createAccount(userWalletId, accountName, icon, derivationIndex)
|
||||
|
||||
val updatedAccounts = (accountList + newAccount).getOrElse {
|
||||
raise(Error.AccountListRequirementsNotMet(it))
|
||||
}
|
||||
|
||||
saveAccounts(updatedAccounts)
|
||||
saveAccounts(accountList = updatedAccounts)
|
||||
|
||||
mainAccountTokensMigration.migrate(userWalletId, derivationIndex)
|
||||
|
||||
newAccount
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.fetchAccountList(userWalletId: UserWalletId) {
|
||||
singleAccountListFetcher(params = SingleAccountListFetcher.Params(userWalletId)).onLeft {
|
||||
raise(Error.DataOperationFailed(cause = it))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAccount(
|
||||
userWalletId: UserWalletId,
|
||||
accountName: AccountName,
|
||||
|
|
@ -70,26 +81,14 @@ class AddCryptoPortfolioUseCase(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): Option<AccountList> {
|
||||
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
|
||||
return catch(
|
||||
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
|
||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun Raise<Error>.createNewAccountList(userWalletId: UserWalletId): AccountList {
|
||||
val userWallet = catch(
|
||||
block = { crudRepository.getUserWallet(userWalletId = userWalletId) },
|
||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||
)
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
return AccountList.empty(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrencies = emptySet(),
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
)
|
||||
.getOrElse {
|
||||
raise(Error.DataOperationFailed(message = "Account list not found for wallet $userWalletId"))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.saveAccounts(accountList: AccountList) {
|
||||
|
|
@ -115,7 +114,8 @@ class AddCryptoPortfolioUseCase(
|
|||
|
||||
/** Error indicating that a data operation failed */
|
||||
data class DataOperationFailed(val cause: Throwable) : Error {
|
||||
override fun toString(): String = "Data operation failed: ${cause.message ?: "Unknown error"}"
|
||||
|
||||
constructor(message: String) : this(cause = IllegalStateException(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -157,7 +157,7 @@ class AccountListTest {
|
|||
createAccount(userWalletId = userWalletId, name = "Name", derivationIndex = 0),
|
||||
createAccount(userWalletId = userWalletId, name = "Name", derivationIndex = 1),
|
||||
),
|
||||
expected = AccountList.Error.DuplicateAccountNames("Custom account names must be unique.").left(),
|
||||
expected = AccountList.Error.DuplicateAccountNames.left(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ 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.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
||||
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase.Error
|
||||
import com.tangem.domain.account.utils.createAccount
|
||||
import com.tangem.domain.account.utils.createAccounts
|
||||
import com.tangem.domain.models.account.Account
|
||||
|
|
@ -23,13 +26,20 @@ import org.junit.jupiter.api.TestInstance
|
|||
class AddCryptoPortfolioUseCaseTest {
|
||||
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val useCase = AddCryptoPortfolioUseCase(crudRepository)
|
||||
private val singleAccountListFetcher = mockk<SingleAccountListFetcher>()
|
||||
private val mainAccountTokensMigration = mockk<MainAccountTokensMigration>()
|
||||
|
||||
private val useCase = AddCryptoPortfolioUseCase(
|
||||
crudRepository = crudRepository,
|
||||
singleAccountListFetcher = singleAccountListFetcher,
|
||||
mainAccountTokensMigration = mainAccountTokensMigration,
|
||||
)
|
||||
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository, userWallet)
|
||||
clearMocks(crudRepository, singleAccountListFetcher, mainAccountTokensMigration, userWallet)
|
||||
|
||||
every { userWallet.walletId } returns userWalletId
|
||||
}
|
||||
|
|
@ -41,7 +51,13 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
val accountList = AccountList.empty(userWallet)
|
||||
val updatedAccountList = (accountList + newAccount).getOrNull()!!
|
||||
|
||||
coEvery {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
} returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
coEvery {
|
||||
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
|
||||
} returns Unit.right()
|
||||
|
||||
// Act
|
||||
val actual = useCase(
|
||||
|
|
@ -55,22 +71,23 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
val expected = newAccount.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) { crudRepository.getUserWallet(userWalletId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should create new account list if none exists`() = runTest {
|
||||
fun `invoke should return error if fetch is failed`() = runTest {
|
||||
// Arrange
|
||||
val newAccount = createNewAccount()
|
||||
val newAccountList = (AccountList.empty(userWallet) + newAccount).getOrNull()!!
|
||||
val exception = Exception("Fetch error")
|
||||
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
|
||||
coEvery { crudRepository.getUserWallet(userWalletId) } returns userWallet
|
||||
coEvery {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
} returns exception.left()
|
||||
|
||||
// Act
|
||||
val actual = useCase(
|
||||
|
|
@ -81,13 +98,54 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
)
|
||||
|
||||
// Assert
|
||||
val expected = newAccount.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
val expected = exception
|
||||
Truth.assertThat((actual.leftOrNull() as Error.DataOperationFailed).cause).isInstanceOf(expected::class.java)
|
||||
Truth.assertThat((actual.leftOrNull() as Error.DataOperationFailed).cause).hasMessageThat()
|
||||
.isEqualTo(expected.message)
|
||||
|
||||
coVerifyOrder {
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getAccountListSync(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
mainAccountTokensMigration.migrate(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if account list none exists`() = runTest {
|
||||
// Arrange
|
||||
val newAccount = createNewAccount()
|
||||
|
||||
coEvery {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
} returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
|
||||
|
||||
// Act
|
||||
val actual = useCase(
|
||||
userWalletId = userWalletId,
|
||||
accountName = newAccount.accountName,
|
||||
icon = newAccount.icon,
|
||||
derivationIndex = newAccount.derivationIndex,
|
||||
)
|
||||
|
||||
// Assert
|
||||
val expected = IllegalStateException("Accounts for $userWalletId are not created")
|
||||
Truth.assertThat((actual.leftOrNull() as Error.DataOperationFailed).cause).isInstanceOf(expected::class.java)
|
||||
Truth.assertThat((actual.leftOrNull() as Error.DataOperationFailed).cause).hasMessageThat()
|
||||
.isEqualTo(expected.message)
|
||||
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.getUserWallet(userWalletId)
|
||||
crudRepository.saveAccounts(newAccountList)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.saveAccounts(any())
|
||||
mainAccountTokensMigration.migrate(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,6 +160,9 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
|
||||
val newAccount = createNewAccount(derivationIndex = 21)
|
||||
|
||||
coEvery {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
} returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
|
|
@ -113,17 +174,20 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
)
|
||||
|
||||
// Assert
|
||||
val expected = AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet(
|
||||
val expected = Error.AccountListRequirementsNotMet(
|
||||
cause = AccountList.Error.ExceedsMaxAccountsCount,
|
||||
).left()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getUserWallet(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
mainAccountTokensMigration.migrate(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +197,9 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
val newAccount = createNewAccount()
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
} returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } throws exception
|
||||
|
||||
// Act
|
||||
|
|
@ -144,14 +211,17 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
)
|
||||
|
||||
// Assert
|
||||
val expected = AddCryptoPortfolioUseCase.Error.DataOperationFailed(cause = exception).left()
|
||||
val expected = Error.DataOperationFailed(cause = exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getUserWallet(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
mainAccountTokensMigration.migrate(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,6 +234,9 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
} returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
|
||||
|
||||
|
|
@ -176,15 +249,54 @@ class AddCryptoPortfolioUseCaseTest {
|
|||
)
|
||||
|
||||
// Assert
|
||||
val expected = AddCryptoPortfolioUseCase.Error.DataOperationFailed(cause = exception).left()
|
||||
val expected = Error.DataOperationFailed(cause = exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) { crudRepository.getUserWallet(userWalletId) }
|
||||
coVerify(inverse = true) {
|
||||
mainAccountTokensMigration.migrate(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return new account if migrate returns error`() = runTest {
|
||||
// Arrange
|
||||
val newAccount = createNewAccount()
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val updatedAccountList = (accountList + newAccount).getOrNull()!!
|
||||
|
||||
val exception = Exception("Migration error")
|
||||
coEvery {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
} returns Unit.right()
|
||||
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
|
||||
coEvery {
|
||||
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
|
||||
} returns exception.left()
|
||||
|
||||
// Act
|
||||
val actual = useCase(
|
||||
userWalletId = userWalletId,
|
||||
accountName = newAccount.accountName,
|
||||
icon = newAccount.icon,
|
||||
derivationIndex = newAccount.derivationIndex,
|
||||
)
|
||||
|
||||
// Assert
|
||||
val expected = newAccount.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifySequence {
|
||||
singleAccountListFetcher(SingleAccountListFetcher.Params(userWalletId))
|
||||
crudRepository.getAccountListSync(userWalletId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
mainAccountTokensMigration.migrate(userWalletId, newAccount.derivationIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.account.createedit
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.common.ui.account.AccountNameUM
|
||||
import com.tangem.common.ui.account.toDomain
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
|
|
@ -18,6 +18,7 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.ToastMessage
|
||||
import com.tangem.core.ui.utils.showErrorDialog
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase
|
||||
import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase
|
||||
|
|
@ -110,14 +111,30 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
derivationIndex = derivationIndex,
|
||||
)
|
||||
uiState.value = uiState.value.toggleProgress(showProgress = false)
|
||||
|
||||
result
|
||||
.onLeft { showMessage(it.toString()) }
|
||||
.onLeft(::handleAddAccountError)
|
||||
.onRight {
|
||||
showMessage(R.string.account_create_success_message)
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddAccountError(error: AddCryptoPortfolioUseCase.Error) {
|
||||
if (error is AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet &&
|
||||
error.cause is AccountList.Error.DuplicateAccountNames
|
||||
) {
|
||||
// TODO("account") show alert that the account name already exists
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: show alert https://www.figma.com/design/09KKG4ZVuFDZhj8WLv5rGJ/%F0%9F%9A%A7-App-experience?node-id=38882-113775&t=vk6TCy4MkYol1cPb-4
|
||||
logError(
|
||||
error = AccountFeatureError.CreateAccount.FailedToCreateAccount(cause = error),
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun editCryptoPortfolio(params: AccountCreateEditComponent.Params.Edit) {
|
||||
val state = uiState.value
|
||||
val name = state.account.name.toDomain().getOrNull() ?: return
|
||||
|
|
@ -203,33 +220,30 @@ internal class AccountCreateEditModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
.onLeft { cause ->
|
||||
handleError(
|
||||
error = AccountFeatureError.CreateAccount.UnableToGetDerivationIndex,
|
||||
message = cause.toString(),
|
||||
val error = AccountFeatureError.CreateAccount.UnableToGetDerivationIndex(cause)
|
||||
|
||||
logError(
|
||||
error = error,
|
||||
params = mapOf(
|
||||
"userWalletId" to userWalletId.stringValue,
|
||||
"cause" to cause.toString(),
|
||||
),
|
||||
)
|
||||
|
||||
messageSender.showErrorDialog(universalError = error, onDismiss = router::pop)
|
||||
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleError(
|
||||
error: AccountFeatureError,
|
||||
message: String? = null,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) {
|
||||
val exception = IllegalStateException("$error. Cause: $message")
|
||||
private fun logError(error: AccountFeatureError, params: Map<String, String> = mapOf()) {
|
||||
val exception = IllegalStateException(error.toString())
|
||||
|
||||
Timber.e(exception)
|
||||
|
||||
analyticsExceptionHandler.sendException(
|
||||
event = ExceptionAnalyticsEvent(exception = exception, params = params),
|
||||
)
|
||||
|
||||
messageSender.showErrorDialog(universalError = error, onDismiss = router::pop)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.features.account.createedit.error
|
||||
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase
|
||||
|
||||
sealed interface AccountFeatureError : UniversalError {
|
||||
|
||||
|
|
@ -14,9 +16,13 @@ sealed interface AccountFeatureError : UniversalError {
|
|||
|
||||
override val subsystemCode: String get() = "001"
|
||||
|
||||
data object UnableToGetDerivationIndex : CreateAccount {
|
||||
data class UnableToGetDerivationIndex(val cause: GetUnoccupiedAccountIndexUseCase.Error) : CreateAccount {
|
||||
override val specificErrorCode: String = "001"
|
||||
}
|
||||
|
||||
data class FailedToCreateAccount(val cause: AddCryptoPortfolioUseCase.Error) : CreateAccount {
|
||||
override val specificErrorCode: String = "002"
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface EditAccount : AccountFeatureError {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue