Updated on 2026-08-14
This commit is contained in:
parent
1edf995ab0
commit
2dadb60975
26 changed files with 1110 additions and 48 deletions
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.common.wallets.UserWalletDataCleaner
|
||||
import com.tangem.domain.common.wallets.UserWalletSelectedHandler
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
|
|
@ -25,6 +26,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSy
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.tap.domain.DefaultUserWalletSelectedHandler
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -188,8 +190,16 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDeleteWalletUseCase(userWalletsListRepository: UserWalletsListRepository): DeleteWalletUseCase {
|
||||
return DeleteWalletUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
fun providesDeleteWalletUseCase(
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
userWalletDataCleaners: Set<@JvmSuppressWildcards UserWalletDataCleaner>,
|
||||
appCoroutineScope: AppCoroutineScope,
|
||||
): DeleteWalletUseCase {
|
||||
return DeleteWalletUseCase(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
userWalletDataCleaners = userWalletDataCleaners,
|
||||
appCoroutineScope = appCoroutineScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.tap.domain.userWalletList.di
|
||||
|
||||
import com.tangem.domain.common.wallets.UserWalletDataCleaner
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.Multibinds
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface UserWalletDataCleanerModule {
|
||||
|
||||
@Multibinds
|
||||
fun userWalletDataCleaners(): Set<UserWalletDataCleaner>
|
||||
}
|
||||
|
|
@ -1,14 +1,27 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.txhistory.DefaultTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemDM
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDMConverter
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDomainConverter
|
||||
import com.tangem.datasource.utils.KotlinxDataStoreSerializer
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -25,9 +38,34 @@ internal object TxHistoryItemsStoreModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemPayTxHistoryItemsStore(): TangemPayTxHistoryItemsStore {
|
||||
fun provideTangemPayTxHistoryItemsStore(
|
||||
@ApplicationContext context: Context,
|
||||
appScope: AppCoroutineScope,
|
||||
toDMConverter: TangemPayTxHistoryItemToDMConverter,
|
||||
toDomainConverter: TangemPayTxHistoryItemToDomainConverter,
|
||||
): TangemPayTxHistoryItemsStore {
|
||||
return DefaultTangemPayTxHistoryItemsStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
dataStore = DataStoreFactory.create(
|
||||
serializer = KotlinxDataStoreSerializer(
|
||||
defaultValue = emptyMap(),
|
||||
serializer = MapSerializer(
|
||||
keySerializer = String.serializer(),
|
||||
valueSerializer = MapSerializer(
|
||||
keySerializer = String.serializer(),
|
||||
valueSerializer = ListSerializer(TangemPayTxHistoryItemDM.serializer()),
|
||||
),
|
||||
),
|
||||
// TangemPayTxHistoryItemDM.Collateral declares a `type` field that would clash with the
|
||||
// default kotlinx polymorphic class discriminator ("type"), so persist sealed subtypes
|
||||
// under a non-conflicting discriminator key.
|
||||
json = KotlinxDataStoreSerializer.jsonBuilder { classDiscriminator = "__type" },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler { emptyMap() },
|
||||
produceFile = { context.dataStoreFile(fileName = "tangem_pay_tx_history") },
|
||||
scope = appScope,
|
||||
),
|
||||
toDMConverter = toDMConverter,
|
||||
toDomainConverter = toDomainConverter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,37 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemDM
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDMConverter
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDomainConverter
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
internal typealias StoredTangemPayTxHistory = Map<String, Map<String, List<TangemPayTxHistoryItemDM>>>
|
||||
|
||||
internal class DefaultTangemPayTxHistoryItemsStore(
|
||||
dataStore: StringKeyDataStore<Map<String, List<TangemPayTxHistoryItem>>>,
|
||||
) : TangemPayTxHistoryItemsStore,
|
||||
StringKeyDataStoreDecorator<String, Map<String, List<TangemPayTxHistoryItem>>>(dataStore) {
|
||||
override fun provideStringKey(key: String): String = key
|
||||
private val dataStore: DataStore<StoredTangemPayTxHistory>,
|
||||
private val toDMConverter: TangemPayTxHistoryItemToDMConverter,
|
||||
private val toDomainConverter: TangemPayTxHistoryItemToDomainConverter,
|
||||
) : TangemPayTxHistoryItemsStore {
|
||||
|
||||
override suspend fun getSyncOrNull(key: String, cursor: String): List<TangemPayTxHistoryItem>? {
|
||||
val storedValue = getSyncOrNull(key)
|
||||
return storedValue?.get(cursor)
|
||||
return dataStore.data.first()[key]?.get(cursor)?.let { toDomainConverter.convertList(it) }
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, cursor: String, value: List<TangemPayTxHistoryItem>) {
|
||||
val oldValue = getSyncOrNull(key).orEmpty()
|
||||
val newValue = oldValue.toMutableMap().apply { put(cursor, value) }
|
||||
store(key, newValue)
|
||||
val page = toDMConverter.convertList(value)
|
||||
dataStore.updateData { stored ->
|
||||
val walletPages = stored[key].orEmpty()
|
||||
stored + (key to walletPages + (cursor to page))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(key: String) {
|
||||
remove(keys = listOf(key))
|
||||
}
|
||||
|
||||
override suspend fun remove(keys: List<String>) {
|
||||
dataStore.updateData { stored -> stored - keys.toSet() }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,5 +8,7 @@ interface TangemPayTxHistoryItemsStore {
|
|||
|
||||
suspend fun remove(key: String)
|
||||
|
||||
suspend fun remove(keys: List<String>)
|
||||
|
||||
suspend fun store(key: String, cursor: String, value: List<TangemPayTxHistoryItem>)
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.datasource.local.visa.entity
|
||||
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.models.serialization.SerializedCurrency
|
||||
import com.tangem.domain.models.serialization.SerializedDateTime
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class TangemPayTxHistoryItemDM {
|
||||
|
||||
abstract val id: String
|
||||
abstract val date: SerializedDateTime
|
||||
abstract val amount: SerializedBigDecimal
|
||||
abstract val currency: SerializedCurrency
|
||||
abstract val jsonRepresentation: String
|
||||
|
||||
@Serializable
|
||||
@SerialName("spend")
|
||||
data class Spend(
|
||||
@SerialName("id") override val id: String,
|
||||
@SerialName("json_representation") override val jsonRepresentation: String,
|
||||
@SerialName("date") override val date: SerializedDateTime,
|
||||
@SerialName("amount") override val amount: SerializedBigDecimal,
|
||||
@SerialName("currency") override val currency: SerializedCurrency,
|
||||
@SerialName("authorized_amount") val authorizedAmount: SerializedBigDecimal,
|
||||
@SerialName("local_amount") val localAmount: SerializedBigDecimal?,
|
||||
@SerialName("local_currency") val localCurrency: SerializedCurrency?,
|
||||
@SerialName("enriched_merchant_name") val enrichedMerchantName: String?,
|
||||
@SerialName("merchant_name") val merchantName: String,
|
||||
@SerialName("enriched_merchant_category") val enrichedMerchantCategory: String?,
|
||||
@SerialName("merchant_category_code") val merchantCategoryCode: String?,
|
||||
@SerialName("merchant_category") val merchantCategory: String?,
|
||||
@SerialName("status") val status: Status,
|
||||
@SerialName("enriched_merchant_icon_url") val enrichedMerchantIconUrl: String?,
|
||||
@SerialName("declined_reason") val declinedReason: String?,
|
||||
) : TangemPayTxHistoryItemDM()
|
||||
|
||||
@Serializable
|
||||
@SerialName("payment")
|
||||
data class Payment(
|
||||
@SerialName("id") override val id: String,
|
||||
@SerialName("json_representation") override val jsonRepresentation: String,
|
||||
@SerialName("date") override val date: SerializedDateTime,
|
||||
@SerialName("amount") override val amount: SerializedBigDecimal,
|
||||
@SerialName("currency") override val currency: SerializedCurrency,
|
||||
@SerialName("transaction_hash") val transactionHash: String?,
|
||||
) : TangemPayTxHistoryItemDM()
|
||||
|
||||
@Serializable
|
||||
@SerialName("fee")
|
||||
data class Fee(
|
||||
@SerialName("id") override val id: String,
|
||||
@SerialName("json_representation") override val jsonRepresentation: String,
|
||||
@SerialName("date") override val date: SerializedDateTime,
|
||||
@SerialName("amount") override val amount: SerializedBigDecimal,
|
||||
@SerialName("currency") override val currency: SerializedCurrency,
|
||||
@SerialName("description") val description: String?,
|
||||
) : TangemPayTxHistoryItemDM()
|
||||
|
||||
@Serializable
|
||||
@SerialName("collateral")
|
||||
data class Collateral(
|
||||
@SerialName("id") override val id: String,
|
||||
@SerialName("json_representation") override val jsonRepresentation: String,
|
||||
@SerialName("date") override val date: SerializedDateTime,
|
||||
@SerialName("amount") override val amount: SerializedBigDecimal,
|
||||
@SerialName("currency") override val currency: SerializedCurrency,
|
||||
@SerialName("transaction_hash") val transactionHash: String,
|
||||
@SerialName("type") val type: Type,
|
||||
) : TangemPayTxHistoryItemDM()
|
||||
|
||||
@Serializable
|
||||
enum class Type {
|
||||
@SerialName("deposit")
|
||||
Deposit,
|
||||
|
||||
@SerialName("withdrawal")
|
||||
Withdrawal,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class Status {
|
||||
@SerialName("pending")
|
||||
PENDING,
|
||||
|
||||
@SerialName("reserved")
|
||||
RESERVED,
|
||||
|
||||
@SerialName("completed")
|
||||
COMPLETED,
|
||||
|
||||
@SerialName("declined")
|
||||
DECLINED,
|
||||
|
||||
@SerialName("reversed")
|
||||
REVERSED,
|
||||
|
||||
@SerialName("unknown")
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package com.tangem.datasource.local.visa.entity
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class TangemPayTxHistoryItemToDMConverter @Inject constructor() :
|
||||
Converter<TangemPayTxHistoryItem, TangemPayTxHistoryItemDM> {
|
||||
|
||||
override fun convert(value: TangemPayTxHistoryItem): TangemPayTxHistoryItemDM = when (value) {
|
||||
is TangemPayTxHistoryItem.Spend -> TangemPayTxHistoryItemDM.Spend(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
authorizedAmount = value.authorizedAmount,
|
||||
localAmount = value.localAmount,
|
||||
localCurrency = value.localCurrency,
|
||||
enrichedMerchantName = value.enrichedMerchantName,
|
||||
merchantName = value.merchantName,
|
||||
enrichedMerchantCategory = value.enrichedMerchantCategory,
|
||||
merchantCategoryCode = value.merchantCategoryCode,
|
||||
merchantCategory = value.merchantCategory,
|
||||
status = value.status.toDM(),
|
||||
enrichedMerchantIconUrl = value.enrichedMerchantIconUrl,
|
||||
declinedReason = value.declinedReason,
|
||||
)
|
||||
is TangemPayTxHistoryItem.Payment -> TangemPayTxHistoryItemDM.Payment(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
transactionHash = value.transactionHash,
|
||||
)
|
||||
is TangemPayTxHistoryItem.Fee -> TangemPayTxHistoryItemDM.Fee(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
description = value.description,
|
||||
)
|
||||
is TangemPayTxHistoryItem.Collateral -> TangemPayTxHistoryItemDM.Collateral(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
transactionHash = value.transactionHash,
|
||||
type = value.type.toDM(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class TangemPayTxHistoryItemToDomainConverter @Inject constructor() :
|
||||
Converter<TangemPayTxHistoryItemDM, TangemPayTxHistoryItem> {
|
||||
|
||||
override fun convert(value: TangemPayTxHistoryItemDM): TangemPayTxHistoryItem = when (value) {
|
||||
is TangemPayTxHistoryItemDM.Spend -> TangemPayTxHistoryItem.Spend(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
authorizedAmount = value.authorizedAmount,
|
||||
localAmount = value.localAmount,
|
||||
localCurrency = value.localCurrency,
|
||||
enrichedMerchantName = value.enrichedMerchantName,
|
||||
merchantName = value.merchantName,
|
||||
enrichedMerchantCategory = value.enrichedMerchantCategory,
|
||||
merchantCategoryCode = value.merchantCategoryCode,
|
||||
merchantCategory = value.merchantCategory,
|
||||
status = value.status.toDomain(),
|
||||
enrichedMerchantIconUrl = value.enrichedMerchantIconUrl,
|
||||
declinedReason = value.declinedReason,
|
||||
)
|
||||
is TangemPayTxHistoryItemDM.Payment -> TangemPayTxHistoryItem.Payment(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
transactionHash = value.transactionHash,
|
||||
)
|
||||
is TangemPayTxHistoryItemDM.Fee -> TangemPayTxHistoryItem.Fee(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
description = value.description,
|
||||
)
|
||||
is TangemPayTxHistoryItemDM.Collateral -> TangemPayTxHistoryItem.Collateral(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
transactionHash = value.transactionHash,
|
||||
type = value.type.toDomain(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TangemPayTxHistoryItem.Status.toDM(): TangemPayTxHistoryItemDM.Status = when (this) {
|
||||
TangemPayTxHistoryItem.Status.PENDING -> TangemPayTxHistoryItemDM.Status.PENDING
|
||||
TangemPayTxHistoryItem.Status.RESERVED -> TangemPayTxHistoryItemDM.Status.RESERVED
|
||||
TangemPayTxHistoryItem.Status.COMPLETED -> TangemPayTxHistoryItemDM.Status.COMPLETED
|
||||
TangemPayTxHistoryItem.Status.DECLINED -> TangemPayTxHistoryItemDM.Status.DECLINED
|
||||
TangemPayTxHistoryItem.Status.REVERSED -> TangemPayTxHistoryItemDM.Status.REVERSED
|
||||
TangemPayTxHistoryItem.Status.UNKNOWN -> TangemPayTxHistoryItemDM.Status.UNKNOWN
|
||||
}
|
||||
|
||||
private fun TangemPayTxHistoryItemDM.Status.toDomain(): TangemPayTxHistoryItem.Status = when (this) {
|
||||
TangemPayTxHistoryItemDM.Status.PENDING -> TangemPayTxHistoryItem.Status.PENDING
|
||||
TangemPayTxHistoryItemDM.Status.RESERVED -> TangemPayTxHistoryItem.Status.RESERVED
|
||||
TangemPayTxHistoryItemDM.Status.COMPLETED -> TangemPayTxHistoryItem.Status.COMPLETED
|
||||
TangemPayTxHistoryItemDM.Status.DECLINED -> TangemPayTxHistoryItem.Status.DECLINED
|
||||
TangemPayTxHistoryItemDM.Status.REVERSED -> TangemPayTxHistoryItem.Status.REVERSED
|
||||
TangemPayTxHistoryItemDM.Status.UNKNOWN -> TangemPayTxHistoryItem.Status.UNKNOWN
|
||||
}
|
||||
|
||||
private fun TangemPayTxHistoryItem.Type.toDM(): TangemPayTxHistoryItemDM.Type = when (this) {
|
||||
TangemPayTxHistoryItem.Type.Deposit -> TangemPayTxHistoryItemDM.Type.Deposit
|
||||
TangemPayTxHistoryItem.Type.Withdrawal -> TangemPayTxHistoryItemDM.Type.Withdrawal
|
||||
}
|
||||
|
||||
private fun TangemPayTxHistoryItemDM.Type.toDomain(): TangemPayTxHistoryItem.Type = when (this) {
|
||||
TangemPayTxHistoryItemDM.Type.Deposit -> TangemPayTxHistoryItem.Type.Deposit
|
||||
TangemPayTxHistoryItemDM.Type.Withdrawal -> TangemPayTxHistoryItem.Type.Withdrawal
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDMConverter
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDomainConverter
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.test.core.datastore.MockStateDataStore
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.joda.time.DateTime
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class DefaultTangemPayTxHistoryItemsStoreTest {
|
||||
|
||||
private lateinit var store: DefaultTangemPayTxHistoryItemsStore
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
store = DefaultTangemPayTxHistoryItemsStore(
|
||||
dataStore = MockStateDataStore(default = emptyMap()),
|
||||
toDMConverter = TangemPayTxHistoryItemToDMConverter(),
|
||||
toDomainConverter = TangemPayTxHistoryItemToDomainConverter(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty store WHEN getSyncOrNull THEN returns null`() = runTest {
|
||||
val result = store.getSyncOrNull(key = WALLET_A, cursor = CURSOR_1)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN stored page WHEN getSyncOrNull with same key and cursor THEN returns page`() = runTest {
|
||||
// Arrange
|
||||
val page = listOf(payment("1"), payment("2"))
|
||||
store.store(key = WALLET_A, cursor = CURSOR_1, value = page)
|
||||
|
||||
// Act
|
||||
val result = store.getSyncOrNull(key = WALLET_A, cursor = CURSOR_1)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isEqualTo(page)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN page stored under one cursor WHEN getSyncOrNull with another cursor THEN returns null`() = runTest {
|
||||
// Arrange
|
||||
store.store(key = WALLET_A, cursor = CURSOR_1, value = listOf(payment("1")))
|
||||
|
||||
// Act
|
||||
val result = store.getSyncOrNull(key = WALLET_A, cursor = CURSOR_2)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN two cursors stored for one wallet WHEN getSyncOrNull THEN both pages are kept`() = runTest {
|
||||
// Arrange
|
||||
val page1 = listOf(payment("1"))
|
||||
val page2 = listOf(payment("2"))
|
||||
store.store(key = WALLET_A, cursor = CURSOR_1, value = page1)
|
||||
store.store(key = WALLET_A, cursor = CURSOR_2, value = page2)
|
||||
|
||||
// Assert
|
||||
assertThat(store.getSyncOrNull(WALLET_A, CURSOR_1)).isEqualTo(page1)
|
||||
assertThat(store.getSyncOrNull(WALLET_A, CURSOR_2)).isEqualTo(page2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN entries for two wallets WHEN remove one wallet THEN only that wallet is cleared`() = runTest {
|
||||
// Arrange
|
||||
store.store(key = WALLET_A, cursor = CURSOR_1, value = listOf(payment("a")))
|
||||
store.store(key = WALLET_B, cursor = CURSOR_1, value = listOf(payment("b")))
|
||||
|
||||
// Act
|
||||
store.remove(WALLET_A)
|
||||
|
||||
// Assert
|
||||
assertThat(store.getSyncOrNull(WALLET_A, CURSOR_1)).isNull()
|
||||
assertThat(store.getSyncOrNull(WALLET_B, CURSOR_1)).isEqualTo(listOf(payment("b")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN entries for two wallets WHEN remove both in one call THEN both are cleared`() = runTest {
|
||||
// Arrange
|
||||
store.store(key = WALLET_A, cursor = CURSOR_1, value = listOf(payment("a")))
|
||||
store.store(key = WALLET_B, cursor = CURSOR_1, value = listOf(payment("b")))
|
||||
|
||||
// Act
|
||||
store.remove(listOf(WALLET_A, WALLET_B))
|
||||
|
||||
// Assert
|
||||
assertThat(store.getSyncOrNull(WALLET_A, CURSOR_1)).isNull()
|
||||
assertThat(store.getSyncOrNull(WALLET_B, CURSOR_1)).isNull()
|
||||
}
|
||||
|
||||
private fun payment(id: String) = TangemPayTxHistoryItem.Payment(
|
||||
id = id,
|
||||
jsonRepresentation = "{}",
|
||||
date = DateTime(DATE_MILLIS),
|
||||
amount = BigDecimal("1.00"),
|
||||
currency = Currency.getInstance("USD"),
|
||||
transactionHash = null,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val WALLET_A = "wallet-a"
|
||||
const val WALLET_B = "wallet-b"
|
||||
const val CURSOR_1 = "cursor-1"
|
||||
const val CURSOR_2 = "cursor-2"
|
||||
const val DATE_MILLIS = 1_700_000_000_000L
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemDM
|
||||
import com.tangem.datasource.utils.KotlinxDataStoreSerializer
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import org.joda.time.DateTime
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class TangemPayTxHistoryStoreSerializationTest {
|
||||
|
||||
private val json = KotlinxDataStoreSerializer.jsonBuilder { classDiscriminator = "__type" }
|
||||
|
||||
private val serializer = MapSerializer(
|
||||
keySerializer = String.serializer(),
|
||||
valueSerializer = MapSerializer(
|
||||
keySerializer = String.serializer(),
|
||||
valueSerializer = ListSerializer(TangemPayTxHistoryItemDM.serializer()),
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN all tx history item types WHEN serialized and deserialized THEN value is preserved`() {
|
||||
// Arrange
|
||||
val original: Map<String, Map<String, List<TangemPayTxHistoryItemDM>>> = mapOf(
|
||||
"wallet-1" to mapOf(
|
||||
"initial_cursor_key" to listOf(spend(), payment(), fee(), collateral()),
|
||||
),
|
||||
)
|
||||
|
||||
// Act
|
||||
val restored = json.decodeFromString(serializer, json.encodeToString(serializer, original))
|
||||
|
||||
// Assert
|
||||
assertThat(restored).isEqualTo(original)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN Collateral with type field WHEN serialized and deserialized THEN discriminator does not clash`() {
|
||||
// Arrange
|
||||
val original: Map<String, Map<String, List<TangemPayTxHistoryItemDM>>> = mapOf(
|
||||
"wallet-1" to mapOf("cursor" to listOf(collateral())),
|
||||
)
|
||||
|
||||
// Act
|
||||
val restored = json.decodeFromString(serializer, json.encodeToString(serializer, original))
|
||||
|
||||
// Assert
|
||||
assertThat(restored).isEqualTo(original)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN tx history item WHEN serialized THEN uses stable SerialName under non-clashing discriminator`() {
|
||||
// Arrange
|
||||
val original: Map<String, Map<String, List<TangemPayTxHistoryItemDM>>> = mapOf(
|
||||
"wallet-1" to mapOf("cursor" to listOf(collateral())),
|
||||
)
|
||||
|
||||
// Act
|
||||
val encoded = json.encodeToString(serializer, original)
|
||||
|
||||
// Assert
|
||||
assertThat(encoded).contains("\"__type\":\"collateral\"")
|
||||
}
|
||||
|
||||
private fun spend() = TangemPayTxHistoryItemDM.Spend(
|
||||
id = "spend-1",
|
||||
jsonRepresentation = "{}",
|
||||
date = DateTime(DATE_MILLIS),
|
||||
amount = BigDecimal("12.34"),
|
||||
currency = Currency.getInstance("USD"),
|
||||
authorizedAmount = BigDecimal("12.34"),
|
||||
localAmount = BigDecimal("11.00"),
|
||||
localCurrency = Currency.getInstance("EUR"),
|
||||
enrichedMerchantName = "Coffee Co",
|
||||
merchantName = "COFFEE CO",
|
||||
enrichedMerchantCategory = "Food",
|
||||
merchantCategoryCode = "5814",
|
||||
merchantCategory = "restaurants",
|
||||
status = TangemPayTxHistoryItemDM.Status.COMPLETED,
|
||||
enrichedMerchantIconUrl = "https://example.com/icon.png",
|
||||
declinedReason = null,
|
||||
)
|
||||
|
||||
private fun payment() = TangemPayTxHistoryItemDM.Payment(
|
||||
id = "payment-1",
|
||||
jsonRepresentation = "{}",
|
||||
date = DateTime(DATE_MILLIS),
|
||||
amount = BigDecimal("100.00"),
|
||||
currency = Currency.getInstance("USD"),
|
||||
transactionHash = "0xabc",
|
||||
)
|
||||
|
||||
private fun fee() = TangemPayTxHistoryItemDM.Fee(
|
||||
id = "fee-1",
|
||||
jsonRepresentation = "{}",
|
||||
date = DateTime(DATE_MILLIS),
|
||||
amount = BigDecimal("0.50"),
|
||||
currency = Currency.getInstance("USD"),
|
||||
description = "network fee",
|
||||
)
|
||||
|
||||
private fun collateral() = TangemPayTxHistoryItemDM.Collateral(
|
||||
id = "collateral-1",
|
||||
jsonRepresentation = "{}",
|
||||
date = DateTime(DATE_MILLIS),
|
||||
amount = BigDecimal("250.00"),
|
||||
currency = Currency.getInstance("USD"),
|
||||
transactionHash = "0xdef",
|
||||
type = TangemPayTxHistoryItemDM.Type.Deposit,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val DATE_MILLIS = 1_700_000_000_000L
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package com.tangem.datasource.local.visa.entity
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import org.joda.time.DateTime
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.EnumSource
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class TangemPayTxHistoryItemDMConverterTest {
|
||||
|
||||
private val toDMConverter = TangemPayTxHistoryItemToDMConverter()
|
||||
private val toDomainConverter = TangemPayTxHistoryItemToDomainConverter()
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun `GIVEN every item subtype WHEN converted to DM and back THEN all fields preserved`(
|
||||
item: TangemPayTxHistoryItem,
|
||||
) {
|
||||
assertRoundTrip(item)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(TangemPayTxHistoryItem.Status::class)
|
||||
fun `GIVEN every Spend status WHEN converted to DM and back THEN status preserved`(
|
||||
status: TangemPayTxHistoryItem.Status,
|
||||
) {
|
||||
assertRoundTrip(spend().copy(status = status))
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(TangemPayTxHistoryItem.Type::class)
|
||||
fun `GIVEN every Collateral type WHEN converted to DM and back THEN type preserved`(
|
||||
type: TangemPayTxHistoryItem.Type,
|
||||
) {
|
||||
assertRoundTrip(collateral().copy(type = type))
|
||||
}
|
||||
|
||||
private fun assertRoundTrip(item: TangemPayTxHistoryItem) {
|
||||
// Act
|
||||
val restored = toDomainConverter.convert(toDMConverter.convert(item))
|
||||
|
||||
// Assert
|
||||
assertThat(restored).isEqualTo(item)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(spend(), payment(), fee(), collateral())
|
||||
|
||||
private fun spend() = TangemPayTxHistoryItem.Spend(
|
||||
id = "spend-1",
|
||||
jsonRepresentation = "{}",
|
||||
date = DateTime(DATE_MILLIS),
|
||||
amount = BigDecimal("12.34"),
|
||||
currency = Currency.getInstance("USD"),
|
||||
authorizedAmount = BigDecimal("12.34"),
|
||||
localAmount = BigDecimal("11.00"),
|
||||
localCurrency = Currency.getInstance("EUR"),
|
||||
enrichedMerchantName = "Coffee Co",
|
||||
merchantName = "COFFEE CO",
|
||||
enrichedMerchantCategory = "Food",
|
||||
merchantCategoryCode = "5814",
|
||||
merchantCategory = "restaurants",
|
||||
status = TangemPayTxHistoryItem.Status.COMPLETED,
|
||||
enrichedMerchantIconUrl = "https://example.com/icon.png",
|
||||
declinedReason = null,
|
||||
)
|
||||
|
||||
private fun payment() = TangemPayTxHistoryItem.Payment(
|
||||
id = "payment-1",
|
||||
jsonRepresentation = "{}",
|
||||
date = DateTime(DATE_MILLIS),
|
||||
amount = BigDecimal("100.00"),
|
||||
currency = Currency.getInstance("USD"),
|
||||
transactionHash = "0xabc",
|
||||
)
|
||||
|
||||
private fun fee() = TangemPayTxHistoryItem.Fee(
|
||||
id = "fee-1",
|
||||
jsonRepresentation = "{}",
|
||||
date = DateTime(DATE_MILLIS),
|
||||
amount = BigDecimal("0.50"),
|
||||
currency = Currency.getInstance("USD"),
|
||||
description = "network fee",
|
||||
)
|
||||
|
||||
private fun collateral() = TangemPayTxHistoryItem.Collateral(
|
||||
id = "collateral-1",
|
||||
jsonRepresentation = "{}",
|
||||
date = DateTime(DATE_MILLIS),
|
||||
amount = BigDecimal("250.00"),
|
||||
currency = Currency.getInstance("USD"),
|
||||
transactionHash = "0xdef",
|
||||
type = TangemPayTxHistoryItem.Type.Deposit,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val DATE_MILLIS = 1_700_000_000_000L
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.data.pay
|
||||
|
||||
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
||||
import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore
|
||||
import com.tangem.domain.common.wallets.UserWalletDataCleaner
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class TangemPayUserWalletDataCleaner @Inject constructor(
|
||||
private val paymentAccountStatusesStore: PaymentAccountStatusesStore,
|
||||
private val txHistoryItemsStore: TangemPayTxHistoryItemsStore,
|
||||
) : UserWalletDataCleaner {
|
||||
|
||||
override suspend fun clear(userWalletIds: List<UserWalletId>) {
|
||||
paymentAccountStatusesStore.remove(userWalletIds)
|
||||
txHistoryItemsStore.remove(userWalletIds.map { it.stringValue })
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
|||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.data.pay.DefaultTangemPayEligibilityManager
|
||||
import com.tangem.data.pay.TangemPayUserWalletDataCleaner
|
||||
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter
|
||||
import com.tangem.data.pay.entity.DefaultTangemPayCurrencyFactory
|
||||
import com.tangem.data.pay.flow.DefaultPaymentAccountStatusFetcher
|
||||
|
|
@ -21,6 +22,7 @@ import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
|||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
||||
import com.tangem.domain.common.wallets.UserWalletDataCleaner
|
||||
import com.tangem.domain.pay.TangemPayCurrencyFactory
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||
|
|
@ -41,6 +43,7 @@ import dagger.Provides
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -117,6 +120,10 @@ internal interface TangemPayDataModule {
|
|||
@Singleton
|
||||
fun bindPaymentAccountStatusFetcher(impl: DefaultPaymentAccountStatusFetcher): PaymentAccountStatusFetcher
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
fun bindTangemPayUserWalletDataCleaner(impl: TangemPayUserWalletDataCleaner): UserWalletDataCleaner
|
||||
|
||||
companion object {
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.datasource.api.pay.models.response.CustomerMeResponse
|
|||
import com.tangem.datasource.api.pay.models.response.OrderResponse
|
||||
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
|
|
@ -49,6 +50,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
private val cardFrozenStateStore: TangemPayCardFrozenStateStore,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val paymentAccountStatusStore: PaymentAccountStatusesStore,
|
||||
private val txHistoryItemsStore: TangemPayTxHistoryItemsStore,
|
||||
) : OnboardingRepository {
|
||||
|
||||
// Save data for a session
|
||||
|
|
@ -271,6 +273,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
}.map {
|
||||
val address = requestHelper.getCustomerWalletAddress(userWalletId)
|
||||
tangemPayStorage.clearAll(userWalletId = userWalletId, customerWalletAddress = address)
|
||||
txHistoryItemsStore.remove(userWalletId.stringValue)
|
||||
setHideMainOnboardingBanner(userWalletId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.pagination.fetcher.BatchFetcher
|
|||
import com.tangem.pagination.fetcher.CursorBatchFetcher
|
||||
import com.tangem.pagination.toBatchFlow
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val INITIAL_CURSOR = "initial_cursor_key"
|
||||
|
|
@ -76,16 +77,22 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
cursor: String?,
|
||||
limit: Int,
|
||||
): List<TangemPayTxHistoryItem> {
|
||||
val storeKey = userWalletId.stringValue
|
||||
val storeCursor = cursor ?: INITIAL_CURSOR
|
||||
|
||||
val fetchResult = runSuspendCatching {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getCacheKey(userWalletId = userWalletId, cursor = cursor),
|
||||
skipCache = config.shouldRefresh,
|
||||
block = { fetch(userWalletId = userWalletId, cursor = cursor, pageSize = limit) },
|
||||
)
|
||||
}
|
||||
val storedPage = txHistoryItemsStore.getSyncOrNull(key = storeKey, cursor = storeCursor)
|
||||
|
||||
return txHistoryItemsStore.getSyncOrNull(
|
||||
key = userWalletId.stringValue,
|
||||
cursor = cursor ?: INITIAL_CURSOR,
|
||||
).orEmpty()
|
||||
return fetchResult.fold(
|
||||
onSuccess = { storedPage.orEmpty() },
|
||||
onFailure = { e -> storedPage?.takeIf { it.isNotEmpty() } ?: throw e },
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCacheKey(userWalletId: UserWalletId, cursor: String?): String {
|
||||
|
|
|
|||
|
|
@ -101,6 +101,20 @@ internal class PaymentAccountStatusesStore(
|
|||
return runtimeStore.getSyncOrDefault(emptyMap()).containsKey(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
suspend fun remove(userWalletId: UserWalletId) {
|
||||
logger.i("remove($userWalletId)")
|
||||
remove(userWalletIds = listOf(userWalletId))
|
||||
}
|
||||
|
||||
suspend fun remove(userWalletIds: List<UserWalletId>) {
|
||||
logger.i("remove($userWalletIds)")
|
||||
val keys = userWalletIds.map { it.stringValue }.toSet()
|
||||
coroutineScope {
|
||||
launch { runtimeStore.update(default = emptyMap()) { it - keys } }
|
||||
launch { persistenceDataStore.updateData { it - keys } }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInRuntime(userWalletId: UserWalletId, status: AccountStatus.Payment) {
|
||||
runtimeStore.update(default = emptyMap()) { stored ->
|
||||
stored.toMutableMap().apply {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.data.pay
|
||||
|
||||
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
||||
import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.Runs
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.just
|
||||
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
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class TangemPayUserWalletDataCleanerTest {
|
||||
|
||||
private val paymentAccountStatusesStore: PaymentAccountStatusesStore = mockk()
|
||||
private val txHistoryItemsStore: TangemPayTxHistoryItemsStore = mockk()
|
||||
|
||||
private val cleaner = TangemPayUserWalletDataCleaner(
|
||||
paymentAccountStatusesStore = paymentAccountStatusesStore,
|
||||
txHistoryItemsStore = txHistoryItemsStore,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(paymentAccountStatusesStore, txHistoryItemsStore)
|
||||
coEvery { paymentAccountStatusesStore.remove(any<List<UserWalletId>>()) } just Runs
|
||||
coEvery { txHistoryItemsStore.remove(any<List<String>>()) } just Runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN wallets WHEN clear THEN each store is cleared once with all ids in a single call`() = runTest {
|
||||
// Act
|
||||
cleaner.clear(listOf(WALLET_A, WALLET_B))
|
||||
|
||||
// Assert
|
||||
coVerify(exactly = 1) { paymentAccountStatusesStore.remove(listOf(WALLET_A, WALLET_B)) }
|
||||
coVerify(exactly = 1) { txHistoryItemsStore.remove(listOf(WALLET_A.stringValue, WALLET_B.stringValue)) }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val WALLET_A = UserWalletId("011")
|
||||
val WALLET_B = UserWalletId("022")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.data.pay.store
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayCurrencyFactory
|
||||
import com.tangem.test.core.TestAppCoroutineScope
|
||||
import com.tangem.test.core.datastore.MockStateDataStore
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class PaymentAccountStatusesStoreTest {
|
||||
|
||||
@Test
|
||||
fun `GIVEN stored status WHEN remove single THEN cleared from both runtime and persistence`() = runTest {
|
||||
// Arrange
|
||||
val persistenceDataStore = MockStateDataStore<WalletIdWithPaymentStatusDM>(default = emptyMap())
|
||||
val store = createStore(persistenceDataStore)
|
||||
advanceUntilIdle()
|
||||
store.storeNotCreated(WALLET_A)
|
||||
advanceUntilIdle()
|
||||
// sanity: present in both stores before removal
|
||||
assertThat(store.getSyncOrNull(WALLET_A)).isNotNull()
|
||||
assertThat(persistenceDataStore.data.first()).containsKey(WALLET_A.stringValue)
|
||||
|
||||
// Act
|
||||
store.remove(WALLET_A)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
assertThat(store.getSyncOrNull(WALLET_A)).isNull()
|
||||
assertThat(persistenceDataStore.data.first()).doesNotContainKey(WALLET_A.stringValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN statuses for two wallets WHEN remove list THEN both cleared from both stores`() = runTest {
|
||||
// Arrange
|
||||
val persistenceDataStore = MockStateDataStore<WalletIdWithPaymentStatusDM>(default = emptyMap())
|
||||
val store = createStore(persistenceDataStore)
|
||||
advanceUntilIdle()
|
||||
store.storeNotCreated(WALLET_A)
|
||||
store.storeNotCreated(WALLET_B)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Act
|
||||
store.remove(listOf(WALLET_A, WALLET_B))
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
assertThat(store.getSyncOrNull(WALLET_A)).isNull()
|
||||
assertThat(store.getSyncOrNull(WALLET_B)).isNull()
|
||||
assertThat(persistenceDataStore.data.first()).doesNotContainKey(WALLET_A.stringValue)
|
||||
assertThat(persistenceDataStore.data.first()).doesNotContainKey(WALLET_B.stringValue)
|
||||
}
|
||||
|
||||
private fun TestScope.createStore(
|
||||
persistenceDataStore: MockStateDataStore<WalletIdWithPaymentStatusDM>,
|
||||
) = PaymentAccountStatusesStore(
|
||||
runtimeStore = RuntimeSharedStore<WalletIdWithPaymentStatus>(),
|
||||
persistenceDataStore = persistenceDataStore,
|
||||
converter = PaymentAccountStatusValueDMConverter(mockk<TangemPayCurrencyFactory>(relaxed = true)),
|
||||
scope = TestAppCoroutineScope(this),
|
||||
)
|
||||
|
||||
private suspend fun PaymentAccountStatusesStore.storeNotCreated(userWalletId: UserWalletId) {
|
||||
store(
|
||||
userWalletId = userWalletId,
|
||||
status = AccountStatus.Payment(
|
||||
account = Account.Payment(userWalletId),
|
||||
value = PaymentAccountStatusValue.NotCreated,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val WALLET_A = UserWalletId("011")
|
||||
val WALLET_B = UserWalletId("022")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.domain.common.wallets
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/** Removes per-wallet data owned by a feature when its wallets are deleted. */
|
||||
interface UserWalletDataCleaner {
|
||||
|
||||
suspend fun clear(userWalletIds: List<UserWalletId>)
|
||||
}
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.common.wallets.UserWalletDataCleaner
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.error.DeleteWalletError
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Use case for deleting user wallet
|
||||
|
|
@ -15,6 +19,8 @@ import com.tangem.utils.logging.TangemLogger
|
|||
*/
|
||||
class DeleteWalletUseCase(
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val userWalletDataCleaners: Set<UserWalletDataCleaner>,
|
||||
private val appCoroutineScope: AppCoroutineScope,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -25,10 +31,22 @@ class DeleteWalletUseCase(
|
|||
* @return [Either] with [com.tangem.domain.common.wallets.error.DeleteWalletError] or [Boolean] which indicates that there are still saved wallets.
|
||||
* */
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<DeleteWalletError, Boolean> {
|
||||
return userWalletsListRepository.delete(userWalletIds = listOf(userWalletId))
|
||||
val userWalletIds = listOf(userWalletId)
|
||||
return userWalletsListRepository.delete(userWalletIds = userWalletIds)
|
||||
.onRight { clearWalletDataInBackground(userWalletIds) }
|
||||
.map { userWalletsListRepository.selectedUserWallet.value != null }
|
||||
.onLeft {
|
||||
TangemLogger.e("Failed to delete wallet with id ${userWalletId.value}: $it")
|
||||
TangemLogger.e("Failed to delete wallet with id ${userWalletId.stringValue}: $it")
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearWalletDataInBackground(userWalletIds: List<UserWalletId>) {
|
||||
if (userWalletDataCleaners.isEmpty()) return
|
||||
appCoroutineScope.launch {
|
||||
userWalletDataCleaners.forEach { cleaner ->
|
||||
runSuspendCatching { cleaner.clear(userWalletIds) }
|
||||
.onFailure { TangemLogger.e("Failed to clear data for wallets $userWalletIds", it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
|
||||
import com.tangem.domain.common.wallets.UserWalletDataCleaner
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.error.DeleteWalletError
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.test.core.TestAppCoroutineScope
|
||||
import io.mockk.Runs
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class DeleteWalletUseCaseTest {
|
||||
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxed = true)
|
||||
private val firstCleaner: UserWalletDataCleaner = mockk()
|
||||
private val secondCleaner: UserWalletDataCleaner = mockk()
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(userWalletsListRepository, firstCleaner, secondCleaner)
|
||||
coEvery { userWalletsListRepository.delete(any()) } returns Unit.right()
|
||||
every { userWalletsListRepository.selectedUserWallet } returns MutableStateFlow<UserWallet?>(null)
|
||||
coEvery { firstCleaner.clear(any()) } just Runs
|
||||
coEvery { secondCleaner.clear(any()) } just Runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN delete succeeds WHEN invoke THEN cleanup runs in background after invoke returns`() = runTest {
|
||||
// Arrange
|
||||
val useCase = createUseCase()
|
||||
|
||||
// Act
|
||||
useCase(WALLET_ID)
|
||||
|
||||
// Assert — cleanup is fire-and-forget: not run yet when invoke returns
|
||||
coVerify(exactly = 0) { firstCleaner.clear(any()) }
|
||||
coVerify(exactly = 0) { secondCleaner.clear(any()) }
|
||||
|
||||
// ...and completes for every wallet once the app scope is drained
|
||||
advanceUntilIdle()
|
||||
coVerify(exactly = 1) { firstCleaner.clear(listOf(WALLET_ID)) }
|
||||
coVerify(exactly = 1) { secondCleaner.clear(listOf(WALLET_ID)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN delete fails WHEN invoke THEN cleaners are not run and error returned`() = runTest {
|
||||
// Arrange
|
||||
coEvery { userWalletsListRepository.delete(any()) } returns DeleteWalletError.UnableToDelete.left()
|
||||
val useCase = createUseCase()
|
||||
|
||||
// Act
|
||||
val result = useCase(WALLET_ID)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
assertThat(result).isEqualTo(DeleteWalletError.UnableToDelete.left())
|
||||
coVerify(exactly = 0) { firstCleaner.clear(any()) }
|
||||
coVerify(exactly = 0) { secondCleaner.clear(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN a wallet still selected WHEN invoke THEN returns right true`() = runTest {
|
||||
// Arrange
|
||||
every { userWalletsListRepository.selectedUserWallet } returns MutableStateFlow(wallet)
|
||||
val useCase = createUseCase()
|
||||
|
||||
// Act
|
||||
val result = useCase(WALLET_ID)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isEqualTo(true.right())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no wallet selected WHEN invoke THEN returns right false`() = runTest {
|
||||
// Arrange
|
||||
val useCase = createUseCase()
|
||||
|
||||
// Act
|
||||
val result = useCase(WALLET_ID)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isEqualTo(false.right())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN one cleaner throws WHEN invoke THEN other cleaners still run and result is right`() = runTest {
|
||||
// Arrange
|
||||
coEvery { firstCleaner.clear(any()) } throws IllegalStateException("boom")
|
||||
val useCase = createUseCase()
|
||||
|
||||
// Act
|
||||
val result = useCase(WALLET_ID)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
assertThat(result).isEqualTo(false.right())
|
||||
coVerify(exactly = 1) { secondCleaner.clear(listOf(WALLET_ID)) }
|
||||
}
|
||||
|
||||
private fun TestScope.createUseCase() = DeleteWalletUseCase(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
userWalletDataCleaners = setOf(firstCleaner, secondCleaner),
|
||||
appCoroutineScope = TestAppCoroutineScope(this),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
val WALLET_ID = UserWalletId("0011")
|
||||
val wallet: UserWallet = MockUserWalletFactory.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
|||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository.Attempts
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository.Companion.MAX_FAST_FORWARD_ATTEMPTS
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.hotwallet.accesscode.ACCESS_CODE_LENGTH
|
||||
import com.tangem.features.hotwallet.accesscoderequest.entity.HotAccessCodeRequestUM
|
||||
|
|
@ -37,6 +38,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val hotAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val deleteWalletUseCase: DeleteWalletUseCase,
|
||||
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val startAssetsDiscoveryUseCase: StartAssetsDiscoveryUseCase,
|
||||
|
|
@ -224,7 +226,7 @@ internal class HotAccessCodeRequestModel @Inject constructor(
|
|||
startAssetsDiscoveryUseCase.cancel(userWallet.walletId)
|
||||
}
|
||||
|
||||
userWalletsListRepository.delete(listOf(userWallet.walletId))
|
||||
deleteWalletUseCase(userWallet.walletId)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ internal sealed class TangemPayDetailsBalanceBlockState {
|
|||
override val cardsBlockState: CardsBlockState?,
|
||||
val fiatBalance: TextReference,
|
||||
val isBalanceFlickering: Boolean,
|
||||
val isMuted: Boolean = false,
|
||||
) : TangemPayDetailsBalanceBlockState()
|
||||
|
||||
data class Error(
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
|||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.feedback.models.WalletMetaInfo
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
|
||||
|
|
@ -139,17 +140,25 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
.onEach { state ->
|
||||
when (state) {
|
||||
is PaymentAccountStatusValue.Deactivated -> {
|
||||
val balanceTransformer = DetailsBalanceTransformer(
|
||||
fiatBalance = state.balance.fiatBalance,
|
||||
isMuted = state.source != StatusSource.ACTUAL,
|
||||
)
|
||||
uiState.update {
|
||||
balanceTransformer.transform(
|
||||
stateFactory.getDeactivatedState(
|
||||
hasWithdrawableBalance = state.balance.hasWithdrawableAmount,
|
||||
),
|
||||
)
|
||||
}
|
||||
uiState.update(DetailsBalanceTransformer(state.balance.fiatBalance))
|
||||
}
|
||||
is PaymentAccountStatusValue.Loaded -> {
|
||||
fetchAddToWalletBanner()
|
||||
uiState.update { stateFactory.getLoadedState(state) }
|
||||
uiState.update(DetailsBalanceTransformer(state.balance.fiatBalance))
|
||||
val balanceTransformer = DetailsBalanceTransformer(
|
||||
fiatBalance = state.balance.fiatBalance,
|
||||
isMuted = !state.isFresh,
|
||||
)
|
||||
uiState.update { balanceTransformer.transform(stateFactory.getLoadedState(state)) }
|
||||
state.cards.firstOrNull()?.let { card ->
|
||||
subscribeToCardFrozenState(card.id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import java.util.Currency
|
|||
|
||||
internal class DetailsBalanceTransformer(
|
||||
private val fiatBalance: PaymentAccountStatusValue.FiatBalance,
|
||||
private val isMuted: Boolean = false,
|
||||
) : Transformer<TangemPayDetailsUM> {
|
||||
|
||||
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
|
||||
|
|
@ -20,6 +21,7 @@ internal class DetailsBalanceTransformer(
|
|||
fiatBalance = getFiatBalanceText(fiatBalance),
|
||||
actionButtons = prevState.balanceBlockState.actionButtons,
|
||||
cardsBlockState = prevState.balanceBlockState.cardsBlockState,
|
||||
isMuted = isMuted,
|
||||
)
|
||||
return prevState.copy(balanceBlockState = balance)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,14 +355,20 @@ private fun BalanceBlock(
|
|||
style = TextShimmerStyle.HEADING_MEDIUM,
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
is TangemPayDetailsBalanceBlockState.Content -> Text(
|
||||
is TangemPayDetailsBalanceBlockState.Content -> {
|
||||
val balanceColor = if (animatedState.isMuted) {
|
||||
TangemTheme.colors3.text.secondary
|
||||
} else {
|
||||
TangemTheme.colors3.text.primary
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE),
|
||||
text = animatedState.fiatBalance.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography3.display.medium.applyBladeBrush(
|
||||
isEnabled = animatedState.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors3.text.primary,
|
||||
textColor = balanceColor,
|
||||
),
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
color = balanceColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
|
|
@ -370,6 +376,7 @@ private fun BalanceBlock(
|
|||
maxFontSize = TangemTheme.typography3.display.medium.fontSize,
|
||||
),
|
||||
)
|
||||
}
|
||||
is TangemPayDetailsBalanceBlockState.Error -> Text(
|
||||
modifier = Modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE),
|
||||
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
|||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.features.tangempay.addFundsButton
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
|
||||
import com.tangem.features.tangempay.tangemPayCard
|
||||
import com.tangem.features.tangempay.withdrawButton
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
|
|
@ -57,15 +58,36 @@ internal class TangemPayDetailsModelTest {
|
|||
model.onDestroy()
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideMutedCases")
|
||||
fun `GIVEN status source WHEN status loaded THEN balance is muted only when cached`(case: MutedCase) = runTest {
|
||||
// Arrange + Act
|
||||
val model = createModel(
|
||||
testScope = this,
|
||||
statusSource = case.statusSource,
|
||||
frozenState = TangemPayCardFrozenState.Unfrozen,
|
||||
availableForWithdrawal = BigDecimal.ZERO,
|
||||
accountError = case.accountError,
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
val balanceState = model.uiState.value.balanceBlockState
|
||||
assertThat(balanceState).isInstanceOf(TangemPayDetailsBalanceBlockState.Content::class.java)
|
||||
assertThat((balanceState as TangemPayDetailsBalanceBlockState.Content).isMuted).isEqualTo(case.expectedMuted)
|
||||
model.onDestroy()
|
||||
}
|
||||
|
||||
private fun createModel(
|
||||
testScope: TestScope,
|
||||
statusSource: StatusSource,
|
||||
frozenState: TangemPayCardFrozenState,
|
||||
availableForWithdrawal: BigDecimal,
|
||||
accountError: PaymentAccountStatusValue.Error? = null,
|
||||
): TangemPayDetailsModel {
|
||||
val loaded: PaymentAccountStatusValue.Loaded = mockk(relaxed = true) {
|
||||
every { source } returns statusSource
|
||||
every { error } returns null
|
||||
every { error } returns accountError
|
||||
every { cards } returns listOf(tangemPayCard())
|
||||
every { balance } returns PaymentAccountStatusValue.Balance(
|
||||
fiatBalance = PaymentAccountStatusValue.FiatBalance(
|
||||
|
|
@ -135,6 +157,12 @@ internal class TangemPayDetailsModelTest {
|
|||
val expectedWithdrawEnabled: Boolean,
|
||||
)
|
||||
|
||||
internal data class MutedCase(
|
||||
val statusSource: StatusSource,
|
||||
val expectedMuted: Boolean,
|
||||
val accountError: PaymentAccountStatusValue.Error? = null,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
@JvmStatic
|
||||
fun provideFreezeCases() = listOf(
|
||||
|
|
@ -167,5 +195,18 @@ internal class TangemPayDetailsModelTest {
|
|||
expectedWithdrawEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun provideMutedCases() = listOf(
|
||||
MutedCase(statusSource = StatusSource.ACTUAL, expectedMuted = false),
|
||||
MutedCase(statusSource = StatusSource.CACHE, expectedMuted = true),
|
||||
MutedCase(statusSource = StatusSource.ONLY_CACHE, expectedMuted = true),
|
||||
// ACTUAL but errored is still not fresh -> muted (guards !isFresh, not just source != ACTUAL)
|
||||
MutedCase(
|
||||
statusSource = StatusSource.ACTUAL,
|
||||
accountError = PaymentAccountStatusValue.Error.Unavailable,
|
||||
expectedMuted = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue