Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-27 14:03:04 +04:00
parent d48ba8a8bd
commit 3d5a8a55ed
20 changed files with 899 additions and 134 deletions

View file

@ -154,7 +154,8 @@ interface TangemTechApi {
suspend fun saveWalletAccounts(
@Path("walletId") walletId: String,
@Header("If-Match") ifMatch: String,
): ApiResponse<SaveWalletAccountsResponse>
@Body body: SaveWalletAccountsResponse,
): ApiResponse<Unit>
@GET("/v1/wallets/{walletId}/accounts/archived")
suspend fun getWalletArchivedAccounts(

View file

@ -1,5 +1,7 @@
package com.tangem.data.account.converter
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.models.wallet.UserWalletId
import javax.inject.Inject
/**
@ -14,7 +16,21 @@ import javax.inject.Inject
[REDACTED_AUTHOR]
*/
internal class AccountConverterFactoryContainer @Inject constructor(
val accountsListCF: AccountListConverter.Factory,
val getWalletAccountsResponseCF: GetWalletAccountsResponseConverter.Factory,
val cryptoPortfolioCF: CryptoPortfolioConverter.Factory,
)
private val accountsListCF: AccountListConverter.Factory,
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory,
private val userWalletsStore: UserWalletsStore,
) {
fun createAccountListConverter(userWalletId: UserWalletId): AccountListConverter {
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
return accountsListCF.create(userWallet)
}
fun createCryptoPortfolioConverter(userWalletId: UserWalletId): CryptoPortfolioConverter {
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
return cryptoPortfolioCF.create(userWallet)
}
}

View file

@ -1,9 +1,13 @@
package com.tangem.data.account.di
import com.tangem.data.account.converter.AccountConverterFactoryContainer
import com.tangem.data.account.repository.DefaultAccountsCRUDRepository
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.store.ArchivedAccountsStoreFactory
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -16,10 +20,20 @@ internal object AccountDataModule {
@Provides
@Singleton
fun provideAccountsCRUDRepository(userWalletsStore: UserWalletsStore): AccountsCRUDRepository {
fun provideAccountsCRUDRepository(
tangemTechApi: TangemTechApi,
accountsResponseStoreFactory: AccountsResponseStoreFactory,
userWalletsStore: UserWalletsStore,
accountConverterFactoryContainer: AccountConverterFactoryContainer,
dispatchers: CoroutineDispatcherProvider,
): AccountsCRUDRepository {
return DefaultAccountsCRUDRepository(
runtimeStore = RuntimeSharedStore(),
tangemTechApi = tangemTechApi,
accountsResponseStoreFactory = accountsResponseStoreFactory,
archivedAccountsStoreFactory = ArchivedAccountsStoreFactory,
userWalletsStore = userWalletsStore,
convertersContainer = accountConverterFactoryContainer,
dispatchers = dispatchers,
)
}
}

View file

@ -1,90 +1,150 @@
package com.tangem.data.account.repository
import arrow.core.Option
import arrow.core.Option.Companion.catch
import arrow.core.none
import arrow.core.raise.option
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import arrow.core.toOption
import com.tangem.data.account.converter.AccountConverterFactoryContainer
import com.tangem.data.account.converter.ArchivedAccountConverter
import com.tangem.data.account.converter.SaveWalletAccountsResponseConverter
import com.tangem.data.account.store.AccountsResponseStore
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.store.ArchivedAccountsStore
import com.tangem.data.account.store.ArchivedAccountsStoreFactory
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.models.ArchivedAccount
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.models.account.*
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.extensions.addOrReplace
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
/**
[REDACTED_AUTHOR]
*/
// TODO: [REDACTED_JIRA]
internal class DefaultAccountsCRUDRepository(
private val runtimeStore: RuntimeSharedStore<List<AccountList>>,
private val tangemTechApi: TangemTechApi,
private val accountsResponseStoreFactory: AccountsResponseStoreFactory,
private val archivedAccountsStoreFactory: ArchivedAccountsStoreFactory,
private val userWalletsStore: UserWalletsStore,
private val convertersContainer: AccountConverterFactoryContainer,
private val dispatchers: CoroutineDispatcherProvider,
) : AccountsCRUDRepository {
override suspend fun getAccounts(userWalletId: UserWalletId): Option<AccountList> = catch {
runtimeStore.getSyncOrNull()
?.firstOrNull { it.userWallet.walletId == userWalletId }
?: return none()
private val saveAccountsMutex = Mutex()
override suspend fun getAccountListSync(userWalletId: UserWalletId): Option<AccountList> = option {
val accountListResponse = getAccountsResponseSync(userWalletId = userWalletId)
ensureNotNull(accountListResponse)
val converter = convertersContainer.createAccountListConverter(userWalletId = userWalletId)
converter.convert(value = accountListResponse)
}
override suspend fun getAccount(accountId: AccountId): Option<Account.CryptoPortfolio> = catch {
runtimeStore.getSyncOrNull().orEmpty()
.flatMap { it.accounts }
.firstOrNull { it.accountId == accountId } as? Account.CryptoPortfolio
?: return none()
override suspend fun getAccountSync(accountId: AccountId): Option<Account.CryptoPortfolio> = option {
val userWalletId = accountId.userWalletId
val accountResponse = getAccountsResponseSync(userWalletId = userWalletId)
?.accounts?.firstOrNull { it.id == accountId.value }
ensureNotNull(accountResponse)
val converter = convertersContainer.createCryptoPortfolioConverter(userWalletId = userWalletId)
converter.convert(value = accountResponse)
}
override suspend fun getArchivedAccount(accountId: AccountId): Option<ArchivedAccount> = option {
createMockArchivedAccount(userWalletId = accountId.userWalletId)
override suspend fun getArchivedAccountSync(accountId: AccountId): Option<ArchivedAccount> {
val store = getArchivedAccountsStore(userWalletId = accountId.userWalletId)
return store.getSyncOrNull()
?.firstOrNull { it.accountId == accountId }
.toOption()
}
override suspend fun getArchivedAccountsSync(userWalletId: UserWalletId): Option<List<ArchivedAccount>> = option {
listOf(
createMockArchivedAccount(userWalletId),
)
override suspend fun getArchivedAccountListSync(userWalletId: UserWalletId): Option<List<ArchivedAccount>> {
val store = getArchivedAccountsStore(userWalletId = userWalletId)
return store.getSyncOrNull().toOption()
}
override fun getArchivedAccounts(userWalletId: UserWalletId): Flow<List<ArchivedAccount>> {
return flow {
getArchivedAccountsSync(userWalletId).getOrNull().orEmpty()
}
val store = getArchivedAccountsStore(userWalletId = userWalletId)
return store.get()
}
override suspend fun fetchArchivedAccounts(userWalletId: UserWalletId) = Unit
override suspend fun fetchArchivedAccounts(userWalletId: UserWalletId) {
val response = withContext(dispatchers.io) {
tangemTechApi.getWalletArchivedAccounts(walletId = userWalletId.stringValue).getOrThrow()
}
val store = getArchivedAccountsStore(userWalletId = userWalletId)
val converter = ArchivedAccountConverter(userWalletId = userWalletId)
val archivedAccounts = converter.convertList(input = response.accounts)
store.store(value = archivedAccounts)
}
override suspend fun saveAccounts(accountList: AccountList) {
runtimeStore.update(emptyList()) {
it.addOrReplace(accountList) { it.userWallet.walletId == accountList.userWallet.walletId }
saveAccountsMutex.withLock {
val store = getAccountsResponseStore(userWalletId = accountList.userWallet.walletId)
val version = store.data.firstOrNull()?.wallet?.version ?: 0
val body = SaveWalletAccountsResponseConverter.convert(value = accountList)
withContext(dispatchers.io) {
tangemTechApi.saveWalletAccounts(
walletId = accountList.userWallet.walletId.stringValue,
ifMatch = version.toString(),
body = body,
)
.getOrThrow()
}
val converter = convertersContainer.getWalletAccountsResponseCF.create(
userWallet = accountList.userWallet,
version = version,
)
val accountsResponse = converter.convert(value = accountList)
store.updateData { accountsResponse }
}
}
override suspend fun getTotalAccountsCount(userWalletId: UserWalletId): Int {
val activeAccountsCount = runtimeStore.getSyncOrNull()?.size ?: 1
override suspend fun getTotalAccountsCount(userWalletId: UserWalletId): Option<Int> = option {
val accountListResponse = getAccountsResponseSync(userWalletId = userWalletId)
return activeAccountsCount + 1
ensureNotNull(accountListResponse)
return accountListResponse.wallet.totalAccounts.toOption()
}
override fun getUserWallet(userWalletId: UserWalletId): UserWallet {
return userWalletsStore.getSyncStrict(userWalletId)
}
private fun createMockArchivedAccount(userWalletId: UserWalletId): ArchivedAccount {
val derivationIndex = DerivationIndex(value = 1000).getOrNull()!!
private suspend fun getAccountsResponseSync(userWalletId: UserWalletId): GetWalletAccountsResponse? {
val store = getAccountsResponseStore(userWalletId = userWalletId)
return store.data.firstOrNull()
}
return ArchivedAccount(
accountId = AccountId.forCryptoPortfolio(
userWalletId = userWalletId,
derivationIndex = derivationIndex,
),
name = AccountName("Archived Account").getOrNull()!!,
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
derivationIndex = derivationIndex,
tokensCount = 2,
networksCount = 1,
)
private fun getAccountsResponseStore(userWalletId: UserWalletId): AccountsResponseStore {
return accountsResponseStoreFactory.create(userWalletId = userWalletId)
}
private fun getArchivedAccountsStore(userWalletId: UserWalletId): ArchivedAccountsStore {
return archivedAccountsStoreFactory.create(userWalletId)
}
}

View file

@ -12,7 +12,7 @@ import java.util.concurrent.ConcurrentHashMap
*
[REDACTED_AUTHOR]
*/
internal class ArchivedAccountsStoreFactory {
internal object ArchivedAccountsStoreFactory {
private val createdRuntimeStores = ConcurrentHashMap<UserWalletId, ArchivedAccountsStore>()

View file

@ -0,0 +1,664 @@
package com.tangem.data.account.repository
import arrow.core.None
import arrow.core.toOption
import com.google.common.truth.Truth
import com.tangem.common.test.utils.getEmittedValues
import com.tangem.data.account.converter.*
import com.tangem.data.account.store.AccountsResponseStore
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.account.store.ArchivedAccountsStore
import com.tangem.data.account.store.ArchivedAccountsStoreFactory
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletArchivedAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.models.ArchivedAccount
import com.tangem.domain.models.account.Account.CryptoPortfolio
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.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.*
import kotlin.time.Duration.Companion.minutes
/**
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DefaultAccountsCRUDRepositoryTest {
private val tangemTechApi: TangemTechApi = mockk()
private val accountsResponseStoreFactory: AccountsResponseStoreFactory = mockk()
private val accountsResponseStore: AccountsResponseStore = mockk()
private val accountsResponseStoreFlow = MutableStateFlow<GetWalletAccountsResponse?>(value = null)
private val archivedAccountsStoreFactory: ArchivedAccountsStoreFactory = mockk()
private val archivedAccountsInnerStore = RuntimeStateStore<List<ArchivedAccount>?>(defaultValue = null)
private val archivedAccountsStore = ArchivedAccountsStore(runtimeStore = archivedAccountsInnerStore)
private val userWalletsStore: UserWalletsStore = mockk()
private val convertersContainer: AccountConverterFactoryContainer = mockk()
private val accountListConverter: AccountListConverter = mockk()
private val cryptoPortfolioConverter: CryptoPortfolioConverter = mockk()
private val repository = DefaultAccountsCRUDRepository(
tangemTechApi = tangemTechApi,
accountsResponseStoreFactory = accountsResponseStoreFactory,
archivedAccountsStoreFactory = archivedAccountsStoreFactory,
userWalletsStore = userWalletsStore,
convertersContainer = convertersContainer,
dispatchers = TestingCoroutineDispatcherProvider(),
)
private val userWalletId = UserWalletId("011")
@BeforeAll
fun setup() {
every { accountsResponseStoreFactory.create(userWalletId) } returns accountsResponseStore
every { accountsResponseStore.data } returns accountsResponseStoreFlow
every { convertersContainer.createAccountListConverter(userWalletId) } returns accountListConverter
every { convertersContainer.createCryptoPortfolioConverter(userWalletId) } returns cryptoPortfolioConverter
}
@BeforeEach
fun setupEach() {
every { archivedAccountsStoreFactory.create(userWalletId) } returns archivedAccountsStore
}
@AfterEach
fun tearDownEach() {
accountsResponseStoreFlow.value = null
archivedAccountsInnerStore.clear()
clearMocks(
tangemTechApi,
archivedAccountsStoreFactory,
accountListConverter,
cryptoPortfolioConverter,
)
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetAccountListSync {
@Test
fun `getAccounts should return None when account list response is null`() = runTest {
// Arrange
accountsResponseStoreFlow.value = null
// Act
val actual = repository.getAccountListSync(userWalletId)
// Assert
Truth.assertThat(actual).isEqualTo(None)
verifyOrder {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
}
verify(inverse = true) { accountListConverter.convert(value = any()) }
}
@Test
fun `getAccounts should return AccountList when account list response is not null`() = runTest {
// Arrange
val response = mockk<GetWalletAccountsResponse>()
val accountList = mockk<AccountList>()
accountsResponseStoreFlow.value = response
every { accountListConverter.convert(response) } returns accountList
// Act
val actual = repository.getAccountListSync(userWalletId)
// Assert
val expected = accountList.toOption()
Truth.assertThat(actual).isEqualTo(expected)
verifyOrder {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
convertersContainer.createAccountListConverter(userWalletId = userWalletId)
accountListConverter.convert(response)
}
}
@Test
fun `getAccounts should throw exception if converter throws exception`() = runTest {
// Arrange
val response = mockk<GetWalletAccountsResponse>()
mockk<AccountList>()
accountsResponseStoreFlow.value = response
val exception = Exception("Test error")
every { accountListConverter.convert(response) } throws exception
// Act
val actual = runCatching { repository.getAccountListSync(userWalletId) }.exceptionOrNull()!!
// Assert
val expected = exception
Truth.assertThat(actual).isSameInstanceAs(expected)
Truth.assertThat(actual).hasMessageThat().isEqualTo(expected.message)
verifyOrder {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
convertersContainer.createAccountListConverter(userWalletId = userWalletId)
accountListConverter.convert(response)
}
}
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetAccountSync {
private val accountId = AccountId.forCryptoPortfolio(userWalletId, DerivationIndex.Main)
@Test
fun `getAccount should return None when account response is null`() = runTest {
// Arrange
val response = null
accountsResponseStoreFlow.value = response
// Act
val actual = repository.getAccountSync(accountId)
// Assert
Truth.assertThat(actual).isEqualTo(None)
verifyOrder {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
}
verify(inverse = true) { convertersContainer.createCryptoPortfolioConverter(userWalletId = any()) }
}
@Test
fun `getAccount should return None when accountDto is not found`() = runTest {
// Arrange
val response = mockk<GetWalletAccountsResponse> {
every { this@mockk.accounts } returns emptyList()
}
accountsResponseStoreFlow.value = response
// Act
val actual = repository.getAccountSync(accountId)
// Assert
Truth.assertThat(actual).isEqualTo(None)
verifyOrder {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
}
verify(inverse = true) { convertersContainer.createCryptoPortfolioConverter(userWalletId = any()) }
}
@Test
fun `getAccount should return Account_CryptoPortfolio when account response is not null`() = runTest {
// Arrange
val accountDTO = mockk<WalletAccountDTO> {
every { this@mockk.id } returns accountId.value
}
val response = mockk<GetWalletAccountsResponse> {
every { this@mockk.accounts } returns listOf(accountDTO)
}
accountsResponseStoreFlow.value = response
val cryptoPortfolio = mockk<CryptoPortfolio>()
every { cryptoPortfolioConverter.convert(accountDTO) } returns cryptoPortfolio
// Act
val actual = repository.getAccountSync(accountId)
// Assert
val expected = cryptoPortfolio.toOption()
Truth.assertThat(actual).isEqualTo(expected)
verifyOrder {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
convertersContainer.createCryptoPortfolioConverter(userWalletId)
cryptoPortfolioConverter.convert(accountDTO)
}
}
@Test
fun `getAccount should throw exception if converter throws exception`() = runTest {
// Arrange
val accountDTO = mockk<WalletAccountDTO> {
every { this@mockk.id } returns accountId.value
}
val response = mockk<GetWalletAccountsResponse> {
every { this@mockk.accounts } returns listOf(accountDTO)
}
accountsResponseStoreFlow.value = response
val exception = Exception("Test error")
every { cryptoPortfolioConverter.convert(accountDTO) } throws exception
// Act
val actual = runCatching { repository.getAccountSync(accountId) }.exceptionOrNull()!!
// Assert
val expected = exception
Truth.assertThat(actual).isSameInstanceAs(expected)
Truth.assertThat(actual).hasMessageThat().isEqualTo(expected.message)
verifyOrder {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
convertersContainer.createCryptoPortfolioConverter(userWalletId)
cryptoPortfolioConverter.convert(accountDTO)
}
}
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetArchivedAccountSync {
private val accountId = AccountId.forCryptoPortfolio(userWalletId, DerivationIndex.Main)
@Test
fun `getArchivedAccount should return None when archived accounts are null`() = runTest {
// Arrange
archivedAccountsInnerStore.store(value = null)
// Act
val actual = repository.getArchivedAccountSync(accountId)
// Assert
Truth.assertThat(actual).isEqualTo(None)
coVerifyOrder {
archivedAccountsStoreFactory.create(userWalletId)
archivedAccountsStore.getSyncOrNull()
}
}
@Test
fun `getArchivedAccount should return None when archived account not found`() = runTest {
// Arrange
archivedAccountsInnerStore.store(value = listOf())
archivedAccountsStore.setTimestamp(time = System.currentTimeMillis() + 3.minutes.inWholeMicroseconds)
// Act
val actual = repository.getArchivedAccountSync(accountId)
// Assert
Truth.assertThat(actual).isEqualTo(None)
coVerifyOrder {
archivedAccountsStoreFactory.create(userWalletId)
archivedAccountsStore.getSyncOrNull()
}
}
@Test
fun `getArchivedAccount should return ArchivedAccount when found`() = runTest {
// Arrange
val archivedAccount = ArchivedAccount(
accountId = accountId,
name = AccountName("Archived Account").getOrNull()!!,
icon = CryptoPortfolioIcon.ofDefaultCustomAccount(),
derivationIndex = DerivationIndex.Main,
tokensCount = 0,
networksCount = 0,
)
archivedAccountsInnerStore.store(value = listOf(archivedAccount))
archivedAccountsStore.setTimestamp(time = System.currentTimeMillis() + 3.minutes.inWholeMicroseconds)
// Act
val actual = repository.getArchivedAccountSync(accountId)
// Assert
val expected = archivedAccount.toOption()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
archivedAccountsStoreFactory.create(userWalletId)
archivedAccountsStore.getSyncOrNull()
}
}
@Test
fun `getArchivedAccount should throws exception when store throws exception`() = runTest {
// Arrange
val exception = Exception("Test error")
coEvery { archivedAccountsStoreFactory.create(userWalletId) } throws exception
// Act
val actual = runCatching { repository.getArchivedAccountSync(accountId) }.exceptionOrNull()!!
// Assert
val expected = exception
Truth.assertThat(actual).isSameInstanceAs(expected)
Truth.assertThat(actual).hasMessageThat().isEqualTo(expected.message)
coVerifyOrder { archivedAccountsStoreFactory.create(userWalletId) }
}
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetArchivedAccountListSync {
@Test
fun `getArchivedAccountListSync should return None when archived accounts are null`() = runTest {
// Arrange
archivedAccountsInnerStore.store(value = null)
// Act
val actual = repository.getArchivedAccountListSync(userWalletId)
// Assert
Truth.assertThat(actual).isEqualTo(None)
coVerifyOrder {
archivedAccountsStoreFactory.create(userWalletId)
archivedAccountsStore.getSyncOrNull()
}
}
@Test
fun `getArchivedAccountListSync should return Option with list when archived accounts exist`() = runTest {
// Arrange
val archivedAccount1 = mockk<ArchivedAccount>()
val archivedAccount2 = mockk<ArchivedAccount>()
val archivedAccounts = listOf(archivedAccount1, archivedAccount2)
archivedAccountsInnerStore.store(value = archivedAccounts)
archivedAccountsStore.setTimestamp(time = System.currentTimeMillis() + 3.minutes.inWholeMicroseconds)
// Act
val actual = repository.getArchivedAccountListSync(userWalletId)
// Assert
val expected = archivedAccounts.toOption()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
archivedAccountsStoreFactory.create(userWalletId)
archivedAccountsStore.getSyncOrNull()
}
}
@Test
fun `getArchivedAccountListSync should throw exception when store throws exception`() = runTest {
// Arrange
val exception = Exception("Test error")
coEvery { archivedAccountsStoreFactory.create(userWalletId) } throws exception
// Act
val actual = runCatching { repository.getArchivedAccountListSync(userWalletId) }.exceptionOrNull()!!
// Assert
Truth.assertThat(actual).isSameInstanceAs(exception)
Truth.assertThat(actual).hasMessageThat().isEqualTo(exception.message)
coVerifyOrder { archivedAccountsStoreFactory.create(userWalletId) }
}
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetArchivedAccounts {
@Test
fun `getArchivedAccounts should emit empty list when no archived accounts`() = runTest {
// Arrange
archivedAccountsInnerStore.store(value = null)
val archivedAccountsFlow = repository.getArchivedAccounts(userWalletId)
// Act
val actual = getEmittedValues(archivedAccountsFlow)
// Assert
Truth.assertThat(actual).isEmpty()
coVerifyOrder {
archivedAccountsStoreFactory.create(userWalletId)
archivedAccountsStore.get()
}
}
@Test
fun `getArchivedAccounts should emit list of archived accounts when present`() = runTest {
// Arrange
val archivedAccount1 = mockk<ArchivedAccount>()
val archivedAccount2 = mockk<ArchivedAccount>()
val archivedAccounts = listOf(archivedAccount1, archivedAccount2)
archivedAccountsInnerStore.store(value = archivedAccounts)
archivedAccountsStore.setTimestamp(time = System.currentTimeMillis() + 3.minutes.inWholeMicroseconds)
val archivedAccountsFlow = repository.getArchivedAccounts(userWalletId)
// Act
val actual = getEmittedValues(archivedAccountsFlow)
// Assert
val expected = listOf(archivedAccounts)
Truth.assertThat(actual).containsExactlyElementsIn(expected)
coVerifyOrder {
archivedAccountsStoreFactory.create(userWalletId)
archivedAccountsStore.get()
}
}
@Test
fun `getArchivedAccounts should throw exception when store throws exception`() = runTest {
// Arrange
val exception = Exception("Test error")
coEvery { archivedAccountsStoreFactory.create(userWalletId) } throws exception
// Act
val actual = runCatching { repository.getArchivedAccounts(userWalletId) }.exceptionOrNull()!!
// Assert
Truth.assertThat(actual).isSameInstanceAs(exception)
Truth.assertThat(actual).hasMessageThat().isEqualTo(exception.message)
coVerifyOrder { archivedAccountsStoreFactory.create(userWalletId) }
}
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class FetchArchivedAccounts {
private val accountId = AccountId.forCryptoPortfolio(userWalletId, DerivationIndex.Main)
@Test
fun `fetchArchivedAccounts should store archived accounts in store`() = runTest {
// Arrange
val accountDTO = WalletAccountDTO(
id = accountId.value,
name = "Archived Account",
derivationIndex = 0,
icon = CryptoPortfolioIcon.Icon.Wallet.name,
iconColor = CryptoPortfolioIcon.Color.DullLavender.name,
totalNetworks = 0,
totalTokens = 0,
)
val apiResponse = mockk<GetWalletArchivedAccountsResponse> {
every { this@mockk.accounts } returns listOf(accountDTO)
}
val archivedAccount = ArchivedAccountConverter(userWalletId).convert(accountDTO)
coEvery {
tangemTechApi.getWalletArchivedAccounts(userWalletId.stringValue)
} returns ApiResponse.Success(apiResponse)
// Act
repository.fetchArchivedAccounts(userWalletId)
val actual = archivedAccountsStore.getSyncOrNull()
// Assert
Truth.assertThat(actual).containsExactly(archivedAccount)
coVerifyOrder {
tangemTechApi.getWalletArchivedAccounts(userWalletId.stringValue)
archivedAccountsStoreFactory.create(userWalletId)
}
}
@Test
fun `fetchArchivedAccounts should throw exception if API returns error`() = runTest { // Arrange
val exception = Exception("API error")
coEvery { tangemTechApi.getWalletArchivedAccounts(userWalletId.stringValue) } throws exception
// Act
val actual = runCatching { repository.fetchArchivedAccounts(userWalletId) }.exceptionOrNull()!!
// Assert
Truth.assertThat(actual).isSameInstanceAs(exception)
Truth.assertThat(actual).hasMessageThat().isEqualTo(exception.message)
Truth.assertThat(archivedAccountsStore.getSyncOrNull()).isNull()
coVerify { tangemTechApi.getWalletArchivedAccounts(userWalletId.stringValue) }
}
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class SaveAccounts {
private val version = 1
@Test
fun `saveAccounts should call API and update store`() = runTest {
// Arrange
val userWallet = mockk<UserWallet> {
every { this@mockk.walletId } returns userWalletId
}
val accountList = AccountList.empty(userWallet = userWallet)
val accountsResponse = mockk<GetWalletAccountsResponse> {
every { this@mockk.wallet.version } returns version
}
accountsResponseStoreFlow.value = accountsResponse
val body = SaveWalletAccountsResponseConverter.convert(value = accountList)
val apiResponse = ApiResponse.Success(Unit)
coEvery {
tangemTechApi.saveWalletAccounts(
walletId = userWalletId.stringValue,
ifMatch = version.toString(),
body = body,
)
} returns apiResponse
val converter = mockk<GetWalletAccountsResponseConverter> {
every { this@mockk.convert(accountList) } returns accountsResponse
}
every {
convertersContainer.getWalletAccountsResponseCF.create(userWallet = userWallet, version = version)
} returns converter
coEvery { accountsResponseStore.updateData(transform = any()) } returns accountsResponse
// Act
repository.saveAccounts(accountList)
// Assert
Truth.assertThat(accountsResponseStoreFlow.value).isEqualTo(accountsResponse)
coVerifyOrder {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
tangemTechApi.saveWalletAccounts(userWalletId.stringValue, version.toString(), body)
convertersContainer.getWalletAccountsResponseCF.create(userWallet, version)
converter.convert(accountList)
accountsResponseStore.updateData(any())
}
}
@Test
fun `saveAccounts if API request is failed`() = runTest {
// Arrange
val userWallet = mockk<UserWallet> {
every { this@mockk.walletId } returns userWalletId
}
val accountList = AccountList.empty(userWallet = userWallet)
val accountsResponse = mockk<GetWalletAccountsResponse> {
every { this@mockk.wallet.version } returns version
}
accountsResponseStoreFlow.value = accountsResponse
val body = SaveWalletAccountsResponseConverter.convert(value = accountList)
val apiResponse = ApiResponse.Error(cause = ApiResponseError.NetworkException) as ApiResponse<Unit>
coEvery {
tangemTechApi.saveWalletAccounts(
walletId = userWalletId.stringValue,
ifMatch = version.toString(),
body = body,
)
} returns apiResponse
// Act
val actual = runCatching { repository.saveAccounts(accountList) }.exceptionOrNull()!!
// Assert
Truth.assertThat(actual).isEqualTo(ApiResponseError.NetworkException)
coVerifyOrder {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
tangemTechApi.saveWalletAccounts(userWalletId.stringValue, version.toString(), body)
}
coVerify(inverse = true) {
convertersContainer.getWalletAccountsResponseCF.create(any(), any())
accountsResponseStore.updateData(any())
}
}
}
}

View file

@ -9,7 +9,7 @@ import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ArchivedAccountsStoreFactoryTest {
private val factory = ArchivedAccountsStoreFactory()
private val factory = ArchivedAccountsStoreFactory
@AfterEach
fun tearDownEach() {

View file

@ -22,7 +22,7 @@ interface AccountsCRUDRepository {
* @param userWalletId the unique identifier of the user wallet
* @return an [Option] containing the [AccountList] if found, or `Option.None` if not
*/
suspend fun getAccounts(userWalletId: UserWalletId): Option<AccountList>
suspend fun getAccountListSync(userWalletId: UserWalletId): Option<AccountList>
/**
* Retrieves a specific account by its unique identifier
@ -30,14 +30,14 @@ interface AccountsCRUDRepository {
* @param accountId the unique identifier of the account
* @return an [Option] containing the [Account.CryptoPortfolio] if found, or `Option.None` if not
*/
suspend fun getAccount(accountId: AccountId): Option<Account.CryptoPortfolio>
suspend fun getAccountSync(accountId: AccountId): Option<Account.CryptoPortfolio>
/**
* Retrieves a archived account by its unique identifier
*
* @param accountId the unique identifier of the account
*/
suspend fun getArchivedAccount(accountId: AccountId): Option<ArchivedAccount>
suspend fun getArchivedAccountSync(accountId: AccountId): Option<ArchivedAccount>
/**
* Retrieves a list of archived accounts associated with a specific user wallet
@ -45,7 +45,7 @@ interface AccountsCRUDRepository {
* @param userWalletId the unique identifier of the user wallet
* @return an [Option] containing a list of [ArchivedAccount] if found, or `Option.None` if not
*/
suspend fun getArchivedAccountsSync(userWalletId: UserWalletId): Option<List<ArchivedAccount>>
suspend fun getArchivedAccountListSync(userWalletId: UserWalletId): Option<List<ArchivedAccount>>
/**
* Provides a flow of archived accounts associated with a specific user wallet
@ -73,7 +73,7 @@ interface AccountsCRUDRepository {
*
* @param userWalletId the unique identifier of the user wallet
*/
suspend fun getTotalAccountsCount(userWalletId: UserWalletId): Int
suspend fun getTotalAccountsCount(userWalletId: UserWalletId): Option<Int>
/**
* Retrieves a user wallet by its unique identifier

View file

@ -55,7 +55,7 @@ class AddCryptoPortfolioUseCase(
newAccount
}
private fun Raise<Error>.createAccount(
private fun createAccount(
userWalletId: UserWalletId,
accountName: AccountName,
icon: CryptoPortfolioIcon,
@ -72,7 +72,7 @@ class AddCryptoPortfolioUseCase(
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): Option<AccountList> {
return catch(
block = { crudRepository.getAccounts(userWalletId = userWalletId) },
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
catch = { raise(Error.DataOperationFailed(cause = it)) },
)
}

View file

@ -40,7 +40,7 @@ class ArchiveCryptoPortfolioUseCase(
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
return catch(
block = { crudRepository.getAccounts(userWalletId = userWalletId) },
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
catch = { raise(Error.DataOperationFailed(cause = it)) },
)
.getOrElse { raise(Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId)) }

View file

@ -57,7 +57,7 @@ class GetArchivedAccountsUseCase(
private suspend fun getArchivedAccounts(userWalletId: UserWalletId): Either<Throwable, ArchivedAccountList> {
return Either.catch {
crudRepository.getArchivedAccountsSync(userWalletId = userWalletId).getOrElse {
crudRepository.getArchivedAccountListSync(userWalletId = userWalletId).getOrElse {
error("Archived accounts not found for user wallet: $userWalletId")
}
}
@ -70,7 +70,11 @@ class GetArchivedAccountsUseCase(
private suspend fun ProducerScope<Lce<Throwable, ArchivedAccountList>>.subscribeOnArchivedAccounts(
userWalletId: UserWalletId,
) {
crudRepository.getArchivedAccounts(userWalletId)
runCatching { crudRepository.getArchivedAccounts(userWalletId) }
.getOrElse {
send(it.lceError())
return
}
.distinctUntilChanged()
.retryWhen { cause, _ ->
send(cause.lceError())

View file

@ -38,6 +38,7 @@ class GetUnoccupiedAccountIndexUseCase(
block = { crudRepository.getTotalAccountsCount(userWalletId = userWalletId) },
catch = { raise(Error.DataOperationFailed(cause = it)) },
)
.getOrElse { raise(Error.DataNotFound) }
}
/**
@ -48,6 +49,10 @@ class GetUnoccupiedAccountIndexUseCase(
val tag: String
get() = this::class.simpleName ?: "GetUnoccupiedAccountIndexUseCase.Error"
data object DataNotFound : Error {
override fun toString(): String = "$tag: Data not found"
}
/** Error indicating that the derivation index is invalid */
data class InvalidDerivationIndex(val cause: DerivationIndex.Error) : Error {
override fun toString(): String = "$tag: Invalid derivation index: $cause"

View file

@ -44,7 +44,7 @@ class RecoverCryptoPortfolioUseCase(
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
return catch(
block = { crudRepository.getAccounts(userWalletId = userWalletId) },
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
catch = { raise(Error.DataOperationFailed(cause = it)) },
)
.getOrElse { raise(Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId)) }
@ -52,7 +52,7 @@ class RecoverCryptoPortfolioUseCase(
private suspend fun Raise<Error>.getArchivedAccount(accountId: AccountId): ArchivedAccount {
return catch(
block = { crudRepository.getArchivedAccount(accountId = accountId) },
block = { crudRepository.getArchivedAccountSync(accountId = accountId) },
catch = { raise(Error.DataOperationFailed(cause = it)) },
)
.getOrElse {

View file

@ -61,7 +61,7 @@ class UpdateCryptoPortfolioUseCase(
private suspend fun Raise<Error>.getAccountList(userWalletId: UserWalletId): AccountList {
return catch(
block = { crudRepository.getAccounts(userWalletId = userWalletId) },
block = { crudRepository.getAccountListSync(userWalletId = userWalletId) },
catch = { raise(Error.DataOperationFailed(cause = it)) },
)
.getOrElse { raise(Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId)) }

View file

@ -41,7 +41,7 @@ class AddCryptoPortfolioUseCaseTest {
val accountList = AccountList.empty(userWallet)
val updatedAccountList = (accountList + newAccount).getOrNull()!!
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
// Act
val actual = useCase(
@ -56,7 +56,7 @@ class AddCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId)
crudRepository.getAccountListSync(userWalletId)
crudRepository.saveAccounts(updatedAccountList)
}
@ -69,7 +69,7 @@ class AddCryptoPortfolioUseCaseTest {
val newAccount = createNewAccount()
val newAccountList = (AccountList.empty(userWallet) + newAccount).getOrNull()!!
coEvery { crudRepository.getAccounts(userWalletId) } returns None
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
coEvery { crudRepository.getUserWallet(userWalletId) } returns userWallet
// Act
@ -85,7 +85,7 @@ class AddCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId)
crudRepository.getAccountListSync(userWalletId)
crudRepository.getUserWallet(userWalletId)
crudRepository.saveAccounts(newAccountList)
}
@ -102,7 +102,7 @@ class AddCryptoPortfolioUseCaseTest {
val newAccount = createNewAccount(derivationIndex = 21)
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
// Act
val actual = useCase(
@ -119,7 +119,7 @@ class AddCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
coVerify(inverse = true) {
crudRepository.getUserWallet(any())
@ -133,7 +133,7 @@ class AddCryptoPortfolioUseCaseTest {
val newAccount = createNewAccount()
val exception = IllegalStateException("Test error")
coEvery { crudRepository.getAccounts(userWalletId) } throws exception
coEvery { crudRepository.getAccountListSync(userWalletId) } throws exception
// Act
val actual = useCase(
@ -147,7 +147,7 @@ class AddCryptoPortfolioUseCaseTest {
val expected = AddCryptoPortfolioUseCase.Error.DataOperationFailed(cause = exception).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
coVerify(inverse = true) {
crudRepository.getUserWallet(any())
@ -164,7 +164,7 @@ class AddCryptoPortfolioUseCaseTest {
val exception = IllegalStateException("Test error")
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
// Act
@ -180,7 +180,7 @@ class AddCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId)
crudRepository.getAccountListSync(userWalletId)
crudRepository.saveAccounts(updatedAccountList)
}

View file

@ -41,7 +41,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
val updatedAccountList = (accountList - account).getOrNull()!!
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
// Act
val actual = useCase(accountId)
@ -51,7 +51,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId)
crudRepository.getAccountListSync(userWalletId)
crudRepository.saveAccounts(updatedAccountList)
}
}
@ -64,7 +64,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
derivationIndex = DerivationIndex.Main,
)
coEvery { crudRepository.getAccounts(userWalletId) } returns None
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
// Act
val actual = useCase(accountId)
@ -73,7 +73,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
val expected = Error.CriticalTechError.AccountsNotCreated(userWalletId).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
}
@ -87,7 +87,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
val exception = IllegalStateException("Test error")
coEvery { crudRepository.getAccounts(userWalletId) } throws exception
coEvery { crudRepository.getAccountListSync(userWalletId) } throws exception
// Act
val actual = useCase(accountId)
@ -96,7 +96,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
val expected = Error.DataOperationFailed(exception).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
}
@ -109,7 +109,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
derivationIndex = DerivationIndex(1).getOrNull()!!,
)
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
// Act
val actual = useCase(accountId)
@ -118,7 +118,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
val expected = Error.CriticalTechError.AccountNotFound(accountId).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
}
@ -133,7 +133,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
val exception = IllegalStateException("Save failed")
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
// Act
@ -144,7 +144,7 @@ class ArchiveCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId)
crudRepository.getAccountListSync(userWalletId)
crudRepository.saveAccounts(updatedAccountList)
}
}

View file

@ -43,7 +43,7 @@ class GetArchivedAccountsUseCaseTest {
mockk<ArchivedAccount>(),
mockk<ArchivedAccount>(),
)
coEvery { crudRepository.getArchivedAccountsSync(userWalletId) } returns archivedAccounts.toOption()
coEvery { crudRepository.getArchivedAccountListSync(userWalletId) } returns archivedAccounts.toOption()
every { crudRepository.getArchivedAccounts(userWalletId) } returns flowOf(archivedAccounts)
// Act
@ -54,7 +54,7 @@ class GetArchivedAccountsUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getArchivedAccountsSync(userWalletId)
crudRepository.getArchivedAccountListSync(userWalletId)
crudRepository.getArchivedAccounts(userWalletId)
}
@ -69,7 +69,7 @@ class GetArchivedAccountsUseCaseTest {
mockk<ArchivedAccount>(),
)
coEvery { crudRepository.getArchivedAccountsSync(userWalletId) } returns None
coEvery { crudRepository.getArchivedAccountListSync(userWalletId) } returns None
every { crudRepository.getArchivedAccounts(userWalletId) } returns flowOf(archivedAccounts)
// Act
@ -83,7 +83,7 @@ class GetArchivedAccountsUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerify(exactly = 1) {
crudRepository.getArchivedAccountsSync(userWalletId)
crudRepository.getArchivedAccountListSync(userWalletId)
crudRepository.fetchArchivedAccounts(userWalletId)
crudRepository.getArchivedAccounts(userWalletId)
}
@ -98,7 +98,7 @@ class GetArchivedAccountsUseCaseTest {
mockk<ArchivedAccount>(),
)
coEvery { crudRepository.getArchivedAccountsSync(userWalletId) } throws exception
coEvery { crudRepository.getArchivedAccountListSync(userWalletId) } throws exception
every { crudRepository.getArchivedAccounts(userWalletId) } returns flowOf(archivedAccounts)
// Act
@ -112,7 +112,7 @@ class GetArchivedAccountsUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerify(exactly = 1) {
crudRepository.getArchivedAccountsSync(userWalletId)
crudRepository.getArchivedAccountListSync(userWalletId)
crudRepository.fetchArchivedAccounts(userWalletId)
crudRepository.getArchivedAccounts(userWalletId)
}
@ -123,7 +123,7 @@ class GetArchivedAccountsUseCaseTest {
// Arrange
val exception = IllegalStateException("Fetch error")
coEvery { crudRepository.getArchivedAccountsSync(userWalletId) } returns None
coEvery { crudRepository.getArchivedAccountListSync(userWalletId) } returns None
every { crudRepository.getArchivedAccounts(userWalletId) } returns emptyFlow()
coEvery { crudRepository.fetchArchivedAccounts(userWalletId) } throws exception
@ -139,7 +139,7 @@ class GetArchivedAccountsUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerify(exactly = 1) {
crudRepository.getArchivedAccountsSync(userWalletId)
crudRepository.getArchivedAccountListSync(userWalletId)
crudRepository.fetchArchivedAccounts(userWalletId)
crudRepository.getArchivedAccounts(userWalletId)
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.account.usecase
import arrow.core.left
import arrow.core.toOption
import com.google.common.truth.Truth
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.models.account.DerivationIndex
@ -29,7 +30,7 @@ class GetUnoccupiedAccountIndexUseCaseTest {
@Test
fun `invoke should return next unoccupied index when repository returns count`() = runTest {
// Arrange
coEvery { crudRepository.getTotalAccountsCount(userWalletId) } returns 3
coEvery { crudRepository.getTotalAccountsCount(userWalletId) } returns 3.toOption()
// Act
val actual = useCase(userWalletId = userWalletId)

View file

@ -52,8 +52,8 @@ class RecoverCryptoPortfolioUseCaseTest {
val updatedAccountList = (accountList + account).getOrNull()!!
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getArchivedAccount(account.accountId) } returns archivedAccount.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns archivedAccount.toOption()
// Act
val actual = useCase(account.accountId)
@ -63,8 +63,8 @@ class RecoverCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId)
crudRepository.getArchivedAccount(account.accountId)
crudRepository.getAccountListSync(userWalletId)
crudRepository.getArchivedAccountSync(account.accountId)
crudRepository.saveAccounts(updatedAccountList)
}
}
@ -77,7 +77,7 @@ class RecoverCryptoPortfolioUseCaseTest {
derivationIndex = DerivationIndex.Main,
)
coEvery { crudRepository.getAccounts(userWalletId) } returns None
coEvery { crudRepository.getAccountListSync(userWalletId) } returns None
// Act
val actual = useCase(accountId)
@ -86,9 +86,9 @@ class RecoverCryptoPortfolioUseCaseTest {
val expected = Error.CriticalTechError.AccountsNotCreated(userWalletId).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
coVerify(inverse = true) {
crudRepository.getArchivedAccount(any())
crudRepository.getArchivedAccountSync(any())
crudRepository.saveAccounts(any())
}
}
@ -102,7 +102,7 @@ class RecoverCryptoPortfolioUseCaseTest {
)
val exception = IllegalStateException("Test error")
coEvery { crudRepository.getAccounts(userWalletId) } throws exception
coEvery { crudRepository.getAccountListSync(userWalletId) } throws exception
// Act
val actual = useCase(accountId)
@ -111,9 +111,9 @@ class RecoverCryptoPortfolioUseCaseTest {
val expected = Error.DataOperationFailed(exception).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId) }
coVerify(inverse = true) {
crudRepository.getArchivedAccount(any())
crudRepository.getArchivedAccountSync(any())
crudRepository.saveAccounts(any())
}
}
@ -125,8 +125,8 @@ class RecoverCryptoPortfolioUseCaseTest {
val accountList = AccountList.empty(userWallet)
val exception = IllegalStateException("Test error")
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getArchivedAccount(account.accountId) } throws exception
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getArchivedAccountSync(account.accountId) } throws exception
// Act
val actual = useCase(account.accountId)
@ -136,8 +136,8 @@ class RecoverCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId)
crudRepository.getArchivedAccount(account.accountId)
crudRepository.getAccountListSync(userWalletId)
crudRepository.getArchivedAccountSync(account.accountId)
}
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
}
@ -148,8 +148,8 @@ class RecoverCryptoPortfolioUseCaseTest {
val account = createAccount(userWalletId)
val accountList = AccountList.empty(userWallet)
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getArchivedAccount(account.accountId) } returns None
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns None
// Act
val actual = useCase(account.accountId)
@ -159,8 +159,8 @@ class RecoverCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId)
crudRepository.getArchivedAccount(account.accountId)
crudRepository.getAccountListSync(userWalletId)
crudRepository.getArchivedAccountSync(account.accountId)
}
coVerify(inverse = true) { crudRepository.saveAccounts(any()) }
}
@ -182,8 +182,8 @@ class RecoverCryptoPortfolioUseCaseTest {
val updatedAccountList = (accountList + account).getOrNull()!!
val exception = IllegalStateException("Save failed")
coEvery { crudRepository.getAccounts(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getArchivedAccount(account.accountId) } returns archivedAccount.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns archivedAccount.toOption()
coEvery { crudRepository.saveAccounts(updatedAccountList) } throws exception
// Act
@ -194,8 +194,8 @@ class RecoverCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId)
crudRepository.getArchivedAccount(account.accountId)
crudRepository.getAccountListSync(userWalletId)
crudRepository.getArchivedAccountSync(account.accountId)
crudRepository.saveAccounts(updatedAccountList)
}
}

