Updated on 2026-08-14
This commit is contained in:
parent
be160483d2
commit
158d625e05
10 changed files with 288 additions and 0 deletions
1
domain/account/.gitignore
vendored
Normal file
1
domain/account/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
23
domain/account/build.gradle.kts
Normal file
23
domain/account/build.gradle.kts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.wallets.models)
|
||||
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.serialization)
|
||||
|
||||
testImplementation(deps.test.junit5)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.mockk)
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.domain.account.models
|
||||
|
||||
import arrow.core.Either
|
||||
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 kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a list of accounts associated with a user wallet
|
||||
*
|
||||
* @property userWallet the user wallet associated with the account list
|
||||
* @property accounts a set of accounts belonging to the user wallet
|
||||
* @property totalAccounts the total number of accounts
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Serializable
|
||||
data class AccountList private constructor(
|
||||
val userWallet: UserWallet,
|
||||
val accounts: Set<Account>,
|
||||
val totalAccounts: Int,
|
||||
) {
|
||||
|
||||
/** Retrieves the main crypto portfolio account from the list of accounts */
|
||||
val mainAccount: Account.CryptoPortfolio
|
||||
get() = accounts.first { it is Account.CryptoPortfolio && it.isMainAccount } as Account.CryptoPortfolio
|
||||
|
||||
/**
|
||||
* Represents possible errors that can occur when creating an `AccountList`
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface Error {
|
||||
|
||||
val tag: String
|
||||
get() = this::class.simpleName ?: "AccountListError"
|
||||
|
||||
@Serializable
|
||||
data object EmptyAccountsList : Error {
|
||||
override fun toString(): String = "$tag: The accounts list cannot be empty"
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object MainAccountNotFound : Error {
|
||||
override fun toString(): String {
|
||||
return "$tag: Account list does not contain a main crypto portfolio account"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object ExceedsMaxMainAccountsCount : Error {
|
||||
override fun toString(): String {
|
||||
return "$tag: There should be at most one main crypto portfolio in the account list"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Factory method to create an `AccountList` instance.
|
||||
* Validates the input to ensure the accounts list is not empty and contains exactly one main account.
|
||||
*
|
||||
* @param userWallet the user wallet associated with the account list
|
||||
* @param accounts a set of accounts belonging to the user wallet
|
||||
* @param totalAccounts the total number of accounts
|
||||
*/
|
||||
operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
accounts: Set<Account>,
|
||||
totalAccounts: Int,
|
||||
): Either<Error, AccountList> = either {
|
||||
ensure(accounts.isNotEmpty()) { Error.EmptyAccountsList }
|
||||
|
||||
val mainAccountsCount = accounts.mainAccountsCount()
|
||||
ensure(mainAccountsCount == 1) {
|
||||
if (mainAccountsCount == 0) {
|
||||
Error.MainAccountNotFound
|
||||
} else {
|
||||
Error.ExceedsMaxMainAccountsCount
|
||||
}
|
||||
}
|
||||
|
||||
AccountList(userWallet = userWallet, accounts = accounts, totalAccounts = totalAccounts)
|
||||
}
|
||||
|
||||
private fun Set<Account>.mainAccountsCount(): Int {
|
||||
return count { (it as? Account.CryptoPortfolio)?.isMainAccount == true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.domain.account.models
|
||||
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a list of account statuses associated with a user wallet
|
||||
*
|
||||
* @property userWallet the user wallet to which the account statuses belong
|
||||
* @property accountStatuses a set of account statuses associated with the user wallet
|
||||
* @property totalAccounts the total number of accounts
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Serializable
|
||||
data class AccountStatusList(
|
||||
val userWallet: UserWallet,
|
||||
val accountStatuses: Set<AccountStatus>,
|
||||
val totalAccounts: Int,
|
||||
)
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package com.tangem.domain.account.models
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.models.account.Account
|
||||
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
|
||||
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
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class AccountListTest {
|
||||
|
||||
@Test
|
||||
fun mainAccount() {
|
||||
// Arrange
|
||||
val mainAccount = createAccount(isMain = true)
|
||||
|
||||
val accountList = AccountList(
|
||||
userWallet = mockk(),
|
||||
accounts = setOf(mainAccount),
|
||||
totalAccounts = 1,
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
||||
// Act
|
||||
val actual = accountList.mainAccount
|
||||
|
||||
// Assert
|
||||
val expected = mainAccount
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Create {
|
||||
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(userWallet)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun invoke(model: CreateTestModel) {
|
||||
// Act
|
||||
val actual = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = model.accounts,
|
||||
totalAccounts = model.accounts.size,
|
||||
)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
CreateTestModel(
|
||||
accounts = emptySet(),
|
||||
expected = AccountList.Error.EmptyAccountsList.left(),
|
||||
),
|
||||
CreateTestModel(
|
||||
accounts = setOf(createAccount(isMain = false)),
|
||||
expected = AccountList.Error.MainAccountNotFound.left(),
|
||||
),
|
||||
CreateTestModel(
|
||||
accounts = setOf(
|
||||
createAccount(isMain = true),
|
||||
createAccount(isMain = true),
|
||||
),
|
||||
expected = AccountList.Error.ExceedsMaxMainAccountsCount.left(),
|
||||
),
|
||||
createAccount(isMain = true).let {
|
||||
CreateTestModel(
|
||||
accounts = setOf(it),
|
||||
expected = AccountList(
|
||||
userWallet = userWallet,
|
||||
accounts = setOf(it),
|
||||
totalAccounts = 1,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
data class CreateTestModel(
|
||||
val accounts: Set<Account>,
|
||||
val expected: Either<AccountList.Error, AccountList>,
|
||||
)
|
||||
|
||||
private fun createAccount(isMain: Boolean = false): Account.CryptoPortfolio {
|
||||
return mockk<Account.CryptoPortfolio> {
|
||||
every { isMainAccount } returns isMain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,12 +8,14 @@ import com.tangem.domain.models.TokensSortType
|
|||
import com.tangem.domain.models.account.Account.CryptoPortfolio.Error.AccountNameError
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents an account
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface Account {
|
||||
|
||||
/** Unique identifier of the account */
|
||||
|
|
@ -36,6 +38,7 @@ sealed interface Account {
|
|||
* @property isArchived indicates whether the account is archived
|
||||
* @property cryptoCurrencyList list of tokens associated with the account
|
||||
*/
|
||||
@Serializable
|
||||
data class CryptoPortfolio private constructor(
|
||||
override val accountId: AccountId,
|
||||
override val name: AccountName,
|
||||
|
|
@ -64,6 +67,7 @@ sealed interface Account {
|
|||
* @property sortType sorting type for the tokens
|
||||
* @property groupType grouping type for the tokens
|
||||
*/
|
||||
@Serializable
|
||||
data class CryptoCurrencyList(
|
||||
val currencies: Set<CryptoCurrency>,
|
||||
val sortType: TokensSortType,
|
||||
|
|
@ -73,14 +77,17 @@ sealed interface Account {
|
|||
/**
|
||||
* Represents possible errors when creating a crypto portfolio account
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface Error {
|
||||
|
||||
/** Error indicating that the account name is blank */
|
||||
@Serializable
|
||||
data class AccountNameError(val cause: AccountName.Error) : Error {
|
||||
override fun toString(): String = cause.toString()
|
||||
}
|
||||
|
||||
/** Error indicating that the derivation index is negative */
|
||||
@Serializable
|
||||
data object NegativeDerivationIndex : Error {
|
||||
override fun toString(): String = "${this::class.simpleName}: Derivation index must be non-negative"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a unique identifier for an account
|
||||
|
|
@ -8,6 +9,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
* @property value a unique string value that distinguishes this account
|
||||
* @property userWalletId the identifier of the user wallet associated with the account
|
||||
*/
|
||||
@Serializable
|
||||
data class AccountId(
|
||||
val value: String,
|
||||
val userWalletId: UserWalletId,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.domain.models.account
|
|||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents an account name
|
||||
|
|
@ -11,6 +12,7 @@ import arrow.core.raise.ensure
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Serializable
|
||||
data class AccountName private constructor(
|
||||
val value: String,
|
||||
) {
|
||||
|
|
@ -18,11 +20,13 @@ data class AccountName private constructor(
|
|||
/**
|
||||
* Represents possible validation errors
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface Error {
|
||||
|
||||
/**
|
||||
* Error indicating that the account name is blank
|
||||
*/
|
||||
@Serializable
|
||||
data object Empty : Error {
|
||||
override fun toString(): String = "${Empty::class.simpleName}: Account name cannot be blank"
|
||||
}
|
||||
|
|
@ -30,6 +34,7 @@ data class AccountName private constructor(
|
|||
/**
|
||||
* Error indicating that the account name exceeds the maximum allowed length
|
||||
*/
|
||||
@Serializable
|
||||
data object ExceedsMaxLength : Error {
|
||||
override fun toString(): String {
|
||||
return "${ExceedsMaxLength::class.simpleName}: Account name cannot exceed $MAX_LENGTH characters"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the status of an account
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface AccountStatus {
|
||||
|
||||
/** The account associated with this status */
|
||||
val account: Account
|
||||
|
||||
/**
|
||||
* Represents the status of a crypto portfolio account
|
||||
*
|
||||
* @property account the crypto portfolio account
|
||||
* @property tokenList the list of tokens associated with the account
|
||||
*/
|
||||
@Serializable
|
||||
data class CryptoPortfolio(
|
||||
override val account: Account.CryptoPortfolio,
|
||||
val tokenList: TokenList,
|
||||
) : AccountStatus
|
||||
}
|
||||
|
|
@ -276,6 +276,7 @@ include(":features:welcome:impl")
|
|||
include(":domain:models")
|
||||
include(":domain:legacy")
|
||||
|
||||
include(":domain:account")
|
||||
include(":domain:card")
|
||||
include(":domain:core")
|
||||
include(":domain:demo")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue