Updated on 2026-08-14
This commit is contained in:
parent
df076f15aa
commit
295f236805
10 changed files with 747 additions and 317 deletions
|
|
@ -31,4 +31,6 @@ dependencies {
|
|||
|
||||
implementation(tangemDeps.blockchain)
|
||||
implementation(tangemDeps.card.core)
|
||||
|
||||
implementation(deps.test.junit5)
|
||||
}
|
||||
|
|
@ -14,20 +14,25 @@ object MockNetworkStatusFactory {
|
|||
|
||||
private val defaultNetwork = MockCryptoCurrencyFactory().ethereum.network
|
||||
|
||||
fun createVerified(network: Network = defaultNetwork, source: StatusSource = StatusSource.ACTUAL): NetworkStatus {
|
||||
fun createVerified(
|
||||
network: Network = defaultNetwork,
|
||||
source: StatusSource = StatusSource.ACTUAL,
|
||||
transform: (NetworkStatus.Verified) -> NetworkStatus.Verified = { it },
|
||||
): NetworkStatus {
|
||||
return NetworkStatus(
|
||||
network = network,
|
||||
value = NetworkStatus.Verified(
|
||||
address = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "0x123",
|
||||
value = "0x1",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
amounts = mapOf(),
|
||||
pendingTransactions = mapOf(),
|
||||
source = source,
|
||||
),
|
||||
)
|
||||
.let(transform),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +42,7 @@ object MockNetworkStatusFactory {
|
|||
value = NetworkStatus.NoAccount(
|
||||
address = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "0x123",
|
||||
value = "0x1",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class MockUpdateWalletManagerResultFactory {
|
|||
return UpdateWalletManagerResult.NoAccount(
|
||||
selectedAddress = "0x1",
|
||||
addresses = setOf(Address(value = "0x1", type = Address.Type.Primary)),
|
||||
amountToCreateAccount = BigDecimal.ZERO,
|
||||
amountToCreateAccount = BigDecimal.ONE,
|
||||
errorMessage = "",
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.common.test.utils
|
||||
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MethodSource("provideTestModels")
|
||||
annotation class ProvideTestModels
|
||||
|
|
@ -9,6 +9,10 @@ android {
|
|||
namespace = "com.tangem.data.common"
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Core */
|
||||
implementation(projects.core.datasource)
|
||||
|
|
@ -39,7 +43,8 @@ dependencies {
|
|||
/* Test */
|
||||
testImplementation(projects.common.test)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.junit5)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.data.common.currency
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
|
|
@ -21,6 +22,18 @@ interface CardCryptoCurrencyFactory {
|
|||
@Throws
|
||||
suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency>
|
||||
|
||||
/**
|
||||
* Create currencies for multi currency card
|
||||
*
|
||||
* @param userWallet user wallet
|
||||
* @param networks networks
|
||||
*/
|
||||
@Throws
|
||||
suspend fun createCurrenciesForMultiCurrencyCard(
|
||||
userWallet: UserWallet,
|
||||
networks: Set<Network>,
|
||||
): Map<Network, List<CryptoCurrency>>
|
||||
|
||||
/**
|
||||
* Create default coins for multi currency card
|
||||
*
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
val blockchain = Blockchain.fromNetworkId(networkId = network.backendId)
|
||||
|
||||
// multi-currency wallet
|
||||
if (userWallet.isMultiCurrency) return getMultiWalletCurrencies(userWallet = userWallet, network = network)
|
||||
if (userWallet.isMultiCurrency) {
|
||||
return getMultiWalletCurrencies(userWallet = userWallet, networks = setOf(network))[network].orEmpty()
|
||||
}
|
||||
|
||||
// check if the blockchain of single-currency wallet is the same as network
|
||||
val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
|
|
@ -52,7 +54,18 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
return createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
|
||||
}
|
||||
|
||||
override suspend fun createCurrenciesForMultiCurrencyCard(
|
||||
userWallet: UserWallet,
|
||||
networks: Set<Network>,
|
||||
): Map<Network, List<CryptoCurrency>> {
|
||||
require(userWallet.isMultiCurrency) { "It isn't multi-currency wallet" }
|
||||
|
||||
return getMultiWalletCurrencies(userWallet = userWallet, networks = networks)
|
||||
}
|
||||
|
||||
override fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
|
||||
require(scanResponse.cardTypesResolver.isMultiwalletAllowed()) { "It isn't multi-currency wallet" }
|
||||
|
||||
val card = scanResponse.card
|
||||
|
||||
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
|
||||
|
|
@ -75,29 +88,39 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
}
|
||||
|
||||
override fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
|
||||
require(scanResponse.cardTypesResolver.isSingleWallet()) { "It isn't single-currency wallet" }
|
||||
|
||||
return with(getSingleWalletCurrencies(scanResponse)) {
|
||||
primaryToken ?: coin
|
||||
}
|
||||
}
|
||||
|
||||
override fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
require(scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
"It isn't single-currency wallet with token"
|
||||
}
|
||||
|
||||
return with(getSingleWalletCurrencies(scanResponse)) {
|
||||
listOfNotNull(coin, primaryToken)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMultiWalletCurrencies(userWallet: UserWallet, network: Network): List<CryptoCurrency> {
|
||||
private suspend fun getMultiWalletCurrencies(
|
||||
userWallet: UserWallet,
|
||||
networks: Set<Network>,
|
||||
): Map<Network, List<CryptoCurrency>> {
|
||||
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
|
||||
?: return emptyList()
|
||||
?: return emptyMap()
|
||||
|
||||
val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
|
||||
return responseCurrenciesFactory.createCurrencies(
|
||||
tokens = response.tokens.filter {
|
||||
it.networkId == network.backendId && it.derivationPath == network.derivationPath.value
|
||||
tokens = response.tokens.filter { token ->
|
||||
networks.any { it.backendId == token.networkId && it.derivationPath.value == token.derivationPath }
|
||||
},
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
.groupBy(CryptoCurrency::network)
|
||||
}
|
||||
|
||||
private fun getSingleWalletCurrencies(scanResponse: ScanResponse): SingleWalletCurrencies {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.common.card.WalletData
|
||||
import com.tangem.common.test.domain.card.MockScanResponseFactory
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.utils.ProvideTestModels
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.configs.GenericCardConfig
|
||||
|
|
@ -14,16 +16,20 @@ import com.tangem.domain.common.util.cardTypesResolver
|
|||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class DefaultCardCryptoCurrencyFactoryTest {
|
||||
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
|
|
@ -36,325 +42,427 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
userTokensResponseStore = userTokensResponseStore,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
private val ethereum = cryptoCurrencyFactory.ethereum.setCanHandleTokens(value = true)
|
||||
private val bitcoin = cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Bitcoin)
|
||||
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
private val iconUri: Uri = mockk()
|
||||
|
||||
@BeforeEach
|
||||
fun init() {
|
||||
clearMocks(userWalletsStore, userTokensResponseStore, iconUri)
|
||||
|
||||
mockkStatic(Uri::class)
|
||||
every { Uri.parse(any()) } returns mockk()
|
||||
every { Uri.parse(any()) } returns iconUri
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse is not empty`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CreateMultiWallet {
|
||||
|
||||
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
|
||||
currencies = listOf(ethereum),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun `create currencies in ETH for multi-currency wallet`(model: CreateTestModel.MultiWallet) = runTest {
|
||||
// Arrange
|
||||
val userWallet = createMultiWallet()
|
||||
val userTokensResponse = model.userTokensResponse
|
||||
val network = ethereum.network
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = userWallet.walletId) } returns userWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId = userWallet.walletId, network = network)
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = userWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
CreateTestModel.MultiWallet(userTokensResponse = null, expected = emptyList()),
|
||||
CreateTestModel.MultiWallet(userTokensResponse = createUserTokensResponse(), expected = emptyList()),
|
||||
CreateTestModel.MultiWallet(
|
||||
userTokensResponse = createUserTokensResponse(currencies = listOf(ethereum)),
|
||||
expected = listOf(ethereum),
|
||||
),
|
||||
CreateTestModel.MultiWallet(
|
||||
userTokensResponse = createUserTokensResponse(listOf(element = bitcoin)),
|
||||
expected = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = listOf(ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse is empty`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
|
||||
currencies = listOf(),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse is null`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns null
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if userTokensResponse does not contain currency of selected network`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val userTokensResponse = UserTokensResponseFactory().createUserTokensResponse(
|
||||
currencies = listOf(bitcoin),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = multiWallet.walletId) } returns multiWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(multiWallet.walletId) } returns userTokensResponse
|
||||
|
||||
val actual = factory.create(userWalletId = multiWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = multiWallet.walletId)
|
||||
userTokensResponseStore.getSyncOrNull(multiWallet.walletId)
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if single wallet has another primary network`() = runTest {
|
||||
val singleWallet = createSingleWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
|
||||
|
||||
val actual = factory.create(userWalletId = singleWallet.walletId, network = bitcoin.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = singleWallet.walletId)
|
||||
singleWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
}
|
||||
|
||||
val expected = emptyList<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if card is single wallet`() = runTest {
|
||||
val singleWallet = createSingleWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
|
||||
|
||||
val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = singleWallet.walletId)
|
||||
singleWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
}
|
||||
|
||||
val expected = listOf(ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create if card is single wallet with token`() = runTest {
|
||||
val singleWallet = createSingleWalletWithToken()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = singleWallet.walletId) } returns singleWallet
|
||||
|
||||
val actual = factory.create(userWalletId = singleWallet.walletId, network = ethereum.network)
|
||||
|
||||
val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = singleWallet.scanResponse,
|
||||
)
|
||||
val expected = listOf(ethereum, token)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createDefaultCoinsForMultiCurrencyCard if card is prod`() = runTest {
|
||||
val multiWallet = createMultiWallet()
|
||||
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
val expected = listOf(bitcoin, ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createDefaultCoinsForMultiCurrencyCard if card is test`() = runTest {
|
||||
val multiWallet = createMultiWallet().let {
|
||||
it.copy(
|
||||
scanResponse = it.scanResponse.copy(
|
||||
card = it.scanResponse.card.copy(cardId = "FF99", batchId = "99FF"),
|
||||
),
|
||||
private fun createUserTokensResponse(currencies: List<CryptoCurrency> = emptyList()): UserTokensResponse {
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = currencies,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
val expected = listOf(
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.BitcoinTestnet),
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.EthereumTestnet).setCanHandleTokens(true),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createDefaultCoinsForMultiCurrencyCard if card is demo`() = runTest {
|
||||
val multiWallet = createMultiWallet().let {
|
||||
it.copy(
|
||||
scanResponse = it.scanResponse.copy(
|
||||
card = it.scanResponse.card.copy(cardId = "AC01000000041225"),
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CreateSingleWallet {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun `create currencies for single-currency wallet (ETH)`(model: CreateTestModel.SingleWallet) = runTest {
|
||||
// Arrange
|
||||
val userWallet = createSingleWallet()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = userWallet.walletId) } returns userWallet
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId = userWallet.walletId, network = model.network)
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = userWallet.walletId)
|
||||
userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
CreateTestModel.SingleWallet(network = ethereum.network, expected = listOf(ethereum)),
|
||||
CreateTestModel.SingleWallet(network = bitcoin.network, expected = emptyList()),
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CreateSingleWalletWithToken {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun `create currencies for single-currency wallet with token (ETH)`(
|
||||
model: CreateTestModel.SingleWalletWithToken,
|
||||
) = runTest {
|
||||
// Arrange
|
||||
val userWallet = createSingleWalletWithToken()
|
||||
|
||||
coEvery { userWalletsStore.getSyncStrict(key = userWallet.walletId) } returns userWallet
|
||||
|
||||
// Act
|
||||
val actual = factory.create(userWalletId = userWallet.walletId, network = model.network)
|
||||
|
||||
// Assert
|
||||
val primaryToken = if (model.isPrimaryTokenExpected) {
|
||||
createPrimaryToken(blockchain = Blockchain.Ethereum)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val expected = listOfNotNull(*model.expected.toTypedArray(), primaryToken)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = userWallet.walletId)
|
||||
userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
CreateTestModel.SingleWalletWithToken(
|
||||
network = ethereum.network,
|
||||
isPrimaryTokenExpected = true,
|
||||
expected = listOf(ethereum),
|
||||
),
|
||||
CreateTestModel.SingleWalletWithToken(
|
||||
network = bitcoin.network,
|
||||
isPrimaryTokenExpected = false,
|
||||
expected = emptyList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface CreateTestModel {
|
||||
|
||||
val expected: List<CryptoCurrency>
|
||||
|
||||
data class MultiWallet(
|
||||
val userTokensResponse: UserTokensResponse?,
|
||||
override val expected: List<CryptoCurrency>,
|
||||
) : CreateTestModel
|
||||
|
||||
data class SingleWallet(
|
||||
val network: Network,
|
||||
override val expected: List<CryptoCurrency>,
|
||||
) : CreateTestModel
|
||||
|
||||
data class SingleWalletWithToken(
|
||||
val network: Network,
|
||||
val isPrimaryTokenExpected: Boolean,
|
||||
override val expected: List<CryptoCurrency>,
|
||||
) : CreateTestModel
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CreateCurrenciesForMultiCurrencyCard {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun `create currencies in ETH and BTC for multi-currency card`(model: CreateCurrenciesForMultiWalletModel) =
|
||||
runTest {
|
||||
// Arrange
|
||||
val userWallet = model.multiWallet
|
||||
val networks = setOf(ethereum.network, bitcoin.network)
|
||||
val userTokensResponse = model.userTokensResponse
|
||||
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse
|
||||
|
||||
// Act
|
||||
val actual = runCatching {
|
||||
factory.createCurrenciesForMultiCurrencyCard(userWallet = userWallet, networks = networks)
|
||||
}
|
||||
|
||||
// Assert
|
||||
actual
|
||||
.onSuccess {
|
||||
val expected = model.expected.getOrNull()!!
|
||||
|
||||
Truth.assertThat(it).isEqualTo(expected)
|
||||
}
|
||||
.onFailure {
|
||||
val exception = model.expected.exceptionOrNull()!!
|
||||
|
||||
Truth.assertThat(it).isInstanceOf(exception::class.java)
|
||||
Truth.assertThat(it).hasMessageThat().isEqualTo(exception.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(),
|
||||
userTokensResponse = null,
|
||||
expected = Result.success(emptyMap()),
|
||||
),
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(),
|
||||
userTokensResponse = createUserTokensResponse(),
|
||||
expected = Result.success(emptyMap()),
|
||||
),
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(),
|
||||
userTokensResponse = createUserTokensResponse(currencies = listOf(bitcoin, ethereum)),
|
||||
expected = mapOf(
|
||||
bitcoin.network to listOf(bitcoin),
|
||||
ethereum.network to listOf(ethereum),
|
||||
).let(Result.Companion::success),
|
||||
),
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createSingleWallet(),
|
||||
userTokensResponse = null,
|
||||
expected = Result.failure(IllegalArgumentException("It isn't multi-currency wallet")),
|
||||
),
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createSingleWalletWithToken(),
|
||||
userTokensResponse = null,
|
||||
expected = Result.failure(IllegalArgumentException("It isn't multi-currency wallet")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class CreateCurrenciesForMultiWalletModel(
|
||||
val multiWallet: UserWallet,
|
||||
val userTokensResponse: UserTokensResponse?,
|
||||
val expected: Result<Map<Network, List<CryptoCurrency>>>,
|
||||
)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CreateDefaultCoinsForMultiCurrencyCard {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun `create default coins for multi currency card`(model: CreateDefaultForMultiWalletModel) = runTest {
|
||||
// Arrange
|
||||
val multiWallet = model.multiWallet
|
||||
|
||||
// Act
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
// PROD card
|
||||
CreateDefaultForMultiWalletModel(multiWallet = createMultiWallet(), expected = listOf(bitcoin, ethereum)),
|
||||
// TEST card
|
||||
CreateDefaultForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(cardId = "FF99", batchId = "99FF"),
|
||||
expected = listOf(
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.BitcoinTestnet),
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.EthereumTestnet).setCanHandleTokens(true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
|
||||
val expected = listOf(
|
||||
bitcoin,
|
||||
ethereum,
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Dogecoin),
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Solana),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createPrimaryCurrencyForSingleCurrencyCard if unable to create token`() = runTest {
|
||||
val singleWallet = UserWallet(
|
||||
name = "Note",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
|
||||
val actual = runCatching {
|
||||
factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
}
|
||||
|
||||
val exception = IllegalArgumentException("Coin for the single currency card cannot be null")
|
||||
|
||||
Truth.assertThat(actual.isFailure).isTrue()
|
||||
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java)
|
||||
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is null`() = runTest {
|
||||
val singleWallet = createSingleWallet()
|
||||
|
||||
val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
|
||||
val expected = ethereum
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createPrimaryCurrencyForSingleCurrencyCard if primaryToken is not null`() = runTest {
|
||||
val singleWallet = createSingleWalletWithToken()
|
||||
|
||||
val actual = factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
|
||||
val expected = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = singleWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = singleWallet.scanResponse,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createCurrenciesForSingleCurrencyCardWithToken if unable to create token`() = runTest {
|
||||
val singleWalletWithToken = UserWallet(
|
||||
name = "Note",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
// // DEMO card
|
||||
CreateDefaultForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(cardId = "AC01000000041225"),
|
||||
expected = listOf(
|
||||
bitcoin,
|
||||
ethereum,
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Dogecoin),
|
||||
cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Solana),
|
||||
),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
|
||||
val actual = runCatching {
|
||||
factory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = singleWalletWithToken.scanResponse)
|
||||
}
|
||||
|
||||
val exception = IllegalArgumentException("Coin for the single currency card cannot be null")
|
||||
|
||||
Truth.assertThat(actual.isFailure).isTrue()
|
||||
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java)
|
||||
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is null`() = runTest {
|
||||
val singleWalletWithToken = createSingleWallet()
|
||||
data class CreateDefaultForMultiWalletModel(val multiWallet: UserWallet, val expected: List<CryptoCurrency>)
|
||||
|
||||
val actual = factory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = singleWalletWithToken.scanResponse,
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CreatePrimaryCurrencyForSingleCurrencyCard {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun `create primary currency for single currency card`(model: CreatePrimaryCurrencyForSingleWalletModel) =
|
||||
runTest {
|
||||
// Arrange
|
||||
val singleWallet = model.singleWallet
|
||||
|
||||
// Act
|
||||
val actual = runCatching {
|
||||
factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
}
|
||||
|
||||
// Assert
|
||||
actual
|
||||
.onSuccess {
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
.onFailure {
|
||||
val exception = model.expected.exceptionOrNull()!!
|
||||
|
||||
Truth.assertThat(actual.exceptionOrNull()).isInstanceOf(exception::class.java)
|
||||
Truth.assertThat(actual.exceptionOrNull()).hasMessageThat().isEqualTo(exception.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
CreatePrimaryCurrencyForSingleWalletModel(
|
||||
singleWallet = createSingleWallet(),
|
||||
expected = Result.success(ethereum),
|
||||
),
|
||||
CreatePrimaryCurrencyForSingleWalletModel(
|
||||
singleWallet = createSingleWallet(batchId = ""),
|
||||
expected = Result.failure(IllegalArgumentException("Coin for the single currency card cannot be null")),
|
||||
),
|
||||
CreatePrimaryCurrencyForSingleWalletModel(
|
||||
singleWallet = createSingleWallet(addWalletData = true),
|
||||
expected = Result.failure(IllegalArgumentException("It isn't single-currency wallet")),
|
||||
),
|
||||
)
|
||||
|
||||
val expected = listOf(ethereum)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test createCurrenciesForSingleCurrencyCardWithToken if primaryToken is not null`() = runTest {
|
||||
val singleWalletWithToken = createSingleWalletWithToken()
|
||||
data class CreatePrimaryCurrencyForSingleWalletModel(
|
||||
val singleWallet: UserWallet,
|
||||
val expected: Result<CryptoCurrency>,
|
||||
)
|
||||
|
||||
val actual = factory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = singleWalletWithToken.scanResponse,
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CreateCurrenciesForSingleCurrencyCardWithToken {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun `create currencies for single currency card with token`(model: CreateForSingleWalletWithTokenModel) =
|
||||
runTest {
|
||||
// Arrange
|
||||
val userWallet = model.singleWalletWithToken
|
||||
|
||||
// Act
|
||||
val actual = runCatching {
|
||||
factory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = userWallet.scanResponse)
|
||||
}
|
||||
|
||||
// Assert
|
||||
actual
|
||||
.onSuccess {
|
||||
val primaryToken = if (model.isPrimaryTokenExpected) {
|
||||
createPrimaryToken(blockchain = Blockchain.Ethereum)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val expected = listOfNotNull(*model.expected.getOrNull()!!.toTypedArray(), primaryToken)
|
||||
|
||||
Truth.assertThat(it).isEqualTo(expected)
|
||||
}
|
||||
.onFailure {
|
||||
val exception = model.expected.exceptionOrNull()!!
|
||||
|
||||
Truth.assertThat(it).isInstanceOf(exception::class.java)
|
||||
Truth.assertThat(it).hasMessageThat().isEqualTo(exception.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
CreateForSingleWalletWithTokenModel(
|
||||
singleWalletWithToken = UserWallet(
|
||||
name = "NODL",
|
||||
walletId = UserWalletId("011"),
|
||||
cardsInWallet = setOf(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
).copy(
|
||||
productType = ProductType.Note,
|
||||
walletData = WalletData(
|
||||
blockchain = "",
|
||||
token = WalletData.Token(
|
||||
name = "Ethereum",
|
||||
symbol = "ETH",
|
||||
contractAddress = "0x",
|
||||
decimals = 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
hasBackupError = false,
|
||||
),
|
||||
expected = Result.failure(IllegalArgumentException("Coin for the single currency card cannot be null")),
|
||||
),
|
||||
CreateForSingleWalletWithTokenModel(
|
||||
singleWalletWithToken = createSingleWalletWithToken(),
|
||||
isPrimaryTokenExpected = true,
|
||||
expected = Result.success(listOf(ethereum)),
|
||||
),
|
||||
)
|
||||
|
||||
val token = CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = singleWalletWithToken.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = singleWalletWithToken.scanResponse,
|
||||
)
|
||||
|
||||
val expected = listOf(ethereum, token)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun createMultiWallet(): UserWallet {
|
||||
data class CreateForSingleWalletWithTokenModel(
|
||||
val singleWalletWithToken: UserWallet,
|
||||
val isPrimaryTokenExpected: Boolean = false,
|
||||
val expected: Result<List<CryptoCurrency>>,
|
||||
)
|
||||
|
||||
private fun createMultiWallet(cardId: String? = null, batchId: String? = null): UserWallet {
|
||||
return UserWallet(
|
||||
name = "Wallet 1",
|
||||
walletId = UserWalletId("011"),
|
||||
|
|
@ -363,12 +471,19 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
),
|
||||
).let {
|
||||
it.copy(
|
||||
card = it.card.copy(
|
||||
cardId = cardId ?: it.card.cardId,
|
||||
batchId = batchId ?: it.card.batchId,
|
||||
),
|
||||
)
|
||||
},
|
||||
hasBackupError = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSingleWallet(): UserWallet {
|
||||
private fun createSingleWallet(batchId: String? = null, addWalletData: Boolean = false): UserWallet {
|
||||
return UserWallet(
|
||||
name = "Note",
|
||||
walletId = UserWalletId("011"),
|
||||
|
|
@ -379,8 +494,21 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
derivedKeys = emptyMap(),
|
||||
).let {
|
||||
it.copy(
|
||||
card = it.card.copy(batchId = "AB10"),
|
||||
card = it.card.copy(batchId = batchId ?: "AB10"),
|
||||
productType = ProductType.Note,
|
||||
walletData = if (addWalletData) {
|
||||
WalletData(
|
||||
blockchain = "ETH",
|
||||
token = WalletData.Token(
|
||||
name = "Ethereum",
|
||||
symbol = "ETH",
|
||||
contractAddress = "0x",
|
||||
decimals = 8,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
it.walletData
|
||||
},
|
||||
)
|
||||
},
|
||||
hasBackupError = false,
|
||||
|
|
@ -412,14 +540,27 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createPrimaryToken(blockchain: Blockchain): CryptoCurrency.Token {
|
||||
val userWallet = createSingleWalletWithToken()
|
||||
|
||||
return CryptoCurrencyFactory(excludedBlockchains = ExcludedBlockchains()).createToken(
|
||||
sdkToken = userWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)!!
|
||||
}
|
||||
|
||||
private fun createUserTokensResponse(currencies: List<CryptoCurrency> = emptyList()): UserTokensResponse {
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = currencies,
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
|
||||
val ethereum = cryptoCurrencyFactory.ethereum.setCanHandleTokens(value = true)
|
||||
|
||||
val bitcoin = cryptoCurrencyFactory.createCoin(blockchain = Blockchain.Bitcoin)
|
||||
|
||||
fun CryptoCurrency.setCanHandleTokens(value: Boolean): CryptoCurrency {
|
||||
return when (this) {
|
||||
is CryptoCurrency.Coin -> copy(network = network.copy(canHandleTokens = value))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.data.networks.fetcher
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.data.networks.store.NetworksStatusesStoreV2
|
||||
import com.tangem.data.networks.store.setSourceAsOnlyCache
|
||||
import com.tangem.data.networks.store.storeStatus
|
||||
import com.tangem.data.networks.utils.NetworkStatusFactory
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Common implementation of network status fetcher
|
||||
*
|
||||
* @property walletManagersFacade wallet managers facade
|
||||
* @property networksStatusesStore networks statuses store
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class CommonNetworkStatusFetcher @Inject constructor(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val networksStatusesStore: NetworksStatusesStoreV2,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Fetch
|
||||
*
|
||||
* @param userWalletId user wallet id
|
||||
* @param network network
|
||||
* @param networkCurrencies network currencies
|
||||
*/
|
||||
suspend fun fetch(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
networkCurrencies: Set<CryptoCurrency>,
|
||||
): Either<Throwable, Unit> {
|
||||
return Either.catchOn(dispatchers.default) {
|
||||
val result = withContext(dispatchers.io) {
|
||||
walletManagersFacade.update(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
extraTokens = networkCurrencies
|
||||
.filterIsInstance<CryptoCurrency.Token>()
|
||||
.toSet(),
|
||||
)
|
||||
}
|
||||
|
||||
val status = NetworkStatusFactory.create(
|
||||
network = network,
|
||||
updatingResult = result,
|
||||
addedCurrencies = networkCurrencies,
|
||||
)
|
||||
|
||||
networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status)
|
||||
}
|
||||
.onLeft {
|
||||
Timber.e("Failed to fetch network status for $userWalletId [${network.id.value}]: $it")
|
||||
networksStatusesStore.setSourceAsOnlyCache(userWalletId = userWalletId, network = network)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
package com.tangem.data.networks.fetcher
|
||||
|
||||
import arrow.core.Either
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.test.domain.network.MockNetworkStatusFactory
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.domain.walletmanager.MockUpdateWalletManagerResultFactory
|
||||
import com.tangem.common.test.utils.ProvideTestModels
|
||||
import com.tangem.data.networks.store.NetworksStatusesStoreV2
|
||||
import com.tangem.data.networks.store.setSourceAsOnlyCache
|
||||
import com.tangem.data.networks.store.storeStatus
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class CommonNetworkStatusFetcherTest {
|
||||
|
||||
private val walletManagersFacade: WalletManagersFacade = mockk()
|
||||
private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxUnitFun = true)
|
||||
|
||||
private val fetcher = CommonNetworkStatusFetcher(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(walletManagersFacade, networksStatusesStore)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch failure if walletManagersFacade throws exception`() = runTest {
|
||||
// Arrange
|
||||
val userWalletId = UserWalletId("011")
|
||||
val network = cryptoCurrencyFactory.ethereum.network
|
||||
val extraTokens = setOf(
|
||||
cryptoCurrencyFactory.createToken(Blockchain.Ethereum) as CryptoCurrency.Token,
|
||||
)
|
||||
val updateException = IllegalStateException()
|
||||
|
||||
coEvery { walletManagersFacade.update(userWalletId, network, extraTokens) } throws updateException
|
||||
|
||||
// Act
|
||||
val actual = fetcher.fetch(userWalletId = userWalletId, network = network, networkCurrencies = extraTokens)
|
||||
|
||||
// Assert
|
||||
val expected = Either.Left(updateException)
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
Truth.assertThat(actual.leftOrNull()).isInstanceOf(expected.leftOrNull()!!::class.java)
|
||||
Truth.assertThat(actual.leftOrNull()).hasMessageThat().isEqualTo(expected.leftOrNull()!!.message)
|
||||
|
||||
coVerifyOrder {
|
||||
walletManagersFacade.update(userWalletId, network, extraTokens)
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun `fetch successfully for any result of walletManagersFacade`(model: SuccessTestModel) = runTest {
|
||||
// Arrange
|
||||
val userWalletId = UserWalletId("011")
|
||||
val network = cryptoCurrencyFactory.ethereum.network
|
||||
val extraTokens = setOf(
|
||||
cryptoCurrencyFactory.createToken(Blockchain.Ethereum) as CryptoCurrency.Token,
|
||||
)
|
||||
val updateResult = model.updateResult
|
||||
val status = model.status
|
||||
|
||||
coEvery { walletManagersFacade.update(userWalletId, network, extraTokens) } returns updateResult
|
||||
coEvery { networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status) } returns Unit
|
||||
|
||||
// Act
|
||||
val actual = fetcher.fetch(userWalletId = userWalletId, network = network, networkCurrencies = extraTokens)
|
||||
|
||||
// Assert
|
||||
val expected = Either.Right(Unit)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
walletManagersFacade.update(userWalletId, network, extraTokens)
|
||||
networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), network = any())
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
SuccessTestModel(
|
||||
updateResult = UpdateWalletManagerResult.MissedDerivation,
|
||||
status = MockNetworkStatusFactory.createMissedDerivation(),
|
||||
),
|
||||
SuccessTestModel(
|
||||
updateResult = MockUpdateWalletManagerResultFactory().createUnreachable(),
|
||||
status = NetworkStatus(
|
||||
network = cryptoCurrencyFactory.ethereum.network,
|
||||
value = NetworkStatus.Unreachable(address = null),
|
||||
),
|
||||
),
|
||||
SuccessTestModel(
|
||||
updateResult = MockUpdateWalletManagerResultFactory().createUnreachableWithAddress(),
|
||||
status = NetworkStatus(
|
||||
network = cryptoCurrencyFactory.ethereum.network,
|
||||
value = NetworkStatus.Unreachable(
|
||||
address = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "0x1",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SuccessTestModel(
|
||||
updateResult = MockUpdateWalletManagerResultFactory().createNoAccount(),
|
||||
status = MockNetworkStatusFactory.createNoAccount(),
|
||||
),
|
||||
SuccessTestModel(
|
||||
updateResult = MockUpdateWalletManagerResultFactory().createVerified(),
|
||||
status = MockNetworkStatusFactory.createVerified(cryptoCurrencyFactory.ethereum.network) {
|
||||
it.copy(
|
||||
amounts = mapOf(
|
||||
CryptoCurrency.ID.fromValue(
|
||||
value = "token⟨ETH⟩NEVER-MIND⚓NEVER-MIND",
|
||||
) to CryptoCurrencyAmountStatus.NotFound,
|
||||
),
|
||||
pendingTransactions = mapOf(
|
||||
CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩NEVER-MIND⚓NEVER-MIND") to emptySet(),
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
data class SuccessTestModel(
|
||||
val updateResult: UpdateWalletManagerResult,
|
||||
val status: NetworkStatus,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue