Skip to content

Commit

Permalink
Add confirmation activity tests for EPMs
Browse files Browse the repository at this point in the history
  • Loading branch information
samer-stripe committed Jan 29, 2025
1 parent 623a55a commit eadea56
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ internal class BacsConfirmationActivityTest {

confirmationHandler.start(CONFIRMATION_ARGUMENTS)

val confirmingWithSavedOption = awaitItem().assertConfirming()
val confirmingWithBacsOption = awaitItem().assertConfirming()

assertThat(confirmingWithSavedOption.option).isEqualTo(CONFIRMATION_OPTION)
assertThat(confirmingWithBacsOption.option).isEqualTo(CONFIRMATION_OPTION)

intendedBacsToBeLaunched()

val confirmingWithSavedOptionWithCvc = awaitItem().assertConfirming()
val confirmingWithNewOption = awaitItem().assertConfirming()

assertThat(confirmingWithSavedOptionWithCvc.option)
assertThat(confirmingWithNewOption.option)
.isEqualTo(
PaymentMethodConfirmationOption.New(
createParams = CONFIRMATION_OPTION.createParams,
Expand Down Expand Up @@ -103,9 +103,9 @@ internal class BacsConfirmationActivityTest {

confirmationHandler.start(CONFIRMATION_ARGUMENTS)

val confirmingWithSavedOption = awaitItem().assertConfirming()
val confirmingWithBacsOption = awaitItem().assertConfirming()

assertThat(confirmingWithSavedOption.option).isEqualTo(CONFIRMATION_OPTION)
assertThat(confirmingWithBacsOption.option).isEqualTo(CONFIRMATION_OPTION)

intendedBacsToBeLaunched()

Expand All @@ -126,9 +126,9 @@ internal class BacsConfirmationActivityTest {

confirmationHandler.start(CONFIRMATION_ARGUMENTS)

val confirmingWithSavedOption = awaitItem().assertConfirming()
val confirmingWithBacsOption = awaitItem().assertConfirming()

assertThat(confirmingWithSavedOption.option).isEqualTo(CONFIRMATION_OPTION)
assertThat(confirmingWithBacsOption.option).isEqualTo(CONFIRMATION_OPTION)

intendedBacsToBeLaunched()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package com.stripe.android.paymentelement.confirmation.epms

import android.app.Activity
import android.app.Application
import android.app.Instrumentation
import android.content.Intent
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.intent.rule.IntentsRule
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import com.stripe.android.core.exception.LocalStripeException
import com.stripe.android.core.strings.resolvableString
import com.stripe.android.isInstanceOf
import com.stripe.android.model.PaymentMethod
import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
import com.stripe.android.paymentelement.confirmation.PaymentElementConfirmationTestActivity
import com.stripe.android.paymentelement.confirmation.assertCanceled
import com.stripe.android.paymentelement.confirmation.assertComplete
import com.stripe.android.paymentelement.confirmation.assertConfirming
import com.stripe.android.paymentelement.confirmation.assertFailed
import com.stripe.android.paymentelement.confirmation.assertIdle
import com.stripe.android.paymentelement.confirmation.assertSucceeded
import com.stripe.android.paymentsheet.ExternalPaymentMethodConfirmHandler
import com.stripe.android.paymentsheet.ExternalPaymentMethodInterceptor
import com.stripe.android.paymentsheet.ExternalPaymentMethodResult
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.addresselement.AddressDetails
import com.stripe.android.paymentsheet.createTestActivityRule
import com.stripe.android.paymentsheet.state.PaymentElementLoader
import com.stripe.android.testing.PaymentIntentFactory
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

@RunWith(RobolectricTestRunner::class)
internal class ExternalPaymentMethodConfirmationActivityTest {
private val application = ApplicationProvider.getApplicationContext<Application>()

@get:Rule
val intentsRule = IntentsRule()

@get:Rule
val testActivityRule = createTestActivityRule<PaymentElementConfirmationTestActivity>()

@Before
fun setup() {
ExternalPaymentMethodInterceptor.externalPaymentMethodConfirmHandler =
ExternalPaymentMethodConfirmHandler { _, _ -> }
}

@After
fun teardown() {
ExternalPaymentMethodInterceptor.externalPaymentMethodConfirmHandler = null
}

@Test
fun `On EPM confirmation option provided, should launch EPM flow and succeed`() = test {
intendingEpmToBeLaunched(
Instrumentation.ActivityResult(
Activity.RESULT_OK,
Intent(),
)
)

confirmationHandler.state.test {
awaitItem().assertIdle()

confirmationHandler.start(CONFIRMATION_ARGUMENTS)

val confirming = awaitItem().assertConfirming()

assertThat(confirming.option).isEqualTo(CONFIRMATION_OPTION)

intendedEpmToBeLaunched()

val successResult = awaitItem().assertComplete().result.assertSucceeded()

assertThat(successResult.intent).isEqualTo(PAYMENT_INTENT)
assertThat(successResult.deferredIntentConfirmationType).isNull()
}
}

@Test
fun `On EPM confirmation option provided, should launch EPM flow and fail`() = test {
val failureMessage = "EPM failure"

intendingEpmToBeLaunched(
Instrumentation.ActivityResult(
Activity.RESULT_FIRST_USER,
Intent().putExtra(
ExternalPaymentMethodResult.Failed.DISPLAY_MESSAGE_EXTRA,
failureMessage,
)
)
)

confirmationHandler.state.test {
awaitItem().assertIdle()

confirmationHandler.start(CONFIRMATION_ARGUMENTS)

val confirming = awaitItem().assertConfirming()

assertThat(confirming.option).isEqualTo(CONFIRMATION_OPTION)

intendedEpmToBeLaunched()

val failedResult = awaitItem().assertComplete().result.assertFailed()

assertThat(failedResult.type).isEqualTo(ConfirmationHandler.Result.Failed.ErrorType.ExternalPaymentMethod)
assertThat(failedResult.message).isEqualTo(failureMessage.resolvableString)

val cause = failedResult.cause

assertThat(cause).isInstanceOf<LocalStripeException>()

val localStripeException = cause.asLocalStripeException()

assertThat(localStripeException.displayMessage).isEqualTo(failureMessage)
assertThat(localStripeException.analyticsValue).isEqualTo("externalPaymentMethodFailure")
}
}

@Test
fun `On EPM confirmation option provided, should launch EPM flow and cancel`() = test {
intendingEpmToBeLaunched(
Instrumentation.ActivityResult(
Activity.RESULT_CANCELED,
Intent(),
)
)

confirmationHandler.state.test {
awaitItem().assertIdle()

confirmationHandler.start(CONFIRMATION_ARGUMENTS)

val confirming = awaitItem().assertConfirming()

assertThat(confirming.option).isEqualTo(CONFIRMATION_OPTION)

intendedEpmToBeLaunched()

val cancelResult = awaitItem().assertComplete().result.assertCanceled()

assertThat(cancelResult.action).isEqualTo(ConfirmationHandler.Result.Canceled.Action.None)
}
}

private fun test(
test: suspend PaymentElementConfirmationTestActivity.() -> Unit
) = runTest(UnconfinedTestDispatcher()) {
val countDownLatch = CountDownLatch(1)

ActivityScenario.launch<PaymentElementConfirmationTestActivity>(
Intent(application, PaymentElementConfirmationTestActivity::class.java)
).use { scenario ->
scenario.onActivity { activity ->
launch {
test(activity)
countDownLatch.countDown()
}
}

countDownLatch.await(10, TimeUnit.SECONDS)
}
}

private fun intendingEpmToBeLaunched(result: Instrumentation.ActivityResult) {
intending(hasComponent(EPM_ACTIVITY_NAME)).respondWith(result)
}

private fun intendedEpmToBeLaunched() {
intended(hasComponent(EPM_ACTIVITY_NAME))
}

private fun Throwable.asLocalStripeException(): LocalStripeException {
return this as LocalStripeException
}

private companion object {
val PAYMENT_INTENT = PaymentIntentFactory.create().copy(
id = "pm_1",
amount = 5000,
currency = "CAD",
paymentMethodOptionsJsonString = """
{"card": {"require_cvc_recollection": true}}
""".trimIndent()
)

val CONFIRMATION_OPTION = ExternalPaymentMethodConfirmationOption(
type = "paypal",
billingDetails = PaymentMethod.BillingDetails(
name = "John Doe",
),
)

val CONFIRMATION_ARGUMENTS = ConfirmationHandler.Args(
confirmationOption = CONFIRMATION_OPTION,
initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent(
clientSecret = "pi_123_secret_123"
),
shippingDetails = AddressDetails(),
intent = PAYMENT_INTENT,
appearance = PaymentSheet.Appearance(),
)

const val EPM_ACTIVITY_NAME =
"com.stripe.android.paymentsheet.ExternalPaymentMethodProxyActivity"
}
}

0 comments on commit eadea56

Please sign in to comment.