Skip to content

Commit

Permalink
Add some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
porter-stripe committed Oct 17, 2024
1 parent 7aee8fb commit 74b4f5f
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 6 deletions.
2 changes: 2 additions & 0 deletions payments-core/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@
<string name="stripe_invalid_card_number">Your card\'s number is invalid.</string>
<!-- Error message for card entry form when CVC/CVV is invalid -->
<string name="stripe_invalid_cvc">Your card\'s security code is invalid.</string>
<!-- Error message for card entry form when a user enters a card with a brand that is not accepted. -->
<string name="stripe_disallowed_card_brand">%s is not accepted</string>
<!-- Error when customer's name is invalid -->
<string name="stripe_invalid_owner_name">Your name is invalid.</string>
<!-- Shipping form error message -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ internal class CardDetailsController(
val numberElement = CardNumberElement(
IdentifierSpec.CardNumber,
DefaultCardNumberController(
cardTextFieldConfig = CardNumberConfig(isCBCEligible = cbcEligibility != CardBrandChoiceEligibility.Ineligible, cardBrandFilter = cardBrandFilter),
cardTextFieldConfig = CardNumberConfig(
isCBCEligible = cbcEligibility != CardBrandChoiceEligibility.Ineligible,
cardBrandFilter = cardBrandFilter

),
cardAccountRangeRepository = cardAccountRangeRepositoryFactory.create(),
uiContext = uiContext,
workContext = workContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,29 @@ import com.stripe.android.R as StripeR
internal class CardNumberConfig(
private val isCBCEligible: Boolean,
private val cardBrandFilter: CardBrandFilter
) : CardDetailsTextFieldConfig {
) : CardDetailsTextFieldConfig {
override val capitalization: KeyboardCapitalization = KeyboardCapitalization.None
override val debugLabel: String = "Card number"
override val label: Int = StripeR.string.stripe_acc_label_card_number
override val keyboard: KeyboardType = KeyboardType.NumberPassword
override val visualTransformation: VisualTransformation = CardNumberVisualTransformation(' ')

// Hardcoded number of card digits entered before we hit the card metadata service in CBC
private var digitsRequiredToFetchBrands = 8

override fun determineState(brand: CardBrand, number: String, numberAllowedDigits: Int): TextFieldState {
val luhnValid = CardUtils.isValidLuhnNumber(number)
val isDigitLimit = brand.getMaxLengthForCardNumber(number) != -1

return if (number.isBlank()) {
TextFieldStateConstants.Error.Blank
} else if (!cardBrandFilter.isAccepted(brand) && (!isCBCEligible || number.length > 8)) {
} else if (!cardBrandFilter.isAccepted(brand) &&
(!isCBCEligible || number.length > digitsRequiredToFetchBrands)
) {
/*
If the merchant is eligible for CBC do not show the disallowed error
until we have had time to hit the card metadata service for a list of possible brands
*/
return TextFieldStateConstants.Error.Invalid(
errorMessageResId = StripeR.string.stripe_disallowed_card_brand,
formatArgs = arrayOf(brand.displayName),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.stripe.android.ui.core.elements

import androidx.compose.ui.text.AnnotatedString
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import com.stripe.android.DefaultCardBrandFilter
import com.stripe.android.model.CardBrand
import com.stripe.android.ui.core.CardNumberFixtures
import com.stripe.android.uicore.elements.TextFieldStateConstants
Expand All @@ -10,7 +12,7 @@ import org.junit.Test
import com.stripe.android.R as StripeR

class CardNumberConfigTest {
private val cardNumberConfig = CardNumberConfig()
private val cardNumberConfig = CardNumberConfig(isCBCEligible = false, cardBrandFilter = DefaultCardBrandFilter)

@Test
fun `visualTransformation formats entered value`() {
Expand Down Expand Up @@ -94,4 +96,94 @@ class CardNumberConfigTest {
Truth.assertThat(state)
.isInstanceOf<TextFieldStateConstants.Valid.Full>()
}

@Test
fun `determineState returns valid for allowed brand without CBC`() {
val cardBrandFilter = FakeCardBrandFilter(disallowedBrands = setOf(CardBrand.MasterCard))
val cardNumberConfig = CardNumberConfig(
isCBCEligible = false,
cardBrandFilter = cardBrandFilter
)

val state = cardNumberConfig.determineState(
brand = CardBrand.Visa,
number = "4242424242424242",
numberAllowedDigits = CardBrand.Visa.getMaxLengthForCardNumber("4242424242424242")
)

assertThat(state).isInstanceOf<TextFieldStateConstants.Valid.Full>()
}

@Test
fun `determineState returns error for disallowed brand without CBC`() {
val cardBrandFilter = FakeCardBrandFilter(disallowedBrands = setOf(CardBrand.MasterCard))
val cardNumberConfig = CardNumberConfig(
isCBCEligible = false,
cardBrandFilter = cardBrandFilter
)

val state = cardNumberConfig.determineState(
brand = CardBrand.MasterCard,
number = "5555555555554444",
numberAllowedDigits = CardBrand.MasterCard.getMaxLengthForCardNumber("5555555555554444")
)

assertThat(state).isInstanceOf<TextFieldStateConstants.Error.Invalid>()
assertThat(state.getError()?.errorMessage)
.isEqualTo(StripeR.string.stripe_disallowed_card_brand)
}

@Test
fun `determineState allows disallowed brand with CBC when number length is less than or equal to 8`() {
val cardBrandFilter = FakeCardBrandFilter(disallowedBrands = setOf(CardBrand.MasterCard))
val cardNumberConfig = CardNumberConfig(
isCBCEligible = true,
cardBrandFilter = cardBrandFilter
)

val state = cardNumberConfig.determineState(
brand = CardBrand.MasterCard,
number = "55555555", // Length is 8
numberAllowedDigits = CardBrand.MasterCard.getMaxLengthForCardNumber("55555555")
)

// Since number length is less than or equal to 8, it should not return disallowed brand error
assertThat(state).isInstanceOf<TextFieldStateConstants.Error.Incomplete>()
}

@Test
fun `determineState returns error for disallowed brand with CBC when number length is greater than 8`() {
val cardBrandFilter = FakeCardBrandFilter(disallowedBrands = setOf(CardBrand.MasterCard))
val cardNumberConfig = CardNumberConfig(
isCBCEligible = true,
cardBrandFilter = cardBrandFilter
)

val state = cardNumberConfig.determineState(
brand = CardBrand.MasterCard,
number = "5555555555554444", // Length is greater than 8
numberAllowedDigits = CardBrand.MasterCard.getMaxLengthForCardNumber("5555555555554444")
)

assertThat(state).isInstanceOf<TextFieldStateConstants.Error.Invalid>()
assertThat(state.getError()?.errorMessage)
.isEqualTo(StripeR.string.stripe_disallowed_card_brand)
}

@Test
fun `determineState returns valid for allowed brand with CBC`() {
val cardBrandFilter = FakeCardBrandFilter(disallowedBrands = setOf(CardBrand.MasterCard))
val cardNumberConfig = CardNumberConfig(
isCBCEligible = true,
cardBrandFilter = cardBrandFilter
)

val state = cardNumberConfig.determineState(
brand = CardBrand.Visa,
number = "4242424242424242",
numberAllowedDigits = CardBrand.Visa.getMaxLengthForCardNumber("4242424242424242")
)

assertThat(state).isInstanceOf<TextFieldStateConstants.Valid.Full>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ internal class CardNumberControllerTest {
cardBrandFilter: CardBrandFilter = DefaultCardBrandFilter
): DefaultCardNumberController {
return DefaultCardNumberController(
cardTextFieldConfig = CardNumberConfig(),
cardTextFieldConfig = CardNumberConfig(isCBCEligible = false, cardBrandFilter = DefaultCardBrandFilter),
cardAccountRangeRepository = repository,
uiContext = testDispatcher,
workContext = testDispatcher,
Expand Down Expand Up @@ -521,7 +521,7 @@ internal class CardNumberControllerTest {
}

@Parcelize
private class FakeCardBrandFilter(
class FakeCardBrandFilter(
private val disallowedBrands: Set<CardBrand>
) : CardBrandFilter {
override fun isAccepted(cardBrand: CardBrand): Boolean {
Expand Down

0 comments on commit 74b4f5f

Please sign in to comment.