diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyOperations.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyOperations.kt new file mode 100644 index 0000000000..c367b214df --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyOperations.kt @@ -0,0 +1,70 @@ +package com.tangem.domain.account.status.utils + +import arrow.core.Option +import arrow.core.raise.option +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.status.model.AccountCryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network + +/** + * Extension functions for retrieving [AccountCryptoCurrency] from an [AccountList]. + * + * This object provides convenient extension functions to search and retrieve [AccountCryptoCurrency] instances + * from an [AccountList] using various parameters such as cryptocurrency ID and network. + * + * Usage example: + * ``` + * val accountList: AccountList? = ... + * val result: Option = accountList.getAccountCryptoCurrency(currency) + * result.fold( + * ifEmpty = { /* handle not found */ }, + * ifSome = { accountCryptoCurrency -> /* use the found result */ } + * ) + * ``` + * + * @see AccountCryptoCurrency + * @see AccountList +[REDACTED_AUTHOR] + */ +object AccountCryptoCurrencyOperations { + + // region AccountList + + /** + * Retrieves the [AccountCryptoCurrency] for the specified [currency] from this [AccountList]. + * + * @receiver the [AccountList] to search within, can be null + * @param currency the cryptocurrency whose account association is to be retrieved + * @return [Option] containing the [AccountCryptoCurrency] if found, or [Option.None] otherwise + */ + fun AccountList?.getAccountCryptoCurrency(currency: CryptoCurrency): Option { + return getAccountCryptoCurrency(currencyId = currency.id, network = currency.network) + } + + /** + * Retrieves the [AccountCryptoCurrency] for the specified [currencyId] and [network] from this [AccountList]. + * + * @receiver the [AccountList] to search within, can be null + * @param currencyId the ID of the cryptocurrency whose account association is to be retrieved + * @param network the network associated with the cryptocurrency, can be null + * @return [Option] containing the [AccountCryptoCurrency] if found, or [Option.None] otherwise + */ + fun AccountList?.getAccountCryptoCurrency( + currencyId: CryptoCurrency.ID, + network: Network?, + ): Option = option { + val accountList = this@getAccountCryptoCurrency + + ensureNotNull(accountList) + + val accountCryptoCurrency = AccountCryptoCurrencyStatusFinder( + accountList = accountList, + currencyId = currencyId, + network = network, + ) + + ensureNotNull(accountCryptoCurrency) + } + // endregion +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusFinder.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusFinder.kt index fe5daf41d0..22033e6c37 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusFinder.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusFinder.kt @@ -1,7 +1,9 @@ package com.tangem.domain.account.status.utils import com.tangem.blockchain.common.Blockchain +import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.status.model.AccountCryptoCurrency import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatuses import com.tangem.domain.models.account.Account @@ -9,16 +11,23 @@ import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.account.filterCryptoPortfolio import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network import com.tangem.lib.crypto.derivation.AccountNodeRecognizer /** - * Finds the status of a specific cryptocurrency associated with an account from the provided account status list. + * Finds a specific cryptocurrency or its status associated with an account from the provided account list. + * + * The base implementation uses [AccountList] for searching. For [AccountStatusList], it first converts + * to [AccountList], finds the [AccountCryptoCurrency], then retrieves the status using accountId and currencyId. * [REDACTED_AUTHOR] */ +@Suppress("MethodOverloading") internal object AccountCryptoCurrencyStatusFinder { + // region AccountCryptoCurrencyStatus methods + /** * Retrieves the [AccountCryptoCurrencyStatus] for the specified [currency] from the given [accountStatusList]. * @@ -48,25 +57,19 @@ internal object AccountCryptoCurrencyStatusFinder { currencyId: CryptoCurrency.ID, network: Network?, ): AccountCryptoCurrencyStatus? { - return accountStatusList.getExpectedAccountStatuses(network) - .asSequence() - .filterCryptoPortfolio() - .mapNotNull { accountStatus -> - val status = accountStatus.flattenCurrencies().firstOrNull { it.currency.id == currencyId } - ?: return@mapNotNull null + val accountList = accountStatusList.toAccountList().getOrNull() ?: return null + val accountCurrency = invoke(accountList, currencyId, network) ?: return null - AccountCryptoCurrencyStatus(account = accountStatus.account, status = status) - } - .firstOrNull() + return accountStatusList.findStatus(accountCurrency) } /** - * Retrieves a map of accounts to their corresponding list of [AccountCryptoCurrencyStatus] for the specified + * Retrieves a map of accounts to their corresponding list of [CryptoCurrencyStatus] for the specified * list of [currencies] from the given [accountStatusList]. * * @param accountStatusList the list of account statuses to search within. * @param currencies the list of cryptocurrencies whose statuses are to be retrieved. - * @return a map where the key is the account and the value is a list of [AccountCryptoCurrencyStatus]. + * @return a map where the key is the account and the value is a list of [CryptoCurrencyStatus]. */ operator fun invoke( accountStatusList: AccountStatusList, @@ -104,15 +107,86 @@ internal object AccountCryptoCurrencyStatusFinder { derivationPath: Network.DerivationPath, contractAddress: String?, ): AccountCryptoCurrencyStatus? { - return accountStatusList.getExpectedAccountStatuses( + val accountList = accountStatusList.toAccountList().getOrNull() ?: return null + val accountCurrency = invoke( + accountList = accountList, + networkId = networkId, + derivationPath = derivationPath, + contractAddress = contractAddress, + ) ?: return null + + return accountStatusList.findStatus(accountCurrency) + } + + // endregion + + // region AccountCryptoCurrency methods + + /** + * Retrieves the [AccountCryptoCurrency] for the specified [currency] from the given [accountList]. + * + * @param accountList the list of accounts to search within. + * @param currency the cryptocurrency to be retrieved. + * @return the [AccountCryptoCurrency] if found, otherwise null. + */ + operator fun invoke(accountList: AccountList, currency: CryptoCurrency): AccountCryptoCurrency? { + return invoke( + accountList = accountList, + currencyId = currency.id, + network = currency.network, + ) + } + + /** + * Retrieves the [AccountCryptoCurrency] for the specified [currencyId] and [network] + * from the given [accountList]. + * + * @param accountList the list of accounts to search within. + * @param currencyId the ID of the cryptocurrency to be retrieved. + * @param network the network associated with the cryptocurrency. + * @return the [AccountCryptoCurrency] if found, otherwise null. + */ + operator fun invoke( + accountList: AccountList, + currencyId: CryptoCurrency.ID, + network: Network?, + ): AccountCryptoCurrency? { + return accountList.getExpectedAccounts(network) + .asSequence() + .filterIsInstance() + .mapNotNull { account -> + val currency = account.cryptoCurrencies.firstOrNull { it.id == currencyId } + ?: return@mapNotNull null + + AccountCryptoCurrency(account = account, cryptoCurrency = currency) + } + .firstOrNull() + } + + /** + * Retrieves the [AccountCryptoCurrency] for the specified [networkId], [derivationPath], + * and optional [contractAddress] from the given [accountList]. + * + * @param accountList the list of accounts to search within. + * @param networkId the ID of the network associated with the cryptocurrency. + * @param derivationPath the derivation path of the account. + * @param contractAddress the optional contract address of the token (if applicable). + * @return the [AccountCryptoCurrency] if found, otherwise null. + */ + operator fun invoke( + accountList: AccountList, + networkId: Network.ID, + derivationPath: Network.DerivationPath, + contractAddress: String?, + ): AccountCryptoCurrency? { + return accountList.getExpectedAccounts( rawNetworkId = networkId.rawId.value, derivationPath = derivationPath, ) .asSequence() - .filterCryptoPortfolio() - .mapNotNull { accountStatus -> - val status = accountStatus.flattenCurrencies().firstOrNull { - val currency = it.currency + .filterIsInstance() + .mapNotNull { account -> + val currency = account.cryptoCurrencies.firstOrNull { currency -> val isContractAddressMatch = contractAddress == null || currency.id.contractAddress.equals(contractAddress, ignoreCase = true) @@ -122,74 +196,81 @@ internal object AccountCryptoCurrencyStatusFinder { } ?: return@mapNotNull null - AccountCryptoCurrencyStatus(account = accountStatus.account, status = status) + AccountCryptoCurrency(account = account, cryptoCurrency = currency) } .firstOrNull() } - private fun AccountStatusList.getExpectedAccountStatuses(network: Network?): List { - return getExpectedAccountStatuses(rawNetworkId = network?.rawId, derivationPath = network?.derivationPath) + // endregion + + // region AccountStatusList helpers + + private fun AccountStatusList.findStatus(accountCurrency: AccountCryptoCurrency): AccountCryptoCurrencyStatus? { + val accountStatus = accountStatuses + .filterCryptoPortfolio() + .firstOrNull { it.account.accountId == accountCurrency.account.accountId } + ?: return null + + val currencyStatus = accountStatus.flattenCurrencies() + .firstOrNull { it.currency.id == accountCurrency.cryptoCurrency.id } + ?: return null + + return AccountCryptoCurrencyStatus(account = accountCurrency.account, status = currencyStatus) } - /** - * Retrieves the expected account statuses based on the provided [rawNetworkId] and [derivationPath]. - * If either parameter is null, all account statuses are returned. - * If both parameters are provided, it filters the accounts based on the derivation index. - * - * @param rawNetworkId the raw ID of the network to filter accounts by, can be null. - * @param derivationPath the derivation path of the network to filter accounts by, can be null. - * @return a list of [AccountStatus] that match the expected criteria. - */ - private fun AccountStatusList.getExpectedAccountStatuses( - rawNetworkId: String?, - derivationPath: Network.DerivationPath?, - ): List { - val possibleAccountIndex = if (rawNetworkId != null && derivationPath != null) { - getAccountIndexOrNull(rawNetworkId, derivationPath) - } else { - null + private fun AccountStatusList.getExpectedAccountStatuses(networks: List): List { + val possibleAccountIndexes = networks.mapNotNull { getAccountIndexOrNull(it.rawId, it.derivationPath) } + + if (possibleAccountIndexes.isEmpty()) return accountStatuses + + val filteredStatuses = accountStatuses.filter { accountStatus -> + val cryptoPortfolio = accountStatus.account as? Account.CryptoPortfolio ?: return@filter false + cryptoPortfolio.derivationIndex.value in possibleAccountIndexes } + return filteredStatuses + listOf(mainAccount) + } + + // endregion + + // region AccountList helpers + + private fun AccountList.getExpectedAccounts(network: Network?): List { + return getExpectedAccounts(rawNetworkId = network?.rawId, derivationPath = network?.derivationPath) + } + + private fun AccountList.getExpectedAccounts( + rawNetworkId: String?, + derivationPath: Network.DerivationPath?, + ): List { + val possibleAccountIndex = getAccountIndexOrNull(rawNetworkId, derivationPath) + return when (possibleAccountIndex) { - // currency can be in any account - null -> accountStatuses - // currency only in the main account + null -> accounts DerivationIndex.Main.value -> listOf(mainAccount) // currency only in the account with specific derivation index or in the main account else -> { - val accountStatus = accountStatuses.firstOrNull { accountStatus -> - val cryptoPortfolio = accountStatus.account as? Account.CryptoPortfolio ?: return@firstOrNull false - + val account = accounts.firstOrNull { account -> + val cryptoPortfolio = account as? Account.CryptoPortfolio ?: return@firstOrNull false cryptoPortfolio.derivationIndex.value == possibleAccountIndex } - - listOfNotNull(accountStatus, mainAccount) + listOfNotNull(account, mainAccount) } } } - private fun AccountStatusList.getExpectedAccountStatuses(networks: List): List { - val possibleAccountIndexes = networks.mapNotNull { it.getAccountIndexOrNull() } + // endregion - if (possibleAccountIndexes.isEmpty()) return this@getExpectedAccountStatuses.accountStatuses + // region Common helpers - val accountStatuses = this@getExpectedAccountStatuses.accountStatuses.filter { accountStatus -> - val cryptoPortfolio = accountStatus.account as? Account.CryptoPortfolio ?: return@filter false + private fun getAccountIndexOrNull(rawNetworkId: String?, derivationPath: Network.DerivationPath?): Int? { + if (rawNetworkId == null || derivationPath == null) return null - cryptoPortfolio.derivationIndex.value in possibleAccountIndexes - } - - return accountStatuses + listOf(mainAccount) - } - - private fun Network.getAccountIndexOrNull(): Int? { - return getAccountIndexOrNull(rawNetworkId = rawId, derivationPath = derivationPath) - } - - private fun getAccountIndexOrNull(rawNetworkId: String, derivationPath: Network.DerivationPath): Int? { val blockchain = Blockchain.fromId(id = rawNetworkId) val recognizer = AccountNodeRecognizer(blockchain) return recognizer.recognize(derivationPath)?.toInt() } + + // endregion } \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusOperations.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusOperations.kt new file mode 100644 index 0000000000..98127c3a49 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusOperations.kt @@ -0,0 +1,77 @@ +package com.tangem.domain.account.status.utils + +import arrow.core.Option +import arrow.core.raise.option +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network + +/** + * Extension functions for retrieving [AccountCryptoCurrencyStatus] from an [AccountStatusList]. + * + * This object provides convenient extension functions to search and retrieve [AccountCryptoCurrencyStatus] + * instances from an [AccountStatusList] using various parameters such as cryptocurrency ID and network. + * + * The primary difference from [AccountCryptoCurrencyOperations] is that this object works with + * [AccountStatusList] and returns status information along with the account-currency association. + * + * Usage example: + * ``` + * val accountStatusList: AccountStatusList? = ... + * val result: Option = accountStatusList.getAccountCryptoCurrencyStatus(currency) + * result.fold( + * ifEmpty = { /* handle not found */ }, + * ifSome = { accountCurrencyStatus -> /* use the found result */ } + * ) + * ``` + * + * @see AccountCryptoCurrencyStatus + * @see AccountStatusList + * @see AccountCryptoCurrencyOperations +[REDACTED_AUTHOR] + */ +object AccountCryptoCurrencyStatusOperations { + + // region AccountStatusList + + /** + * Retrieves the [AccountCryptoCurrencyStatus] for the specified [currency] from this [AccountStatusList]. + * + * @receiver the [AccountStatusList] to search within, can be null + * @param currency the cryptocurrency whose account status association is to be retrieved + * @return [Option] containing the [AccountCryptoCurrencyStatus] if found, or [Option.None] otherwise + */ + fun AccountStatusList?.getAccountCryptoCurrencyStatus( + currency: CryptoCurrency, + ): Option { + return getAccountCryptoCurrencyStatus(currencyId = currency.id, network = currency.network) + } + + /** + * Retrieves the [AccountCryptoCurrencyStatus] for the specified [currencyId] and [network] + * from this [AccountStatusList]. + * + * @receiver the [AccountStatusList] to search within, can be null + * @param currencyId the ID of the cryptocurrency whose account status association is to be retrieved + * @param network the network associated with the cryptocurrency, can be null + * @return [Option] containing the [AccountCryptoCurrencyStatus] if found, or [Option.None] otherwise + */ + fun AccountStatusList?.getAccountCryptoCurrencyStatus( + currencyId: CryptoCurrency.ID, + network: Network?, + ): Option = option { + val accountStatusList = this@getAccountCryptoCurrencyStatus + + ensureNotNull(accountStatusList) + + val accountCryptoCurrencyStatus = AccountCryptoCurrencyStatusFinder( + accountStatusList = accountStatusList, + currencyId = currencyId, + network = network, + ) + + ensureNotNull(accountCryptoCurrencyStatus) + } + // endregion +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyOperations.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyOperations.kt new file mode 100644 index 0000000000..7fdc8d9cce --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyOperations.kt @@ -0,0 +1,78 @@ +package com.tangem.domain.account.status.utils + +import arrow.core.Option +import arrow.core.toOption +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.status.utils.AccountCryptoCurrencyOperations.getAccountCryptoCurrency +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network + +/** + * Extension functions for retrieving [CryptoCurrency] from an [AccountList] or [Account.CryptoPortfolio]. + * + * This object provides convenient extension functions to search and retrieve [CryptoCurrency] instances + * from account-related data structures using various parameters such as cryptocurrency ID and network. + * + * Unlike [AccountCryptoCurrencyOperations], this object returns only the [CryptoCurrency] without + * the associated account information. + * + * Usage example: + * ``` + * val accountList: AccountList? = ... + * val result: Option = accountList.getCryptoCurrency(currencyId, network) + * result.fold( + * ifEmpty = { /* handle not found */ }, + * ifSome = { currency -> /* use the found currency */ } + * ) + * ``` + * + * @see CryptoCurrency + * @see AccountList + * @see Account.CryptoPortfolio + * @see AccountCryptoCurrencyOperations +[REDACTED_AUTHOR] + */ +object CryptoCurrencyOperations { + + // region AccountList + + /** + * Retrieves the [CryptoCurrency] matching the specified [cryptoCurrency] from this [AccountList]. + * + * @receiver the [AccountList] to search within, can be null + * @param cryptoCurrency the cryptocurrency to match + * @return [Option] containing the [CryptoCurrency] if found, or [Option.None] otherwise + */ + fun AccountList?.getCryptoCurrency(cryptoCurrency: CryptoCurrency): Option { + return getCryptoCurrency(currencyId = cryptoCurrency.id, network = cryptoCurrency.network) + } + + /** + * Retrieves the [CryptoCurrency] for the specified [currencyId] and [network] from this [AccountList]. + * + * @receiver the [AccountList] to search within, can be null + * @param currencyId the ID of the cryptocurrency to be retrieved + * @param network the network associated with the cryptocurrency, can be null + * @return [Option] containing the [CryptoCurrency] if found, or [Option.None] otherwise + */ + fun AccountList?.getCryptoCurrency(currencyId: CryptoCurrency.ID, network: Network?): Option { + return getAccountCryptoCurrency(currencyId, network) + .map { it.cryptoCurrency } + } + // endregion + + // region Account.CryptoPortfolio + + /** + * Retrieves the [CryptoCurrency] matching the specified [currencyId] from this [Account.CryptoPortfolio]. + * + * @receiver the [Account.CryptoPortfolio] to search within + * @param currencyId the ID of the cryptocurrency to be retrieved + * @return [Option] containing the [CryptoCurrency] if found, or [Option.None] otherwise + */ + fun Account.CryptoPortfolio.getCryptoCurrency(currencyId: CryptoCurrency.ID): Option { + return cryptoCurrencies.firstOrNull { it.id == currencyId }.toOption() + } + // endregion +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusOperations.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusOperations.kt new file mode 100644 index 0000000000..b9e72590a1 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyStatusOperations.kt @@ -0,0 +1,89 @@ +package com.tangem.domain.account.status.utils + +import arrow.core.Option +import arrow.core.toOption +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus +import com.tangem.domain.account.status.utils.AccountCryptoCurrencyStatusOperations.getAccountCryptoCurrencyStatus +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.network.Network + +/** + * Extension functions for retrieving [CryptoCurrencyStatus] from an [AccountStatusList] or + * [AccountStatus.CryptoPortfolio]. + * + * This object provides convenient extension functions to search and retrieve [CryptoCurrencyStatus] + * instances from account status-related data structures using various parameters such as cryptocurrency ID + * and network. + * + * Unlike [AccountCryptoCurrencyStatusOperations], this object returns only the [CryptoCurrencyStatus] without + * the associated account information. + * + * Usage example: + * ``` + * val accountStatusList: AccountStatusList? = ... + * val result: Option = accountStatusList.getCryptoCurrencyStatus(currency) + * result.fold( + * ifEmpty = { /* handle not found */ }, + * ifSome = { status -> /* use the found status */ } + * ) + * ``` + * + * @see CryptoCurrencyStatus + * @see AccountStatusList + * @see AccountStatus.CryptoPortfolio + * @see AccountCryptoCurrencyStatusOperations +[REDACTED_AUTHOR] + */ +object CryptoCurrencyStatusOperations { + + // region AccountStatusList + + /** + * Retrieves the [CryptoCurrencyStatus] for the specified [currency] from this [AccountStatusList]. + * + * @receiver the [AccountStatusList] to search within, can be null + * @param currency the cryptocurrency whose status is to be retrieved + * @return [Option] containing the [CryptoCurrencyStatus] if found, or [Option.None] otherwise + */ + fun AccountStatusList?.getCryptoCurrencyStatus(currency: CryptoCurrency): Option { + return getCryptoCurrencyStatus(currencyId = currency.id, network = currency.network) + } + + /** + * Retrieves the [CryptoCurrencyStatus] for the specified [currencyId] and [network] + * from this [AccountStatusList]. + * + * @receiver the [AccountStatusList] to search within, can be null + * @param currencyId the ID of the cryptocurrency whose status is to be retrieved + * @param network the network associated with the cryptocurrency, can be null + * @return [Option] containing the [CryptoCurrencyStatus] if found, or [Option.None] otherwise + */ + fun AccountStatusList?.getCryptoCurrencyStatus( + currencyId: CryptoCurrency.ID, + network: Network?, + ): Option { + return getAccountCryptoCurrencyStatus(currencyId = currencyId, network = network) + .map(AccountCryptoCurrencyStatus::status) + } + // endregion + + // region AccountStatus.CryptoPortfolio + + /** + * Retrieves the [CryptoCurrencyStatus] for the specified [currencyId] + * from this [AccountStatus.CryptoPortfolio]. + * + * @receiver the [AccountStatus.CryptoPortfolio] to search within + * @param currencyId the ID of the cryptocurrency whose status is to be retrieved + * @return [Option] containing the [CryptoCurrencyStatus] if found, or [Option.None] otherwise + */ + fun AccountStatus.CryptoPortfolio.getCryptoCurrencyStatus( + currencyId: CryptoCurrency.ID, + ): Option { + return flattenCurrencies().firstOrNull { it.currency.id == currencyId }.toOption() + } + // endregion +} \ No newline at end of file diff --git a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/GetAccountCurrencyStatusUseCaseTest.kt b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/GetAccountCurrencyStatusUseCaseTest.kt index 5c35e33427..fbea44938c 100644 --- a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/GetAccountCurrencyStatusUseCaseTest.kt +++ b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/GetAccountCurrencyStatusUseCaseTest.kt @@ -7,11 +7,10 @@ import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.models.TokensGroupType import com.tangem.domain.models.TokensSortType import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.account.AccountStatus -import com.tangem.domain.models.account.DerivationIndex +import com.tangem.domain.models.account.* import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network import com.tangem.domain.models.tokenlist.TokenList @@ -19,7 +18,10 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.test.core.assertNone import com.tangem.test.core.assertSome import com.tangem.test.core.getEmittedValues -import io.mockk.* +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.coVerifyOrder +import io.mockk.mockk import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest @@ -78,9 +80,15 @@ class GetAccountCurrencyStatusUseCaseTest { priceChangeLce = lceLoading(), ) - val accountStatusList = mockk(relaxed = true) { - every { this@mockk.accountStatuses } returns listOf(accountStatus) - } + val accountStatusList = AccountStatusList( + userWalletId = userWalletId, + accountStatuses = listOf(accountStatus), + totalAccounts = 1, + totalArchivedAccounts = 0, + totalFiatBalance = TotalFiatBalance.Loading, + sortType = TokensSortType.NONE, + groupType = TokensGroupType.NONE, + ) coEvery { supplier.getSyncOrNull(supplierParams) } returns accountStatusList @@ -101,10 +109,14 @@ class GetAccountCurrencyStatusUseCaseTest { priceChangeLce = lceLoading(), ) - val account = mockk(relaxed = true) { - every { this@mockk.derivationIndex } returns DerivationIndex(1).getOrNull()!! - every { this@mockk.cryptoCurrencies } returns listOf(currency) - } + val derivationIndex = DerivationIndex(1).getOrNull()!! + val account = Account.CryptoPortfolio( + accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex), + accountName = AccountName("Test Account").getOrNull()!!, + icon = CryptoPortfolioIcon.ofDefaultCustomAccount(), + derivationIndex = derivationIndex, + cryptoCurrencies = listOf(currency), + ) val currencyStatus = CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading) val accountStatus = AccountStatus.CryptoPortfolio( account = account, @@ -116,9 +128,15 @@ class GetAccountCurrencyStatusUseCaseTest { priceChangeLce = lceLoading(), ) - val accountStatusList = mockk(relaxed = true) { - every { this@mockk.accountStatuses } returns listOf(mainAccountStatus, accountStatus, mockk()) - } + val accountStatusList = AccountStatusList( + userWalletId = userWalletId, + accountStatuses = listOf(mainAccountStatus, accountStatus), + totalAccounts = 2, + totalArchivedAccounts = 0, + totalFiatBalance = TotalFiatBalance.Loading, + sortType = TokensSortType.NONE, + groupType = TokensGroupType.NONE, + ) coEvery { supplier.getSyncOrNull(supplierParams) } returns accountStatusList @@ -139,9 +157,10 @@ class GetAccountCurrencyStatusUseCaseTest { @Test fun `invokeSync returns Some if network is null`() = runTest { // Arrange - val account = mockk(relaxed = true) { - every { this@mockk.cryptoCurrencies } returns listOf(currency) - } + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) val currencyStatus = CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading) val accountStatus = AccountStatus.CryptoPortfolio( account = account, @@ -153,9 +172,15 @@ class GetAccountCurrencyStatusUseCaseTest { priceChangeLce = lceLoading(), ) - val accountStatusList = mockk(relaxed = true) { - every { this@mockk.accountStatuses } returns listOf(accountStatus) - } + val accountStatusList = AccountStatusList( + userWalletId = userWalletId, + accountStatuses = listOf(accountStatus), + totalAccounts = 1, + totalArchivedAccounts = 0, + totalFiatBalance = TotalFiatBalance.Loading, + sortType = TokensSortType.NONE, + groupType = TokensGroupType.NONE, + ) coEvery { supplier.getSyncOrNull(supplierParams) } returns accountStatusList @@ -197,9 +222,15 @@ class GetAccountCurrencyStatusUseCaseTest { priceChangeLce = lceLoading(), ) - val accountStatusList = mockk(relaxed = true) { - every { this@mockk.accountStatuses } returns listOf(accountStatus) - } + val accountStatusList = AccountStatusList( + userWalletId = userWalletId, + accountStatuses = listOf(accountStatus), + totalAccounts = 1, + totalArchivedAccounts = 0, + totalFiatBalance = TotalFiatBalance.Loading, + sortType = TokensSortType.NONE, + groupType = TokensGroupType.NONE, + ) coEvery { supplier(supplierParams) } returns flowOf(accountStatusList) @@ -221,10 +252,14 @@ class GetAccountCurrencyStatusUseCaseTest { priceChangeLce = lceLoading(), ) - val account = mockk(relaxed = true) { - every { this@mockk.derivationIndex } returns DerivationIndex(1).getOrNull()!! - every { this@mockk.cryptoCurrencies } returns listOf(currency) - } + val derivationIndex = DerivationIndex(1).getOrNull()!! + val account = Account.CryptoPortfolio( + accountId = AccountId.forCryptoPortfolio(userWalletId, derivationIndex), + accountName = AccountName("Test Account").getOrNull()!!, + icon = CryptoPortfolioIcon.ofDefaultCustomAccount(), + derivationIndex = derivationIndex, + cryptoCurrencies = listOf(currency), + ) val currencyStatus = CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading) val accountStatus = AccountStatus.CryptoPortfolio( account = account, @@ -236,9 +271,15 @@ class GetAccountCurrencyStatusUseCaseTest { priceChangeLce = lceLoading(), ) - val accountStatusList = mockk(relaxed = true) { - every { this@mockk.accountStatuses } returns listOf(mainAccountStatus, accountStatus, mockk()) - } + val accountStatusList = AccountStatusList( + userWalletId = userWalletId, + accountStatuses = listOf(mainAccountStatus, accountStatus), + totalAccounts = 2, + totalArchivedAccounts = 0, + totalFiatBalance = TotalFiatBalance.Loading, + sortType = TokensSortType.NONE, + groupType = TokensGroupType.NONE, + ) coEvery { supplier(supplierParams) } returns flowOf(accountStatusList) @@ -256,9 +297,10 @@ class GetAccountCurrencyStatusUseCaseTest { @Test fun `invoke returns data if network is null`() = runTest { // Arrange - val account = mockk(relaxed = true) { - every { this@mockk.cryptoCurrencies } returns listOf(currency) - } + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) val currencyStatus = CryptoCurrencyStatus(currency = currency, value = CryptoCurrencyStatus.Loading) val accountStatus = AccountStatus.CryptoPortfolio( account = account, @@ -270,9 +312,15 @@ class GetAccountCurrencyStatusUseCaseTest { priceChangeLce = lceLoading(), ) - val accountStatusList = mockk(relaxed = true) { - every { this@mockk.accountStatuses } returns listOf(accountStatus) - } + val accountStatusList = AccountStatusList( + userWalletId = userWalletId, + accountStatuses = listOf(accountStatus), + totalAccounts = 1, + totalArchivedAccounts = 0, + totalFiatBalance = TotalFiatBalance.Loading, + sortType = TokensSortType.NONE, + groupType = TokensGroupType.NONE, + ) coEvery { supplier(supplierParams) } returns flowOf(accountStatusList) diff --git a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/AccountCryptoCurrencyOperationsTest.kt b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/AccountCryptoCurrencyOperationsTest.kt new file mode 100644 index 0000000000..ef84efc6f8 --- /dev/null +++ b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/AccountCryptoCurrencyOperationsTest.kt @@ -0,0 +1,169 @@ +package com.tangem.domain.account.status.utils + +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.status.model.AccountCryptoCurrency +import com.tangem.domain.account.status.utils.AccountCryptoCurrencyOperations.getAccountCryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.test.core.assertNone +import com.tangem.test.core.assertSome +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +/** + * Tests for [AccountCryptoCurrencyOperations]. + * +[REDACTED_AUTHOR] + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AccountCryptoCurrencyOperationsTest { + + private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() + private val userWalletId = UserWalletId("011") + private val currency = cryptoCurrencyFactory.ethereum + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetAccountCryptoCurrencyByCurrency { + + @Test + fun `returns None when AccountList is null`() { + // Arrange + val accountList: AccountList? = null + // Act + val result = accountList.getAccountCryptoCurrency(currency) + // Assert + assertNone(result) + } + + @Test + fun `returns None when currency is not found in AccountList`() { + // Arrange + val accountList = AccountList.empty(userWalletId) + // Act + val result = accountList.getAccountCryptoCurrency(currency) + // Assert + assertNone(result) + } + + @Test + fun `returns Some when currency is found in AccountList`() { + // Arrange + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + val expected = AccountCryptoCurrency( + account = accountList.mainAccount, + cryptoCurrency = currency, + ) + // Act + val result = accountList.getAccountCryptoCurrency(currency) + // Assert + assertSome(result, expected) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetAccountCryptoCurrencyByCurrencyIdAndNetwork { + + @Test + fun `returns None when AccountList is null`() { + // Arrange + val accountList: AccountList? = null + // Act + val result = accountList.getAccountCryptoCurrency( + currencyId = currency.id, + network = currency.network, + ) + // Assert + assertNone(result) + } + + @Test + fun `returns None when currency id is not found`() { + // Arrange + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + val otherCurrencyId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId("bitcoin"), + suffix = CryptoCurrency.ID.Suffix.RawID("bitcoin"), + ) + // Act + val result = accountList.getAccountCryptoCurrency( + currencyId = otherCurrencyId, + network = currency.network, + ) + // Assert + assertNone(result) + } + + @Test + fun `returns Some when currency id is found with null network`() { + // Arrange + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + val expected = AccountCryptoCurrency( + account = accountList.mainAccount, + cryptoCurrency = currency, + ) + // Act + val result = accountList.getAccountCryptoCurrency( + currencyId = currency.id, + network = null, + ) + // Assert + assertSome(result, expected) + } + + @Test + fun `returns Some when currency id and network match`() { + // Arrange + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + val expected = AccountCryptoCurrency( + account = accountList.mainAccount, + cryptoCurrency = currency, + ) + // Act + val result = accountList.getAccountCryptoCurrency( + currencyId = currency.id, + network = currency.network, + ) + // Assert + assertSome(result, expected) + } + + @Test + fun `returns Some with first matching currency when multiple currencies exist`() { + // Arrange + val currencies = cryptoCurrencyFactory.ethereumAndStellar + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = currencies, + ) + val targetCurrency = currencies.first() + val expected = AccountCryptoCurrency( + account = accountList.mainAccount, + cryptoCurrency = targetCurrency, + ) + // Act + val result = accountList.getAccountCryptoCurrency( + currencyId = targetCurrency.id, + network = targetCurrency.network, + ) + // Assert + assertSome(result, expected) + } + } +} \ No newline at end of file diff --git a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusOperationsTest.kt b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusOperationsTest.kt new file mode 100644 index 0000000000..046e1a64a4 --- /dev/null +++ b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/AccountCryptoCurrencyStatusOperationsTest.kt @@ -0,0 +1,205 @@ +package com.tangem.domain.account.status.utils + +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus +import com.tangem.domain.account.status.utils.AccountCryptoCurrencyStatusOperations.getAccountCryptoCurrencyStatus +import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.models.TokensGroupType +import com.tangem.domain.models.TokensSortType +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.tokenlist.TokenList +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.test.core.assertNone +import com.tangem.test.core.assertSome +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +/** + * Tests for [AccountCryptoCurrencyStatusOperations]. + * +[REDACTED_AUTHOR] + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AccountCryptoCurrencyStatusOperationsTest { + + private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() + private val userWalletId = UserWalletId("011") + private val currency = cryptoCurrencyFactory.ethereum + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetAccountCryptoCurrencyStatusByCurrency { + + @Test + fun `returns None when AccountStatusList is null`() { + val accountStatusList: AccountStatusList? = null + val result = accountStatusList.getAccountCryptoCurrencyStatus(currency) + assertNone(result) + } + + @Test + fun `returns None when currency is not found in AccountStatusList`() { + val accountStatusList = createAccountStatusList(currencies = emptyList()) + val result = accountStatusList.getAccountCryptoCurrencyStatus(currency) + assertNone(result) + } + + @Test + fun `returns Some when currency is found in AccountStatusList`() { + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val accountStatusList = createAccountStatusList( + currencies = listOf(currency), + currencyStatuses = listOf(currencyStatus), + ) + val expected = AccountCryptoCurrencyStatus( + account = accountStatusList.mainAccount.account, + status = currencyStatus, + ) + val result = accountStatusList.getAccountCryptoCurrencyStatus(currency) + assertSome(result, expected) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetAccountCryptoCurrencyStatusByCurrencyIdAndNetwork { + + @Test + fun `returns None when AccountStatusList is null`() { + val accountStatusList: AccountStatusList? = null + val result = accountStatusList.getAccountCryptoCurrencyStatus( + currencyId = currency.id, + network = currency.network, + ) + assertNone(result) + } + + @Test + fun `returns None when currency id is not found`() { + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val accountStatusList = createAccountStatusList( + currencies = listOf(currency), + currencyStatuses = listOf(currencyStatus), + ) + val otherCurrencyId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId("bitcoin"), + suffix = CryptoCurrency.ID.Suffix.RawID("bitcoin"), + ) + val result = accountStatusList.getAccountCryptoCurrencyStatus( + currencyId = otherCurrencyId, + network = currency.network, + ) + assertNone(result) + } + + @Test + fun `returns Some when currency id is found with null network`() { + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val accountStatusList = createAccountStatusList( + currencies = listOf(currency), + currencyStatuses = listOf(currencyStatus), + ) + val expected = AccountCryptoCurrencyStatus( + account = accountStatusList.mainAccount.account, + status = currencyStatus, + ) + val result = accountStatusList.getAccountCryptoCurrencyStatus( + currencyId = currency.id, + network = null, + ) + assertSome(result, expected) + } + + @Test + fun `returns Some when currency id and network match`() { + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val accountStatusList = createAccountStatusList( + currencies = listOf(currency), + currencyStatuses = listOf(currencyStatus), + ) + val expected = AccountCryptoCurrencyStatus( + account = accountStatusList.mainAccount.account, + status = currencyStatus, + ) + val result = accountStatusList.getAccountCryptoCurrencyStatus( + currencyId = currency.id, + network = currency.network, + ) + assertSome(result, expected) + } + + @Test + fun `returns Some with first matching currency when multiple currencies exist`() { + val currencies = cryptoCurrencyFactory.ethereumAndStellar + val currencyStatuses = currencies.map { + CryptoCurrencyStatus(currency = it, value = CryptoCurrencyStatus.Loading) + } + val accountStatusList = createAccountStatusList( + currencies = currencies, + currencyStatuses = currencyStatuses, + ) + val targetCurrency = currencies.first() + val expected = AccountCryptoCurrencyStatus( + account = accountStatusList.mainAccount.account, + status = currencyStatuses.first(), + ) + val result = accountStatusList.getAccountCryptoCurrencyStatus( + currencyId = targetCurrency.id, + network = targetCurrency.network, + ) + assertSome(result, expected) + } + } + + private fun createAccountStatusList( + currencies: List, + currencyStatuses: List = emptyList(), + ): AccountStatusList { + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = currencies, + ) + val tokenList = if (currencyStatuses.isEmpty()) { + TokenList.Empty + } else { + TokenList.Ungrouped( + totalFiatBalance = TotalFiatBalance.Loading, + sortedBy = TokensSortType.NONE, + currencies = currencyStatuses, + ) + } + val accountStatus = AccountStatus.CryptoPortfolio( + account = account, + tokenList = tokenList, + priceChangeLce = lceLoading(), + ) + return AccountStatusList( + userWalletId = userWalletId, + accountStatuses = listOf(accountStatus), + totalAccounts = 1, + totalArchivedAccounts = 0, + totalFiatBalance = TotalFiatBalance.Loading, + sortType = TokensSortType.NONE, + groupType = TokensGroupType.NONE, + ) + } +} \ No newline at end of file diff --git a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/CryptoCurrencyOperationsTest.kt b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/CryptoCurrencyOperationsTest.kt new file mode 100644 index 0000000000..043571ab75 --- /dev/null +++ b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/CryptoCurrencyOperationsTest.kt @@ -0,0 +1,207 @@ +package com.tangem.domain.account.status.utils + +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.status.utils.CryptoCurrencyOperations.getCryptoCurrency +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.test.core.assertNone +import com.tangem.test.core.assertSome +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +/** + * Tests for [CryptoCurrencyOperations]. + * +[REDACTED_AUTHOR] + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class CryptoCurrencyOperationsTest { + + private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() + private val userWalletId = UserWalletId("011") + private val currency = cryptoCurrencyFactory.ethereum + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetCryptoCurrencyFromAccountListByCurrency { + + @Test + fun `returns None when currency is not found in AccountList`() { + // Arrange + val accountList = AccountList.empty(userWalletId) + // Act + val result = accountList.getCryptoCurrency(currency) + // Assert + assertNone(result) + } + + @Test + fun `returns Some when currency is found in AccountList`() { + // Arrange + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + // Act + val result = accountList.getCryptoCurrency(currency) + // Assert + assertSome(result, currency) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetCryptoCurrencyFromAccountListByCurrencyIdAndNetwork { + + @Test + fun `returns None when AccountList is null`() { + // Arrange + val accountList: AccountList? = null + // Act + val result = accountList.getCryptoCurrency( + currencyId = currency.id, + network = currency.network, + ) + // Assert + assertNone(result) + } + + @Test + fun `returns None when currency id is not found`() { + // Arrange + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + val otherCurrencyId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId("bitcoin"), + suffix = CryptoCurrency.ID.Suffix.RawID("bitcoin"), + ) + // Act + val result = accountList.getCryptoCurrency( + currencyId = otherCurrencyId, + network = currency.network, + ) + // Assert + assertNone(result) + } + + @Test + fun `returns Some when currency id is found with null network`() { + // Arrange + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + // Act + val result = accountList.getCryptoCurrency( + currencyId = currency.id, + network = null, + ) + // Assert + assertSome(result, currency) + } + + @Test + fun `returns Some when currency id and network match`() { + // Arrange + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + // Act + val result = accountList.getCryptoCurrency( + currencyId = currency.id, + network = currency.network, + ) + // Assert + assertSome(result, currency) + } + + @Test + fun `returns Some with first matching currency when multiple currencies exist`() { + // Arrange + val currencies = cryptoCurrencyFactory.ethereumAndStellar + val accountList = AccountList.empty( + userWalletId = userWalletId, + cryptoCurrencies = currencies, + ) + val targetCurrency = currencies.first() + // Act + val result = accountList.getCryptoCurrency( + currencyId = targetCurrency.id, + network = targetCurrency.network, + ) + // Assert + assertSome(result, targetCurrency) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetCryptoCurrencyFromCryptoPortfolio { + + @Test + fun `returns None when currency id is not found in CryptoPortfolio`() { + // Arrange + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + val otherCurrencyId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId("bitcoin"), + suffix = CryptoCurrency.ID.Suffix.RawID("bitcoin"), + ) + // Act + val result = account.getCryptoCurrency(otherCurrencyId) + // Assert + assertNone(result) + } + + @Test + fun `returns Some when currency id is found in CryptoPortfolio`() { + // Arrange + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + // Act + val result = account.getCryptoCurrency(currency.id) + // Assert + assertSome(result, currency) + } + + @Test + fun `returns None when CryptoPortfolio has no currencies`() { + // Arrange + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = emptyList(), + ) + // Act + val result = account.getCryptoCurrency(currency.id) + // Assert + assertNone(result) + } + + @Test + fun `returns Some with matching currency when multiple currencies exist`() { + // Arrange + val currencies = cryptoCurrencyFactory.ethereumAndStellar + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = currencies, + ) + val targetCurrency = currencies.last() + // Act + val result = account.getCryptoCurrency(targetCurrency.id) + // Assert + assertSome(result, targetCurrency) + } + } +} \ No newline at end of file diff --git a/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/CryptoCurrencyStatusOperationsTest.kt b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/CryptoCurrencyStatusOperationsTest.kt new file mode 100644 index 0000000000..e79ed8bfbb --- /dev/null +++ b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/utils/CryptoCurrencyStatusOperationsTest.kt @@ -0,0 +1,321 @@ +package com.tangem.domain.account.status.utils + +import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.account.status.utils.CryptoCurrencyStatusOperations.getCryptoCurrencyStatus +import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.models.TokensGroupType +import com.tangem.domain.models.TokensSortType +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.tokenlist.TokenList +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.test.core.assertNone +import com.tangem.test.core.assertSome +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +/** + * Tests for [CryptoCurrencyStatusOperations]. + * +[REDACTED_AUTHOR] + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class CryptoCurrencyStatusOperationsTest { + + private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() + private val userWalletId = UserWalletId("011") + private val currency = cryptoCurrencyFactory.ethereum + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetCryptoCurrencyStatusFromAccountStatusListByCurrency { + + @Test + fun `returns None when AccountStatusList is null`() { + // Arrange + val accountStatusList: AccountStatusList? = null + // Act + val result = accountStatusList.getCryptoCurrencyStatus(currency) + // Assert + assertNone(result) + } + + @Test + fun `returns None when currency is not found in AccountStatusList`() { + // Arrange + val accountStatusList = createAccountStatusList(currencies = emptyList()) + // Act + val result = accountStatusList.getCryptoCurrencyStatus(currency) + // Assert + assertNone(result) + } + + @Test + fun `returns Some when currency is found in AccountStatusList`() { + // Arrange + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val accountStatusList = createAccountStatusList( + currencies = listOf(currency), + currencyStatuses = listOf(currencyStatus), + ) + // Act + val result = accountStatusList.getCryptoCurrencyStatus(currency) + // Assert + assertSome(result, currencyStatus) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetCryptoCurrencyStatusFromAccountStatusListByCurrencyIdAndNetwork { + + @Test + fun `returns None when AccountStatusList is null`() { + // Arrange + val accountStatusList: AccountStatusList? = null + // Act + val result = accountStatusList.getCryptoCurrencyStatus( + currencyId = currency.id, + network = currency.network, + ) + // Assert + assertNone(result) + } + + @Test + fun `returns None when currency id is not found`() { + // Arrange + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val accountStatusList = createAccountStatusList( + currencies = listOf(currency), + currencyStatuses = listOf(currencyStatus), + ) + val otherCurrencyId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId("bitcoin"), + suffix = CryptoCurrency.ID.Suffix.RawID("bitcoin"), + ) + // Act + val result = accountStatusList.getCryptoCurrencyStatus( + currencyId = otherCurrencyId, + network = currency.network, + ) + // Assert + assertNone(result) + } + + @Test + fun `returns Some when currency id is found with null network`() { + // Arrange + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val accountStatusList = createAccountStatusList( + currencies = listOf(currency), + currencyStatuses = listOf(currencyStatus), + ) + // Act + val result = accountStatusList.getCryptoCurrencyStatus( + currencyId = currency.id, + network = null, + ) + // Assert + assertSome(result, currencyStatus) + } + + @Test + fun `returns Some when currency id and network match`() { + // Arrange + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val accountStatusList = createAccountStatusList( + currencies = listOf(currency), + currencyStatuses = listOf(currencyStatus), + ) + // Act + val result = accountStatusList.getCryptoCurrencyStatus( + currencyId = currency.id, + network = currency.network, + ) + // Assert + assertSome(result, currencyStatus) + } + + @Test + fun `returns Some with first matching currency status when multiple currencies exist`() { + // Arrange + val currencies = cryptoCurrencyFactory.ethereumAndStellar + val currencyStatuses = currencies.map { + CryptoCurrencyStatus(currency = it, value = CryptoCurrencyStatus.Loading) + } + val accountStatusList = createAccountStatusList( + currencies = currencies, + currencyStatuses = currencyStatuses, + ) + val targetCurrency = currencies.first() + val expectedStatus = currencyStatuses.first() + // Act + val result = accountStatusList.getCryptoCurrencyStatus( + currencyId = targetCurrency.id, + network = targetCurrency.network, + ) + // Assert + assertSome(result, expectedStatus) + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class GetCryptoCurrencyStatusFromCryptoPortfolio { + + @Test + fun `returns None when currency id is not found in CryptoPortfolio`() { + // Arrange + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + val accountStatus = AccountStatus.CryptoPortfolio( + account = account, + tokenList = TokenList.Ungrouped( + totalFiatBalance = TotalFiatBalance.Loading, + sortedBy = TokensSortType.NONE, + currencies = listOf(currencyStatus), + ), + priceChangeLce = lceLoading(), + ) + val otherCurrencyId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId("bitcoin"), + suffix = CryptoCurrency.ID.Suffix.RawID("bitcoin"), + ) + // Act + val result = accountStatus.getCryptoCurrencyStatus(otherCurrencyId) + // Assert + assertNone(result) + } + + @Test + fun `returns Some when currency id is found in CryptoPortfolio`() { + // Arrange + val currencyStatus = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loading, + ) + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = listOf(currency), + ) + val accountStatus = AccountStatus.CryptoPortfolio( + account = account, + tokenList = TokenList.Ungrouped( + totalFiatBalance = TotalFiatBalance.Loading, + sortedBy = TokensSortType.NONE, + currencies = listOf(currencyStatus), + ), + priceChangeLce = lceLoading(), + ) + // Act + val result = accountStatus.getCryptoCurrencyStatus(currency.id) + // Assert + assertSome(result, currencyStatus) + } + + @Test + fun `returns None when CryptoPortfolio has empty token list`() { + // Arrange + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = emptyList(), + ) + val accountStatus = AccountStatus.CryptoPortfolio( + account = account, + tokenList = TokenList.Empty, + priceChangeLce = lceLoading(), + ) + // Act + val result = accountStatus.getCryptoCurrencyStatus(currency.id) + // Assert + assertNone(result) + } + + @Test + fun `returns Some with matching currency status when multiple currencies exist`() { + // Arrange + val currencies = cryptoCurrencyFactory.ethereumAndStellar + val currencyStatuses = currencies.map { + CryptoCurrencyStatus(currency = it, value = CryptoCurrencyStatus.Loading) + } + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = currencies, + ) + val accountStatus = AccountStatus.CryptoPortfolio( + account = account, + tokenList = TokenList.Ungrouped( + totalFiatBalance = TotalFiatBalance.Loading, + sortedBy = TokensSortType.NONE, + currencies = currencyStatuses, + ), + priceChangeLce = lceLoading(), + ) + val targetCurrency = currencies.last() + val expectedStatus = currencyStatuses.last() + // Act + val result = accountStatus.getCryptoCurrencyStatus(targetCurrency.id) + // Assert + assertSome(result, expectedStatus) + } + } + + private fun createAccountStatusList( + currencies: List, + currencyStatuses: List = emptyList(), + ): AccountStatusList { + val account = Account.CryptoPortfolio.createMainAccount( + userWalletId = userWalletId, + cryptoCurrencies = currencies, + ) + val tokenList = if (currencyStatuses.isEmpty()) { + TokenList.Empty + } else { + TokenList.Ungrouped( + totalFiatBalance = TotalFiatBalance.Loading, + sortedBy = TokensSortType.NONE, + currencies = currencyStatuses, + ) + } + val accountStatus = AccountStatus.CryptoPortfolio( + account = account, + tokenList = tokenList, + priceChangeLce = lceLoading(), + ) + return AccountStatusList( + userWalletId = userWalletId, + accountStatuses = listOf(accountStatus), + totalAccounts = 1, + totalArchivedAccounts = 0, + totalFiatBalance = TotalFiatBalance.Loading, + sortType = TokensSortType.NONE, + groupType = TokensGroupType.NONE, + ) + } +} \ No newline at end of file