Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-24 14:45:20 +04:00
parent 080592f9c7
commit 468843f7b9
9 changed files with 54 additions and 123 deletions

View file

@ -29,7 +29,7 @@ internal class AccountListConverter @AssistedInject constructor(
override fun convert(value: GetWalletAccountsResponse): AccountList {
return AccountList(
userWalletId = userWallet.walletId,
accounts = value.accounts.map(cryptoPortfolioConverter::convert).toSet(),
accounts = value.accounts.map(cryptoPortfolioConverter::convert),
totalAccounts = value.wallet.totalAccounts,
sortType = TokensSortTypeConverter.convert(value.wallet.sort),
groupType = TokensGroupTypeConverter.convert(value.wallet.group),

View file

@ -77,7 +77,7 @@ internal fun createAccountList(
): AccountList {
return AccountList(
userWalletId = userWalletId,
accounts = setOf(createCryptoPortfolio(userWalletId)),
accounts = listOf(createCryptoPortfolio(userWalletId)),
totalAccounts = 1,
sortType = sortType,
groupType = groupType,

View file

@ -2,8 +2,6 @@ package com.tangem.data.account.converter
import com.google.common.truth.Truth
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountName
import com.tangem.domain.models.wallet.UserWalletId
import org.junit.jupiter.api.Test
@ -16,13 +14,7 @@ class SaveWalletAccountsResponseConverterTest {
fun convert() {
// Arrange
val userWalletId = UserWalletId("011")
val accountList = AccountList(
userWalletId = userWalletId,
accounts = setOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWalletId)),
totalAccounts = 1,
)
.getOrNull()!!
val accountList = createAccountList(userWalletId)
// Act
val actual = SaveWalletAccountsResponseConverter.convert(value = accountList)

View file

@ -16,7 +16,7 @@ import kotlinx.serialization.Serializable
* Represents a list of accounts associated with a user wallet
*
* @property userWalletId the user wallet id associated with the account list
* @property accounts a set of accounts belonging to the user wallet
* @property accounts a list of accounts belonging to the user wallet
* @property totalAccounts the total number of accounts
*
[REDACTED_AUTHOR]
@ -24,7 +24,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class AccountList private constructor(
val userWalletId: UserWalletId,
val accounts: Set<Account>,
val accounts: List<Account>,
val totalAccounts: Int,
val sortType: TokensSortType,
val groupType: TokensGroupType,
@ -48,11 +48,11 @@ data class AccountList private constructor(
*/
operator fun plus(other: Account): Either<Error, AccountList> {
val isNewAccount = this.accounts.none { it.accountId == other.accountId }
val accounts = this.accounts.toList().addOrReplace(other) { it.accountId == other.accountId }
val accounts = this.accounts.addOrReplace(other) { it.accountId == other.accountId }
return invoke(
userWalletId = this.userWalletId,
accounts = accounts.toSet(),
accounts = accounts,
totalAccounts = this.totalAccounts + if (isNewAccount) 1 else 0,
sortType = this.sortType,
groupType = this.groupType,
@ -68,7 +68,7 @@ data class AccountList private constructor(
*/
operator fun minus(other: Account): Either<Error, AccountList> {
val isExistingAccount = this.accounts.any { it.accountId == other.accountId }
val accounts = this.accounts.toMutableSet().apply {
val accounts = this.accounts.toMutableList().apply {
removeIf { it.accountId == other.accountId }
}
@ -140,7 +140,7 @@ data class AccountList private constructor(
*/
operator fun invoke(
userWalletId: UserWalletId,
accounts: Set<Account>,
accounts: List<Account>,
totalAccounts: Int,
sortType: TokensSortType = TokensSortType.NONE,
groupType: TokensGroupType = TokensGroupType.NONE,
@ -190,7 +190,7 @@ data class AccountList private constructor(
): AccountList {
return AccountList(
userWalletId = userWalletId,
accounts = setOf(
accounts = listOf(
Account.CryptoPortfolio.createMainAccount(
userWalletId = userWalletId,
cryptoCurrencies = cryptoCurrencies,
@ -202,7 +202,7 @@ data class AccountList private constructor(
)
}
private fun Set<Account>.mainAccountsCount(): Int {
private fun List<Account>.mainAccountsCount(): Int {
return count { (it as? Account.CryptoPortfolio)?.isMainAccount == true }
}
}

View file

@ -28,7 +28,7 @@ class AccountListTest {
val accountList = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount),
accounts = listOf(mainAccount),
totalAccounts = 1,
)
.getOrNull()!!
@ -69,7 +69,7 @@ class AccountListTest {
// Assert
val expected = AccountList(
userWalletId = userWalletId,
accounts = setOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWalletId)),
accounts = listOf(Account.CryptoPortfolio.createMainAccount(userWalletId = userWalletId)),
totalAccounts = 1,
).getOrNull()!!
@ -96,17 +96,17 @@ class AccountListTest {
private fun provideTestModels() = listOf(
CreateTestModel(
accounts = emptySet(),
accounts = emptyList(),
expected = AccountList.Error.EmptyAccountsList.left(),
),
CreateTestModel(
accounts = setOf(
accounts = listOf(
createAccount(userWalletId = userWalletId, derivationIndex = 1),
),
expected = AccountList.Error.MainAccountNotFound.left(),
),
CreateTestModel(
accounts = setOf(
accounts = listOf(
Account.CryptoPortfolio.createMainAccount(userWalletId),
Account.CryptoPortfolio.createMainAccount(userWalletId).copy(
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
@ -131,7 +131,7 @@ class AccountListTest {
expected = AccountList.Error.ExceedsMaxAccountsCount.left(),
),
CreateTestModel(
accounts = setOf(
accounts = listOf(
createAccount(userWalletId = userWalletId, derivationIndex = 0),
createAccount(userWalletId = userWalletId, derivationIndex = 1),
createAccount(userWalletId = userWalletId, derivationIndex = 1),
@ -139,7 +139,7 @@ class AccountListTest {
expected = AccountList.Error.DuplicateAccountIds.left(),
),
CreateTestModel(
accounts = setOf(
accounts = listOf(
createAccount(userWalletId = userWalletId, name = "Name", derivationIndex = 0),
createAccount(userWalletId = userWalletId, name = "Name", derivationIndex = 1),
),
@ -149,7 +149,7 @@ class AccountListTest {
}
data class CreateTestModel(
val accounts: Set<Account>,
val accounts: List<Account>,
val expected: Either<AccountList.Error, AccountList>,
)
@ -176,13 +176,13 @@ class AccountListTest {
PlusTestModel(
initial = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount),
accounts = listOf(mainAccount),
totalAccounts = 1,
).getOrNull()!!,
toAdd = newAccount,
expected = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount, newAccount),
accounts = listOf(mainAccount, newAccount),
totalAccounts = 2,
),
)
@ -196,13 +196,13 @@ class AccountListTest {
PlusTestModel(
initial = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount),
accounts = listOf(mainAccount),
totalAccounts = 1,
).getOrNull()!!,
toAdd = newAccount,
expected = AccountList(
userWalletId = userWalletId,
accounts = setOf(newAccount),
accounts = listOf(newAccount),
totalAccounts = 1,
),
)
@ -249,13 +249,13 @@ class AccountListTest {
MinusTestModel(
initial = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount, secondaryAccount),
accounts = listOf(mainAccount, secondaryAccount),
totalAccounts = 2,
).getOrNull()!!,
toRemove = secondaryAccount,
expected = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount),
accounts = listOf(mainAccount),
totalAccounts = 1,
),
)
@ -269,13 +269,13 @@ class AccountListTest {
MinusTestModel(
initial = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount),
accounts = listOf(mainAccount),
totalAccounts = 1,
).getOrNull()!!,
toRemove = notInList,
expected = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount),
accounts = listOf(mainAccount),
totalAccounts = 1,
),
)
@ -288,7 +288,7 @@ class AccountListTest {
MinusTestModel(
initial = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount),
accounts = listOf(mainAccount),
totalAccounts = 1,
).getOrNull()!!,
toRemove = mainAccount,
@ -304,7 +304,7 @@ class AccountListTest {
MinusTestModel(
initial = AccountList(
userWalletId = userWalletId,
accounts = setOf(mainAccount, secondaryAccount),
accounts = listOf(mainAccount, secondaryAccount),
totalAccounts = 2,
).getOrNull()!!,
toRemove = mainAccount,

View file

@ -1,7 +1,5 @@
package com.tangem.domain.account.usecase
import arrow.core.None
import arrow.core.toOption
import com.google.common.truth.Truth
import com.tangem.domain.account.models.ArchivedAccount
import com.tangem.domain.account.repository.AccountsCRUDRepository
@ -12,7 +10,6 @@ import com.tangem.domain.models.wallet.UserWalletId
import io.mockk.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
@ -23,6 +20,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@Suppress("UnusedFlow")
@OptIn(ExperimentalCoroutinesApi::class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class GetArchivedAccountsUseCaseTest {
@ -43,76 +41,20 @@ class GetArchivedAccountsUseCaseTest {
mockk<ArchivedAccount>(),
mockk<ArchivedAccount>(),
)
coEvery { crudRepository.getArchivedAccountListSync(userWalletId) } returns archivedAccounts.toOption()
every { crudRepository.getArchivedAccounts(userWalletId) } returns flowOf(archivedAccounts)
// Act
val actual = getEmittedValues(useCase(userWalletId))
// Assert
val expected = listOf(archivedAccounts.lceContent())
val expected = listOf(
lceLoading(),
archivedAccounts.lceContent(),
)
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getArchivedAccountListSync(userWalletId)
crudRepository.getArchivedAccounts(userWalletId)
}
coVerify(exactly = 0) { crudRepository.fetchArchivedAccounts(any()) }
}
@Test
fun `invoke should emit loading and fetch when accounts not found`() = runTest {
// Arrange
val archivedAccounts = listOf(
mockk<ArchivedAccount>(),
mockk<ArchivedAccount>(),
)
coEvery { crudRepository.getArchivedAccountListSync(userWalletId) } returns None
every { crudRepository.getArchivedAccounts(userWalletId) } returns flowOf(archivedAccounts)
// Act
val actual = getEmittedValues(useCase(userWalletId))
// Assert
val expected = listOf(
lceLoading(),
archivedAccounts.lceContent(),
)
Truth.assertThat(actual).isEqualTo(expected)
coVerify(exactly = 1) {
crudRepository.getArchivedAccountListSync(userWalletId)
crudRepository.fetchArchivedAccounts(userWalletId)
crudRepository.getArchivedAccounts(userWalletId)
}
}
@Test
fun `invoke should emit error if getArchivedAccountsSync throws exception`() = runTest {
// Arrange
val exception = IllegalStateException("Test error")
val archivedAccounts = listOf(
mockk<ArchivedAccount>(),
mockk<ArchivedAccount>(),
)
coEvery { crudRepository.getArchivedAccountListSync(userWalletId) } throws exception
every { crudRepository.getArchivedAccounts(userWalletId) } returns flowOf(archivedAccounts)
// Act
val actual = getEmittedValues(useCase(userWalletId))
// Assert
val expected = listOf(
lceLoading(),
archivedAccounts.lceContent(),
)
Truth.assertThat(actual).isEqualTo(expected)
coVerify(exactly = 1) {
crudRepository.getArchivedAccountListSync(userWalletId)
crudRepository.fetchArchivedAccounts(userWalletId)
crudRepository.getArchivedAccounts(userWalletId)
}
@ -121,11 +63,14 @@ class GetArchivedAccountsUseCaseTest {
@Test
fun `invoke should emit error if fetchArchivedAccounts throws exception`() = runTest {
// Arrange
val exception = IllegalStateException("Fetch error")
val exception = IllegalStateException("Test error")
val archivedAccounts = listOf(
mockk<ArchivedAccount>(),
mockk<ArchivedAccount>(),
)
coEvery { crudRepository.getArchivedAccountListSync(userWalletId) } returns None
every { crudRepository.getArchivedAccounts(userWalletId) } returns emptyFlow()
coEvery { crudRepository.fetchArchivedAccounts(userWalletId) } throws exception
every { crudRepository.getArchivedAccounts(userWalletId) } returns flowOf(archivedAccounts)
// Act
val actual = getEmittedValues(useCase(userWalletId))
@ -135,14 +80,10 @@ class GetArchivedAccountsUseCaseTest {
lceLoading(),
exception.lceError(),
)
Truth.assertThat(actual).isEqualTo(expected)
coVerify(exactly = 1) {
crudRepository.getArchivedAccountListSync(userWalletId)
crudRepository.fetchArchivedAccounts(userWalletId)
crudRepository.getArchivedAccounts(userWalletId)
}
coVerifyOrder { crudRepository.fetchArchivedAccounts(userWalletId) }
coVerify(inverse = true) { crudRepository.getArchivedAccounts(any()) }
}
@OptIn(ExperimentalCoroutinesApi::class)

View file

@ -31,7 +31,7 @@ class GetUnoccupiedAccountIndexUseCaseTest {
@Test
fun `invoke should return error if repository returns 0`() = runTest {
// Arrange
coEvery { crudRepository.getTotalActiveAccountsCountSync(userWalletId) } returns 0.toOption()
coEvery { crudRepository.getTotalAccountsCountSync(userWalletId) } returns 0.toOption()
// Act
val actual = useCase(userWalletId = userWalletId).leftOrNull() as Error.DataOperationFailed
@ -42,13 +42,13 @@ class GetUnoccupiedAccountIndexUseCaseTest {
Truth.assertThat(actual.cause).isInstanceOf(expected::class.java)
Truth.assertThat(actual.cause).hasMessageThat().isEqualTo(expected.message)
coVerify { crudRepository.getTotalActiveAccountsCountSync(userWalletId) }
coVerify { crudRepository.getTotalAccountsCountSync(userWalletId) }
}
@Test
fun `invoke should return next unoccupied index when repository returns count`() = runTest {
// Arrange
coEvery { crudRepository.getTotalActiveAccountsCountSync(userWalletId) } returns 3.toOption()
coEvery { crudRepository.getTotalAccountsCountSync(userWalletId) } returns 3.toOption()
// Act
val actual = useCase(userWalletId = userWalletId)
@ -57,14 +57,14 @@ class GetUnoccupiedAccountIndexUseCaseTest {
val expected = DerivationIndex(3)
Truth.assertThat(actual).isEqualTo(expected)
coVerify { crudRepository.getTotalActiveAccountsCountSync(userWalletId) }
coVerify { crudRepository.getTotalAccountsCountSync(userWalletId) }
}
@Test
fun `invoke should return error if repository throws exception`() = runTest {
// Arrange
val exception = IllegalStateException("Test error")
coEvery { crudRepository.getTotalActiveAccountsCountSync(userWalletId) } throws exception
coEvery { crudRepository.getTotalAccountsCountSync(userWalletId) } throws exception
// Act
val actual = useCase(userWalletId = userWalletId)
@ -73,6 +73,6 @@ class GetUnoccupiedAccountIndexUseCaseTest {
val expected = Error.DataOperationFailed(exception).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerify { crudRepository.getTotalActiveAccountsCountSync(userWalletId) }
coVerify { crudRepository.getTotalAccountsCountSync(userWalletId) }
}
}

View file

@ -4,8 +4,8 @@ import com.tangem.domain.models.account.*
import com.tangem.domain.models.wallet.UserWalletId
import kotlin.random.Random
fun createAccounts(userWalletId: UserWalletId, count: Int): Set<Account.CryptoPortfolio> {
return buildSet {
fun createAccounts(userWalletId: UserWalletId, count: Int): List<Account.CryptoPortfolio> {
return buildList {
add(Account.CryptoPortfolio.createMainAccount(userWalletId))
repeat(count - 1) {

View file

@ -106,7 +106,7 @@ internal class TesterAccountsViewModel @Inject constructor(
AccountsUM.Button(title = "Fetch accounts", onClick = ::fetchAccounts),
AccountsUM.Button(title = "Fill out the list (up to 20)", onClick = ::fillOutAccountList),
AccountsUM.Button(title = "Archive all", onClick = ::archiveAllAccounts),
// AccountsUM.Button(title = "Sort by derivation index", onClick = ::sortAccountsByIndex),
AccountsUM.Button(title = "Sort by derivation index", onClick = ::sortAccountsByIndex),
AccountsUM.Button(title = "Clear ETag") { clearETag() },
),
)
@ -217,7 +217,7 @@ internal class TesterAccountsViewModel @Inject constructor(
withContext(dispatchers.default) {
val updatedAccountList = AccountList.invoke(
userWalletId = accountList.userWalletId,
accounts = setOf(accountList.mainAccount),
accounts = listOf(accountList.mainAccount),
totalAccounts = accountList.totalAccounts,
sortType = accountList.sortType,
groupType = accountList.groupType,
@ -231,7 +231,6 @@ internal class TesterAccountsViewModel @Inject constructor(
}
}
@Suppress("UnusedPrivateMember")
private fun sortAccountsByIndex(title: String) {
val userWalletId = getUserWallet()?.walletId ?: return
val accountList = walletAccounts.value[userWalletId] ?: return
@ -244,8 +243,7 @@ internal class TesterAccountsViewModel @Inject constructor(
userWalletId = accountList.userWalletId,
accounts = accountList.accounts
.filterIsInstance<Account.CryptoPortfolio>()
.sortedBy { it.derivationIndex.value }
.toSet(),
.sortedBy { it.derivationIndex.value },
totalAccounts = accountList.totalAccounts,
sortType = accountList.sortType,
groupType = accountList.groupType,