Updated on 2026-08-14
This commit is contained in:
parent
4e69a47c32
commit
c470e8ef27
8 changed files with 429 additions and 98 deletions
|
|
@ -3,7 +3,7 @@ package com.tangem.data.networks.single
|
|||
import arrow.core.Either
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.store.NetworksStatusesStoreV2
|
||||
import com.tangem.data.tokens.utils.NetworkStatusFactory
|
||||
import com.tangem.data.networks.utils.NetworkStatusFactory
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -31,8 +31,6 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : SingleNetworkStatusFetcher {
|
||||
|
||||
private val networkStatusFactory = NetworkStatusFactory()
|
||||
|
||||
override suspend fun invoke(params: SingleNetworkStatusFetcher.Params) = Either.catchOn(dispatchers.default) {
|
||||
val networkCurrencies = when (params) {
|
||||
is SingleNetworkStatusFetcher.Params.Prepared -> params.addedNetworkCurrencies
|
||||
|
|
@ -56,10 +54,10 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
val status = networkStatusFactory.createNetworkStatus(
|
||||
val status = NetworkStatusFactory.create(
|
||||
network = params.network,
|
||||
result = result,
|
||||
currencies = networkCurrencies.toSet(),
|
||||
updatingResult = result,
|
||||
addedCurrencies = networkCurrencies.toSet(),
|
||||
)
|
||||
|
||||
val statusValue = status.value
|
||||
|
|
|
|||
|
|
@ -0,0 +1,171 @@
|
|||
package com.tangem.data.networks.utils
|
||||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.walletmanager.model.Address
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction
|
||||
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
|
||||
import timber.log.Timber
|
||||
|
||||
/** Factory for creating [NetworkStatus] */
|
||||
object NetworkStatusFactory {
|
||||
|
||||
/**
|
||||
* Create [NetworkStatus]
|
||||
*
|
||||
* @param network network
|
||||
* @param updatingResult result of updating wallet manager
|
||||
* @param addedCurrencies added currencies
|
||||
*/
|
||||
fun create(
|
||||
network: Network,
|
||||
updatingResult: UpdateWalletManagerResult,
|
||||
addedCurrencies: Set<CryptoCurrency>,
|
||||
): NetworkStatus {
|
||||
return NetworkStatus(
|
||||
network = network,
|
||||
value = when (updatingResult) {
|
||||
is UpdateWalletManagerResult.MissedDerivation -> NetworkStatus.MissedDerivation
|
||||
is UpdateWalletManagerResult.Unreachable -> createUnreachableStatus(result = updatingResult)
|
||||
is UpdateWalletManagerResult.NoAccount -> createNoAccount(result = updatingResult)
|
||||
is UpdateWalletManagerResult.Verified -> {
|
||||
createVerifiedStatus(result = updatingResult, addedCurrencies = addedCurrencies)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun createUnreachableStatus(result: UpdateWalletManagerResult.Unreachable): NetworkStatus.Unreachable {
|
||||
return NetworkStatus.Unreachable(
|
||||
address = getNetworkAddressOrNull(
|
||||
selectedAddress = result.selectedAddress,
|
||||
availableAddresses = result.addresses,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNoAccount(result: UpdateWalletManagerResult.NoAccount): NetworkStatus.NoAccount {
|
||||
return NetworkStatus.NoAccount(
|
||||
address = getNetworkAddress(
|
||||
selectedAddress = result.selectedAddress,
|
||||
availableAddresses = result.addresses,
|
||||
),
|
||||
amountToCreateAccount = result.amountToCreateAccount,
|
||||
errorMessage = result.errorMessage,
|
||||
source = StatusSource.ACTUAL,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createVerifiedStatus(
|
||||
result: UpdateWalletManagerResult.Verified,
|
||||
addedCurrencies: Set<CryptoCurrency>,
|
||||
): NetworkStatus.Verified {
|
||||
return NetworkStatus.Verified(
|
||||
address = getNetworkAddress(
|
||||
selectedAddress = result.selectedAddress,
|
||||
availableAddresses = result.addresses,
|
||||
),
|
||||
amounts = formatAmounts(amounts = result.currenciesAmounts, currencies = addedCurrencies),
|
||||
pendingTransactions = formatTransactions(
|
||||
transactions = result.currentTransactions,
|
||||
currencies = addedCurrencies,
|
||||
),
|
||||
source = StatusSource.ACTUAL,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNetworkAddressOrNull(selectedAddress: String?, availableAddresses: Set<Address>?): NetworkAddress? {
|
||||
if (selectedAddress.isNullOrBlank() || availableAddresses == null) return null
|
||||
|
||||
return getNetworkAddress(selectedAddress, availableAddresses)
|
||||
}
|
||||
|
||||
private fun formatAmounts(
|
||||
amounts: Set<CryptoCurrencyAmount>,
|
||||
currencies: Set<CryptoCurrency>,
|
||||
): Map<CryptoCurrency.ID, CryptoCurrencyAmountStatus> {
|
||||
return currencies.associate { currency ->
|
||||
val amount = when (currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
amounts.singleOrNull { it is CryptoCurrencyAmount.Coin }
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
amounts.firstOrNull { amount ->
|
||||
amount is CryptoCurrencyAmount.Token &&
|
||||
currency.id.rawCurrencyId == amount.tokenId &&
|
||||
currency.contractAddress.equals(amount.tokenContractAddress, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (amount == null) {
|
||||
Timber.w("Unable to find amount for cryptocurrency: $currency")
|
||||
currency.id to CryptoCurrencyAmountStatus.NotFound
|
||||
} else {
|
||||
currency.id to CryptoCurrencyAmountStatus.Loaded(amount.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatTransactions(
|
||||
transactions: Set<CryptoCurrencyTransaction>,
|
||||
currencies: Set<CryptoCurrency>,
|
||||
): Map<CryptoCurrency.ID, Set<TxHistoryItem>> {
|
||||
if (transactions.isEmpty()) return emptyMap()
|
||||
|
||||
return currencies
|
||||
.asSequence()
|
||||
.map { currency ->
|
||||
val currencyTransactions = when (currency) {
|
||||
is CryptoCurrency.Coin -> transactions.filterTo(hashSetOf()) { transaction ->
|
||||
transaction is CryptoCurrencyTransaction.Coin
|
||||
}
|
||||
is CryptoCurrency.Token -> transactions.filterTo(hashSetOf()) { transaction ->
|
||||
transaction is CryptoCurrencyTransaction.Token &&
|
||||
transaction.tokenContractAddress.equals(currency.contractAddress, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
currency.id to createCurrentTransactions(currencyTransactions)
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
private fun createCurrentTransactions(transactions: Set<CryptoCurrencyTransaction>): Set<TxHistoryItem> {
|
||||
return transactions.mapTo(hashSetOf()) { it.txHistoryItem }
|
||||
}
|
||||
|
||||
private fun getNetworkAddress(selectedAddress: String, availableAddresses: Set<Address>): NetworkAddress {
|
||||
val defaultAddress = availableAddresses
|
||||
.firstOrNull { it.value == selectedAddress }
|
||||
?.let(::mapToDomainAddress)
|
||||
|
||||
require(defaultAddress != null && defaultAddress.value.isNotBlank()) {
|
||||
"Selected address must not be null"
|
||||
}
|
||||
|
||||
return if (availableAddresses.size != 1) {
|
||||
NetworkAddress.Selectable(
|
||||
defaultAddress = defaultAddress,
|
||||
availableAddresses = availableAddresses.mapTo(hashSetOf(), ::mapToDomainAddress),
|
||||
)
|
||||
} else {
|
||||
NetworkAddress.Single(defaultAddress)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapToDomainAddress(address: Address): NetworkAddress.Address {
|
||||
val type = when (address.type) {
|
||||
Address.Type.Primary -> NetworkAddress.Address.Type.Primary
|
||||
Address.Type.Secondary -> NetworkAddress.Address.Type.Secondary
|
||||
}
|
||||
|
||||
if (address.value.isBlank()) {
|
||||
Timber.w("Address value is blank")
|
||||
}
|
||||
|
||||
return NetworkAddress.Address(address.value, type)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
package com.tangem.data.networks.utils
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.walletmanager.model.Address
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
|
||||
import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction
|
||||
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@RunWith(Parameterized::class)
|
||||
internal class NetworkStatusFactoryTest(private val model: Model) {
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
val actual = runCatching {
|
||||
NetworkStatusFactory.create(
|
||||
network = model.network,
|
||||
updatingResult = model.result,
|
||||
addedCurrencies = model.currencies,
|
||||
)
|
||||
}
|
||||
|
||||
actual
|
||||
.onSuccess {
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
.onFailure {
|
||||
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(it::class.java)
|
||||
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(it.message)
|
||||
}
|
||||
}
|
||||
|
||||
data class Model(
|
||||
val network: Network,
|
||||
val result: UpdateWalletManagerResult,
|
||||
val currencies: Set<CryptoCurrency>,
|
||||
val expected: Result<NetworkStatus>,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
|
||||
val selectedAddressThrowable = IllegalArgumentException("Selected address must not be null")
|
||||
|
||||
val currencies = with(MockCryptoCurrencyFactory()) { setOf(ethereum, createToken(Blockchain.Ethereum)) }
|
||||
|
||||
val txHistoryItem = TxHistoryItem(
|
||||
txHash = "erroribus",
|
||||
timestampInMillis = 2771,
|
||||
isOutgoing = false,
|
||||
destinationType = TxHistoryItem.DestinationType.Single(
|
||||
addressType = TxHistoryItem.AddressType.User("0x1"),
|
||||
),
|
||||
sourceType = TxHistoryItem.SourceType.Single("0x2"),
|
||||
interactionAddressType = null,
|
||||
status = TxHistoryItem.TransactionStatus.Confirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
amount = BigDecimal.ONE,
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun data(): Collection<Model> = listOf(
|
||||
// region MissedDerivation
|
||||
createSuccess(
|
||||
result = UpdateWalletManagerResult.MissedDerivation,
|
||||
status = NetworkStatus.MissedDerivation,
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region Unreachable
|
||||
createSuccess(
|
||||
result = UpdateWalletManagerResult.Unreachable(selectedAddress = null, addresses = null),
|
||||
status = NetworkStatus.Unreachable(address = null),
|
||||
),
|
||||
createSuccess(
|
||||
result = UpdateWalletManagerResult.Unreachable(
|
||||
selectedAddress = "",
|
||||
addresses = emptySet(),
|
||||
),
|
||||
status = NetworkStatus.Unreachable(address = null),
|
||||
),
|
||||
createSuccess(
|
||||
result = UpdateWalletManagerResult.Unreachable(
|
||||
selectedAddress = "",
|
||||
addresses = setOf(Address(value = "", type = Address.Type.Primary)),
|
||||
),
|
||||
status = NetworkStatus.Unreachable(address = null),
|
||||
),
|
||||
createSuccess(
|
||||
result = UpdateWalletManagerResult.Unreachable(
|
||||
selectedAddress = "0x1",
|
||||
addresses = setOf(Address(value = "0x1", type = Address.Type.Primary)),
|
||||
),
|
||||
status = NetworkStatus.Unreachable(
|
||||
address = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "0x1",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
createFailure(
|
||||
result = UpdateWalletManagerResult.Unreachable(
|
||||
selectedAddress = "0x1",
|
||||
addresses = emptySet(),
|
||||
),
|
||||
throwable = selectedAddressThrowable,
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region NoAccount
|
||||
createFailure(
|
||||
result = UpdateWalletManagerResult.NoAccount(
|
||||
selectedAddress = "",
|
||||
addresses = emptySet(),
|
||||
amountToCreateAccount = BigDecimal.ZERO,
|
||||
errorMessage = "",
|
||||
),
|
||||
throwable = selectedAddressThrowable,
|
||||
),
|
||||
createFailure(
|
||||
result = UpdateWalletManagerResult.NoAccount(
|
||||
selectedAddress = "",
|
||||
addresses = setOf(Address(value = "", type = Address.Type.Primary)),
|
||||
amountToCreateAccount = BigDecimal.ZERO,
|
||||
errorMessage = "",
|
||||
),
|
||||
throwable = selectedAddressThrowable,
|
||||
),
|
||||
createFailure(
|
||||
result = UpdateWalletManagerResult.NoAccount(
|
||||
selectedAddress = "0x1",
|
||||
addresses = setOf(Address(value = "0x2", type = Address.Type.Primary)),
|
||||
amountToCreateAccount = BigDecimal.ZERO,
|
||||
errorMessage = "",
|
||||
),
|
||||
throwable = selectedAddressThrowable,
|
||||
),
|
||||
createSuccess(
|
||||
result = UpdateWalletManagerResult.NoAccount(
|
||||
selectedAddress = "0x1",
|
||||
addresses = setOf(Address(value = "0x1", type = Address.Type.Primary)),
|
||||
amountToCreateAccount = BigDecimal.ZERO,
|
||||
errorMessage = "",
|
||||
),
|
||||
status = NetworkStatus.NoAccount(
|
||||
address = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "0x1",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
amountToCreateAccount = BigDecimal.ZERO,
|
||||
errorMessage = "",
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
),
|
||||
// endregion
|
||||
|
||||
// region Verified
|
||||
createFailure(
|
||||
result = UpdateWalletManagerResult.Verified(
|
||||
selectedAddress = "",
|
||||
addresses = emptySet(),
|
||||
currenciesAmounts = emptySet(),
|
||||
currentTransactions = emptySet(),
|
||||
),
|
||||
throwable = selectedAddressThrowable,
|
||||
),
|
||||
createFailure(
|
||||
result = UpdateWalletManagerResult.Verified(
|
||||
selectedAddress = "",
|
||||
addresses = setOf(Address(value = "", type = Address.Type.Primary)),
|
||||
currenciesAmounts = emptySet(),
|
||||
currentTransactions = emptySet(),
|
||||
),
|
||||
throwable = selectedAddressThrowable,
|
||||
),
|
||||
createFailure(
|
||||
result = UpdateWalletManagerResult.Verified(
|
||||
selectedAddress = "0x1",
|
||||
addresses = setOf(Address(value = "0x2", type = Address.Type.Primary)),
|
||||
currenciesAmounts = emptySet(),
|
||||
currentTransactions = emptySet(),
|
||||
),
|
||||
throwable = selectedAddressThrowable,
|
||||
),
|
||||
createFailure(
|
||||
result = UpdateWalletManagerResult.Verified(
|
||||
selectedAddress = "0x1",
|
||||
addresses = setOf(Address(value = "0x2", type = Address.Type.Primary)),
|
||||
currenciesAmounts = emptySet(),
|
||||
currentTransactions = emptySet(),
|
||||
),
|
||||
throwable = selectedAddressThrowable,
|
||||
),
|
||||
createSuccess(
|
||||
result = UpdateWalletManagerResult.Verified(
|
||||
selectedAddress = "0x1",
|
||||
addresses = setOf(Address(value = "0x1", type = Address.Type.Primary)),
|
||||
currenciesAmounts = setOf(
|
||||
CryptoCurrencyAmount.Coin(value = BigDecimal.ONE),
|
||||
),
|
||||
currentTransactions = setOf(
|
||||
CryptoCurrencyTransaction.Coin(txHistoryItem),
|
||||
),
|
||||
),
|
||||
currencies = currencies,
|
||||
status = NetworkStatus.Verified(
|
||||
address = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "0x1",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
amounts = mapOf(
|
||||
currencies.first().id to CryptoCurrencyAmountStatus.Loaded(BigDecimal.ONE),
|
||||
currencies.last().id to CryptoCurrencyAmountStatus.NotFound,
|
||||
),
|
||||
pendingTransactions = mapOf(
|
||||
currencies.first().id to setOf(txHistoryItem),
|
||||
currencies.last().id to setOf(),
|
||||
),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
),
|
||||
// endregion
|
||||
)
|
||||
|
||||
fun createSuccess(
|
||||
result: UpdateWalletManagerResult,
|
||||
currencies: Set<CryptoCurrency> = emptySet(),
|
||||
status: NetworkStatus.Value,
|
||||
): Model {
|
||||
val network = MockCryptoCurrencyFactory().ethereum.network
|
||||
|
||||
return Model(
|
||||
network = network,
|
||||
result = result,
|
||||
currencies = currencies,
|
||||
expected = Result.success(
|
||||
value = NetworkStatus(network = network, value = status),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createFailure(
|
||||
result: UpdateWalletManagerResult,
|
||||
currencies: Set<CryptoCurrency> = emptySet(),
|
||||
throwable: Throwable,
|
||||
): Model {
|
||||
val network = MockCryptoCurrencyFactory().ethereum.network
|
||||
|
||||
return Model(
|
||||
network = network,
|
||||
result = result,
|
||||
currencies = currencies,
|
||||
expected = Result.failure(throwable),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue