Updated on 2026-08-14
This commit is contained in:
parent
5ce84d1500
commit
f2891683e1
8 changed files with 780 additions and 0 deletions
|
|
@ -0,0 +1,106 @@
|
||||||
|
package com.tangem.data.pay.repository
|
||||||
|
|
||||||
|
import arrow.core.left
|
||||||
|
import arrow.core.right
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.data.pay.util.CashbackAccrualDocsConverter
|
||||||
|
import com.tangem.data.pay.util.CashbackPromotionsConverter
|
||||||
|
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.CashbackAccrualDocsResponse
|
||||||
|
import com.tangem.datasource.api.pay.models.response.CashbackPromotionsResponse
|
||||||
|
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.mockk
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class DefaultCashbackRepositoryTest {
|
||||||
|
|
||||||
|
private val tangemPayApi: TangemPayApi = mockk()
|
||||||
|
private val requestHelper: TangemPayRequestPerformer = mockk()
|
||||||
|
|
||||||
|
private val userWalletId = UserWalletId("011")
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
clearMocks(tangemPayApi, requestHelper)
|
||||||
|
coEvery {
|
||||||
|
requestHelper.performRequest<Any>(userWalletId = any(), requestBlock = any())
|
||||||
|
} coAnswers {
|
||||||
|
val block = secondArg<suspend (String) -> ApiResponse<Any>>()
|
||||||
|
when (val response = block(AUTH_HEADER)) {
|
||||||
|
is ApiResponse.Success -> response.data.right()
|
||||||
|
is ApiResponse.Error -> VisaApiError.Unspecified.left()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN promotions response WHEN getCashbackPromotions THEN converter result is returned`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
val response = CashbackPromotionsResponse(cashbackOnCards = null, additionalCashback = null)
|
||||||
|
coEvery { tangemPayApi.getCashbackPromotions(any(), any()) } returns ApiResponse.Success(response)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = createRepository().getCashbackPromotions(userWalletId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(actual).isEqualTo(CashbackPromotionsConverter.convert(response).right())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN backend error WHEN getCashbackPromotions THEN error is propagated`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
coEvery { tangemPayApi.getCashbackPromotions(any(), any()) } returns
|
||||||
|
ApiResponse.Error(ApiResponseError.NetworkException()) as ApiResponse<CashbackPromotionsResponse>
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = createRepository().getCashbackPromotions(userWalletId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(actual).isEqualTo(VisaApiError.Unspecified.left())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN docs response WHEN getCashbackAccrualDocs THEN converter result is returned`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
val response = CashbackAccrualDocsResponse(
|
||||||
|
docs = listOf(CashbackAccrualDocsResponse.Doc(id = "1", title = "Terms", url = "https://a")),
|
||||||
|
)
|
||||||
|
coEvery { tangemPayApi.getCashbackAccrualDocs(any(), any()) } returns ApiResponse.Success(response)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = createRepository().getCashbackAccrualDocs(userWalletId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(actual).isEqualTo(CashbackAccrualDocsConverter.convert(response).right())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN backend error WHEN getCashbackAccrualDocs THEN error is propagated`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
coEvery { tangemPayApi.getCashbackAccrualDocs(any(), any()) } returns
|
||||||
|
ApiResponse.Error(ApiResponseError.NetworkException()) as ApiResponse<CashbackAccrualDocsResponse>
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val actual = createRepository().getCashbackAccrualDocs(userWalletId)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(actual).isEqualTo(VisaApiError.Unspecified.left())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRepository() = DefaultCashbackRepository(
|
||||||
|
tangemPayApi = tangemPayApi,
|
||||||
|
requestHelper = requestHelper,
|
||||||
|
storage = mockk(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val AUTH_HEADER = "auth-header"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.tangem.data.pay.util
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.datasource.api.pay.models.response.CashbackAccrualDocsResponse
|
||||||
|
import com.tangem.datasource.api.pay.models.response.CashbackAccrualDocsResponse.Doc
|
||||||
|
import com.tangem.domain.pay.model.CashbackDocument
|
||||||
|
import com.tangem.test.core.ProvideTestModels
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
internal class CashbackAccrualDocsConverterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN valid docs WHEN convert THEN all docs mapped in order`() {
|
||||||
|
// Arrange
|
||||||
|
val response = response(
|
||||||
|
doc(id = "1", title = "All categories", url = "https://a"),
|
||||||
|
doc(id = "2", title = "Full terms", url = "https://b"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = CashbackAccrualDocsConverter.convert(response)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).containsExactly(
|
||||||
|
CashbackDocument(id = "1", title = "All categories", url = "https://a"),
|
||||||
|
CashbackDocument(id = "2", title = "Full terms", url = "https://b"),
|
||||||
|
).inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ProvideTestModels
|
||||||
|
fun `GIVEN doc missing a required field WHEN convert THEN it is dropped`(malformed: Doc) {
|
||||||
|
// Arrange
|
||||||
|
val response = response(doc(), malformed)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = CashbackAccrualDocsConverter.convert(response)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).containsExactly(CashbackDocument(id = "1", title = "Title", url = "https://a"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN null docs WHEN convert THEN empty list`() {
|
||||||
|
// Act
|
||||||
|
val result = CashbackAccrualDocsConverter.convert(CashbackAccrualDocsResponse(docs = null))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun provideTestModels() = listOf(
|
||||||
|
doc(id = null),
|
||||||
|
doc(title = null),
|
||||||
|
doc(url = null),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun response(vararg docs: Doc) = CashbackAccrualDocsResponse(docs = docs.toList())
|
||||||
|
|
||||||
|
private fun doc(id: String? = "1", title: String? = "Title", url: String? = "https://a") =
|
||||||
|
Doc(id = id, title = title, url = url)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package com.tangem.data.pay.util
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.datasource.api.pay.models.response.CashbackPromotionsResponse
|
||||||
|
import com.tangem.domain.pay.model.CashbackPromotions
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
internal class CashbackPromotionsConverterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN tiers WHEN convert THEN each tier mapped with its fields`() {
|
||||||
|
// Arrange
|
||||||
|
val response = response(tier(min = BigDecimal("30"), cap = BigDecimal("100")))
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = CashbackPromotionsConverter.convert(response)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isEqualTo(
|
||||||
|
CashbackPromotions(
|
||||||
|
cardTiers = listOf(
|
||||||
|
CashbackPromotions.CardTier(
|
||||||
|
tier = "basic",
|
||||||
|
label = "Basic",
|
||||||
|
scope = "All purchases",
|
||||||
|
minTransactionAmount = BigDecimal("30"),
|
||||||
|
monthlyCapAmount = BigDecimal("100"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN null cashbackOnCards WHEN convert THEN no card tiers`() {
|
||||||
|
// Arrange
|
||||||
|
val response = CashbackPromotionsResponse(cashbackOnCards = null, additionalCashback = null)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = CashbackPromotionsConverter.convert(response)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.cardTiers).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN tier with null strings WHEN convert THEN strings default to empty and amounts stay null`() {
|
||||||
|
// Arrange
|
||||||
|
val response = response(tier(tier = null, label = null, scope = null))
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = CashbackPromotionsConverter.convert(response)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.cardTiers).containsExactly(
|
||||||
|
CashbackPromotions.CardTier(
|
||||||
|
tier = "",
|
||||||
|
label = "",
|
||||||
|
scope = "",
|
||||||
|
minTransactionAmount = null,
|
||||||
|
monthlyCapAmount = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun response(vararg tiers: CashbackPromotionsResponse.CardTier) = CashbackPromotionsResponse(
|
||||||
|
cashbackOnCards = CashbackPromotionsResponse.CashbackOnCards(
|
||||||
|
tiers = tiers.toList(),
|
||||||
|
monthlyCapAmount = null,
|
||||||
|
monthlyCapCurrency = null,
|
||||||
|
),
|
||||||
|
additionalCashback = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun tier(
|
||||||
|
tier: String? = "basic",
|
||||||
|
label: String? = "Basic",
|
||||||
|
scope: String? = "All purchases",
|
||||||
|
min: BigDecimal? = null,
|
||||||
|
cap: BigDecimal? = null,
|
||||||
|
) = CashbackPromotionsResponse.CardTier(
|
||||||
|
tier = tier,
|
||||||
|
label = label,
|
||||||
|
scope = scope,
|
||||||
|
minTransactionAmount = min,
|
||||||
|
tierMonthlyCapAmount = cap,
|
||||||
|
promotionId = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.tangem.features.tangempay.cashback.impl.model
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.domain.pay.model.CashbackDocument
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class TangemPayCashbackAccrualsConverterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN docs present WHEN convert THEN doc rows carry titles and info rows are static`() {
|
||||||
|
// Arrange
|
||||||
|
val converter = TangemPayCashbackAccrualsConverter(onDocClick = {})
|
||||||
|
val docs = listOf(
|
||||||
|
CashbackDocument(id = "1", title = "All categories without cashback", url = "https://a"),
|
||||||
|
CashbackDocument(id = "2", title = "Full terms of cashback program", url = "https://b"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(docs)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.title).isEqualTo(stringReference("Accruals"))
|
||||||
|
assertThat(result.infoRows).hasSize(3)
|
||||||
|
assertThat(result.docRows.map { it.title }).containsExactly(
|
||||||
|
stringReference("All categories without cashback"),
|
||||||
|
stringReference("Full terms of cashback program"),
|
||||||
|
).inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN empty docs WHEN convert THEN no doc rows but info rows remain`() {
|
||||||
|
// Arrange
|
||||||
|
val converter = TangemPayCashbackAccrualsConverter(onDocClick = {})
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(emptyList())
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.docRows).isEmpty()
|
||||||
|
assertThat(result.infoRows).hasSize(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN a doc row WHEN its onClick invoked THEN the doc url is opened`() {
|
||||||
|
// Arrange
|
||||||
|
var openedUrl: String? = null
|
||||||
|
val converter = TangemPayCashbackAccrualsConverter(onDocClick = { openedUrl = it })
|
||||||
|
val docs = listOf(CashbackDocument(id = "1", title = "Terms", url = "https://terms"))
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(docs)
|
||||||
|
result.docRows.single().onClick()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(openedUrl).isEqualTo("https://terms")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package com.tangem.features.tangempay.cashback.impl.model
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class TangemPayCashbackDetailsConverterTest {
|
||||||
|
|
||||||
|
private val converter = TangemPayCashbackDetailsConverter()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN single tier WHEN convert THEN title and one row with rate scope label min and cap`() {
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(
|
||||||
|
listOf(tier(rate = 1, label = "Basic cards", scope = "All purchases", min = "$30", cap = "$100")),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.title).isEqualTo(stringReference("Cashback 1%"))
|
||||||
|
assertThat(result.rows).containsExactly(
|
||||||
|
stringReference("1% for All purchases with your Basic cards, min purchase $30, up to $100 per month"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN two tiers WHEN convert THEN up-to title and one row per tier in order`() {
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(
|
||||||
|
listOf(
|
||||||
|
tier(rate = 1, label = "Basic cards", scope = "All purchases", min = "$30", cap = "$100"),
|
||||||
|
tier(rate = 2, label = "Plus cards", scope = "All purchases", min = "$30", cap = "$300"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.title).isEqualTo(stringReference("Cashback up to 2%"))
|
||||||
|
assertThat(result.rows).containsExactly(
|
||||||
|
stringReference("1% for All purchases with your Basic cards, min purchase $30, up to $100 per month"),
|
||||||
|
stringReference("2% for All purchases with your Plus cards, min purchase $30, up to $300 per month"),
|
||||||
|
).inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN tier without min or cap WHEN convert THEN row is rate scope and label only`() {
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(
|
||||||
|
listOf(tier(rate = 1, label = "Basic cards", scope = "All purchases", min = null, cap = null)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.rows).containsExactly(stringReference("1% for All purchases with your Basic cards"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN tier with unknown rate WHEN convert THEN plain Cashback title and row without percent prefix`() {
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(
|
||||||
|
listOf(tier(rate = null, label = "Gold cards", scope = "All purchases", min = null, cap = null)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.title).isEqualTo(stringReference("Cashback"))
|
||||||
|
assertThat(result.rows).containsExactly(stringReference("All purchases with your Gold cards"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN no tiers WHEN convert THEN plain Cashback title and empty rows`() {
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(emptyList())
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.title).isEqualTo(stringReference("Cashback"))
|
||||||
|
assertThat(result.rows).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tier(
|
||||||
|
rate: Int? = 1,
|
||||||
|
label: String = "Basic cards",
|
||||||
|
scope: String = "All purchases",
|
||||||
|
min: String? = null,
|
||||||
|
cap: String? = null,
|
||||||
|
) = CashbackTier(
|
||||||
|
planType = TangemPayTariffPlan.Type.UNKNOWN,
|
||||||
|
rate = rate,
|
||||||
|
label = label,
|
||||||
|
scope = scope,
|
||||||
|
minPurchase = min,
|
||||||
|
monthlyCap = cap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.tangem.features.tangempay.cashback.impl.model
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
internal class TangemPayCashbackInfoTilesConverterTest {
|
||||||
|
|
||||||
|
private val converter = TangemPayCashbackInfoTilesConverter(
|
||||||
|
onRateClick = {},
|
||||||
|
onAccrualsClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("rateSelectionModels")
|
||||||
|
fun `GIVEN plan WHEN convert THEN rate tile shows the tier rate and the plan subtitle`(model: RateSelectionModel) {
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(twoTiers(), model.plan, model.planName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.rate.title).isEqualTo(stringReference(model.expectedTitle))
|
||||||
|
assertThat(result.rate.subtitle).isEqualTo(model.expectedSubtitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN empty tiers and unknown plan WHEN convert THEN rate tile has no percent and empty subtitle`() {
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(emptyList(), TangemPayTariffPlan.Type.UNKNOWN, currentPlanName = null)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.rate.title).isEqualTo(stringReference("Cashback"))
|
||||||
|
assertThat(result.rate.subtitle).isEqualTo(TextReference.EMPTY)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN any tiers WHEN convert THEN accruals tile is static`() {
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(twoTiers(), TangemPayTariffPlan.Type.BASIC, currentPlanName = "Basic")
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result.accruals.title).isEqualTo(stringReference("Accruals"))
|
||||||
|
assertThat(result.accruals.subtitle).isEqualTo(stringReference("Limits and exceptions"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rateSelectionModels() = listOf(
|
||||||
|
RateSelectionModel(
|
||||||
|
plan = TangemPayTariffPlan.Type.PLUS,
|
||||||
|
planName = "Plus",
|
||||||
|
expectedTitle = "Cashback 2%",
|
||||||
|
expectedSubtitle = stringReference("With your Plus plan"),
|
||||||
|
),
|
||||||
|
RateSelectionModel(
|
||||||
|
plan = TangemPayTariffPlan.Type.BASIC,
|
||||||
|
planName = "Basic",
|
||||||
|
expectedTitle = "Cashback 1%",
|
||||||
|
expectedSubtitle = stringReference("With your Basic plan"),
|
||||||
|
),
|
||||||
|
RateSelectionModel(
|
||||||
|
plan = TangemPayTariffPlan.Type.UNKNOWN,
|
||||||
|
planName = null,
|
||||||
|
expectedTitle = "Cashback 1%",
|
||||||
|
expectedSubtitle = TextReference.EMPTY,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun twoTiers() = listOf(
|
||||||
|
tier(TangemPayTariffPlan.Type.BASIC, rate = 1),
|
||||||
|
tier(TangemPayTariffPlan.Type.PLUS, rate = 2),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun tier(planType: TangemPayTariffPlan.Type = TangemPayTariffPlan.Type.UNKNOWN, rate: Int? = null) =
|
||||||
|
CashbackTier(
|
||||||
|
planType = planType,
|
||||||
|
rate = rate,
|
||||||
|
label = "label",
|
||||||
|
scope = "scope",
|
||||||
|
minPurchase = null,
|
||||||
|
monthlyCap = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RateSelectionModel(
|
||||||
|
val plan: TangemPayTariffPlan.Type,
|
||||||
|
val planName: String?,
|
||||||
|
val expectedTitle: String,
|
||||||
|
val expectedSubtitle: TextReference,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
package com.tangem.features.tangempay.cashback.impl.model
|
||||||
|
|
||||||
|
import arrow.core.right
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.core.decompose.model.MutableParamsContainer
|
||||||
|
import com.tangem.core.decompose.navigation.Router
|
||||||
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
|
||||||
|
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.pay.model.CashbackDocument
|
||||||
|
import com.tangem.domain.pay.model.CashbackPromotions
|
||||||
|
import com.tangem.domain.pay.model.CashbackSummary
|
||||||
|
import com.tangem.domain.pay.model.CustomerInfo
|
||||||
|
import com.tangem.domain.pay.repository.CashbackRepository
|
||||||
|
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||||
|
import com.tangem.features.tangempay.cashback.api.TangemPayCashbackComponent
|
||||||
|
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||||
|
import io.mockk.clearMocks
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
internal class TangemPayCashbackModelTest {
|
||||||
|
|
||||||
|
private val defaultLocale = Locale.getDefault()
|
||||||
|
private val userWalletId = UserWalletId("123")
|
||||||
|
|
||||||
|
private val router: Router = mockk(relaxed = true)
|
||||||
|
private val urlOpener: UrlOpener = mockk(relaxed = true)
|
||||||
|
private val cashbackRepository: CashbackRepository = mockk()
|
||||||
|
private val onboardingRepository: OnboardingRepository = mockk()
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
Locale.setDefault(Locale.US)
|
||||||
|
clearMocks(cashbackRepository, onboardingRepository)
|
||||||
|
coEvery { cashbackRepository.getCashbackSummary(any()) } returns CashbackSummary.Disabled.right()
|
||||||
|
coEvery { cashbackRepository.getCashbackPromotions(any()) } returns promotions().right()
|
||||||
|
coEvery { cashbackRepository.getCashbackAccrualDocs(any()) } returns docs().right()
|
||||||
|
coEvery { onboardingRepository.getCustomerInfo(any()) } returns
|
||||||
|
customerInfo(TangemPayTariffPlan.Type.BASIC).right()
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
Locale.setDefault(defaultLocale)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN all sources load WHEN model created THEN tiles and sheets populated`() {
|
||||||
|
// Act
|
||||||
|
val model = createModel()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(model.uiState.value.infoTiles).isNotNull()
|
||||||
|
assertThat(model.uiState.value.infoTiles?.rate?.title).isEqualTo(stringReference("Cashback 1%"))
|
||||||
|
assertThat(model.detailsSheet.value.rows).hasSize(2)
|
||||||
|
assertThat(model.accrualsSheet.value.docRows).hasSize(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN PLUS plan WHEN model created THEN rate tile shows the Plus tier rate`() {
|
||||||
|
// Arrange
|
||||||
|
coEvery { onboardingRepository.getCustomerInfo(any()) } returns
|
||||||
|
customerInfo(TangemPayTariffPlan.Type.PLUS).right()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val model = createModel()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(model.uiState.value.infoTiles?.rate?.title).isEqualTo(stringReference("Cashback 2%"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN promotions load fails WHEN model created THEN tiles hidden and details rows empty`() {
|
||||||
|
// Arrange
|
||||||
|
coEvery { cashbackRepository.getCashbackPromotions(any()) } throws RuntimeException("boom")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val model = createModel()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(model.uiState.value.infoTiles).isNull()
|
||||||
|
assertThat(model.detailsSheet.value.rows).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN summary load fails WHEN model created THEN tiles still shown`() {
|
||||||
|
// Arrange
|
||||||
|
coEvery { cashbackRepository.getCashbackSummary(any()) } throws RuntimeException("boom")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val model = createModel()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(model.uiState.value.infoTiles).isNotNull()
|
||||||
|
assertThat(model.detailsSheet.value.rows).hasSize(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN customer info fails WHEN model created THEN rate tile falls back to the first tier`() {
|
||||||
|
// Arrange
|
||||||
|
coEvery { onboardingRepository.getCustomerInfo(any()) } throws RuntimeException("boom")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val model = createModel()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(model.uiState.value.infoTiles?.rate?.title).isEqualTo(stringReference("Cashback 1%"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN docs load fails WHEN model created THEN accrual doc rows empty but info rows kept`() {
|
||||||
|
// Arrange
|
||||||
|
coEvery { cashbackRepository.getCashbackAccrualDocs(any()) } throws RuntimeException("boom")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val model = createModel()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(model.accrualsSheet.value.docRows).isEmpty()
|
||||||
|
assertThat(model.accrualsSheet.value.infoRows).isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createModel() = TangemPayCashbackModel(
|
||||||
|
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||||
|
paramsContainer = MutableParamsContainer(TangemPayCashbackComponent.Params(userWalletId = userWalletId)),
|
||||||
|
router = router,
|
||||||
|
urlOpener = urlOpener,
|
||||||
|
cashbackRepository = cashbackRepository,
|
||||||
|
onboardingRepository = onboardingRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun promotions() = CashbackPromotions(
|
||||||
|
cardTiers = listOf(
|
||||||
|
CashbackPromotions.CardTier(
|
||||||
|
tier = "basic",
|
||||||
|
label = "Basic cards",
|
||||||
|
scope = "All purchases",
|
||||||
|
minTransactionAmount = BigDecimal("30"),
|
||||||
|
monthlyCapAmount = BigDecimal("100"),
|
||||||
|
),
|
||||||
|
CashbackPromotions.CardTier(
|
||||||
|
tier = "plus",
|
||||||
|
label = "Plus cards",
|
||||||
|
scope = "All purchases",
|
||||||
|
minTransactionAmount = BigDecimal("30"),
|
||||||
|
monthlyCapAmount = BigDecimal("300"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun docs() = listOf(
|
||||||
|
CashbackDocument(id = "excluded", title = "All categories without cashback", url = "https://x/excluded.pdf"),
|
||||||
|
CashbackDocument(id = "terms", title = "Full terms of cashback program", url = "https://x/terms.pdf"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun customerInfo(planType: TangemPayTariffPlan.Type): CustomerInfo {
|
||||||
|
val tariffPlanMock = mockk<TangemPayTariffPlan> {
|
||||||
|
every { type } returns planType
|
||||||
|
every { name } returns planType.name
|
||||||
|
}
|
||||||
|
val customerTariffPlanMock = mockk<TangemPayCustomerTariffPlan> { every { plan } returns tariffPlanMock }
|
||||||
|
return mockk { every { tariffPlan } returns customerTariffPlanMock }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.tangem.features.tangempay.cashback.impl.model
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||||
|
import com.tangem.domain.pay.model.CashbackPromotions
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
internal class TangemPayCashbackTiersConverterTest {
|
||||||
|
|
||||||
|
private val defaultLocale = Locale.getDefault()
|
||||||
|
private val converter = TangemPayCashbackTiersConverter()
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
Locale.setDefault(Locale.US)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
Locale.setDefault(defaultLocale)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN known tiers WHEN convert THEN rate plan and formatted amounts resolved once`() {
|
||||||
|
// Arrange
|
||||||
|
val promotions = promotions(
|
||||||
|
tier(id = "basic", label = "Basic cards", scope = "All purchases", min = "30", cap = "100"),
|
||||||
|
tier(id = "plus", label = "Plus cards", scope = "Everywhere", min = null, cap = "300"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(promotions)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).containsExactly(
|
||||||
|
CashbackTier(
|
||||||
|
planType = TangemPayTariffPlan.Type.BASIC,
|
||||||
|
rate = 1,
|
||||||
|
label = "Basic cards",
|
||||||
|
scope = "All purchases",
|
||||||
|
minPurchase = "$30",
|
||||||
|
monthlyCap = "$100",
|
||||||
|
),
|
||||||
|
CashbackTier(
|
||||||
|
planType = TangemPayTariffPlan.Type.PLUS,
|
||||||
|
rate = 2,
|
||||||
|
label = "Plus cards",
|
||||||
|
scope = "Everywhere",
|
||||||
|
minPurchase = null,
|
||||||
|
monthlyCap = "$300",
|
||||||
|
),
|
||||||
|
).inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN unknown tier WHEN convert THEN rate null and plan type UNKNOWN`() {
|
||||||
|
// Arrange
|
||||||
|
val promotions = promotions(tier(id = "gold", label = "Gold", scope = "All", min = null, cap = null))
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(promotions)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).containsExactly(
|
||||||
|
CashbackTier(
|
||||||
|
planType = TangemPayTariffPlan.Type.UNKNOWN,
|
||||||
|
rate = null,
|
||||||
|
label = "Gold",
|
||||||
|
scope = "All",
|
||||||
|
minPurchase = null,
|
||||||
|
monthlyCap = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN no tiers WHEN convert THEN empty list`() {
|
||||||
|
// Act
|
||||||
|
val result = converter.convert(CashbackPromotions(cardTiers = emptyList()))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun promotions(vararg tiers: CashbackPromotions.CardTier) = CashbackPromotions(cardTiers = tiers.toList())
|
||||||
|
|
||||||
|
private fun tier(
|
||||||
|
id: String = "basic",
|
||||||
|
label: String = "Basic cards",
|
||||||
|
scope: String = "All purchases",
|
||||||
|
min: String? = null,
|
||||||
|
cap: String? = null,
|
||||||
|
) = CashbackPromotions.CardTier(
|
||||||
|
tier = id,
|
||||||
|
label = label,
|
||||||
|
scope = scope,
|
||||||
|
minTransactionAmount = min?.let(::BigDecimal),
|
||||||
|
monthlyCapAmount = cap?.let(::BigDecimal),
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue