Updated on 2026-08-14
This commit is contained in:
parent
47f1ccb854
commit
8a83664e9a
19 changed files with 218 additions and 39 deletions
|
|
@ -10,15 +10,15 @@ internal class DefaultTangemPayCloseCardStore(
|
||||||
private val prefs: AppPreferencesStore,
|
private val prefs: AppPreferencesStore,
|
||||||
) : TangemPayCloseCardStore {
|
) : TangemPayCloseCardStore {
|
||||||
|
|
||||||
override suspend fun setCloseOrderId(cardId: String, orderId: String?) {
|
override suspend fun storeCloseOrderId(cardId: String, orderId: String) {
|
||||||
if (orderId == null) {
|
prefs.store(
|
||||||
prefs.edit { it.remove(getCloseKey(cardId)) }
|
key = getCloseKey(cardId),
|
||||||
} else {
|
value = orderId,
|
||||||
prefs.store(
|
)
|
||||||
key = getCloseKey(cardId),
|
}
|
||||||
value = orderId,
|
|
||||||
)
|
override suspend fun removeCloseOrderId(cardId: String) {
|
||||||
}
|
prefs.edit { it.remove(getCloseKey(cardId)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getOrderId(cardId: String): String? {
|
override suspend fun getOrderId(cardId: String): String? {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.datasource.local.visa
|
package com.tangem.datasource.local.visa
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
|
@ -31,6 +32,10 @@ internal class DefaultTangemPayReissueCardStore(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun removeReissueOrderId(cardId: String) {
|
||||||
|
prefs.edit { it.remove(getReissueKey(cardId)) }
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getOrderId(cardId: String): String? {
|
override suspend fun getOrderId(cardId: String): String? {
|
||||||
return prefs.getSyncOrNull(key = getReissueKey(cardId))
|
return prefs.getSyncOrNull(key = getReissueKey(cardId))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package com.tangem.datasource.local.visa
|
||||||
|
|
||||||
interface TangemPayCloseCardStore {
|
interface TangemPayCloseCardStore {
|
||||||
|
|
||||||
suspend fun setCloseOrderId(cardId: String, orderId: String?)
|
suspend fun storeCloseOrderId(cardId: String, orderId: String)
|
||||||
|
|
||||||
|
suspend fun removeCloseOrderId(cardId: String)
|
||||||
|
|
||||||
suspend fun getOrderId(cardId: String): String?
|
suspend fun getOrderId(cardId: String): String?
|
||||||
}
|
}
|
||||||
|
|
@ -11,5 +11,7 @@ interface TangemPayReissueCardStore {
|
||||||
|
|
||||||
suspend fun storeReissueOrderId(cardId: String, orderId: String)
|
suspend fun storeReissueOrderId(cardId: String, orderId: String)
|
||||||
|
|
||||||
|
suspend fun removeReissueOrderId(cardId: String)
|
||||||
|
|
||||||
suspend fun getOrderId(cardId: String): String?
|
suspend fun getOrderId(cardId: String): String?
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.tangem.datasource.local.visa
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.emptyPreferences
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.domain.models.pay.TangemPayReissueCardFee
|
||||||
|
import com.tangem.test.core.datastore.MockStateDataStore
|
||||||
|
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for [DefaultTangemPayReissueCardStore], focused on the reissue order-id lifecycle.
|
||||||
|
*
|
||||||
|
* Uses a real [AppPreferencesStore] backed by an in-memory [MockStateDataStore] so the preferences
|
||||||
|
* round-trip (store / read / remove) is exercised end-to-end. The remove path backs the [REDACTED_TASK_KEY] fix:
|
||||||
|
* a terminal reissue order must be forgotten so the payment-account refresh stops re-polling
|
||||||
|
* `GET /order/{id}` for it.
|
||||||
|
*/
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
internal class DefaultTangemPayReissueCardStoreTest {
|
||||||
|
|
||||||
|
private val dataStore = MockStateDataStore(default = emptyPreferences())
|
||||||
|
private val prefs = AppPreferencesStore(
|
||||||
|
moshi = Moshi.Builder().build(),
|
||||||
|
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||||
|
preferencesDataStore = dataStore,
|
||||||
|
)
|
||||||
|
private val feeStore: RuntimeDataStore<TangemPayReissueCardFee> = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private val store = DefaultTangemPayReissueCardStore(feeStore = feeStore, prefs = prefs)
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun resetStore() {
|
||||||
|
runBlocking { dataStore.updateData { emptyPreferences() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN order id stored WHEN getOrderId THEN returns stored id`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
store.storeReissueOrderId(CARD_ID, ORDER_ID)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = store.getOrderId(CARD_ID)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isEqualTo(ORDER_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN order id stored WHEN removeReissueOrderId THEN order id is cleared`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
store.storeReissueOrderId(CARD_ID, ORDER_ID)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
store.removeReissueOrderId(CARD_ID)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(store.getOrderId(CARD_ID)).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN clearing one card WHEN another card has an order THEN the other is untouched`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
store.storeReissueOrderId(CARD_ID, ORDER_ID)
|
||||||
|
store.storeReissueOrderId(OTHER_CARD_ID, OTHER_ORDER_ID)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
store.removeReissueOrderId(CARD_ID)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(store.getOrderId(CARD_ID)).isNull()
|
||||||
|
assertThat(store.getOrderId(OTHER_CARD_ID)).isEqualTo(OTHER_ORDER_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val CARD_ID = "card-1"
|
||||||
|
const val OTHER_CARD_ID = "card-2"
|
||||||
|
const val ORDER_ID = "reissue-order-1"
|
||||||
|
const val OTHER_ORDER_ID = "reissue-order-2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -462,7 +462,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
||||||
return if (closingOrderId != null) {
|
return if (closingOrderId != null) {
|
||||||
val order = cardDetailsRepository.getOrderInfo(userWalletId, closingOrderId).getOrNull()
|
val order = cardDetailsRepository.getOrderInfo(userWalletId, closingOrderId).getOrNull()
|
||||||
if (order != null && order.orderStatus.isTerminal) {
|
if (order != null && order.orderStatus.isTerminal) {
|
||||||
closeCardRepository.setCloseOrderId(cardId, null)
|
closeCardRepository.removeCloseOrderId(cardId)
|
||||||
TangemPayCardState.Active
|
TangemPayCardState.Active
|
||||||
} else {
|
} else {
|
||||||
TangemPayCardState.Closing
|
TangemPayCardState.Closing
|
||||||
|
|
@ -470,6 +470,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
||||||
} else if (reissueOrderId != null) {
|
} else if (reissueOrderId != null) {
|
||||||
val order = cardDetailsRepository.getOrderInfo(userWalletId, reissueOrderId).getOrNull()
|
val order = cardDetailsRepository.getOrderInfo(userWalletId, reissueOrderId).getOrNull()
|
||||||
if (order != null && order.orderStatus.isTerminal) {
|
if (order != null && order.orderStatus.isTerminal) {
|
||||||
|
reissueCardRepository.removeReissueOrderId(cardId)
|
||||||
TangemPayCardState.Active
|
TangemPayCardState.Active
|
||||||
} else {
|
} else {
|
||||||
TangemPayCardState.Reissuing
|
TangemPayCardState.Reissuing
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,21 @@ internal class DefaultCloseCardRepository @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setCloseOrderId(cardId: String, orderId: String?): Either<UniversalError, Unit> =
|
override suspend fun storeCloseOrderId(cardId: String, orderId: String): Either<UniversalError, Unit> =
|
||||||
runSuspendCatching {
|
runSuspendCatching {
|
||||||
tangemPayCloseCardStore.setCloseOrderId(cardId, orderId)
|
tangemPayCloseCardStore.storeCloseOrderId(cardId, orderId)
|
||||||
}.fold(
|
}.fold(
|
||||||
onSuccess = { Unit.right() },
|
onSuccess = { Unit.right() },
|
||||||
onFailure = { Either.Left(VisaApiError.Unspecified) },
|
onFailure = { Either.Left(VisaApiError.Unspecified) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun removeCloseOrderId(cardId: String): Either<UniversalError, Unit> = runSuspendCatching {
|
||||||
|
tangemPayCloseCardStore.removeCloseOrderId(cardId)
|
||||||
|
}.fold(
|
||||||
|
onSuccess = { Unit.right() },
|
||||||
|
onFailure = { Either.Left(VisaApiError.Unspecified) },
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun getCloseOrderId(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?> =
|
override suspend fun getCloseOrderId(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?> =
|
||||||
either {
|
either {
|
||||||
runSuspendCatching { tangemPayCloseCardStore.getOrderId(cardId) }.getOrNull()
|
runSuspendCatching { tangemPayCloseCardStore.getOrderId(cardId) }.getOrNull()
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,13 @@ internal class DefaultReissueCardRepository @Inject constructor(
|
||||||
onFailure = { Either.Left(VisaApiError.Unspecified) },
|
onFailure = { Either.Left(VisaApiError.Unspecified) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun removeReissueOrderId(cardId: String): Either<UniversalError, Unit> = runSuspendCatching {
|
||||||
|
tangemPayReissueCardStore.removeReissueOrderId(cardId)
|
||||||
|
}.fold(
|
||||||
|
onSuccess = { Unit.right() },
|
||||||
|
onFailure = { Either.Left(VisaApiError.Unspecified) },
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun getReissueOrderId(
|
override suspend fun getReissueOrderId(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cardId: String,
|
cardId: String,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ interface TangemPayCloseCardRepository {
|
||||||
|
|
||||||
suspend fun closeCard(userWalletId: UserWalletId, cardId: String): Either<VisaApiError, TangemPayOrderInfo>
|
suspend fun closeCard(userWalletId: UserWalletId, cardId: String): Either<VisaApiError, TangemPayOrderInfo>
|
||||||
|
|
||||||
suspend fun setCloseOrderId(cardId: String, orderId: String?): Either<UniversalError, Unit>
|
suspend fun storeCloseOrderId(cardId: String, orderId: String): Either<UniversalError, Unit>
|
||||||
|
|
||||||
|
suspend fun removeCloseOrderId(cardId: String): Either<UniversalError, Unit>
|
||||||
|
|
||||||
suspend fun getCloseOrderId(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?>
|
suspend fun getCloseOrderId(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?>
|
||||||
}
|
}
|
||||||
|
|
@ -15,5 +15,7 @@ interface TangemPayReissueCardRepository {
|
||||||
|
|
||||||
suspend fun storeReissueOrderId(cardId: String, orderId: String): Either<UniversalError, Unit>
|
suspend fun storeReissueOrderId(cardId: String, orderId: String): Either<UniversalError, Unit>
|
||||||
|
|
||||||
|
suspend fun removeReissueOrderId(cardId: String): Either<UniversalError, Unit>
|
||||||
|
|
||||||
suspend fun getReissueOrderId(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?>
|
suspend fun getReissueOrderId(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?>
|
||||||
}
|
}
|
||||||
|
|
@ -23,11 +23,15 @@ class CloseTangemPayCardUseCase(
|
||||||
raise(VisaApiError.Unspecified)
|
raise(VisaApiError.Unspecified)
|
||||||
}
|
}
|
||||||
|
|
||||||
closeCardRepository.setCloseOrderId(cardId, order.orderId)
|
closeCardRepository.storeCloseOrderId(cardId, order.orderId)
|
||||||
paymentAccountStatusFetcher.invoke(userWalletId)
|
paymentAccountStatusFetcher.invoke(userWalletId)
|
||||||
|
|
||||||
appCoroutineScope.launch {
|
appCoroutineScope.launch {
|
||||||
startTangemPayOrderPollingUseCase(order, userWalletId)
|
startTangemPayOrderPollingUseCase(
|
||||||
|
order = order,
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
onTerminalReached = { closeCardRepository.removeCloseOrderId(cardId) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +79,7 @@ class IssueAdditionalCardUseCase(
|
||||||
startTangemPayOrderPollingUseCase(
|
startTangemPayOrderPollingUseCase(
|
||||||
order = TangemPayOrderInfo(orderId = order.id, orderStatus = order.status),
|
order = TangemPayOrderInfo(orderId = order.id, orderStatus = order.status),
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
|
onTerminalReached = { issueCardRepository.removeIssueOrderId(userWalletId, order.id) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,11 @@ class ReissueTangemPayCardUseCase(
|
||||||
paymentAccountStatusFetcher.invoke(userWalletId)
|
paymentAccountStatusFetcher.invoke(userWalletId)
|
||||||
|
|
||||||
appCoroutineScope.launch {
|
appCoroutineScope.launch {
|
||||||
startTangemPayOrderPollingUseCase(order, userWalletId)
|
startTangemPayOrderPollingUseCase(
|
||||||
|
order = order,
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
onTerminalReached = { reissueCardRepository.removeReissueOrderId(cardId) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,6 +56,7 @@ class RestoreActiveIssueOrdersUseCase(
|
||||||
startTangemPayOrderPollingUseCase(
|
startTangemPayOrderPollingUseCase(
|
||||||
order = TangemPayOrderInfo(orderId = order.id, orderStatus = order.status),
|
order = TangemPayOrderInfo(orderId = order.id, orderStatus = order.status),
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
|
onTerminalReached = { issueCardRepository.removeIssueOrderId(userWalletId, order.id) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,16 @@ class StartTangemPayOrderPollingUseCase(
|
||||||
*/
|
*/
|
||||||
private val activeOrders = ConcurrentHashMap.newKeySet<String>()
|
private val activeOrders = ConcurrentHashMap.newKeySet<String>()
|
||||||
|
|
||||||
suspend operator fun invoke(order: TangemPayOrderInfo, userWalletId: UserWalletId): Boolean {
|
/**
|
||||||
|
* @param onTerminalReached invoked once the order is terminal, **before** the status refresh. Callers
|
||||||
|
* use it to forget the locally stored order-id hint (issue / reissue / close) so the refresh does not
|
||||||
|
* re-issue a `GET /order/{id}` for the order that was just resolved.
|
||||||
|
*/
|
||||||
|
suspend operator fun invoke(
|
||||||
|
order: TangemPayOrderInfo,
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
onTerminalReached: (suspend () -> Unit)? = null,
|
||||||
|
): Boolean {
|
||||||
// A poller for this exact order is already running — `false` only reaches fire-and-forget issue
|
// A poller for this exact order is already running — `false` only reaches fire-and-forget issue
|
||||||
// callers (restore / issue-additional); the awaiting freeze caller always polls a fresh order id.
|
// callers (restore / issue-additional); the awaiting freeze caller always polls a fresh order id.
|
||||||
val key = "${userWalletId.stringValue}:${order.orderId}"
|
val key = "${userWalletId.stringValue}:${order.orderId}"
|
||||||
|
|
@ -35,6 +44,7 @@ class StartTangemPayOrderPollingUseCase(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newOrder != null && newOrder.orderStatus.isTerminal) {
|
if (newOrder != null && newOrder.orderStatus.isTerminal) {
|
||||||
|
onTerminalReached?.invoke()
|
||||||
paymentAccountStatusFetcher.invoke(userWalletId)
|
paymentAccountStatusFetcher.invoke(userWalletId)
|
||||||
return newOrder.orderStatus == OrderStatus.COMPLETED
|
return newOrder.orderStatus == OrderStatus.COMPLETED
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ internal class ChangeCardFrozenStateUseCaseTest {
|
||||||
cardDetailsRepository.setCardFrozenState(CARD_ID, TangemPayCardFrozenState.Pending)
|
cardDetailsRepository.setCardFrozenState(CARD_ID, TangemPayCardFrozenState.Pending)
|
||||||
cardDetailsRepository.setCardFrozenState(CARD_ID, TangemPayCardFrozenState.Unfrozen)
|
cardDetailsRepository.setCardFrozenState(CARD_ID, TangemPayCardFrozenState.Unfrozen)
|
||||||
}
|
}
|
||||||
coVerify(exactly = 0) { startPollingUseCase(any(), any()) }
|
coVerify(exactly = 0) { startPollingUseCase(any(), any(), any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -56,7 +56,7 @@ internal class ChangeCardFrozenStateUseCaseTest {
|
||||||
cardDetailsRepository.setCardFrozenState(CARD_ID, TangemPayCardFrozenState.Pending)
|
cardDetailsRepository.setCardFrozenState(CARD_ID, TangemPayCardFrozenState.Pending)
|
||||||
cardDetailsRepository.setCardFrozenState(CARD_ID, TangemPayCardFrozenState.Frozen)
|
cardDetailsRepository.setCardFrozenState(CARD_ID, TangemPayCardFrozenState.Frozen)
|
||||||
}
|
}
|
||||||
coVerify(exactly = 0) { startPollingUseCase(any(), any()) }
|
coVerify(exactly = 0) { startPollingUseCase(any(), any(), any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -65,7 +65,7 @@ internal class ChangeCardFrozenStateUseCaseTest {
|
||||||
val useCase = createUseCase()
|
val useCase = createUseCase()
|
||||||
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.COMPLETED)
|
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.COMPLETED)
|
||||||
coEvery { cardDetailsRepository.freezeCard(USER_WALLET_ID, CARD_ID) } returns order.right()
|
coEvery { cardDetailsRepository.freezeCard(USER_WALLET_ID, CARD_ID) } returns order.right()
|
||||||
coEvery { startPollingUseCase(order, USER_WALLET_ID) } returns true
|
coEvery { startPollingUseCase(order, USER_WALLET_ID, any()) } returns true
|
||||||
|
|
||||||
val result = useCase(USER_WALLET_ID, CARD_ID, isFreezing = true)
|
val result = useCase(USER_WALLET_ID, CARD_ID, isFreezing = true)
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ internal class ChangeCardFrozenStateUseCaseTest {
|
||||||
val useCase = createUseCase()
|
val useCase = createUseCase()
|
||||||
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.COMPLETED)
|
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.COMPLETED)
|
||||||
coEvery { cardDetailsRepository.unfreezeCard(USER_WALLET_ID, CARD_ID) } returns order.right()
|
coEvery { cardDetailsRepository.unfreezeCard(USER_WALLET_ID, CARD_ID) } returns order.right()
|
||||||
coEvery { startPollingUseCase(order, USER_WALLET_ID) } returns true
|
coEvery { startPollingUseCase(order, USER_WALLET_ID, any()) } returns true
|
||||||
|
|
||||||
val result = useCase(USER_WALLET_ID, CARD_ID, isFreezing = false)
|
val result = useCase(USER_WALLET_ID, CARD_ID, isFreezing = false)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ internal class CloseTangemPayCardUseCaseTest {
|
||||||
val result = useCase(USER_WALLET_ID, CARD_ID)
|
val result = useCase(USER_WALLET_ID, CARD_ID)
|
||||||
|
|
||||||
assertThat(result.isLeft()).isTrue()
|
assertThat(result.isLeft()).isTrue()
|
||||||
coVerify(exactly = 0) { closeCardRepository.setCloseOrderId(any(), any()) }
|
coVerify(exactly = 0) { closeCardRepository.storeCloseOrderId(any(), any()) }
|
||||||
coVerify(exactly = 0) { paymentAccountStatusFetcher.invoke(any<UserWalletId>()) }
|
coVerify(exactly = 0) { paymentAccountStatusFetcher.invoke(any<UserWalletId>()) }
|
||||||
coVerify(exactly = 0) { startPollingUseCase(any(), any()) }
|
coVerify(exactly = 0) { startPollingUseCase(any(), any(), any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -46,9 +46,9 @@ internal class CloseTangemPayCardUseCaseTest {
|
||||||
val result = useCase(USER_WALLET_ID, CARD_ID)
|
val result = useCase(USER_WALLET_ID, CARD_ID)
|
||||||
|
|
||||||
assertThat(result.isLeft()).isTrue()
|
assertThat(result.isLeft()).isTrue()
|
||||||
coVerify(exactly = 0) { closeCardRepository.setCloseOrderId(any(), any()) }
|
coVerify(exactly = 0) { closeCardRepository.storeCloseOrderId(any(), any()) }
|
||||||
coVerify(exactly = 0) { paymentAccountStatusFetcher.invoke(any<UserWalletId>()) }
|
coVerify(exactly = 0) { paymentAccountStatusFetcher.invoke(any<UserWalletId>()) }
|
||||||
coVerify(exactly = 0) { startPollingUseCase(any(), any()) }
|
coVerify(exactly = 0) { startPollingUseCase(any(), any(), any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -57,17 +57,17 @@ internal class CloseTangemPayCardUseCaseTest {
|
||||||
val useCase = createUseCase()
|
val useCase = createUseCase()
|
||||||
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.PROCESSING)
|
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.PROCESSING)
|
||||||
coEvery { closeCardRepository.closeCard(USER_WALLET_ID, CARD_ID) } returns order.right()
|
coEvery { closeCardRepository.closeCard(USER_WALLET_ID, CARD_ID) } returns order.right()
|
||||||
coEvery { closeCardRepository.setCloseOrderId(CARD_ID, ORDER_ID) } returns Unit.right()
|
coEvery { closeCardRepository.storeCloseOrderId(CARD_ID, ORDER_ID) } returns Unit.right()
|
||||||
coEvery { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) } returns Unit.right()
|
coEvery { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) } returns Unit.right()
|
||||||
coEvery { startPollingUseCase(order, USER_WALLET_ID) } returns true
|
coEvery { startPollingUseCase(order, USER_WALLET_ID, any()) } returns true
|
||||||
|
|
||||||
val result = useCase(USER_WALLET_ID, CARD_ID)
|
val result = useCase(USER_WALLET_ID, CARD_ID)
|
||||||
|
|
||||||
assertThat(result.isRight()).isTrue()
|
assertThat(result.isRight()).isTrue()
|
||||||
coVerifyOrder {
|
coVerifyOrder {
|
||||||
closeCardRepository.setCloseOrderId(CARD_ID, ORDER_ID)
|
closeCardRepository.storeCloseOrderId(CARD_ID, ORDER_ID)
|
||||||
paymentAccountStatusFetcher.invoke(USER_WALLET_ID)
|
paymentAccountStatusFetcher.invoke(USER_WALLET_ID)
|
||||||
startPollingUseCase(order, USER_WALLET_ID)
|
startPollingUseCase(order, USER_WALLET_ID, any())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,16 +77,16 @@ internal class CloseTangemPayCardUseCaseTest {
|
||||||
val useCase = createUseCase()
|
val useCase = createUseCase()
|
||||||
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.COMPLETED)
|
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.COMPLETED)
|
||||||
coEvery { closeCardRepository.closeCard(USER_WALLET_ID, CARD_ID) } returns order.right()
|
coEvery { closeCardRepository.closeCard(USER_WALLET_ID, CARD_ID) } returns order.right()
|
||||||
coEvery { closeCardRepository.setCloseOrderId(CARD_ID, ORDER_ID) } returns Unit.right()
|
coEvery { closeCardRepository.storeCloseOrderId(CARD_ID, ORDER_ID) } returns Unit.right()
|
||||||
coEvery { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) } returns Unit.right()
|
coEvery { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) } returns Unit.right()
|
||||||
coEvery { startPollingUseCase(order, USER_WALLET_ID) } returns true
|
coEvery { startPollingUseCase(order, USER_WALLET_ID, any()) } returns true
|
||||||
|
|
||||||
val result = useCase(USER_WALLET_ID, CARD_ID)
|
val result = useCase(USER_WALLET_ID, CARD_ID)
|
||||||
|
|
||||||
assertThat(result.isRight()).isTrue()
|
assertThat(result.isRight()).isTrue()
|
||||||
coVerify(exactly = 1) { closeCardRepository.setCloseOrderId(CARD_ID, ORDER_ID) }
|
coVerify(exactly = 1) { closeCardRepository.storeCloseOrderId(CARD_ID, ORDER_ID) }
|
||||||
coVerify(exactly = 1) { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) }
|
coVerify(exactly = 1) { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) }
|
||||||
coVerify(exactly = 1) { startPollingUseCase(order, USER_WALLET_ID) }
|
coVerify(exactly = 1) { startPollingUseCase(order, USER_WALLET_ID, any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createUseCase() = CloseTangemPayCardUseCase(
|
private fun createUseCase() = CloseTangemPayCardUseCase(
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,10 @@ internal class RestoreActiveIssueOrdersUseCaseTest {
|
||||||
coVerify(exactly = 1) { issueCardRepository.storeIssueOrderId(userWalletId, first.id) }
|
coVerify(exactly = 1) { issueCardRepository.storeIssueOrderId(userWalletId, first.id) }
|
||||||
coVerify(exactly = 1) { issueCardRepository.storeIssueOrderId(userWalletId, second.id) }
|
coVerify(exactly = 1) { issueCardRepository.storeIssueOrderId(userWalletId, second.id) }
|
||||||
coVerify(exactly = 1) {
|
coVerify(exactly = 1) {
|
||||||
startTangemPayOrderPollingUseCase(TangemPayOrderInfo(first.id, first.status), userWalletId)
|
startTangemPayOrderPollingUseCase(TangemPayOrderInfo(first.id, first.status), userWalletId, any())
|
||||||
}
|
}
|
||||||
coVerify(exactly = 1) {
|
coVerify(exactly = 1) {
|
||||||
startTangemPayOrderPollingUseCase(TangemPayOrderInfo(second.id, second.status), userWalletId)
|
startTangemPayOrderPollingUseCase(TangemPayOrderInfo(second.id, second.status), userWalletId, any())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ internal class RestoreActiveIssueOrdersUseCaseTest {
|
||||||
// Assert
|
// Assert
|
||||||
assertThat(result.isRight()).isTrue()
|
assertThat(result.isRight()).isTrue()
|
||||||
coVerify(exactly = 0) { issueCardRepository.storeIssueOrderId(any(), any()) }
|
coVerify(exactly = 0) { issueCardRepository.storeIssueOrderId(any(), any()) }
|
||||||
coVerify(exactly = 0) { startTangemPayOrderPollingUseCase(any(), any()) }
|
coVerify(exactly = 0) { startTangemPayOrderPollingUseCase(any(), any(), any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -89,7 +89,7 @@ internal class RestoreActiveIssueOrdersUseCaseTest {
|
||||||
// Assert
|
// Assert
|
||||||
assertThat(result.isRight()).isTrue()
|
assertThat(result.isRight()).isTrue()
|
||||||
coVerify(exactly = 0) { issueCardRepository.storeIssueOrderId(any(), any()) }
|
coVerify(exactly = 0) { issueCardRepository.storeIssueOrderId(any(), any()) }
|
||||||
coVerify(exactly = 0) { startTangemPayOrderPollingUseCase(any(), any()) }
|
coVerify(exactly = 0) { startTangemPayOrderPollingUseCase(any(), any(), any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -105,7 +105,7 @@ internal class RestoreActiveIssueOrdersUseCaseTest {
|
||||||
// Assert
|
// Assert
|
||||||
assertThat(result.leftOrNull()).isEqualTo(VisaApiError.Unspecified)
|
assertThat(result.leftOrNull()).isEqualTo(VisaApiError.Unspecified)
|
||||||
coVerify(exactly = 0) { issueCardRepository.storeIssueOrderId(any(), any()) }
|
coVerify(exactly = 0) { issueCardRepository.storeIssueOrderId(any(), any()) }
|
||||||
coVerify(exactly = 0) { startTangemPayOrderPollingUseCase(any(), any()) }
|
coVerify(exactly = 0) { startTangemPayOrderPollingUseCase(any(), any(), any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun order(id: String, type: OrderType, status: OrderStatus): Order = Order(
|
private fun order(id: String, type: OrderType, status: OrderStatus): Order = Order(
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,49 @@ internal class StartTangemPayOrderPollingUseCaseTest {
|
||||||
firstPoller.cancel()
|
firstPoller.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN processing order WHEN poll returns terminal THEN onTerminalReached runs before status fetch`() = runTest {
|
||||||
|
// Arrange — recording both callbacks proves the order hint is cleared before the refresh that
|
||||||
|
// would otherwise re-poll GET /order/{id} for the just-resolved order.
|
||||||
|
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.PROCESSING)
|
||||||
|
val events = mutableListOf<String>()
|
||||||
|
coEvery {
|
||||||
|
cardDetailsRepository.getOrderInfo(USER_WALLET_ID, ORDER_ID)
|
||||||
|
} returns TangemPayOrderInfo(ORDER_ID, OrderStatus.COMPLETED).right()
|
||||||
|
coEvery { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) } answers {
|
||||||
|
events.add("fetch")
|
||||||
|
Unit.right()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = useCase(order, USER_WALLET_ID, onTerminalReached = { events.add("clear") })
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isTrue()
|
||||||
|
assertThat(events).containsExactly("clear", "fetch").inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN order already being polled WHEN invoke again THEN onTerminalReached is not invoked for duplicate`() =
|
||||||
|
runTest {
|
||||||
|
// Arrange — first poller never reaches terminal, so it keeps polling.
|
||||||
|
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.PROCESSING)
|
||||||
|
coEvery { cardDetailsRepository.getOrderInfo(USER_WALLET_ID, ORDER_ID) } returns
|
||||||
|
TangemPayOrderInfo(ORDER_ID, OrderStatus.PROCESSING).right()
|
||||||
|
var duplicateCleared = false
|
||||||
|
|
||||||
|
// Act — start the first poller, then invoke again for the same order.
|
||||||
|
val firstPoller = launch { useCase(order, USER_WALLET_ID) }
|
||||||
|
runCurrent()
|
||||||
|
val secondResult = useCase(order, USER_WALLET_ID, onTerminalReached = { duplicateCleared = true })
|
||||||
|
|
||||||
|
// Assert — the duplicate is a no-op: it must not clear the hint of the live poller.
|
||||||
|
assertThat(secondResult).isFalse()
|
||||||
|
assertThat(duplicateCleared).isFalse()
|
||||||
|
|
||||||
|
firstPoller.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
val USER_WALLET_ID = UserWalletId("aabbcc112233")
|
val USER_WALLET_ID = UserWalletId("aabbcc112233")
|
||||||
const val ORDER_ID = "order-test-1"
|
const val ORDER_ID = "order-test-1"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue