Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-17 09:17:35 +03:00
parent e6de882b3b
commit c9b99c587d
5 changed files with 277 additions and 0 deletions

View file

@ -48,4 +48,12 @@ object MockYieldBalanceWrapperDTOFactory {
integrationId = stakingId.integrationId,
)
}
fun createWithEmptyBalance(stakingId: StakingID = defaultStakingId): YieldBalanceWrapperDTO {
return YieldBalanceWrapperDTO(
addresses = Address(address = stakingId.address),
balances = emptyList(),
integrationId = stakingId.integrationId,
)
}
}

View file

@ -0,0 +1,44 @@
package com.tangem.data.staking.multi
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.multi.MultiYieldBalanceProducer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEmpty
/**
* Default implementation of [MultiYieldBalanceProducer]
*
* @property params params
* @property yieldsBalancesStore yields balances store
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
internal class DefaultMultiYieldBalanceProducer @AssistedInject constructor(
@Assisted val params: MultiYieldBalanceProducer.Params,
private val yieldsBalancesStore: YieldsBalancesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : MultiYieldBalanceProducer {
override val fallback: Set<YieldBalance>
get() = setOf()
override fun produce(): Flow<Set<YieldBalance>> {
return yieldsBalancesStore.get(userWalletId = params.userWalletId)
.distinctUntilChanged()
.onEmpty { emit(value = hashSetOf()) }
.flowOn(dispatchers.default)
}
@AssistedFactory
interface Factory : MultiYieldBalanceProducer.Factory {
override fun create(params: MultiYieldBalanceProducer.Params): DefaultMultiYieldBalanceProducer
}
}

View file

@ -0,0 +1,190 @@
package com.tangem.data.staking.multi
import com.google.common.truth.Truth
import com.tangem.common.test.data.staking.MockYieldBalanceWrapperDTOFactory
import com.tangem.common.test.utils.getEmittedValues
import com.tangem.data.staking.store.YieldsBalancesStore
import com.tangem.data.staking.toDomain
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.multi.MultiYieldBalanceProducer
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.runTest
import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
internal class DefaultMultiYieldBalanceProducerTest {
private val params = MultiYieldBalanceProducer.Params(userWalletId = UserWalletId("011"))
private val yieldsBalancesStore = mockk<YieldsBalancesStore>()
private val dispatchers = TestingCoroutineDispatcherProvider()
private val producer = DefaultMultiYieldBalanceProducer(
params = params,
yieldsBalancesStore = yieldsBalancesStore,
dispatchers = dispatchers,
)
@Test
fun `test that flow is mapped for user wallet id from params`() = runTest {
val balances = setOf(
MockYieldBalanceWrapperDTOFactory.createWithBalance(tonId).toDomain(),
MockYieldBalanceWrapperDTOFactory.createWithBalance(solanaId).toDomain(),
)
val networksStatusesFlow = flowOf(balances)
every { yieldsBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
val values = getEmittedValues(flow = actual)
Truth.assertThat(values.size).isEqualTo(1)
Truth.assertThat(values.first()).isEqualTo(balances)
}
@Test
fun `test that flow is updated if balances are updated`() = runTest {
val networksStatusesFlow = MutableSharedFlow<Set<YieldBalance>>(replay = 2)
every { yieldsBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
// first emit
val balances = setOf(
MockYieldBalanceWrapperDTOFactory.createWithEmptyBalance(tonId).toDomain(),
MockYieldBalanceWrapperDTOFactory.createWithEmptyBalance(solanaId).toDomain(),
)
networksStatusesFlow.emit(balances)
val values1 = getEmittedValues(flow = actual)
Truth.assertThat(values1.size).isEqualTo(1)
Truth.assertThat(values1.first()).isEqualTo(balances)
// second emit
val updatedWrappers = setOf(
MockYieldBalanceWrapperDTOFactory.createWithBalance(tonId).toDomain(),
MockYieldBalanceWrapperDTOFactory.createWithBalance(solanaId).toDomain(),
)
networksStatusesFlow.emit(updatedWrappers)
val values2 = getEmittedValues(flow = actual)
val expected = listOf(balances, updatedWrappers)
Truth.assertThat(values2.size).isEqualTo(2)
Truth.assertThat(values2).isEqualTo(expected)
}
@Test
fun `test that flow is filtered the same balance`() = runTest {
val networksStatusesFlow = MutableSharedFlow<Set<YieldBalance>>(replay = 2)
every { yieldsBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
// first emit
val wrappers = setOf(
MockYieldBalanceWrapperDTOFactory.createWithEmptyBalance(tonId).toDomain(),
MockYieldBalanceWrapperDTOFactory.createWithEmptyBalance(solanaId).toDomain(),
)
networksStatusesFlow.emit(wrappers)
val values1 = getEmittedValues(flow = actual)
Truth.assertThat(values1.size).isEqualTo(1)
Truth.assertThat(values1.first()).isEqualTo(wrappers)
// second emit
networksStatusesFlow.emit(wrappers)
val values2 = getEmittedValues(flow = actual)
Truth.assertThat(values2.size).isEqualTo(1)
Truth.assertThat(values2.first()).isEqualTo(wrappers)
}
@Test
fun `test if flow throws exception`() = runTest {
val exception = IllegalStateException()
val balances = setOf(
MockYieldBalanceWrapperDTOFactory.createWithBalance(tonId).toDomain(),
MockYieldBalanceWrapperDTOFactory.createWithBalance(solanaId).toDomain(),
)
val innerFlow = MutableStateFlow(value = false)
val networksStatusesFlow = flow {
if (innerFlow.value) {
emit(balances)
} else {
throw exception
}
}
.buffer(capacity = 5)
every { yieldsBalancesStore.get(params.userWalletId) } returns networksStatusesFlow
val actual = producer.produceWithFallback()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
val values1 = getEmittedValues(flow = actual)
Truth.assertThat(values1.size).isEqualTo(1)
Truth.assertThat(values1).isEqualTo(listOf(emptySet<YieldBalance>()))
innerFlow.emit(value = true)
val values2 = getEmittedValues(flow = actual)
Truth.assertThat(values2.size).isEqualTo(1)
Truth.assertThat(values2).isEqualTo(listOf(balances))
}
@Test
fun `test that flow is empty`() = runTest {
every { yieldsBalancesStore.get(params.userWalletId) } returns emptyFlow()
val actual = producer.produce()
// check after producer.produce()
verify { yieldsBalancesStore.get(params.userWalletId) }
val values = getEmittedValues(flow = actual)
Truth.assertThat(values.size).isEqualTo(1)
Truth.assertThat(values).isEqualTo(listOf(emptySet<YieldBalance>()))
}
private companion object {
val tonId = MockYieldBalanceWrapperDTOFactory.defaultStakingId
val solanaId = YieldsBalancesStore.StakingID(
integrationId = "solana-sol-native-multivalidator-staking",
address = "0x1",
)
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.domain.staking.multi
import com.tangem.domain.core.flow.FlowProducer
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.wallets.models.UserWalletId
/**
* Producer of all yield balances for selected wallet [UserWalletId]
*
[REDACTED_AUTHOR]
*/
interface MultiYieldBalanceProducer : FlowProducer<Set<YieldBalance>> {
data class Params(val userWalletId: UserWalletId)
interface Factory : FlowProducer.Factory<Params, MultiYieldBalanceProducer>
}

View file

@ -0,0 +1,18 @@
package com.tangem.domain.staking.multi
import com.tangem.domain.core.flow.FlowCachingSupplier
import com.tangem.domain.core.flow.FlowProducer
import com.tangem.domain.staking.model.stakekit.YieldBalance
/**
* Supplier of all yield balances for selected wallet [MultiYieldBalanceProducer.Params]
*
* @property factory factory for creating [MultiYieldBalanceProducer]
* @property keyCreator key creator
*
[REDACTED_AUTHOR]
*/
abstract class MultiYieldBalanceSupplier(
override val factory: FlowProducer.Factory<MultiYieldBalanceProducer.Params, MultiYieldBalanceProducer>,
override val keyCreator: (MultiYieldBalanceProducer.Params) -> String,
) : FlowCachingSupplier<MultiYieldBalanceProducer, MultiYieldBalanceProducer.Params, Set<YieldBalance>>()