View file

@ -48,7 +48,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val updatedAccount = accountList.mainAccount.copy(accountName = newAccountName)
val updatedAccountList = (accountList + updatedAccount).getOrNull()!!
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId = userWalletId) } returns accountList.toOption()
// Act
val actual = useCase(accountId = accountId, accountName = newAccountName)
@ -58,7 +58,7 @@ class UpdateCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId = userWalletId)
crudRepository.getAccountListSync(userWalletId = userWalletId)
crudRepository.saveAccounts(accountList = updatedAccountList)
}
}
@ -76,7 +76,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val updatedAccount = accountList.mainAccount.copy(icon = newAccountIcon)
val updatedAccountList = (accountList + updatedAccount).getOrNull()!!
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId = userWalletId) } returns accountList.toOption()
// Act
val actual = useCase(accountId = accountId, icon = newAccountIcon)
@ -86,7 +86,7 @@ class UpdateCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId = userWalletId)
crudRepository.getAccountListSync(userWalletId = userWalletId)
crudRepository.saveAccounts(accountList = updatedAccountList)
}
}
@ -105,7 +105,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val updatedAccount = accountList.mainAccount.copy(accountName = newAccountName, icon = newAccountIcon)
val updatedAccountList = (accountList + updatedAccount).getOrNull()!!
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId = userWalletId) } returns accountList.toOption()
// Act
val actual = useCase(accountId = accountId, accountName = newAccountName, icon = newAccountIcon)
@ -115,7 +115,7 @@ class UpdateCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId = userWalletId)
crudRepository.getAccountListSync(userWalletId = userWalletId)
crudRepository.saveAccounts(accountList = updatedAccountList)
}
}
@ -126,7 +126,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val accountList = AccountList.empty(userWallet = userWallet)
val accountId = accountList.mainAccount.accountId
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId = userWalletId) } returns accountList.toOption()
// Act
val actual = useCase(accountId = accountId)
@ -136,7 +136,7 @@ class UpdateCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerify(inverse = true) {
crudRepository.getAccounts(userWalletId = any())
crudRepository.getAccountListSync(userWalletId = any())
crudRepository.saveAccounts(accountList = any())
}
}
@ -151,7 +151,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val exception = IllegalStateException("Test exception")
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } throws exception
coEvery { crudRepository.getAccountListSync(userWalletId = userWalletId) } throws exception
// Act
val actual = useCase(accountId = accountId, accountName = newAccountName)
@ -160,7 +160,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val expected = Error.DataOperationFailed(cause = exception).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId = userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId = userWalletId) }
coVerify(inverse = true) { crudRepository.saveAccounts(accountList = any()) }
}
@ -175,7 +175,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val newAccountName = AccountName("New name").getOrNull()!!
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList
coEvery { crudRepository.getAccountListSync(userWalletId = userWalletId) } returns accountList
// Act
val actual = useCase(accountId = accountId, accountName = newAccountName)
@ -184,7 +184,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val expected = Error.CriticalTechError.AccountsNotCreated(userWalletId = userWalletId).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId = userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId = userWalletId) }
coVerify(inverse = true) { crudRepository.saveAccounts(accountList = any()) }
}
@ -199,7 +199,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val newAccountName = AccountName("New name").getOrNull()!!
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId = userWalletId) } returns accountList.toOption()
// Act
val actual = useCase(accountId = accountId, accountName = newAccountName)
@ -208,7 +208,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val expected = Error.CriticalTechError.AccountNotFound(accountId = accountId).left()
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder { crudRepository.getAccounts(userWalletId = userWalletId) }
coVerifyOrder { crudRepository.getAccountListSync(userWalletId = userWalletId) }
coVerify(inverse = true) { crudRepository.saveAccounts(accountList = any()) }
}
@ -224,7 +224,7 @@ class UpdateCryptoPortfolioUseCaseTest {
val exception = IllegalStateException("Save failed")
coEvery { crudRepository.getAccounts(userWalletId = userWalletId) } returns accountList.toOption()
coEvery { crudRepository.getAccountListSync(userWalletId = userWalletId) } returns accountList.toOption()
coEvery { crudRepository.saveAccounts(accountList = updatedAccountList) } throws exception
// Act
@ -235,7 +235,7 @@ class UpdateCryptoPortfolioUseCaseTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
crudRepository.getAccounts(userWalletId = userWalletId)
crudRepository.getAccountListSync(userWalletId = userWalletId)
crudRepository.saveAccounts(accountList = updatedAccountList)
}
}