From 44c240ae01d09c3a123e266ffe04b4f1b5c44ef5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Jun 2026 16:56:56 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../DefaultCustomerOffersRepository.kt | 9 ++ .../DefaultCustomerOffersRepositoryTest.kt | 101 ++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultCustomerOffersRepositoryTest.kt diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOffersRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOffersRepository.kt index f50aa806e6..eb8f689971 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOffersRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOffersRepository.kt @@ -1,8 +1,10 @@ package com.tangem.data.pay.repository import arrow.core.Either +import arrow.core.right import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.response.CustomerOffersResponse +import com.tangem.datasource.local.datastore.RuntimeSharedStore import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.model.Offer import com.tangem.domain.pay.model.OrderType @@ -16,11 +18,18 @@ internal class DefaultCustomerOffersRepository @Inject constructor( private val requestHelper: TangemPayRequestPerformer, ) : CustomerOffersRepository { + private val offersCache = RuntimeSharedStore>>() + override suspend fun getOffers(userWalletId: UserWalletId): Either> { + val key = userWalletId.stringValue + offersCache.getSyncOrNull()?.get(key)?.let { return it.right() } + return requestHelper.performRequest(userWalletId) { authHeader -> tangemPayApi.getCustomerOffers(authHeader = authHeader) }.map { response -> response.result.map { it.toDomain() } + }.onRight { offers -> + offersCache.update(default = emptyMap()) { it + (key to offers) } } } diff --git a/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultCustomerOffersRepositoryTest.kt b/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultCustomerOffersRepositoryTest.kt new file mode 100644 index 0000000000..c4a97ce566 --- /dev/null +++ b/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultCustomerOffersRepositoryTest.kt @@ -0,0 +1,101 @@ +package com.tangem.data.pay.repository + +import arrow.core.left +import arrow.core.right +import com.google.common.truth.Truth.assertThat +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.common.response.ApiResponseError +import com.tangem.datasource.api.pay.TangemPayApi +import com.tangem.datasource.api.pay.models.response.CustomerOffersResponse +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.visa.error.VisaApiError +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +internal class DefaultCustomerOffersRepositoryTest { + + private val tangemPayApi: TangemPayApi = mockk() + private val requestHelper: TangemPayRequestPerformer = mockk() + + private val userWalletId = UserWalletId("011") + + private val offersResponse = CustomerOffersResponse( + result = listOf( + CustomerOffersResponse.Offer( + type = "CARD_ISSUE_VIRTUAL_RAIN", + fee = CustomerOffersResponse.Fee(amount = BigDecimal("1.00"), currency = "USD"), + data = CustomerOffersResponse.Data( + specificationName = "SP_000004", + orderType = "CARD_ISSUE_ADDITIONAL", + ), + ), + ), + ) + + @BeforeEach + fun setUp() { + clearMocks(tangemPayApi, requestHelper) + + // performRequest is a transparent pass-through: it invokes the request block and maps the + // ApiResponse to Either, so each test drives behaviour via the TangemPayApi mock. + coEvery { + requestHelper.performRequest(userWalletId = any(), requestBlock = any()) + } coAnswers { + val block = secondArg ApiResponse>() + when (val response = block(AUTH_HEADER)) { + is ApiResponse.Success -> response.data.right() + is ApiResponse.Error -> VisaApiError.Unspecified.left() + } + } + coEvery { tangemPayApi.getCustomerOffers(any()) } returns ApiResponse.Success(offersResponse) + } + + @Test + fun `GIVEN offers fetched once WHEN getOffers called twice THEN backend is hit only once`() = runTest { + // Arrange + val repository = createRepository() + + // Act + val first = repository.getOffers(userWalletId) + val second = repository.getOffers(userWalletId) + + // Assert + assertThat(first.isRight()).isTrue() + assertThat(second).isEqualTo(first) + coVerify(exactly = 1) { tangemPayApi.getCustomerOffers(any()) } + } + + @Test + fun `GIVEN backend error WHEN getOffers called again THEN error is not cached and backend is hit again`() = runTest { + // Arrange + coEvery { tangemPayApi.getCustomerOffers(any()) } returnsMany listOf( + ApiResponse.Error(ApiResponseError.NetworkException()) as ApiResponse, + ApiResponse.Success(offersResponse), + ) + val repository = createRepository() + + // Act + val first = repository.getOffers(userWalletId) + val second = repository.getOffers(userWalletId) + + // Assert + assertThat(first.isLeft()).isTrue() + assertThat(second.isRight()).isTrue() + coVerify(exactly = 2) { tangemPayApi.getCustomerOffers(any()) } + } + + private fun createRepository() = DefaultCustomerOffersRepository( + tangemPayApi = tangemPayApi, + requestHelper = requestHelper, + ) + + private companion object { + const val AUTH_HEADER = "auth-header" + } +} \ No newline at end of file