Updated on 2026-08-14
This commit is contained in:
parent
077b2e9ecf
commit
34f07ed52d
9 changed files with 781 additions and 31 deletions
|
|
@ -28,6 +28,10 @@ data class AccountList private constructor(
|
|||
val mainAccount: Account.CryptoPortfolio
|
||||
get() = accounts.first { it is Account.CryptoPortfolio && it.isMainAccount } as Account.CryptoPortfolio
|
||||
|
||||
/** Returns true if more accounts can be added (the maximum number of accounts has not been reached) */
|
||||
val canAddMoreAccounts: Boolean
|
||||
get() = accounts.size < MAX_ACCOUNTS_COUNT
|
||||
|
||||
/**
|
||||
* Adds an account to the account list.
|
||||
* If an account with the same identifier already exists, it will be replaced.
|
||||
|
|
@ -55,12 +59,15 @@ data class AccountList private constructor(
|
|||
* @param other the account to remove
|
||||
*/
|
||||
operator fun minus(other: Account): Either<Error, AccountList> {
|
||||
val isExistingAccount = this.accounts.any { it.accountId == other.accountId }
|
||||
val accounts = this.accounts.toMutableSet().apply {
|
||||
removeIf { it.accountId == other.accountId }
|
||||
}
|
||||
|
||||
return invoke(
|
||||
userWallet = this.userWallet,
|
||||
accounts = this.accounts.toMutableSet().apply {
|
||||
removeIf { it.accountId == other.accountId }
|
||||
},
|
||||
totalAccounts = this.totalAccounts - 1,
|
||||
accounts = accounts,
|
||||
totalAccounts = this.totalAccounts - if (isExistingAccount) 1 else 0,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +147,21 @@ data class AccountList private constructor(
|
|||
AccountList(userWallet = userWallet, accounts = accounts, totalAccounts = totalAccounts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create an empty [AccountList] with a main crypto portfolio account
|
||||
*
|
||||
* @param userWallet the user wallet associated with the account list
|
||||
*/
|
||||
fun createEmpty(userWallet: UserWallet): AccountList {
|
||||
return AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(
|
||||
Account.CryptoPortfolio.createMainAccount(userWalletId = userWallet.walletId),
|
||||
),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Set<Account>.mainAccountsCount(): Int {
|
||||
return count { (it as? Account.CryptoPortfolio)?.isMainAccount == true }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
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.models.AccountList
|
||||
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase.Error.AccountCreation
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account
|
||||
|
|
@ -14,19 +18,56 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Use case for adding a new crypto portfolio account
|
||||
*
|
||||
* @property crudRepository the repository used for performing CRUD operations on accounts
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AddCryptoPortfolioUseCase {
|
||||
class AddCryptoPortfolioUseCase(
|
||||
private val crudRepository: AccountsCRUDRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Adds a new crypto portfolio account to the repository
|
||||
*
|
||||
* @param userWalletId the unique identifier of the user wallet
|
||||
* @param accountName the name of the new account
|
||||
* @param icon the icon representing the new account
|
||||
* @param derivationIndex the derivation index for the account
|
||||
*
|
||||
|
||||
*/
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
accountName: AccountName,
|
||||
icon: CryptoPortfolioIcon,
|
||||
derivationIndex: Int,
|
||||
): Either<Error, Account.CryptoPortfolio> = either {
|
||||
Account.CryptoPortfolio(
|
||||
val newAccount = createAccount(userWalletId, accountName, icon, derivationIndex)
|
||||
|
||||
val accountList = getAccountList(userWalletId = userWalletId).getOrElse {
|
||||
createNewAccountList(userWalletId = userWalletId)
|
||||
}
|
||||
|
||||
val updatedAccounts = (accountList + newAccount)
|
||||
.getOrElse { raise(Error.AccountListRequirementsNotMet(it)) }
|
||||
|
||||
saveAccounts(updatedAccounts)
|
||||
|
||||
newAccount
|
||||
}
|
||||
|
||||
private fun Raise<Error>.createAccount(
|
||||
userWalletId: UserWalletId,
|
||||
accountName: AccountName,
|
||||
icon: CryptoPortfolioIcon,
|
||||
derivationIndex: Int,
|
||||
): Account.CryptoPortfolio {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
return Account.CryptoPortfolio(
|
||||
accountId = AccountId(userWalletId = userWalletId, value = UUID.randomUUID().toString()),
|
||||
name = accountName.value,
|
||||
accountName = accountName,
|
||||
accountIcon = icon,
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = false,
|
||||
|
|
@ -36,20 +77,56 @@ class AddCryptoPortfolioUseCase {
|
|||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
)
|
||||
.mapLeft(::AccountCreation)
|
||||
.bind()
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
// Save to local store
|
||||
// Save to backend (tokens migration) – asynchronously
|
||||
.getOrElse { raise(Error.AccountCreation(it)) }
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): Option<AccountList> {
|
||||
return catch(
|
||||
block = { crudRepository.getAccounts(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)) },
|
||||
)
|
||||
|
||||
return AccountList.createEmpty(userWallet = userWallet)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.saveAccounts(accountList: AccountList) {
|
||||
catch(
|
||||
block = { crudRepository.saveAccounts(accountList) },
|
||||
catch = { raise(Error.DataOperationFailed(cause = it)) },
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents possible errors that can occur during the add operation
|
||||
*/
|
||||
sealed interface Error {
|
||||
|
||||
/**
|
||||
* Error indicating that the account creation failed
|
||||
*
|
||||
* @property cause the underlying cause of the failure
|
||||
*/
|
||||
data class AccountCreation(val cause: Account.CryptoPortfolio.Error) : Error
|
||||
|
||||
data class AccountListRequirementsNotMet(val cause: AccountList.Error) : Error
|
||||
/**
|
||||
* 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) : Error {
|
||||
override fun toString(): String = "Account list requirements not met: $cause"
|
||||
}
|
||||
|
||||
data object DataOperationFailed : Error
|
||||
/** 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"}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,14 @@ 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.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 com.tangem.domain.models.wallet.UserWallet
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
|
|
@ -16,6 +19,7 @@ import org.junit.jupiter.api.Test
|
|||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -43,6 +47,44 @@ class AccountListTest {
|
|||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canAddMoreAccounts() {
|
||||
// Arrange
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
accounts = createAccounts(count = 2),
|
||||
totalAccounts = 2,
|
||||
).getOrNull()!!
|
||||
|
||||
val fullAccountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
accounts = createAccounts(20),
|
||||
totalAccounts = 20,
|
||||
).getOrNull()!!
|
||||
|
||||
// Act & Assert
|
||||
Truth.assertThat(accountList.canAddMoreAccounts).isTrue()
|
||||
Truth.assertThat(fullAccountList.canAddMoreAccounts).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createEmpty() {
|
||||
// Arrange
|
||||
val userWallet = mockk<UserWallet>(relaxed = true)
|
||||
|
||||
// Act
|
||||
val actual = AccountList.createEmpty(userWallet)
|
||||
|
||||
// Assert
|
||||
val expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWallet.walletId)),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Create {
|
||||
|
|
@ -129,6 +171,224 @@ class AccountListTest {
|
|||
val expected: Either<AccountList.Error, AccountList>,
|
||||
)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Plus {
|
||||
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun invoke(model: PlusTestModel) {
|
||||
// Act
|
||||
val actual = model.initial.plus(other = model.toAdd)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
// region Add new account
|
||||
run {
|
||||
val mainAccount = createAccount(
|
||||
accountId = AccountId(value = "1", userWalletId = mockk()),
|
||||
isMain = true,
|
||||
)
|
||||
|
||||
val newAccount = createAccount(isMain = false)
|
||||
|
||||
PlusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!,
|
||||
toAdd = newAccount,
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(mainAccount, newAccount),
|
||||
totalAccounts = 2,
|
||||
),
|
||||
)
|
||||
},
|
||||
// endregion
|
||||
// region Replace existing account
|
||||
run {
|
||||
val mainAccount = createAccount(
|
||||
accountId = AccountId(value = "1", userWalletId = mockk()),
|
||||
isMain = true,
|
||||
)
|
||||
|
||||
val newAccount = mainAccount.copy(accountName = AccountName("New Name").getOrNull()!!)
|
||||
|
||||
PlusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!,
|
||||
toAdd = newAccount,
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(newAccount),
|
||||
totalAccounts = 1,
|
||||
),
|
||||
)
|
||||
},
|
||||
// endregion
|
||||
PlusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = createAccounts(20),
|
||||
totalAccounts = 20,
|
||||
).getOrNull()!!,
|
||||
toAdd = createAccount(isMain = false),
|
||||
expected = AccountList.Error.ExceedsMaxAccountsCount.left(),
|
||||
),
|
||||
PlusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(createAccount(isMain = true)),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!,
|
||||
toAdd = createAccount(isMain = true),
|
||||
expected = AccountList.Error.ExceedsMaxMainAccountsCount.left(),
|
||||
),
|
||||
PlusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(
|
||||
createAccount(isMain = true),
|
||||
createAccount(isMain = false),
|
||||
),
|
||||
totalAccounts = 2,
|
||||
).getOrNull()!!,
|
||||
toAdd = createAccount(isMain = true),
|
||||
expected = AccountList.Error.ExceedsMaxMainAccountsCount.left(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class PlusTestModel(
|
||||
val initial: AccountList,
|
||||
val toAdd: Account,
|
||||
val expected: Either<AccountList.Error, AccountList>,
|
||||
)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Minus {
|
||||
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun invoke(model: MinusTestModel) {
|
||||
// Act
|
||||
val actual = model.initial.minus(model.toRemove)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
// region Remove existing account
|
||||
run {
|
||||
val mainAccount = createAccount(
|
||||
accountId = AccountId(value = "1", userWalletId = mockk()),
|
||||
isMain = true,
|
||||
)
|
||||
|
||||
val secondaryAccount = createAccount(
|
||||
accountId = AccountId(value = "2", userWalletId = mockk()),
|
||||
isMain = false,
|
||||
)
|
||||
|
||||
MinusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(mainAccount, secondaryAccount),
|
||||
totalAccounts = 2,
|
||||
).getOrNull()!!,
|
||||
toRemove = secondaryAccount,
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
),
|
||||
)
|
||||
},
|
||||
// endregion
|
||||
// region Remove unexisting account
|
||||
run {
|
||||
val mainAccount = createAccount(
|
||||
accountId = AccountId(value = "1", userWalletId = mockk()),
|
||||
isMain = true,
|
||||
)
|
||||
val notInList = createAccount(
|
||||
accountId = AccountId(value = "3", userWalletId = mockk()),
|
||||
isMain = false,
|
||||
)
|
||||
MinusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!,
|
||||
toRemove = notInList,
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
),
|
||||
)
|
||||
},
|
||||
// endregion
|
||||
// region EmptyAccountsList
|
||||
run {
|
||||
val mainAccount = createAccount(isMain = true)
|
||||
|
||||
MinusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!,
|
||||
toRemove = mainAccount,
|
||||
expected = AccountList.Error.EmptyAccountsList.left(),
|
||||
)
|
||||
},
|
||||
// endregion
|
||||
// region MainAccountNotFound
|
||||
run {
|
||||
val mainAccount = createAccount(
|
||||
accountId = AccountId(value = "1", userWalletId = mockk()),
|
||||
isMain = true,
|
||||
)
|
||||
val secondaryAccount = createAccount(
|
||||
accountId = AccountId(value = "2", userWalletId = mockk()),
|
||||
isMain = false,
|
||||
)
|
||||
MinusTestModel(
|
||||
initial = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(mainAccount, secondaryAccount),
|
||||
totalAccounts = 2,
|
||||
).getOrNull()!!,
|
||||
toRemove = mainAccount,
|
||||
expected = AccountList.Error.MainAccountNotFound.left(),
|
||||
)
|
||||
},
|
||||
// endregion
|
||||
)
|
||||
}
|
||||
|
||||
data class MinusTestModel(
|
||||
val initial: AccountList,
|
||||
val toRemove: Account,
|
||||
val expected: Either<AccountList.Error, AccountList>,
|
||||
)
|
||||
|
||||
private fun createAccounts(count: Int): Set<Account.CryptoPortfolio> {
|
||||
return buildSet {
|
||||
add(createAccount(isMain = true))
|
||||
|
|
@ -139,12 +399,22 @@ class AccountListTest {
|
|||
}
|
||||
|
||||
private fun createAccount(
|
||||
accountId: AccountId = AccountId(value = randomAccountId(5), userWalletId = mockk()),
|
||||
isMain: Boolean = false,
|
||||
accountId: AccountId = AccountId(value = randomAccountId(length = 5), userWalletId = mockk()),
|
||||
accountIcon: CryptoPortfolioIcon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
isMain: Boolean,
|
||||
): Account.CryptoPortfolio {
|
||||
return mockk<Account.CryptoPortfolio> {
|
||||
every { this@mockk.accountId } returns accountId
|
||||
every { this@mockk.isMainAccount } returns isMain
|
||||
}
|
||||
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,317 @@
|
|||
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.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 com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
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 java.util.UUID
|
||||
import kotlin.random.Random
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class AddCryptoPortfolioUseCaseTest {
|
||||
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val useCase = AddCryptoPortfolioUseCase(crudRepository)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository, userWallet)
|
||||
|
||||
every { userWallet.walletId } returns userWalletId
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should add new crypto portfolio account to existing list`() = runTest {
|
||||
// Arrange
|
||||
val existingAccount = createAccount(
|
||||
name = "Main Account",
|
||||
derivationIndex = 0,
|
||||
icon = CryptoPortfolioIcon.ofMainAccount(userWalletId = userWalletId),
|
||||
)
|
||||
|
||||
val accountList = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(existingAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!
|
||||
|
||||
val fakeUUID = UUID.randomUUID()
|
||||
|
||||
mockkStatic(UUID::class)
|
||||
every { UUID.randomUUID() } returns fakeUUID
|
||||
|
||||
val newAccount = createAccount(
|
||||
accountId = AccountId(value = fakeUUID.toString(), userWalletId = userWalletId),
|
||||
name = "New Account",
|
||||
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
derivationIndex = 1,
|
||||
)
|
||||
|
||||
val updatedAccountList = (accountList + newAccount).getOrNull()!!
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.saveAccounts(updatedAccountList) } just Runs
|
||||
|
||||
// Act
|
||||
val actual = useCase(
|
||||
userWalletId = userWalletId,
|
||||
accountName = newAccount.name,
|
||||
icon = newAccount.icon,
|
||||
derivationIndex = newAccount.derivationIndex,
|
||||
)
|
||||
|
||||
// Assert
|
||||
val expected = newAccount.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccounts(userWalletId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) { crudRepository.getUserWallet(userWalletId) }
|
||||
|
||||
unmockkStatic(UUID::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should create new account list if none exists`() = runTest {
|
||||
// Arrange
|
||||
val fakeUUID = UUID.randomUUID()
|
||||
|
||||
mockkStatic(UUID::class)
|
||||
every { UUID.randomUUID() } returns fakeUUID
|
||||
|
||||
val newAccount = createAccount(
|
||||
accountId = AccountId(value = fakeUUID.toString(), userWalletId = userWalletId),
|
||||
name = "New Account",
|
||||
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
derivationIndex = 1,
|
||||
)
|
||||
|
||||
val newAccountList = (AccountList.createEmpty(userWallet) + newAccount).getOrNull()!!
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } returns None
|
||||
coEvery { crudRepository.getUserWallet(userWalletId) } returns userWallet
|
||||
coEvery { crudRepository.saveAccounts(newAccountList) } just Runs
|
||||
|
||||
// Act
|
||||
val actual = useCase(
|
||||
userWalletId = userWalletId,
|
||||
accountName = newAccount.name,
|
||||
icon = newAccount.icon,
|
||||
derivationIndex = newAccount.derivationIndex,
|
||||
)
|
||||
|
||||
// Assert
|
||||
val expected = newAccount.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccounts(userWalletId)
|
||||
crudRepository.getUserWallet(userWalletId)
|
||||
crudRepository.saveAccounts(newAccountList)
|
||||
}
|
||||
|
||||
unmockkStatic(UUID::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if account creation fails`() = runTest {
|
||||
// Act
|
||||
val actual = useCase(
|
||||
userWalletId = userWalletId,
|
||||
accountName = AccountName.Main,
|
||||
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
derivationIndex = -1,
|
||||
)
|
||||
|
||||
// Assert
|
||||
val expected = AddCryptoPortfolioUseCase.Error.AccountCreation(
|
||||
cause = Account.CryptoPortfolio.Error.NegativeDerivationIndex,
|
||||
).left()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getAccounts(any())
|
||||
crudRepository.getUserWallet(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if account list requirements not met`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = createAccounts(count = 20),
|
||||
totalAccounts = 20,
|
||||
).getOrNull()!!
|
||||
|
||||
val newAccount = createAccount(
|
||||
name = "New Account",
|
||||
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
derivationIndex = 1,
|
||||
)
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(
|
||||
userWalletId = userWalletId,
|
||||
accountName = newAccount.name,
|
||||
icon = newAccount.icon,
|
||||
derivationIndex = newAccount.derivationIndex,
|
||||
)
|
||||
|
||||
// Assert
|
||||
val expected = AddCryptoPortfolioUseCase.Error.AccountListRequirementsNotMet(
|
||||
cause = AccountList.Error.ExceedsMaxAccountsCount,
|
||||
).left()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
|
||||
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getUserWallet(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if getAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val newAccount = createAccount(
|
||||
name = "New Account",
|
||||
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
derivationIndex = 1,
|
||||
)
|
||||
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = useCase(
|
||||
userWalletId = userWalletId,
|
||||
accountName = newAccount.name,
|
||||
icon = newAccount.icon,
|
||||
derivationIndex = newAccount.derivationIndex,
|
||||
)
|
||||
|
||||
// Assert
|
||||
val expected = AddCryptoPortfolioUseCase.Error.DataOperationFailed(cause = exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
|
||||
|
||||
coVerify(inverse = true) {
|
||||
crudRepository.getUserWallet(any())
|
||||
crudRepository.saveAccounts(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if saveAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val existingAccount = createAccount(
|
||||
name = "Main Account",
|
||||
derivationIndex = 0,
|
||||
icon = CryptoPortfolioIcon.ofMainAccount(userWalletId = userWalletId),
|
||||
)
|
||||
|
||||
val accountList = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(existingAccount),
|
||||
totalAccounts = 1,
|
||||
).getOrNull()!!
|
||||
|
||||
val fakeUUID = UUID.randomUUID()
|
||||
|
||||
mockkStatic(UUID::class)
|
||||
every { UUID.randomUUID() } returns fakeUUID
|
||||
|
||||
val newAccount = createAccount(
|
||||
accountId = AccountId(value = fakeUUID.toString(), userWalletId = userWalletId),
|
||||
name = "New Account",
|
||||
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
derivationIndex = 1,
|
||||
)
|
||||
|
||||
val updatedAccountList = (accountList + newAccount).getOrNull()!!
|
||||
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
|
||||
|
||||
// Act
|
||||
useCase(
|
||||
userWalletId = userWalletId,
|
||||
accountName = newAccount.name,
|
||||
icon = newAccount.icon,
|
||||
derivationIndex = newAccount.derivationIndex,
|
||||
)
|
||||
|
||||
// Assert
|
||||
// val expected = AddCryptoPortfolioUseCase.Error.DataOperationFailed(cause = exception).left()
|
||||
// Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccounts(userWalletId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) { crudRepository.getUserWallet(userWalletId) }
|
||||
}
|
||||
|
||||
private fun createAccounts(count: Int): Set<Account.CryptoPortfolio> {
|
||||
return buildSet {
|
||||
add(createAccount(derivationIndex = 0))
|
||||
repeat(count - 1) {
|
||||
add(createAccount())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAccount(
|
||||
accountId: AccountId? = null,
|
||||
name: String = "Test Account",
|
||||
icon: CryptoPortfolioIcon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
|
||||
derivationIndex: Int = Random.nextInt(1, 21),
|
||||
): Account.CryptoPortfolio {
|
||||
return Account.CryptoPortfolio(
|
||||
accountId = accountId ?: AccountId(value = UUID.randomUUID().toString(), userWalletId = userWalletId),
|
||||
accountName = AccountName(name).getOrNull()!!,
|
||||
accountIcon = icon,
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = Account.CryptoPortfolio.CryptoCurrencyList(
|
||||
currencies = emptySet(),
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
).getOrNull()!!
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue