Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-02 11:53:48 +04:00
parent fa6b5817bf
commit cd572639c7
9 changed files with 386 additions and 1 deletions

View file

@ -8,6 +8,10 @@ android {
namespace = "com.tangem.domain.tokens"
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies {
/** Project - Domain */
@ -49,9 +53,12 @@ dependencies {
implementation(deps.reKotlin)
/** Tests */
testImplementation(deps.test.junit)
testImplementation(deps.test.coroutine)
testImplementation(deps.test.junit5)
testRuntimeOnly(deps.test.junit5.engine)
testImplementation(deps.test.mockk)
testImplementation(deps.test.truth)
testImplementation(projects.common.test)
testImplementation(tangemDeps.blockchain) {
exclude(module = "joda-time")
}

View file

@ -0,0 +1,18 @@
package com.tangem.domain.tokens.wallet
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
/**
* Base contract for implementation of wallet's balance fetcher
*
[REDACTED_AUTHOR]
*/
internal interface BaseWalletBalanceFetcher {
/** Fetching sources */
val fetchingSources: Set<FetchingSource>
/** Get crypto currencies of wallet with [userWalletId] */
suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set<CryptoCurrency>
}

View file

@ -0,0 +1,12 @@
package com.tangem.domain.tokens.wallet
/**
* Source type that is necessary to load cryptocurrency data
*
[REDACTED_AUTHOR]
*/
internal enum class FetchingSource {
NETWORK,
QUOTE,
STAKING,
}

View file

@ -0,0 +1,44 @@
package com.tangem.domain.tokens.wallet.implementor
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.wallet.BaseWalletBalanceFetcher
import com.tangem.domain.tokens.wallet.FetchingSource
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.firstOrNull
import timber.log.Timber
/**
* Implementation of [BaseWalletBalanceFetcher] for MULTI-CURRENCY wallet
*
* @property multiWalletCryptoCurrenciesFetcher multi wallet fetcher of crypto currencies
* @property multiWalletCryptoCurrenciesSupplier multi wallet supplier of crypto currencies
*
[REDACTED_AUTHOR]
*/
internal class MultiWalletBalanceFetcher(
private val multiWalletCryptoCurrenciesFetcher: MultiWalletCryptoCurrenciesFetcher,
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
) : BaseWalletBalanceFetcher {
override val fetchingSources: Set<FetchingSource> = setOf(
FetchingSource.NETWORK,
FetchingSource.QUOTE,
FetchingSource.STAKING,
)
override suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set<CryptoCurrency> {
multiWalletCryptoCurrenciesFetcher(
params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId),
)
.onLeft(Timber::e)
return multiWalletCryptoCurrenciesSupplier(
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId),
)
.firstOrNull()
.orEmpty()
}
}

View file

@ -0,0 +1,33 @@
package com.tangem.domain.tokens.wallet.implementor
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.wallet.BaseWalletBalanceFetcher
import com.tangem.domain.tokens.wallet.FetchingSource
import com.tangem.domain.wallets.models.UserWalletId
/**
* Implementation of [BaseWalletBalanceFetcher] for SINGLE-CURRENCY wallet
*
* @property currenciesRepository currencies repository
*
[REDACTED_AUTHOR]
*/
internal class SingleWalletBalanceFetcher(
private val currenciesRepository: CurrenciesRepository,
) : BaseWalletBalanceFetcher {
override val fetchingSources: Set<FetchingSource> = setOf(
FetchingSource.NETWORK,
FetchingSource.QUOTE,
)
override suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set<CryptoCurrency> {
val primaryCurrency = currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(
userWalletId = userWalletId,
refresh = true,
)
return setOf(primaryCurrency)
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.domain.tokens.wallet.implementor
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.wallet.BaseWalletBalanceFetcher
import com.tangem.domain.tokens.wallet.FetchingSource
import com.tangem.domain.wallets.models.UserWalletId
/**
* Implementation of [BaseWalletBalanceFetcher] for SINGLE-CURRENCY wallet WITH TOKEN (like, NODL)
*
* @property currenciesRepository currencies repository
*
[REDACTED_AUTHOR]
*/
internal class SingleWalletWithTokenBalanceFetcher(
private val currenciesRepository: CurrenciesRepository,
) : BaseWalletBalanceFetcher {
override val fetchingSources: Set<FetchingSource> = setOf(
FetchingSource.NETWORK,
FetchingSource.QUOTE,
)
override suspend fun getCryptoCurrencies(userWalletId: UserWalletId): Set<CryptoCurrency> {
return currenciesRepository.getSingleCurrencyWalletWithCardCurrencies(
userWalletId = userWalletId,
refresh = true,
).toSet()
}
}

View file

@ -0,0 +1,122 @@
package com.tangem.domain.tokens.wallet.implementor
import arrow.core.left
import arrow.core.right
import com.google.common.truth.Truth
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.wallet.FetchingSource
import com.tangem.domain.wallets.models.UserWalletId
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
/**
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MultiWalletBalanceFetcherTest {
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
private val multiWalletFetcher: MultiWalletCryptoCurrenciesFetcher = mockk()
private val multiWalletSupplier: MultiWalletCryptoCurrenciesSupplier = mockk()
private val fetcher = MultiWalletBalanceFetcher(
multiWalletCryptoCurrenciesFetcher = multiWalletFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletSupplier,
)
@BeforeEach
fun resetMocks() {
clearMocks(multiWalletFetcher)
}
@Test
fun getFetchingSources() {
// Act
val actual = fetcher.fetchingSources
// Assert
val expected = setOf(FetchingSource.NETWORK, FetchingSource.QUOTE, FetchingSource.STAKING)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `getCryptoCurrencies successfully if multiWalletCryptoCurrenciesFetcher RETURNS SUCCESS`() = runTest {
// Arrange
val currencies: Set<CryptoCurrency> = cryptoCurrencyFactory.ethereumAndStellar.toSet()
val supplierFlow = flowOf(currencies)
coEvery {
multiWalletFetcher(params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId))
} returns Unit.right()
every {
multiWalletSupplier(params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId))
} returns supplierFlow
// Act
val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId)
// Assert
val expected = currencies.toSet()
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `getCryptoCurrencies successfully if multiWalletCryptoCurrenciesFetcher RETURNS ERROR`() = runTest {
// Arrange
val currencies: Set<CryptoCurrency> = cryptoCurrencyFactory.ethereumAndStellar.toSet()
val supplierFlow = flowOf(currencies)
coEvery {
multiWalletFetcher(params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId))
} returns IllegalStateException().left()
every {
multiWalletSupplier(params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId))
} returns supplierFlow
// Act
val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId)
// Assert
val expected = currencies.toSet()
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `getCryptoCurrencies successfully if multiWalletCryptoCurrenciesSupplier IS EMPTY FLOW`() = runTest {
// Arrange
val supplierFlow = emptyFlow<Set<CryptoCurrency>>()
coEvery {
multiWalletFetcher(params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId))
} returns Unit.right()
every {
multiWalletSupplier(params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId))
} returns supplierFlow
// Act
val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId)
// Assert
val expected = emptySet<CryptoCurrency>()
Truth.assertThat(actual).isEqualTo(expected)
}
private companion object {
val userWalletId = UserWalletId("011")
}
}

View file

@ -0,0 +1,59 @@
package com.tangem.domain.tokens.wallet.implementor
import com.google.common.truth.Truth
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.wallet.FetchingSource
import com.tangem.domain.wallets.models.UserWalletId
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
/**
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SingleWalletBalanceFetcherTest {
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
private val currenciesRepository: CurrenciesRepository = mockk()
private val fetcher = SingleWalletBalanceFetcher(currenciesRepository = currenciesRepository)
@BeforeEach
fun resetMocks() {
clearMocks(currenciesRepository)
}
@Test
fun getFetchingSources() {
// Act
val actual = fetcher.fetchingSources
// Assert
val expected = setOf(FetchingSource.NETWORK, FetchingSource.QUOTE)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun getCryptoCurrencies() = runTest {
// Arrange
val userWalletId = UserWalletId("011")
val currency = cryptoCurrencyFactory.ethereum
coEvery {
currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId = userWalletId, refresh = true)
} returns currency
// Act
val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId)
// Assert
val expected = setOf(currency)
Truth.assertThat(actual).isEqualTo(expected)
}
}

View file

@ -0,0 +1,59 @@
package com.tangem.domain.tokens.wallet.implementor
import com.google.common.truth.Truth
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.wallet.FetchingSource
import com.tangem.domain.wallets.models.UserWalletId
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
/**
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SingleWalletWithTokenBalanceFetcherTest {
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
private val currenciesRepository: CurrenciesRepository = mockk()
private val fetcher = SingleWalletWithTokenBalanceFetcher(currenciesRepository = currenciesRepository)
@BeforeEach
fun resetMocks() {
clearMocks(currenciesRepository)
}
@Test
fun getFetchingSources() {
// Act
val actual = fetcher.fetchingSources
// Assert
val expected = setOf(FetchingSource.NETWORK, FetchingSource.QUOTE)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun getCryptoCurrencies() = runTest {
// Arrange
val userWalletId = UserWalletId("011")
val currencies = cryptoCurrencyFactory.ethereumAndStellar
coEvery {
currenciesRepository.getSingleCurrencyWalletWithCardCurrencies(userWalletId = userWalletId, refresh = true)
} returns currencies
// Act
val actual = fetcher.getCryptoCurrencies(userWalletId = userWalletId)
// Assert
val expected = currencies.toSet()
Truth.assertThat(actual).isEqualTo(expected)
}
}