diff --git a/data/account/build.gradle.kts b/data/account/build.gradle.kts index d5c297866a..58b5628a0d 100644 --- a/data/account/build.gradle.kts +++ b/data/account/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { // region Project - Domain api(projects.domain.account) api(projects.domain.card) + api(projects.domain.common) api(projects.domain.models) // endregion diff --git a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt index a4fe4cd17d..5740e30ccb 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt @@ -2,6 +2,7 @@ package com.tangem.data.account.fetcher import com.tangem.data.account.store.AccountsResponseStore import com.tangem.data.account.store.AccountsResponseStoreFactory +import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory import com.tangem.data.account.utils.assignTokens import com.tangem.data.account.utils.toUserTokensResponse import com.tangem.data.common.account.WalletAccountsFetcher @@ -14,6 +15,7 @@ import com.tangem.datasource.api.common.response.ApiResponseError.HttpException. import com.tangem.datasource.api.common.response.ETAG_HEADER import com.tangem.datasource.api.common.response.isNetworkError import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO @@ -31,17 +33,20 @@ import javax.inject.Singleton * @property accountsResponseStoreFactory factory to create [AccountsResponseStore] * @property userTokensSaver saves user tokens to the database * @property fetchWalletAccountsErrorHandler handles errors during fetching wallet accounts + * @property defaultWalletAccountsResponseFactory creates [GetWalletAccountsResponse] from [UserTokensResponse] * @property eTagsStore store for ETags to manage caching * @property dispatchers dispatchers * [REDACTED_AUTHOR] */ +@Suppress("LongParameterList") @Singleton internal class DefaultWalletAccountsFetcher @Inject constructor( private val tangemTechApi: TangemTechApi, private val accountsResponseStoreFactory: AccountsResponseStoreFactory, private val userTokensSaver: UserTokensSaver, private val fetchWalletAccountsErrorHandler: FetchWalletAccountsErrorHandler, + private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory, private val eTagsStore: ETagsStore, private val dispatchers: CoroutineDispatcherProvider, ) : WalletAccountsFetcher, WalletAccountsSaver { @@ -49,9 +54,11 @@ internal class DefaultWalletAccountsFetcher @Inject constructor( override suspend fun fetch(userWalletId: UserWalletId) { val savedAccountsResponse = getAccountsResponseStore(userWalletId = userWalletId).getSyncOrNull() val accountsResponse = fetchWalletAccounts(userWalletId, savedAccountsResponse) - val unassignedTokens = accountsResponse?.unassignedTokens + ?: return - if (!unassignedTokens.isNullOrEmpty()) { + if (accountsResponse.accounts.isEmpty()) { + initializeAccounts(userWalletId, accountsResponse) + } else if (accountsResponse.unassignedTokens.isNotEmpty()) { assignTokens(userWalletId, accountsResponse) } } @@ -131,12 +138,23 @@ internal class DefaultWalletAccountsFetcher @Inject constructor( pushWalletAccounts = ::push, storeWalletAccounts = ::store, ) - - null }, ) } + private suspend fun initializeAccounts(userWalletId: UserWalletId, accountsResponse: GetWalletAccountsResponse) { + val response = defaultWalletAccountsResponseFactory.create( + userWalletId = userWalletId, + userTokensResponse = UserTokensResponse( + group = accountsResponse.wallet.group, + sort = accountsResponse.wallet.sort, + tokens = accountsResponse.unassignedTokens, + ), + ) + + pushAndStore(userWalletId, response) + } + private suspend fun assignTokens(userWalletId: UserWalletId, accountsResponse: GetWalletAccountsResponse) { val accountsResponseWithTokens = accountsResponse.assignTokens(userWalletId) diff --git a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt index 3c7d1af215..6f284edf5b 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt @@ -1,10 +1,8 @@ package com.tangem.data.account.fetcher -import com.tangem.data.account.converter.CryptoPortfolioConverter -import com.tangem.data.account.utils.assignTokens +import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory import com.tangem.data.account.utils.toUserTokensResponse -import com.tangem.data.common.currency.CardCryptoCurrencyFactory -import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.data.common.currency.UserTokensResponseAccountIdEnricher import com.tangem.data.common.currency.UserTokensSaver import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.Code @@ -13,10 +11,6 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.datasource.local.token.UserTokensResponseStore -import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.account.models.AccountList -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import timber.log.Timber import javax.inject.Inject @@ -24,12 +18,9 @@ import javax.inject.Inject /** * Handles errors that occur during the fetching of wallet accounts * - * @property userTokensSaver saves user tokens to the storage - * @property userWalletsStore provides access to user wallet data - * @property userTokensResponseStore provides access to user token responses. - * @property cryptoPortfolioCF factory for converting crypto portfolios - * @property userTokensResponseFactory factory for creating user token responses - * @property cardCryptoCurrencyFactory factory for creating default cryptocurrencies for multi-currency wallets + * @property userTokensSaver saves user tokens to the storage + * @property userTokensResponseStore provides access to user token responses. + * @property defaultWalletAccountsResponseFactory creates [GetWalletAccountsResponse] from [UserTokensResponse] * * @see DefaultWalletAccountsFetcher * @@ -37,11 +28,8 @@ import javax.inject.Inject */ internal class FetchWalletAccountsErrorHandler @Inject constructor( private val userTokensSaver: UserTokensSaver, - private val userWalletsStore: UserWalletsStore, private val userTokensResponseStore: UserTokensResponseStore, - private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory, - private val userTokensResponseFactory: UserTokensResponseFactory, - private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, + private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory, ) { /** @@ -61,20 +49,19 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor( savedAccountsResponse: GetWalletAccountsResponse?, pushWalletAccounts: suspend (userWalletId: UserWalletId, accounts: List) -> Unit, storeWalletAccounts: suspend (userWalletId: UserWalletId, response: GetWalletAccountsResponse) -> Unit, - ) { + ): GetWalletAccountsResponse? { val isResponseUpToDate = error.isNetworkError(code = Code.NOT_MODIFIED) if (isResponseUpToDate) { Timber.e("ETag is up to date, no need to update accounts for wallet: $userWalletId") - return + return savedAccountsResponse } - val (accountDTOs, userTokensResponse) = if (savedAccountsResponse == null) { - val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) + val response = savedAccountsResponse ?: defaultWalletAccountsResponseFactory.create( + userWalletId = userWalletId, + userTokensResponse = getFromLegacyStore(userWalletId), + ) - createDefaultAccountDTOs(userWallet) to getFromLegacyStore(userWalletId).orDefault(userWallet) - } else { - savedAccountsResponse.accounts to savedAccountsResponse.toUserTokensResponse() - } + val (accountDTOs, userTokensResponse) = response.accounts to response.toUserTokensResponse() val isNotFoundError = error.isNetworkError(code = Code.NOT_FOUND) if (isNotFoundError) { @@ -82,49 +69,18 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor( userTokensSaver.push(userWalletId = userWalletId, response = userTokensResponse) } - val response = savedAccountsResponse.orDefault(userWalletId, accountDTOs, userTokensResponse) storeWalletAccounts(userWalletId, response) - } - private fun createDefaultAccountDTOs(userWallet: UserWallet): List { - val accounts = AccountList.empty(userWallet.walletId).accounts - .filterIsInstance() - - val converter = cryptoPortfolioCF.create(userWallet = userWallet) - - return converter.convertListBack(input = accounts) + return response } private suspend fun getFromLegacyStore(userWalletId: UserWalletId): UserTokensResponse? { return userTokensResponseStore.getSyncOrNull(userWalletId) + ?.let { + it.copy( + tokens = UserTokensResponseAccountIdEnricher(userWalletId = userWalletId, tokens = it.tokens), + ) + } .also { userTokensResponseStore.clear(userWalletId) } } - - private fun UserTokensResponse?.orDefault(userWallet: UserWallet): UserTokensResponse { - if (this != null) return this - - return userTokensResponseFactory.createUserTokensResponse( - currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet = userWallet), - isGroupedByNetwork = false, - isSortedByBalance = false, - ) - } - - private fun GetWalletAccountsResponse?.orDefault( - userWalletId: UserWalletId, - accountDTOs: List, - userTokensResponse: UserTokensResponse, - ): GetWalletAccountsResponse { - if (this != null) return this - - return GetWalletAccountsResponse( - wallet = GetWalletAccountsResponse.Wallet( - group = userTokensResponse.group, - sort = userTokensResponse.sort, - totalAccounts = accountDTOs.size, - ), - accounts = accountDTOs.assignTokens(userWalletId = userWalletId, tokens = userTokensResponse.tokens), - unassignedTokens = emptyList(), - ) - } } \ No newline at end of file diff --git a/data/account/src/main/kotlin/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactory.kt b/data/account/src/main/kotlin/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactory.kt new file mode 100644 index 0000000000..8dad68c1de --- /dev/null +++ b/data/account/src/main/kotlin/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactory.kt @@ -0,0 +1,68 @@ +package com.tangem.data.account.utils + +import com.tangem.data.account.converter.CryptoPortfolioConverter +import com.tangem.data.common.currency.CardCryptoCurrencyFactory +import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse +import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.common.wallets.UserWalletsListRepository +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import javax.inject.Inject + +/** + * Factory to create default [GetWalletAccountsResponse]. + * + * @property userWalletsListRepository repository to get user wallet information + * @property cryptoPortfolioCF converter factory to convert crypto portfolio accounts + * @property userTokensResponseFactory factory to create [UserTokensResponse] + * @property cardCryptoCurrencyFactory factory to get default coins for multi-currency wallet + * +[REDACTED_AUTHOR] + */ +internal class DefaultWalletAccountsResponseFactory @Inject constructor( + private val userWalletsListRepository: UserWalletsListRepository, + private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory, + private val userTokensResponseFactory: UserTokensResponseFactory, + private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, +) { + + suspend fun create(userWalletId: UserWalletId, userTokensResponse: UserTokensResponse?): GetWalletAccountsResponse { + val userWallet = userWalletsListRepository.userWalletsSync().firstOrNull { it.walletId == userWalletId } + + val accountDTOs = userWallet?.let(::createDefaultAccountDTOs).orEmpty() + val response = userTokensResponse.orDefault(userWallet = userWallet) + + return GetWalletAccountsResponse( + wallet = GetWalletAccountsResponse.Wallet( + group = response.group, + sort = response.sort, + totalAccounts = accountDTOs.size, + ), + accounts = accountDTOs.assignTokens(userWalletId = userWalletId, tokens = response.tokens), + unassignedTokens = emptyList(), + ) + } + + private fun createDefaultAccountDTOs(userWallet: UserWallet): List { + val accounts = AccountList.empty(userWallet.walletId).accounts + .filterIsInstance() + + val converter = cryptoPortfolioCF.create(userWallet = userWallet) + + return converter.convertListBack(input = accounts) + } + + private fun UserTokensResponse?.orDefault(userWallet: UserWallet?): UserTokensResponse { + if (this != null) return this + + return userTokensResponseFactory.createUserTokensResponse( + currencies = userWallet?.let(cardCryptoCurrencyFactory::createDefaultCoinsForMultiCurrencyWallet).orEmpty(), + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + } +} \ No newline at end of file diff --git a/data/account/src/test/java/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcherTest.kt b/data/account/src/test/java/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcherTest.kt index ac012e76fb..4494f5af61 100644 --- a/data/account/src/test/java/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcherTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcherTest.kt @@ -5,6 +5,7 @@ import com.tangem.data.account.converter.createGetWalletAccountsResponse import com.tangem.data.account.converter.createWalletAccountDTO import com.tangem.data.account.store.AccountsResponseStore import com.tangem.data.account.store.AccountsResponseStoreFactory +import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory import com.tangem.data.common.cache.etag.ETagsStore import com.tangem.data.common.currency.UserTokensSaver import com.tangem.datasource.api.common.response.ApiResponse @@ -36,6 +37,7 @@ class DefaultWalletAccountsFetcherTest { private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true) private val fetchWalletAccountsErrorHandler: FetchWalletAccountsErrorHandler = mockk(relaxUnitFun = true) + private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory = mockk() private val eTagsStore: ETagsStore = mockk(relaxUnitFun = true) private val fetcher: DefaultWalletAccountsFetcher = DefaultWalletAccountsFetcher( @@ -43,6 +45,7 @@ class DefaultWalletAccountsFetcherTest { accountsResponseStoreFactory = accountsResponseStoreFactory, userTokensSaver = userTokensSaver, fetchWalletAccountsErrorHandler = fetchWalletAccountsErrorHandler, + defaultWalletAccountsResponseFactory = defaultWalletAccountsResponseFactory, eTagsStore = eTagsStore, dispatchers = TestingCoroutineDispatcherProvider(), ) @@ -205,6 +208,16 @@ class DefaultWalletAccountsFetcherTest { tangemTechApi.getWalletAccounts(walletId = userWalletId.stringValue, eTag = eTag) } returns apiError as ApiResponse + coEvery { + fetchWalletAccountsErrorHandler.handle( + error = apiError.cause, + userWalletId = userWalletId, + savedAccountsResponse = null, + pushWalletAccounts = any(), + storeWalletAccounts = any(), + ) + } returns savedAccountsResponse + // Act fetcher.fetch(userWalletId) diff --git a/data/account/src/test/java/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandlerTest.kt b/data/account/src/test/java/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandlerTest.kt index c174b4d463..e8a3341536 100644 --- a/data/account/src/test/java/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandlerTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandlerTest.kt @@ -1,9 +1,7 @@ package com.tangem.data.account.fetcher -import com.tangem.data.account.converter.CryptoPortfolioConverter +import com.tangem.data.account.utils.DefaultWalletAccountsResponseFactory import com.tangem.data.account.utils.toUserTokensResponse -import com.tangem.data.common.currency.CardCryptoCurrencyFactory -import com.tangem.data.common.currency.UserTokensResponseFactory import com.tangem.data.common.currency.UserTokensSaver import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.Code @@ -11,12 +9,11 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO import com.tangem.datasource.local.token.UserTokensResponseStore -import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.account.models.AccountList -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId -import io.mockk.* +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -29,35 +26,21 @@ import org.junit.jupiter.api.TestInstance class FetchWalletAccountsErrorHandlerTest { private val userTokensSaver: UserTokensSaver = mockk(relaxUnitFun = true) - private val userWalletsStore: UserWalletsStore = mockk() private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxUnitFun = true) - private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory = mockk() - private val cryptoPortfolioConverter = mockk() - private val userTokensResponseFactory: UserTokensResponseFactory = mockk() - private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk() + private val defaultWalletAccountsResponseFactory: DefaultWalletAccountsResponseFactory = mockk() private val handler = FetchWalletAccountsErrorHandler( userTokensSaver = userTokensSaver, - userWalletsStore = userWalletsStore, userTokensResponseStore = userTokensResponseStore, - cryptoPortfolioCF = cryptoPortfolioCF, - userTokensResponseFactory = userTokensResponseFactory, - cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, + defaultWalletAccountsResponseFactory = defaultWalletAccountsResponseFactory, ) - private val userWallet = mockk { - every { this@mockk.walletId } returns userWalletId - } - @BeforeEach fun setupEach() { clearMocks( userTokensSaver, - userWalletsStore, userTokensResponseStore, - cryptoPortfolioCF, - cryptoPortfolioConverter, - cardCryptoCurrencyFactory, + defaultWalletAccountsResponseFactory, ) } @@ -84,12 +67,8 @@ class FetchWalletAccountsErrorHandlerTest { // Assert coVerify(inverse = true) { - userWalletsStore.getSyncStrict(key = any()) userTokensResponseStore.getSyncOrNull(userWalletId = any()) - userTokensResponseFactory.createUserTokensResponse(any(), any(), any()) - cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any()) - cryptoPortfolioCF.create(any()) - cryptoPortfolioConverter.convertListBack(any()) + defaultWalletAccountsResponseFactory.create(userWalletId = any(), userTokensResponse = any()) pushWalletAccounts(any(), any()) userTokensSaver.push(userWalletId = any(), response = any()) storeWalletAccounts(any(), any()) @@ -146,12 +125,8 @@ class FetchWalletAccountsErrorHandlerTest { } coVerify(inverse = true) { - userWalletsStore.getSyncStrict(key = any()) userTokensResponseStore.getSyncOrNull(userWalletId = any()) - userTokensResponseFactory.createUserTokensResponse(any(), any(), any()) - cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any()) - cryptoPortfolioCF.create(any()) - cryptoPortfolioConverter.convertListBack(any()) + defaultWalletAccountsResponseFactory.create(userWalletId = any(), userTokensResponse = any()) } } @@ -160,9 +135,6 @@ class FetchWalletAccountsErrorHandlerTest { // Arrange val error = ApiResponseError.TimeoutException() - val accounts = AccountList.empty(userWalletId).accounts - .filterIsInstance() - val accountDTO = WalletAccountDTO( id = "nibh", name = "Michael Dotson", @@ -186,18 +158,10 @@ class FetchWalletAccountsErrorHandlerTest { val userTokensResponse = savedAccountsResponse.toUserTokensResponse() - every { userWalletsStore.getSyncStrict(userWalletId) } returns userWallet - every { cryptoPortfolioCF.create(userWallet) } returns cryptoPortfolioConverter - every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountDTO) - coEvery { userTokensResponseStore.getSyncOrNull(userWalletId) } returns null - every { - userTokensResponseFactory.createUserTokensResponse( - currencies = emptyList(), - isGroupedByNetwork = false, - isSortedByBalance = false, - ) - } returns userTokensResponse - every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns emptyList() + coEvery { userTokensResponseStore.getSyncOrNull(userWalletId) } returns userTokensResponse + coEvery { + defaultWalletAccountsResponseFactory.create(userWalletId, userTokensResponse) + } returns savedAccountsResponse val pushWalletAccounts: suspend (UserWalletId, List) -> Unit = mockk(relaxed = true) val storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit = mockk(relaxed = true) @@ -213,16 +177,8 @@ class FetchWalletAccountsErrorHandlerTest { // Assert coVerify { - userWalletsStore.getSyncStrict(userWalletId) - cryptoPortfolioCF.create(userWallet) - cryptoPortfolioConverter.convertListBack(accounts) userTokensResponseStore.getSyncOrNull(userWalletId) - userTokensResponseFactory.createUserTokensResponse( - currencies = emptyList(), - isGroupedByNetwork = false, - isSortedByBalance = false, - ) - cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) + defaultWalletAccountsResponseFactory.create(userWalletId, userTokensResponse) storeWalletAccounts(userWalletId, any()) } diff --git a/data/account/src/test/java/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactoryTest.kt b/data/account/src/test/java/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactoryTest.kt new file mode 100644 index 0000000000..e270dacc98 --- /dev/null +++ b/data/account/src/test/java/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactoryTest.kt @@ -0,0 +1,240 @@ +package com.tangem.data.account.utils + +import com.google.common.truth.Truth +import com.tangem.data.account.converter.CryptoPortfolioConverter +import com.tangem.data.account.converter.createWalletAccountDTO +import com.tangem.data.common.currency.CardCryptoCurrencyFactory +import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.common.wallets.UserWalletsListRepository +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrency +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.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class DefaultWalletAccountsResponseFactoryTest { + + private val userWalletsListRepository = mockk() + private val cryptoPortfolioCF = mockk() + private val cryptoPortfolioConverter = mockk() + private val userTokensResponseFactory = mockk() + private val cardCryptoCurrencyFactory = mockk() + + private val factory = DefaultWalletAccountsResponseFactory( + userWalletsListRepository = userWalletsListRepository, + cryptoPortfolioCF = cryptoPortfolioCF, + userTokensResponseFactory = userTokensResponseFactory, + cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, + ) + + private val userWalletId = UserWalletId("011") + + @BeforeEach + fun setUpEach() { + every { cryptoPortfolioCF.create(any()) } returns cryptoPortfolioConverter + } + + @AfterEach + fun tearDownEach() { + clearMocks( + userWalletsListRepository, + cryptoPortfolioCF, + cryptoPortfolioConverter, + userTokensResponseFactory, + cardCryptoCurrencyFactory, + ) + } + + @Test + fun `create returns empty accounts when user wallet not found`() = runTest { + // Arrange + val userTokensResponse = UserTokensResponse( + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + + coEvery { userWalletsListRepository.userWalletsSync() } returns emptyList() + every { + userTokensResponseFactory.createUserTokensResponse( + currencies = emptyList(), + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + } returns userTokensResponse + + // Act + val actual = factory.create(userWalletId = userWalletId, userTokensResponse = null) + + // Assert + val expected = GetWalletAccountsResponse( + wallet = GetWalletAccountsResponse.Wallet( + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + totalAccounts = 0, + ), + accounts = emptyList(), + unassignedTokens = emptyList(), + ) + Truth.assertThat(actual).isEqualTo(expected) + + coVerifyOrder { + userWalletsListRepository.userWalletsSync() + userTokensResponseFactory.createUserTokensResponse( + currencies = emptyList(), + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + } + } + + @Test + fun `create returns response with default tokens when userTokensResponse is null`() = runTest { + // Arrange + val userWallet = mockk(relaxed = true) { + every { walletId } returns userWalletId + } + + val defaultCoins = listOf(mockk()) + coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet) + every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns defaultCoins + + val defaultResponse = UserTokensResponse( + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = listOf(mockk(relaxed = true)), + ) + + every { + userTokensResponseFactory.createUserTokensResponse( + currencies = defaultCoins, + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + } returns defaultResponse + + val accounts = AccountList.empty(userWallet.walletId).accounts + .filterIsInstance() + + val accountsDTO = createWalletAccountDTO(userWalletId) + every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountsDTO) + + // Act + val actual = factory.create(userWalletId, null) + + // Assert + val expected = GetWalletAccountsResponse( + wallet = GetWalletAccountsResponse.Wallet( + group = defaultResponse.group, + sort = defaultResponse.sort, + totalAccounts = 1, + ), + accounts = listOf(accountsDTO), + unassignedTokens = emptyList(), + ) + + Truth.assertThat(actual).isEqualTo(expected) + + coVerifyOrder { + userWalletsListRepository.userWalletsSync() + cryptoPortfolioConverter.convertListBack(accounts) + cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) + userTokensResponseFactory.createUserTokensResponse( + currencies = defaultCoins, + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + } + } + + @Test + fun `create returns response with default tokens when userTokensResponse is null and no default coins`() = runTest { + // Arrange + val userWallet = mockk(relaxed = true) { + every { walletId } returns userWalletId + } + coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet) + every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns emptyList() + val defaultResponse = UserTokensResponse( + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = emptyList(), + ) + every { + userTokensResponseFactory.createUserTokensResponse( + currencies = emptyList(), + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + } returns defaultResponse + val accounts = AccountList.empty(userWallet.walletId).accounts + .filterIsInstance() + every { cryptoPortfolioConverter.convertListBack(accounts) } returns emptyList() + + // Act + val actual = factory.create(userWalletId, null) + + // Assert + val expected = GetWalletAccountsResponse( + wallet = GetWalletAccountsResponse.Wallet( + group = defaultResponse.group, + sort = defaultResponse.sort, + totalAccounts = 0, + ), + accounts = emptyList(), + unassignedTokens = emptyList(), + ) + Truth.assertThat(actual).isEqualTo(expected) + } + + @Test + fun `create returns response with assigned tokens`() = runTest { + // Arrange + val userWallet = mockk(relaxed = true) { + every { walletId } returns userWalletId + } + val assignedTokens = listOf(mockk(), mockk()) + coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet) + val userTokensResponse = UserTokensResponse( + group = UserTokensResponse.GroupType.NETWORK, + sort = UserTokensResponse.SortType.BALANCE, + tokens = listOf(mockk(relaxed = true)), + ) + every { + userTokensResponseFactory.createUserTokensResponse( + currencies = assignedTokens, + isGroupedByNetwork = false, + isSortedByBalance = false, + ) + } returns userTokensResponse + + val accounts = AccountList.empty(userWallet.walletId).accounts + .filterIsInstance() + val accountsDTO = createWalletAccountDTO(userWalletId) + every { cryptoPortfolioConverter.convertListBack(accounts) } returns listOf(accountsDTO) + + // Act + val actual = factory.create(userWalletId, userTokensResponse) + + // Assert + val expected = GetWalletAccountsResponse( + wallet = GetWalletAccountsResponse.Wallet( + group = userTokensResponse.group, + sort = userTokensResponse.sort, + totalAccounts = 1, + ), + accounts = listOf(accountsDTO), + unassignedTokens = emptyList(), + ) + Truth.assertThat(actual).isEqualTo(expected) + } +} \ No newline at end of file