Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-06 18:05:37 +04:00
parent 077b2e9ecf
commit 34f07ed52d
9 changed files with 781 additions and 31 deletions

View file

@ -17,8 +17,8 @@ internal object AccountDomainModule {
@Provides
@Singleton
fun provideAddCryptoPortfolioUseCase(): AddCryptoPortfolioUseCase {
return AddCryptoPortfolioUseCase()
fun provideAddCryptoPortfolioUseCase(accountsCRUDRepository: AccountsCRUDRepository): AddCryptoPortfolioUseCase {
return AddCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
}
@Provides

View file

@ -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 }
}

View file

@ -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"}"
}
}
}

View file

@ -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()!!
}
}

View file

@ -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()!!
}
}

View file

@ -176,6 +176,27 @@ sealed interface Account {
)
}
}
/**
* Creates a main account for the given user wallet ID
*
* @param userWalletId the ID of the user wallet
*/
fun createMainAccount(userWalletId: UserWalletId): CryptoPortfolio {
// TODO: [REDACTED_JIRA]
return CryptoPortfolio(
accountId = AccountId(userWalletId = userWalletId, value = "main_account"),
name = AccountName.Main,
icon = CryptoPortfolioIcon.ofMainAccount(userWalletId),
derivationIndex = 0,
isArchived = false,
cryptoCurrencyList = CryptoCurrencyList(
currencies = emptySet(),
sortType = TokensSortType.NONE,
groupType = TokensGroupType.NONE,
),
)
}
}
}
}

View file

@ -44,8 +44,13 @@ data class AccountName private constructor(
companion object {
private const val MAIN_ACCOUNT_NAME = "Main Account"
private const val MAX_LENGTH = 20
/** Default name for the main account */
val Main: AccountName
get() = AccountName(value = MAIN_ACCOUNT_NAME)
/**
* Factory method to create an `AccountName` instance.
* Validates the input string to ensure it is not blank and does not exceed the maximum length.

View file

@ -3,6 +3,7 @@ package com.tangem.domain.models.account
import arrow.core.Either
import arrow.core.left
import com.google.common.truth.Truth
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
@ -13,6 +14,16 @@ import org.junit.jupiter.params.provider.MethodSource
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AccountNameTest {
@Test
fun main_returnsMainAccountName() {
// Act
val main = AccountName.Main.value
// Assert
val expected = "Main Account"
Truth.assertThat(main).isEqualTo(expected)
}
@ParameterizedTest
@MethodSource("provideTestModels")
fun invoke(model: InvokeTestModel) {

View file

@ -3,6 +3,7 @@ package com.tangem.domain.models.account
import com.google.common.truth.Truth
import com.tangem.domain.models.TokensGroupType
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.account.Account.CryptoPortfolio
import com.tangem.domain.models.account.Account.CryptoPortfolio.CryptoCurrencyList
import com.tangem.domain.models.account.Account.CryptoPortfolio.Error.AccountNameError
import com.tangem.domain.models.currency.CryptoCurrency
@ -99,7 +100,7 @@ class AccountTest {
val name = ""
// Act
val actual = Account.CryptoPortfolio(
val actual = CryptoPortfolio(
accountId = mockk(),
name = name,
accountIcon = mockk(),
@ -120,7 +121,7 @@ class AccountTest {
val derivationIndex = -1
// Act
val actual = Account.CryptoPortfolio(
val actual = CryptoPortfolio(
accountId = mockk(),
name = "Test Account",
accountIcon = mockk(),
@ -131,14 +132,14 @@ class AccountTest {
.leftOrNull()!!
// Assert
val expected = Account.CryptoPortfolio.Error.NegativeDerivationIndex
val expected = CryptoPortfolio.Error.NegativeDerivationIndex
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `invoke returns CryptoPortfolio`() {
// Act
val actual = Account.CryptoPortfolio(
val actual = CryptoPortfolio(
accountId = AccountId(
value = "value",
userWalletId = UserWalletId("011"),
@ -159,6 +160,32 @@ class AccountTest {
val expected = createCryptoPortfolioStub()
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun createMainAccount() {
// Arrange
val userWalletId = UserWalletId("011")
// Act
val actual = CryptoPortfolio.createMainAccount(userWalletId = userWalletId)
// Assert
// TODO: [REDACTED_JIRA]
val expected = CryptoPortfolio(
accountId = AccountId(userWalletId = userWalletId, value = "main_account"),
accountName = AccountName.Main,
accountIcon = CryptoPortfolioIcon.ofMainAccount(userWalletId),
derivationIndex = 0,
isArchived = false,
cryptoCurrencyList = CryptoCurrencyList(
currencies = emptySet(),
sortType = TokensSortType.NONE,
groupType = TokensGroupType.NONE,
),
).getOrNull()
Truth.assertThat(actual).isEqualTo(expected)
}
}
private fun createCryptoPortfolioStub(
@ -166,8 +193,8 @@ class AccountTest {
name: String = "Test Account",
derivationIndex: Int = 0,
currencies: Set<CryptoCurrency> = emptySet(),
): Account.CryptoPortfolio {
return Account.CryptoPortfolio.invoke(
): CryptoPortfolio {
return CryptoPortfolio.invoke(
accountId = AccountId(
value = "value",
userWalletId = userWalletId,