Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-16 13:32:13 +04:00
parent a5a8cee12c
commit 9b1bf5813f
15 changed files with 895 additions and 30 deletions

View file

@ -0,0 +1,222 @@
package com.tangem.data.account.producer
import com.google.common.truth.Truth
import com.tangem.common.test.utils.getEmittedValues
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
/**
[REDACTED_AUTHOR]
*/
@Suppress("UnusedFlow")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DefaultMultiAccountListProducerTest {
private val userWalletsStore: UserWalletsStore = mockk()
private val walletAccountListFlowFactory: WalletAccountListFlowFactory = mockk()
private val producer = DefaultMultiAccountListProducer(
params = Unit,
userWalletsStore = userWalletsStore,
walletAccountListFlowFactory = walletAccountListFlowFactory,
dispatchers = TestingCoroutineDispatcherProvider(),
)
private val userWalletId = UserWalletId("011")
private val userWallet = mockk<UserWallet> {
every { this@mockk.walletId } returns userWalletId
}
@AfterEach
fun tearDownEach() {
clearMocks(userWalletsStore, walletAccountListFlowFactory)
}
@Test
fun produce() = runTest {
// Arrange
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
val accountList = AccountList.empty(userWallet)
every { walletAccountListFlowFactory.create(userWallet) } returns flowOf(accountList)
// Act
val actual = producer.produce().let(::getEmittedValues)
// Assert
val expected = listOf(accountList)
Truth.assertThat(actual).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
}
}
@Test
fun `flow will updated if factoryFlow is updated`() = runTest {
// Arrange
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
val accountList = AccountList.empty(userWallet)
val updatedAccountList = AccountList.empty(userWallet = userWallet, sortType = TokensSortType.NONE)
val factoryFlow = MutableStateFlow<AccountList?>(null)
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
// Act (first emission)
factoryFlow.value = accountList
val firstEmission = producer.produce().let(::getEmittedValues)
// Assert (first emission)
Truth.assertThat(firstEmission).containsExactly(listOf(accountList))
// Act (second emission)
factoryFlow.value = updatedAccountList
val secondEmission = producer.produce().let(::getEmittedValues)
// Assert (second emission)
Truth.assertThat(secondEmission).containsExactly(listOf(updatedAccountList))
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
}
}
@Test
fun `flow is filtered the same response`() = runTest {
// Arrange
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
val accountList = AccountList.empty(userWallet)
val factoryFlow = MutableStateFlow<AccountList?>(null)
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
// Act (first emission)
factoryFlow.value = accountList
val firstEmission = producer.produce().let(::getEmittedValues)
// Assert (first emission)
Truth.assertThat(firstEmission).containsExactly(listOf(accountList))
// Act (second emission) - the same status
factoryFlow.value = accountList
val secondEmission = producer.produce().let(::getEmittedValues)
// Assert (second emission)
Truth.assertThat(secondEmission).containsExactly(listOf(accountList))
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
}
}
@Test
fun `flow returns empty list if factory throws exception`() = runTest {
// Arrange
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
val exception = RuntimeException("Converter error")
every { walletAccountListFlowFactory.create(userWallet) } throws exception
// Act
val actual = producer.produceWithFallback().let(::getEmittedValues)
// Assert
val expected = emptyList<AccountList>()
Truth.assertThat(actual).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
}
}
@Test
fun `flow is empty if userWalletsFlow returns empty flow`() = runTest {
// Arrange
val userWalletsFlow = emptyFlow<List<UserWallet>>()
every { userWalletsStore.userWallets } returns userWalletsFlow
// Act
val actual = producer.produce().let(::getEmittedValues)
// Assert
Truth.assertThat(actual).isEmpty() // no emissions
coVerify(exactly = 1) { userWalletsStore.userWallets }
coVerify(inverse = true) { walletAccountListFlowFactory.create(any()) }
}
@Test
fun `flow is empty if factory returns empty flow`() = runTest {
// Arrange
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
every { walletAccountListFlowFactory.create(userWallet) } returns emptyFlow()
// Act
val actual = producer.produce().let(::getEmittedValues)
// Assert
Truth.assertThat(actual).isEmpty() // no emissions
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
}
}
@Test
fun `flow is empty if one of factoryFlow is empty`() = runTest {
// Arrange
val userWalletId2 = UserWalletId("012")
val userWallet2 = mockk<UserWallet> {
every { this@mockk.walletId } returns userWalletId2
}
val userWalletsFlow = MutableStateFlow(listOf(userWallet, userWallet2))
every { userWalletsStore.userWallets } returns userWalletsFlow
val accountList = AccountList.empty(userWallet)
every { walletAccountListFlowFactory.create(userWallet) } returns flowOf(accountList)
every { walletAccountListFlowFactory.create(userWallet2) } returns emptyFlow()
// Act
val actual = producer.produce().let(::getEmittedValues)
// Assert
Truth.assertThat(actual).isEmpty() // no emissions
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
walletAccountListFlowFactory.create(userWallet2)
}
}
}

View file

@ -0,0 +1,198 @@
package com.tangem.data.account.producer
import com.google.common.truth.Truth
import com.tangem.common.test.utils.getEmittedValues
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
/**
[REDACTED_AUTHOR]
*/
@Suppress("UnusedFlow")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DefaultSingleAccountListProducerTest {
private val userWalletsStore: UserWalletsStore = mockk()
private val walletAccountListFlowFactory: WalletAccountListFlowFactory = mockk()
private val userWalletId = UserWalletId("011")
private val userWallet = mockk<UserWallet> {
every { this@mockk.walletId } returns userWalletId
}
private val producer = DefaultSingleAccountListProducer(
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
userWalletsStore = userWalletsStore,
walletAccountListFlowFactory = walletAccountListFlowFactory,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@AfterEach
fun tearDownEach() {
clearMocks(userWalletsStore, walletAccountListFlowFactory)
}
@Test
fun produce() = runTest {
// Arrange
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
val accountList = AccountList.empty(userWallet)
every { walletAccountListFlowFactory.create(userWallet) } returns flowOf(accountList)
// Act
val actual = producer.produce().let(::getEmittedValues)
// Assert
val expected = accountList
Truth.assertThat(actual).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
}
}
@Test
fun `flow will updated if factoryFlow is updated`() = runTest {
// Arrange
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
val accountList = AccountList.empty(userWallet)
val updatedAccountList = AccountList.empty(userWallet = userWallet, sortType = TokensSortType.NONE)
val factoryFlow = MutableStateFlow<AccountList?>(null)
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
// Act (first emission)
factoryFlow.value = accountList
val firstEmission = producer.produce().let(::getEmittedValues)
// Assert (first emission)
Truth.assertThat(firstEmission).containsExactly(accountList)
// Act (second emission)
factoryFlow.value = updatedAccountList
val secondEmission = producer.produce().let(::getEmittedValues)
// Assert (second emission)
Truth.assertThat(secondEmission).containsExactly(updatedAccountList)
coVerifyOrder {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
}
}
@Test
fun `flow is filtered the same response`() = runTest {
// Arrange
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
val accountList = AccountList.empty(userWallet)
val factoryFlow = MutableStateFlow<AccountList?>(null)
every { walletAccountListFlowFactory.create(userWallet) } returns factoryFlow.filterNotNull()
// Act (first emission)
factoryFlow.value = accountList
val firstEmission = producer.produce().let(::getEmittedValues)
// Assert (first emission)
Truth.assertThat(firstEmission).containsExactly(accountList)
// Act (second emission) - the same status
factoryFlow.value = accountList
val secondEmission = producer.produce().let(::getEmittedValues)
// Assert (second emission)
Truth.assertThat(secondEmission).containsExactly(accountList)
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
}
}
@Test
fun `flow is empty if factory throws exception`() = runTest {
// Arrange
val userWalletsFlow = MutableStateFlow(value = listOf(userWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
val exception = RuntimeException("Converter error")
every { walletAccountListFlowFactory.create(userWallet) } throws exception
// Act
val actual = producer.produceWithFallback().let(::getEmittedValues)
// Assert
Truth.assertThat(actual).isEmpty() // no emissions
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
walletAccountListFlowFactory.create(userWallet)
}
}
@Test
fun `flow is empty if userWalletsFlow returns empty flow`() = runTest {
// Arrange
val userWalletsFlow = emptyFlow<List<UserWallet>>()
every { userWalletsStore.userWallets } returns userWalletsFlow
// Act
val actual = producer.produce().let(::getEmittedValues)
// Assert
Truth.assertThat(actual).isEmpty() // no emissions
coVerify(exactly = 1) { userWalletsStore.userWallets }
coVerify(inverse = true) { walletAccountListFlowFactory.create(any()) }
}
@Test
fun `flow is empty if userWalletsFlow doesn't contains userWalletId from params`() = runTest {
// Arrange
val unknownId = UserWalletId("012")
val unknownWallet = mockk<UserWallet> {
every { this@mockk.walletId } returns unknownId
}
val userWalletsFlow = MutableStateFlow(listOf(unknownWallet))
every { userWalletsStore.userWallets } returns userWalletsFlow
// Act
val actual = producer.produce().let(::getEmittedValues)
// Assert
Truth.assertThat(actual).isEmpty() // no emissions
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsStore.userWallets
}
coVerify(inverse = true) { walletAccountListFlowFactory.create(any()) }
}
}

View file

@ -0,0 +1,148 @@
package com.tangem.data.account.producer
import com.google.common.truth.Truth
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
import com.tangem.common.test.utils.getEmittedValues
import com.tangem.data.account.converter.AccountListConverter
import com.tangem.data.account.converter.createGetWalletAccountsResponse
import com.tangem.data.account.store.AccountsResponseStore
import com.tangem.data.account.store.AccountsResponseStoreFactory
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import io.mockk.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
/**
[REDACTED_AUTHOR]
*/
@Suppress("UnusedFlow")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class WalletAccountListFlowFactoryTest {
private val accountsResponseStoreFactory: AccountsResponseStoreFactory = mockk()
private val accountsResponseStore: AccountsResponseStore = mockk()
private val accountsResponseStoreFlow = MutableStateFlow<GetWalletAccountsResponse?>(value = null)
private val accountListConverterFactory: AccountListConverter.Factory = mockk()
private val accountListConverter: AccountListConverter = mockk()
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
private val factory = WalletAccountListFlowFactory(
accountsResponseStoreFactory = accountsResponseStoreFactory,
accountListConverterFactory = accountListConverterFactory,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
)
private val userWalletId = UserWalletId("011")
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
@AfterEach
fun tearDownEach() {
clearMocks(accountListConverter)
accountsResponseStoreFlow.value = null
}
@Test
fun `create for multi wallet`() = runTest {
// Arrange
val userWallet = mockk<UserWallet> {
every { this@mockk.walletId } returns userWalletId
every { this@mockk.isMultiCurrency } returns true
}
val accountsResponse = createGetWalletAccountsResponse(userWalletId)
every { accountsResponseStoreFactory.create(userWalletId) } returns accountsResponseStore
every { accountsResponseStore.data } returns accountsResponseStoreFlow
accountsResponseStoreFlow.value = accountsResponse
val accountList = AccountList.empty(userWallet)
every { accountListConverterFactory.create(userWallet) } returns accountListConverter
every { accountListConverter.convert(accountsResponse) } returns accountList
// Act
val actual = factory.create(userWallet).let(::getEmittedValues)
// Assert
val expected = accountList
Truth.assertThat(actual).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
accountsResponseStoreFactory.create(userWalletId)
accountsResponseStore.data
accountListConverterFactory.create(userWallet)
accountListConverter.convert(accountsResponse)
}
coVerify(inverse = true) {
cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(any())
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(any())
}
}
@Test
fun `create for single wallet`() = runTest {
val userWallet = MockUserWalletFactory.create().copy(isMultiCurrency = false)
val currency = cryptoCurrencyFactory.ethereum
every { cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet) } returns currency
// Act
val actual = factory.create(userWallet).let(::getEmittedValues)
// Assert
val expected = AccountList.empty(userWallet = userWallet, cryptoCurrencies = setOf(currency))
Truth.assertThat(actual).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet)
}
coVerify(inverse = true) {
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet = any())
accountsResponseStoreFactory.create(any())
accountsResponseStore.data
accountListConverterFactory.create(any())
accountListConverter.convert(any())
}
}
@Test
fun `flow is created for single wallet with token`() = runTest {
val nodl = MockUserWalletFactory.createSingleWalletWithToken()
val currencies = cryptoCurrencyFactory.ethereumAndStellar.toSet()
every {
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet = nodl)
} returns currencies.toList()
// Act
val actual = factory.create(nodl).let(::getEmittedValues)
// Assert
val expected = AccountList.empty(userWallet = nodl, cryptoCurrencies = currencies)
Truth.assertThat(actual).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet = nodl)
}
coVerify(inverse = true) {
cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(any())
accountsResponseStoreFactory.create(any())
accountsResponseStore.data
accountListConverterFactory.create(any())
accountListConverter.convert(any())
}
}
}