Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-16 17:58:26 +07:00
parent 7bbab0a40b
commit 46dd1333ff
5 changed files with 306 additions and 2 deletions

View file

@ -1,11 +1,13 @@
package com.tangem.data.account.fetcher
import arrow.core.right
import com.google.common.truth.Truth
import com.tangem.data.account.converter.createGetWalletAccountsResponse
import com.tangem.data.account.converter.createWalletAccountDTO
import com.tangem.data.account.fetcher.DefaultWalletAccountsFetcher.FetchResult
import com.tangem.data.account.store.AccountsResponseStore
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.tokens.DefaultMainAccountTokensMigration
import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory
import com.tangem.data.common.cache.etag.ETagsStore
import com.tangem.data.common.currency.UserTokensSaver
@ -35,6 +37,7 @@ class DefaultWalletAccountsFetcherTest {
private val accountsResponseStoreFactory: AccountsResponseStoreFactory = mockk()
private val accountsResponseStore: AccountsResponseStore = mockk()
private val accountsResponseStoreFlow = MutableStateFlow<GetWalletAccountsResponse?>(value = null)
private val tokensMigration: DefaultMainAccountTokensMigration = mockk(relaxed = true)
private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true)
private val fetchWalletAccountsErrorHandler: FetchWalletAccountsErrorHandler = mockk(relaxUnitFun = true)
@ -49,16 +52,19 @@ class DefaultWalletAccountsFetcherTest {
defaultWalletAccountsResponseFactory = defaultWalletAccountsResponseFactory,
eTagsStore = eTagsStore,
dispatchers = TestingCoroutineDispatcherProvider(),
mainAccountTokensMigration = tokensMigration,
)
private val userWalletId = UserWalletId("011")
private val eTag = "etag"
private val migratedAccountsResponse = createGetWalletAccountsResponse(userWalletId)
@BeforeAll
fun setUp() {
every { accountsResponseStoreFactory.create(userWalletId) } returns accountsResponseStore
every { accountsResponseStore.data } returns accountsResponseStoreFlow
coEvery { tokensMigration.migrate(userWalletId) } returns migratedAccountsResponse.right()
coEvery { eTagsStore.getSyncOrNull(userWalletId, ETagsStore.Key.WalletAccounts) } returns eTag
}
@ -134,6 +140,7 @@ class DefaultWalletAccountsFetcherTest {
accountsResponseStore.updateData(any())
accountsResponseStoreFactory.create(userWalletId = userWalletId)
accountsResponseStore.updateData(any())
tokensMigration.migrate(userWalletId)
}
coVerify(inverse = true) {
@ -181,6 +188,7 @@ class DefaultWalletAccountsFetcherTest {
eTagsStore.store(userWalletId = userWalletId, key = ETagsStore.Key.WalletAccounts, value = newETag)
accountsResponseStoreFactory.create(userWalletId = userWalletId)
accountsResponseStore.updateData(any())
tokensMigration.migrate(userWalletId)
}
coVerify(inverse = true) {
@ -236,6 +244,7 @@ class DefaultWalletAccountsFetcherTest {
pushWalletAccounts = any(),
storeWalletAccounts = any(),
)
tokensMigration.migrate(userWalletId)
}
coVerify(inverse = true) {

View file

@ -1,5 +1,6 @@
package com.tangem.data.account.token
import arrow.core.right
import com.tangem.data.account.converter.createGetWalletAccountsResponse
import com.tangem.data.account.converter.createWalletAccountDTO
import com.tangem.data.account.store.AccountsResponseStore
@ -13,6 +14,7 @@ import com.tangem.datasource.local.accounts.AccountTokenMigrationStore
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.test.core.assertEither
import com.tangem.test.core.assertEitherLeft
import com.tangem.test.core.assertEitherRight
import io.mockk.*
@ -214,6 +216,212 @@ class DefaultMainAccountTokensMigrationTest {
}
}
@Test
fun `migrate updates tokens for all accounts`() = runTest {
// Arrange
val unassignedToken1 = createBitcoin(accountIndex = 1)
val unassignedToken2 = createBitcoin(accountIndex = 2)
val derivationIndex1 = DerivationIndex(1).getOrNull()!!
val derivationIndex2 = DerivationIndex(2).getOrNull()!!
val mainAccount = createWalletAccountDTO(
userWalletId = userWalletId,
accountId = AccountId.forCryptoPortfolio(userWalletId, DerivationIndex.Main).value,
derivationIndex = DerivationIndex.Main.value,
tokens = listOf(
createBitcoin(accountIndex = 0),
unassignedToken1,
unassignedToken2,
),
)
val account1 = createWalletAccountDTO(
userWalletId = userWalletId,
accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex1).value,
derivationIndex = derivationIndex1.value,
tokens = emptyList(),
)
val account2 = createWalletAccountDTO(
userWalletId = userWalletId,
accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex2).value,
derivationIndex = derivationIndex2.value,
tokens = emptyList(),
)
val response = GetWalletAccountsResponse(
wallet = GetWalletAccountsResponse.Wallet(
group = UserTokensResponse.GroupType.NONE,
sort = UserTokensResponse.SortType.MANUAL,
totalAccounts = 3,
totalArchivedAccounts = 0,
),
accounts = listOf(mainAccount, account1, account2),
unassignedTokens = emptyList(),
)
accountsResponseStoreFlow.value = response
coEvery { accountsResponseStore.updateData(any()) } returns mockk()
// Act
val actual = migration.migrate(userWalletId)
val migratedResponse = response.copy(
accounts = listOf(
mainAccount.copy(tokens = mainAccount.tokens!! - unassignedToken1 - unassignedToken2),
account1.copy(tokens = listOf(unassignedToken1)),
account2.copy(tokens = listOf(unassignedToken2)),
),
)
// Assert
assertEither(actual, migratedResponse.right())
coVerifySequence {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
accountsResponseStore.updateData(any())
userTokensSaver.pushWithRetryer(
userWalletId = userWalletId,
response = migratedResponse.toUserTokensResponse(),
onFailSend = any(),
)
}
}
@Test
fun `migrate updates tokens only for one account`() = runTest {
// Arrange
val unassignedToken1 = createBitcoin(accountIndex = 1)
val derivationIndex1 = DerivationIndex(1).getOrNull()!!
val derivationIndex2 = DerivationIndex(2).getOrNull()!!
val mainAccount = createWalletAccountDTO(
userWalletId = userWalletId,
accountId = AccountId.forCryptoPortfolio(userWalletId, DerivationIndex.Main).value,
derivationIndex = DerivationIndex.Main.value,
tokens = listOf(
createBitcoin(accountIndex = 0),
unassignedToken1,
),
)
val account1 = createWalletAccountDTO(
userWalletId = userWalletId,
accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex1).value,
derivationIndex = derivationIndex1.value,
tokens = emptyList(),
)
val account2 = createWalletAccountDTO(
userWalletId = userWalletId,
accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex2).value,
derivationIndex = derivationIndex2.value,
tokens = emptyList(),
)
val response = GetWalletAccountsResponse(
wallet = GetWalletAccountsResponse.Wallet(
group = UserTokensResponse.GroupType.NONE,
sort = UserTokensResponse.SortType.MANUAL,
totalAccounts = 3,
totalArchivedAccounts = 0,
),
accounts = listOf(mainAccount, account1, account2),
unassignedTokens = emptyList(),
)
accountsResponseStoreFlow.value = response
coEvery { accountsResponseStore.updateData(any()) } returns mockk()
// Act
val actual = migration.migrate(userWalletId)
val migratedResponse = response.copy(
accounts = listOf(
mainAccount.copy(tokens = mainAccount.tokens!! - unassignedToken1),
account1.copy(tokens = listOf(unassignedToken1)),
account2,
),
)
// Assert
assertEither(actual, migratedResponse.right())
coVerifySequence {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
accountsResponseStore.updateData(any())
userTokensSaver.pushWithRetryer(
userWalletId = userWalletId,
response = migratedResponse.toUserTokensResponse(),
onFailSend = any(),
)
}
}
@Test
fun `migrate all skips when no unassigned tokens`() = runTest {
// Arrange
val derivationIndex1 = DerivationIndex(1).getOrNull()!!
val derivationIndex2 = DerivationIndex(2).getOrNull()!!
val mainAccount = createWalletAccountDTO(
userWalletId = userWalletId,
accountId = AccountId.forCryptoPortfolio(userWalletId, DerivationIndex.Main).value,
derivationIndex = DerivationIndex.Main.value,
tokens = listOf(
createBitcoin(accountIndex = 0),
),
)
val account1 = createWalletAccountDTO(
userWalletId = userWalletId,
accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex1).value,
derivationIndex = derivationIndex1.value,
tokens = emptyList(),
)
val account2 = createWalletAccountDTO(
userWalletId = userWalletId,
accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex2).value,
derivationIndex = derivationIndex2.value,
tokens = emptyList(),
)
val response = GetWalletAccountsResponse(
wallet = GetWalletAccountsResponse.Wallet(
group = UserTokensResponse.GroupType.NONE,
sort = UserTokensResponse.SortType.MANUAL,
totalAccounts = 3,
totalArchivedAccounts = 0,
),
accounts = listOf(mainAccount, account1, account2),
unassignedTokens = emptyList(),
)
accountsResponseStoreFlow.value = response
// Act
val actual = migration.migrate(userWalletId)
// Assert
assertEither(actual, response.right())
coVerifySequence {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
}
coVerify(inverse = true) {
userTokensSaver.pushWithRetryer(userWalletId = any(), response = any(), onFailSend = any())
}
}
private fun createBitcoin(accountIndex: Int): UserTokensResponse.Token {
return UserTokensResponse.Token(
id = "ne",