Updated on 2026-08-14
This commit is contained in:
parent
934b6ed6c6
commit
2a044a5582
3 changed files with 247 additions and 8 deletions
|
|
@ -1,22 +1,102 @@
|
|||
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.account.models.AccountList
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Use case for archiving a crypto portfolio.
|
||||
* This class provides functionality to archive a specific account within a user's crypto portfolio.
|
||||
* It ensures that the account exists and meets the necessary requirements before performing the operation.
|
||||
*
|
||||
* @property crudRepository repository for performing CRUD operations on accounts
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ArchiveCryptoPortfolioUseCase {
|
||||
class ArchiveCryptoPortfolioUseCase(
|
||||
private val crudRepository: AccountsCRUDRepository,
|
||||
) {
|
||||
|
||||
/** Archives the specified account by its [accountId] */
|
||||
suspend operator fun invoke(accountId: AccountId): Either<Error, Unit> = either {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
// Remove the account from the list of active accounts
|
||||
// Add the account to the list of archived accounts
|
||||
// Save to backend
|
||||
val accountList = getAccountList(userWalletId = accountId.userWalletId)
|
||||
|
||||
val archivingAccount = accountList.accounts
|
||||
.firstOrNull { it.accountId == accountId }
|
||||
?: raise(Error.CriticalTechError.AccountNotFound(accountId = accountId))
|
||||
|
||||
val updatedAccounts = (accountList - archivingAccount).getOrElse {
|
||||
raise(Error.CriticalTechError.AccountListRequirementsNotMet(cause = it))
|
||||
}
|
||||
|
||||
saveAccounts(updatedAccounts)
|
||||
}
|
||||
|
||||
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)) },
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents possible errors that can occur during the archiving process
|
||||
*/
|
||||
sealed interface Error {
|
||||
data object DataOperationFailed : Error
|
||||
|
||||
/** Error indicating that a data operation failed */
|
||||
data class DataOperationFailed(val cause: Throwable) : Error {
|
||||
override fun toString(): String = "$this: 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 {
|
||||
return "${this.javaClass.simpleName}: 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 = "${this.javaClass.simpleName}: 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 {
|
||||
return "${this.javaClass.simpleName}: Account list requirements not met: $cause"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
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.ArchiveCryptoPortfolioUseCase.Error
|
||||
import com.tangem.domain.account.utils.createAccount
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
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
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class ArchiveCryptoPortfolioUseCaseTest {
|
||||
|
||||
private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true)
|
||||
private val useCase = ArchiveCryptoPortfolioUseCase(crudRepository)
|
||||
private val userWallet = mockk<UserWallet>()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(crudRepository, userWallet)
|
||||
every { userWallet.walletId } returns userWalletId
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should archive existing crypto portfolio account`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(userWalletId)
|
||||
val accountList = (AccountList.empty(userWallet) + account).getOrNull()!!
|
||||
val accountId = account.accountId
|
||||
|
||||
val archivedAccount = account.copy(isArchived = true)
|
||||
val updatedAccountList = (accountList - archivedAccount).getOrNull()!!
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Unit.right()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccounts(userWalletId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if getAccounts returns None`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex.Main,
|
||||
)
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } returns None
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.CriticalTechError.AccountsNotCreated(userWalletId).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if getAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex.Main,
|
||||
)
|
||||
|
||||
val exception = IllegalStateException("Test error")
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.DataOperationFailed(exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if account not found`() = runTest {
|
||||
// Arrange
|
||||
val accountList = AccountList.empty(userWallet)
|
||||
val accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = DerivationIndex(1).getOrNull()!!,
|
||||
)
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.CriticalTechError.AccountNotFound(accountId).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
|
||||
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke should return error if saveAccounts throws exception`() = runTest {
|
||||
// Arrange
|
||||
val account = createAccount(userWalletId)
|
||||
val accountList = (AccountList.empty(userWallet) + account).getOrNull()!!
|
||||
val accountId = account.accountId
|
||||
|
||||
val archivedAccount = account.copy(isArchived = true)
|
||||
val updatedAccountList = (accountList - archivedAccount).getOrNull()!!
|
||||
|
||||
val exception = IllegalStateException("Save failed")
|
||||
|
||||
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
|
||||
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
|
||||
|
||||
// Act
|
||||
val actual = useCase(accountId)
|
||||
|
||||
// Assert
|
||||
val expected = Error.DataOperationFailed(exception).left()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
crudRepository.getAccounts(userWalletId)
|
||||
crudRepository.saveAccounts(updatedAccountList)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val userWalletId = UserWalletId("011")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue