Updated on 2026-08-14
This commit is contained in:
parent
231127ad4d
commit
d369d87c2f
8 changed files with 565 additions and 32 deletions
|
|
@ -16,8 +16,9 @@ dependencies {
|
|||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.serialization)
|
||||
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit5)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import arrow.core.raise.either
|
|||
import arrow.core.raise.ensure
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
|
|
@ -27,6 +28,42 @@ data class AccountList private constructor(
|
|||
val mainAccount: Account.CryptoPortfolio
|
||||
get() = accounts.first { it is Account.CryptoPortfolio && it.isMainAccount } as Account.CryptoPortfolio
|
||||
|
||||
/**
|
||||
* Adds an account to the account list.
|
||||
* If an account with the same identifier already exists, it will be replaced.
|
||||
* Returns a new [AccountList] instance with the updated accounts set, or a validation error if constraints are
|
||||
* violated (e.g., maximum number of accounts exceeded).
|
||||
*
|
||||
* @param other the account to add or replace
|
||||
*/
|
||||
operator fun plus(other: Account): Either<Error, AccountList> {
|
||||
val isNewAccount = this.accounts.none { it.accountId == other.accountId }
|
||||
val accounts = this.accounts.addOrReplace(other) { it.accountId == other.accountId }
|
||||
|
||||
return invoke(
|
||||
userWallet = this.userWallet,
|
||||
accounts = accounts,
|
||||
totalAccounts = this.totalAccounts + if (isNewAccount) 1 else 0,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified account from the account list.
|
||||
* Returns a new [AccountList] instance with the updated accounts set, or a validation error if constraints are
|
||||
* violated (e.g., the list becomes empty).
|
||||
*
|
||||
* @param other the account to remove
|
||||
*/
|
||||
operator fun minus(other: Account): Either<Error, AccountList> {
|
||||
return invoke(
|
||||
userWallet = this.userWallet,
|
||||
accounts = this.accounts.toMutableSet().apply {
|
||||
removeIf { it.accountId == other.accountId }
|
||||
},
|
||||
totalAccounts = this.totalAccounts - 1,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents possible errors that can occur when creating an `AccountList`
|
||||
*/
|
||||
|
|
@ -54,10 +91,23 @@ data class AccountList private constructor(
|
|||
return "$tag: There should be at most one main crypto portfolio in the account list"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object ExceedsMaxAccountsCount : Error {
|
||||
override fun toString(): String = "$tag: The number of accounts must not exceed 20"
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object DuplicateAccountIds : Error {
|
||||
override fun toString(): String = "$tag: Account list contains duplicate account IDs"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val MAX_ACCOUNTS_COUNT = 20
|
||||
private const val MAX_MAIN_ACCOUNTS_COUNT = 1
|
||||
|
||||
/**
|
||||
* Factory method to create an `AccountList` instance.
|
||||
* Validates the input to ensure the accounts list is not empty and contains exactly one main account.
|
||||
|
|
@ -73,8 +123,10 @@ data class AccountList private constructor(
|
|||
): Either<Error, AccountList> = either {
|
||||
ensure(accounts.isNotEmpty()) { Error.EmptyAccountsList }
|
||||
|
||||
ensure(accounts.size <= MAX_ACCOUNTS_COUNT) { Error.ExceedsMaxAccountsCount }
|
||||
|
||||
val mainAccountsCount = accounts.mainAccountsCount()
|
||||
ensure(mainAccountsCount == 1) {
|
||||
ensure(mainAccountsCount == MAX_MAIN_ACCOUNTS_COUNT) {
|
||||
if (mainAccountsCount == 0) {
|
||||
Error.MainAccountNotFound
|
||||
} else {
|
||||
|
|
@ -82,6 +134,9 @@ data class AccountList private constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val uniqueAccountIdsCount = accounts.map { it.accountId.value }.distinct().size
|
||||
ensure(accounts.size == uniqueAccountIdsCount) { Error.DuplicateAccountIds }
|
||||
|
||||
AccountList(userWallet = userWallet, accounts = accounts, totalAccounts = totalAccounts)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +1,129 @@
|
|||
package com.tangem.domain.account.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import kotlin.random.Random
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Use case for updating a crypto portfolio account.
|
||||
*
|
||||
* @property crudRepository the repository used for performing CRUD operations on accounts
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class UpdateCryptoPortfolioUseCase {
|
||||
class UpdateCryptoPortfolioUseCase(
|
||||
private val crudRepository: AccountsCRUDRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Updates a crypto portfolio account with the provided name and/or icon
|
||||
*
|
||||
* @param accountId the unique identifier of the account to update
|
||||
* @param accountName the new name for the account (optional)
|
||||
* @param icon the new icon for the account (optional)
|
||||
* @return an [Either] containing the updated [Account.CryptoPortfolio] on success, or an [Error] on failure
|
||||
*/
|
||||
suspend operator fun invoke(
|
||||
accountId: AccountId,
|
||||
name: AccountName? = null,
|
||||
accountName: AccountName? = null,
|
||||
icon: CryptoPortfolioIcon? = null,
|
||||
): Either<Error, Account.CryptoPortfolio> = either {
|
||||
Account.CryptoPortfolio(
|
||||
accountId = accountId,
|
||||
name = name?.value ?: "Account",
|
||||
accountIcon = icon ?: CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
derivationIndex = Random.nextInt(from = 0, until = 21),
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = Account.CryptoPortfolio.CryptoCurrencyList(
|
||||
currencies = emptySet(),
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
)
|
||||
.mapLeft { Error.DataOperationFailed }
|
||||
.bind()
|
||||
ensure(accountName != null || icon != null) { Error.NothingToUpdate }
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
// Create a domain model AccountName
|
||||
// Get the current account by [accountId]
|
||||
// Create a new domain model Account from old data considering new parameters
|
||||
// Save information in local storage
|
||||
// Save to backend
|
||||
val accountList = getAccountList(userWalletId = accountId.userWalletId)
|
||||
|
||||
val account = accountList.accounts
|
||||
.firstOrNull { it.accountId == accountId } as? Account.CryptoPortfolio
|
||||
?: raise(Error.CriticalTechError.AccountNotFound(accountId = accountId))
|
||||
|
||||
val updatedAccount = account
|
||||
.setName(name = accountName)
|
||||
.setIcon(icon = icon)
|
||||
|
||||
val updatedAccounts = (accountList + updatedAccount)
|
||||
.getOrElse { raise(Error.CriticalTechError.AccountListRequirementsNotMet(it)) }
|
||||
|
||||
saveAccounts(updatedAccounts)
|
||||
|
||||
updatedAccount
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
|
||||
return catch(
|
||||
block = { crudRepository.getAccounts(userWalletId = userWalletId) },
|
||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||
)
|
||||
.getOrElse { raise(Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId)) }
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.saveAccounts(accountList: AccountList) {
|
||||
catch(
|
||||
block = { crudRepository.saveAccounts(accountList) },
|
||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun Account.CryptoPortfolio.setName(name: AccountName?): Account.CryptoPortfolio {
|
||||
return if (name != null) this.copy(accountName = name) else this
|
||||
}
|
||||
|
||||
private fun Account.CryptoPortfolio.setIcon(icon: CryptoPortfolioIcon?): Account.CryptoPortfolio {
|
||||
return if (icon != null) this.copy(accountIcon = icon) else this
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents possible errors that can occur during the update operation
|
||||
*/
|
||||
sealed interface Error {
|
||||
|
||||
data object DataOperationFailed : Error
|
||||
/** Error indicating that there is nothing to update */
|
||||
data object NothingToUpdate : Error {
|
||||
override fun toString(): String = "Nothing to update: both account name and icon are null"
|
||||
}
|
||||
|
||||
/** 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"}"
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents critical technical errors that can occur during the update operation.
|
||||
* These errors are a consequence of an inconsistent state.
|
||||
*/
|
||||
sealed interface CriticalTechError : Error {
|
||||
|
||||
/**
|
||||
|
||||
*
|
||||
* @property userWalletId the unique identifier of the user wallet
|
||||
*/
|
||||
data class AccountsNotCreated(val userWalletId: UserWalletId) : CriticalTechError {
|
||||
override fun toString(): String = "Accounts for $userWalletId are not created"
|
||||
}
|
||||
|
||||
/** Error indicating that the account with [accountId] was not found */
|
||||
data class AccountNotFound(val accountId: AccountId) : CriticalTechError {
|
||||
override fun toString(): String = "Account with ID $accountId not found"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error indicating that the account list requirements were not met.
|
||||
*
|
||||
* @property cause the underlying cause of the error
|
||||
*/
|
||||
data class AccountListRequirementsNotMet(val cause: AccountList.Error) : CriticalTechError {
|
||||
override fun toString(): String = "Account list requirements not met: $cause"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,9 @@ package com.tangem.domain.account.models
|
|||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.account.utils.randomAccountId
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
|
|
@ -92,6 +94,33 @@ class AccountListTest {
|
|||
),
|
||||
)
|
||||
},
|
||||
createAccounts(count = 20).let {
|
||||
CreateTestModel(
|
||||
accounts = it,
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = it,
|
||||
totalAccounts = 20,
|
||||
),
|
||||
)
|
||||
},
|
||||
CreateTestModel(
|
||||
accounts = createAccounts(21),
|
||||
expected = AccountList.Error.ExceedsMaxAccountsCount.left(),
|
||||
),
|
||||
CreateTestModel(
|
||||
accounts = setOf(
|
||||
createAccount(
|
||||
accountId = AccountId(value = "1", userWalletId = mockk()),
|
||||
isMain = true,
|
||||
),
|
||||
createAccount(
|
||||
accountId = AccountId(value = "1", userWalletId = mockk()),
|
||||
isMain = false,
|
||||
),
|
||||
),
|
||||
expected = AccountList.Error.DuplicateAccountIds.left(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -100,9 +129,22 @@ class AccountListTest {
|
|||
val expected: Either<AccountList.Error, AccountList>,
|
||||
)
|
||||
|
||||
private fun createAccount(isMain: Boolean = false): Account.CryptoPortfolio {
|
||||
private fun createAccounts(count: Int): Set<Account.CryptoPortfolio> {
|
||||
return buildSet {
|
||||
add(createAccount(isMain = true))
|
||||
repeat(count - 1) {
|
||||
add(createAccount(isMain = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAccount(
|
||||
accountId: AccountId = AccountId(value = randomAccountId(5), userWalletId = mockk()),
|
||||
isMain: Boolean = false,
|
||||
): Account.CryptoPortfolio {
|
||||
return mockk<Account.CryptoPortfolio> {
|
||||
every { isMainAccount } returns isMain
|
||||
every { this@mockk.accountId } returns accountId
|
||||
every { this@mockk.isMainAccount } returns isMain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
package com.tangem.domain.account.usecase
|
||||
|
||||
import arrow.core.None
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import arrow.core.toOption
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase.Error
|
||||
import com.tangem.domain.account.utils.randomAccountId
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
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
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class UpdateCryptoPortfolioUseCaseTest {
|
||||
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val useCase = UpdateCryptoPortfolioUseCase(crudRepository = crudRepository)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should update crypto portfolio account with new name`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId(value = "test-account-id", userWalletId = mockk())
|
||||
val userWalletId = accountId.userWalletId
|
||||
val account = createAccount(accountId = accountId, isMain = true)
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
accounts = setOf(account),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = accountId, accountName = newAccountName)
|
||||
|
||||
// Assert
|
||||
val updatedAccount = account.copy(accountName = newAccountName)
|
||||
val expected = updatedAccount.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
val updatedAccountList = (accountList + updatedAccount).getOrNull()!!
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccounts(userWalletId = userWalletId)
|
||||
crudRepository.saveAccounts(accountList = updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should update crypto portfolio account with new icon`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId(value = "test-account-id", userWalletId = mockk())
|
||||
val userWalletId = accountId.userWalletId
|
||||
val account = createAccount(accountId = accountId, isMain = true)
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
accounts = setOf(account),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
||||
val newAccountIcon = CryptoPortfolioIcon.ofCustomAccount(
|
||||
value = CryptoPortfolioIcon.Icon.Star,
|
||||
color = CryptoPortfolioIcon.Color.CaribbeanBlue,
|
||||
)
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = accountId, icon = newAccountIcon)
|
||||
|
||||
// Assert
|
||||
val updatedAccount = account.copy(accountIcon = newAccountIcon)
|
||||
val expected = updatedAccount.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
val updatedAccountList = (accountList + updatedAccount).getOrNull()!!
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccounts(userWalletId = userWalletId)
|
||||
crudRepository.saveAccounts(accountList = updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should update crypto portfolio account with new name and icon`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId(value = "test-account-id", userWalletId = mockk())
|
||||
val userWalletId = accountId.userWalletId
|
||||
val account = createAccount(accountId = accountId, isMain = true)
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
accounts = setOf(account),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
val newAccountIcon = CryptoPortfolioIcon.ofCustomAccount(
|
||||
value = CryptoPortfolioIcon.Icon.Star,
|
||||
color = CryptoPortfolioIcon.Color.CaribbeanBlue,
|
||||
)
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = accountId, accountName = newAccountName, icon = newAccountIcon)
|
||||
|
||||
// Assert
|
||||
val updatedAccount = account.copy(accountName = newAccountName, accountIcon = newAccountIcon)
|
||||
val expected = updatedAccount.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
val updatedAccountList = (accountList + updatedAccount).getOrNull()!!
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccounts(userWalletId = userWalletId)
|
||||
crudRepository.saveAccounts(accountList = updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke if name and icon are null`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId(value = "test-account-id", userWalletId = mockk())
|
||||
val userWalletId = accountId.userWalletId
|
||||
val account = createAccount(accountId = accountId, isMain = true)
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
accounts = setOf(account),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.NothingToUpdate.left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getAccounts(userWalletId = any())
|
||||
crudRepository.saveAccounts(accountList = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke if getAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId(value = "test-account-id", userWalletId = mockk())
|
||||
val userWalletId = accountId.userWalletId
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
val exception = IllegalStateException("Test exception")
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = accountId, accountName = newAccountName)
|
||||
|
||||
// Assert
|
||||
val expected = Error.DataOperationFailed(cause = exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccounts(userWalletId = userWalletId) }
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(accountList = any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke if getAccounts returns None`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId(value = "test-account-id", userWalletId = mockk())
|
||||
val userWalletId = accountId.userWalletId
|
||||
val accountList = None
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = accountId, accountName = newAccountName)
|
||||
|
||||
// Assert
|
||||
val expected = Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccounts(userWalletId = userWalletId) }
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(accountList = any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke if getAccounts does not contain accountId`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId(value = "test-account-id", userWalletId = mockk())
|
||||
val userWalletId = accountId.userWalletId
|
||||
val account = createAccount(
|
||||
accountId = AccountId(value = "another-account-id", userWalletId = mockk()),
|
||||
isMain = true,
|
||||
)
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
accounts = setOf(account),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = accountId, accountName = newAccountName)
|
||||
|
||||
// Assert
|
||||
val expected = Error.CriticalTechError.AccountNotFound(accountId = accountId).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccounts(userWalletId = userWalletId) }
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(accountList = any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke if saveAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId(value = "test-account-id", userWalletId = mockk())
|
||||
val userWalletId = accountId.userWalletId
|
||||
val account = createAccount(accountId = accountId, isMain = true)
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
accounts = setOf(account),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!
|
||||
|
||||
val newAccountName = AccountName("New name").getOrNull()!!
|
||||
val updatedAccount = account.copy(accountName = newAccountName)
|
||||
val updatedAccountList = (accountList + updatedAccount).getOrNull()!!
|
||||
|
||||
val exception = IllegalStateException("Save failed")
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.saveAccounts(accountList = updatedAccountList) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId = accountId, accountName = newAccountName)
|
||||
|
||||
// Assert
|
||||
val expected = Error.DataOperationFailed(cause = exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccounts(userWalletId = userWalletId)
|
||||
crudRepository.saveAccounts(accountList = updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAccount(
|
||||
accountId: AccountId = AccountId(value = randomAccountId(length = 5), userWalletId = mockk()),
|
||||
accountIcon: CryptoPortfolioIcon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
isMain: Boolean,
|
||||
): Account.CryptoPortfolio {
|
||||
return Account.CryptoPortfolio(
|
||||
accountId = accountId,
|
||||
name = "Test Account",
|
||||
accountIcon = accountIcon,
|
||||
derivationIndex = if (isMain) 0 else Random.nextInt(1, 21),
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = Account.CryptoPortfolio.CryptoCurrencyList(
|
||||
currencies = emptySet(),
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
)
|
||||
.getOrNull()!!
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.account.utils
|
||||
|
||||
fun randomAccountId(length: Int): String {
|
||||
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
return (1..length)
|
||||
.map { chars.random() }
|
||||
.joinToString("")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue