Updated on 2026-08-14
This commit is contained in:
parent
1edf995ab0
commit
2dadb60975
26 changed files with 1110 additions and 48 deletions
|
|
@ -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> {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getCacheKey(userWalletId = userWalletId, cursor = cursor),
|
||||
skipCache = config.shouldRefresh,
|
||||
block = { fetch(userWalletId = userWalletId, cursor = cursor, pageSize = limit) },
|
||||
)
|
||||
val storeKey = userWalletId.stringValue
|
||||
val storeCursor = cursor ?: INITIAL_CURSOR
|
||||
|
||||
return txHistoryItemsStore.getSyncOrNull(
|
||||
key = userWalletId.stringValue,
|
||||
cursor = cursor ?: INITIAL_CURSOR,
|
||||
).orEmpty()
|
||||
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 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")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue