Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-14 15:33:20 +04:00
parent 59117bf97c
commit 1b33e6b02e
5 changed files with 389 additions and 1 deletions

View file

@ -9,6 +9,10 @@ android {
namespace = "com.tangem.data.networks"
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies {
// region Project - Core
implementation(projects.core.datasource)
@ -52,7 +56,8 @@ dependencies {
// region Tests
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)
testImplementation(projects.common.test)

View file

@ -0,0 +1,88 @@
package com.tangem.data.networks.repository
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.networks.store.NetworksStatusesStoreV2
import com.tangem.data.networks.store.storeStatus
import com.tangem.data.networks.utils.NetworkStatusFactory
import com.tangem.domain.networks.repository.NetworksRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
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
/**
* Default implementation of [NetworksRepository]
*
* @property cardCryptoCurrencyFactory card crypto currency factory
* @property walletManagersFacade wallet managers facade
* @property networksStatusesStore networks statuses store
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
internal class DefaultNetworksRepository(
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val walletManagersFacade: WalletManagersFacade,
private val networksStatusesStore: NetworksStatusesStoreV2,
private val dispatchers: CoroutineDispatcherProvider,
) : NetworksRepository {
override suspend fun fetchPendingTransactions(userWalletId: UserWalletId, network: Network) {
withContext(dispatchers.default) {
val currencies = runCatching {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
}
.getOrElse {
Timber.e(it, "Unable to create wallet currencies")
return@withContext
}
fetchPendingTransactions(userWalletId = userWalletId, network = network, currencies = currencies)
}
}
override suspend fun getNetworkAddresses(
userWalletId: UserWalletId,
network: Network,
): List<CryptoCurrencyAddress> {
return runCatching { cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network) }
.getOrElse {
Timber.e(it, "Unable to create wallet currencies")
return emptyList()
}
.map { currency ->
CryptoCurrencyAddress(
cryptoCurrency = currency,
address = getDefaultAddress(userWalletId, network),
)
}
}
private suspend fun fetchPendingTransactions(
userWalletId: UserWalletId,
network: Network,
currencies: List<CryptoCurrency>,
) {
val result = withContext(dispatchers.io) {
walletManagersFacade.updatePendingTransactions(userWalletId = userWalletId, network = network)
}
val networkStatus = NetworkStatusFactory.create(
network = network,
updatingResult = result,
addedCurrencies = currencies.toSet(),
)
networksStatusesStore.storeStatus(userWalletId = userWalletId, status = networkStatus)
}
private suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String {
return withContext(dispatchers.io) {
walletManagersFacade.getDefaultAddress(userWalletId = userWalletId, network = network).orEmpty()
}
}
}

View file

@ -0,0 +1,268 @@
package com.tangem.data.networks.repository
import com.google.common.truth.Truth
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.common.test.domain.walletmanager.MockUpdateWalletManagerResultFactory
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.networks.store.NetworksStatusesStoreV2
import com.tangem.data.networks.store.storeStatus
import com.tangem.data.networks.utils.NetworkStatusFactory
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
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.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
/**
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultNetworksRepositoryTest {
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
private val walletManagersFacade: WalletManagersFacade = mockk()
private val networksStatusesStore: NetworksStatusesStoreV2 = mockk(relaxUnitFun = true)
private val repository: DefaultNetworksRepository = DefaultNetworksRepository(
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
walletManagersFacade = walletManagersFacade,
networksStatusesStore = networksStatusesStore,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@BeforeEach
fun resetMocks() {
clearMocks(cardCryptoCurrencyFactory, walletManagersFacade, networksStatusesStore)
}
@Nested
inner class FetchPendingTransactions {
@Test
fun `walletManagersFacade returns Verified`() = runTest {
// Arrange
val result = updateWalletManagerResultFactory.createVerified()
val status = result.toNetworkStatus()
coEvery {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
} returns listOf(cryptoCurrency)
coEvery {
walletManagersFacade.updatePendingTransactions(userWalletId = userWalletId, network = network)
} returns result
coEvery { networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status) } returns Unit
// Act
repository.fetchPendingTransactions(userWalletId = userWalletId, network = network)
// Assert
coVerifyOrder {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
walletManagersFacade.updatePendingTransactions(userWalletId = userWalletId, network = network)
networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status)
}
}
@Test
fun `walletManagersFacade returns NoAccount`() = runTest {
// Arrange
val result = updateWalletManagerResultFactory.createNoAccount()
val status = result.toNetworkStatus()
coEvery {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
} returns listOf(cryptoCurrency)
coEvery {
walletManagersFacade.updatePendingTransactions(userWalletId = userWalletId, network = network)
} returns result
coEvery { networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status) } returns Unit
// Act
repository.fetchPendingTransactions(userWalletId = userWalletId, network = network)
// Assert
coVerifyOrder {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
walletManagersFacade.updatePendingTransactions(userWalletId = userWalletId, network = network)
networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status)
}
}
@Test
fun `walletManagersFacade returns MissedDerivation`() = runTest {
// Arrange
val result = UpdateWalletManagerResult.MissedDerivation
val status = result.toNetworkStatus()
coEvery {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
} returns listOf(cryptoCurrency)
coEvery {
walletManagersFacade.updatePendingTransactions(userWalletId = userWalletId, network = network)
} returns result
coEvery { networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status) } returns Unit
// Act
repository.fetchPendingTransactions(userWalletId = userWalletId, network = network)
// Assert
coVerifyOrder {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
walletManagersFacade.updatePendingTransactions(userWalletId = userWalletId, network = network)
networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status)
}
}
@Test
fun `walletManagersFacade returns Unreachable`() = runTest {
// Arrange
val result = updateWalletManagerResultFactory.createUnreachableWithAddress()
val status = result.toNetworkStatus()
coEvery {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
} returns listOf(cryptoCurrency)
coEvery {
walletManagersFacade.updatePendingTransactions(userWalletId = userWalletId, network = network)
} returns result
coEvery { networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status) } returns Unit
// Act
repository.fetchPendingTransactions(userWalletId = userWalletId, network = network)
// Assert
coVerifyOrder {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
walletManagersFacade.updatePendingTransactions(userWalletId = userWalletId, network = network)
networksStatusesStore.storeStatus(userWalletId = userWalletId, status = status)
}
}
@Test
fun `cardCryptoCurrencyFactory throws exception`() = runTest {
// Arrange
coEvery {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
} throws IllegalStateException()
// Act
repository.fetchPendingTransactions(userWalletId = userWalletId, network = network)
// Assert
coVerify {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
}
coVerify(inverse = true) {
walletManagersFacade.updatePendingTransactions(userWalletId = any(), network = any())
}
}
}
@Nested
inner class GetNetworkAddresses {
@Test
fun `cardCryptoCurrencyFactory create throws exception`() = runTest {
// Arrange
coEvery {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
} throws IllegalStateException()
// Act
val actual = repository.getNetworkAddresses(userWalletId = userWalletId, network = network)
// Assert
val expected = emptyList<CryptoCurrencyAddress>()
Truth.assertThat(actual).isEqualTo(expected)
coVerify { cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network) }
coVerify(inverse = true) {
walletManagersFacade.getDefaultAddress(userWalletId = userWalletId, network = network)
}
}
@Test
fun `cardCryptoCurrencyFactory create returns empty list`() = runTest {
// Arrange
coEvery {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
} returns emptyList()
// Act
val actual = repository.getNetworkAddresses(userWalletId = userWalletId, network = network)
// Assert
val expected = emptyList<CryptoCurrencyAddress>()
Truth.assertThat(actual).isEqualTo(expected)
coVerify { cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network) }
coVerify(inverse = true) {
walletManagersFacade.getDefaultAddress(userWalletId = userWalletId, network = network)
}
}
@Test
fun `cardCryptoCurrencyFactory create returns not empty list`() = runTest {
// Arrange
val currencies = listOf(cryptoCurrency)
coEvery {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = network)
} returns currencies
coEvery {
walletManagersFacade.getDefaultAddress(userWalletId = userWalletId, network = cryptoCurrency.network)
} returns "address"
// Act
val actual = repository.getNetworkAddresses(userWalletId = userWalletId, network = network)
// Assert
val expected = listOf(
CryptoCurrencyAddress(cryptoCurrency = cryptoCurrency, address = "address"),
)
Truth.assertThat(actual).isEqualTo(expected)
coVerify {
cardCryptoCurrencyFactory.create(userWalletId = userWalletId, network = cryptoCurrency.network)
walletManagersFacade.getDefaultAddress(userWalletId = userWalletId, network = cryptoCurrency.network)
}
}
}
private companion object {
val userWalletId = UserWalletId("011")
val cryptoCurrency = MockCryptoCurrencyFactory().ethereum
val network = MockCryptoCurrencyFactory().ethereum.network
val updateWalletManagerResultFactory = MockUpdateWalletManagerResultFactory()
fun UpdateWalletManagerResult.toNetworkStatus(): NetworkStatus {
return NetworkStatusFactory.create(
network = network,
updatingResult = this,
addedCurrencies = setOf(cryptoCurrency),
)
}
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.domain.networks.repository
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
/**
* Repository for working with pending transactions
*
[REDACTED_AUTHOR]
*/
interface NetworksRepository {
/** Fetches pending transactions for given [network] in selected [userWalletId] */
suspend fun fetchPendingTransactions(userWalletId: UserWalletId, network: Network)
/**
* Returns addresses and crypto currency
*
* @param userWalletId the unique identifier of the user wallet
* @param network network
*/
suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List<CryptoCurrencyAddress>
}

View file

@ -100,6 +100,7 @@ detekt = "1.22.0"
espresso = "3.5.1"
espresso-intents = "3.5.1"
junit = "4.13.2"
junit5 = "5.8.2"
junitAndroidExt = "1.1.5"
mockk = "1.13.4"
turbine = "1.2.0"
@ -197,6 +198,8 @@ test-coroutine = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ver
test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso-intents" }
test-junit = { module = "junit:junit", version.ref = "junit" }
test-junit5 = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" }
test-junit5-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit5" }
test-junit-android = { module = "androidx.test.ext:junit", version.ref = "junitAndroidExt" }
test-truth = { module = "com.google.truth:truth", version.ref = "truth" }
test-mockk = { module = "io.mockk:mockk", version.ref = "mockk" }