From 742ae4562fb2320b2cc5f54b33e7d39f2108e460 Mon Sep 17 00:00:00 2001 From: Samer Alabi <141707240+samer-stripe@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:05:03 -0500 Subject: [PATCH] Refactor `ConfirmationMediator` tests to isolate from implemented definitions & to track function calls (#9705) --- .../confirmation/ConfirmationMediatorTest.kt | 398 ++++++++++-------- .../FakeConfirmationDefinition.kt | 135 ++---- .../RecordingConfirmationDefinition.kt | 149 +++++++ 3 files changed, 400 insertions(+), 282 deletions(-) create mode 100644 paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/RecordingConfirmationDefinition.kt diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationMediatorTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationMediatorTest.kt index 203e7357113..4a40b350d1e 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationMediatorTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationMediatorTest.kt @@ -1,19 +1,16 @@ package com.stripe.android.paymentelement.confirmation +import android.os.Parcelable import androidx.activity.result.ActivityResultCaller import androidx.lifecycle.SavedStateHandle import com.google.common.truth.Truth.assertThat import com.stripe.android.core.strings.resolvableString import com.stripe.android.isInstanceOf import com.stripe.android.model.PaymentIntentFixtures -import com.stripe.android.model.PaymentMethodFixtures -import com.stripe.android.paymentelement.confirmation.epms.ExternalPaymentMethodConfirmationOption import com.stripe.android.paymentelement.confirmation.intent.DeferredIntentConfirmationType import com.stripe.android.paymentsheet.R -import com.stripe.android.paymentsheet.state.PaymentElementLoader -import com.stripe.android.testing.PaymentMethodFactory -import com.stripe.android.testing.SetupIntentFactory import kotlinx.coroutines.test.runTest +import kotlinx.parcelize.Parcelize import org.junit.Test import org.mockito.kotlin.mock import java.util.concurrent.CountDownLatch @@ -21,46 +18,37 @@ import java.util.concurrent.TimeUnit class ConfirmationMediatorTest { @Test - fun `On can confirm, should return true if definition is the same type`() = runTest { + fun `On can confirm, should return true if definition is the same type`() = test { val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), - definition = FakeConfirmationDefinition() + definition = definition, ) val canConfirm = mediator.canConfirm( - confirmationOption = PaymentMethodConfirmationOption.Saved( - initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent( - clientSecret = "pi_123_secret_123", - ), - optionsParams = null, - shippingDetails = null, - paymentMethod = PaymentMethodFactory.card(), - ), + confirmationOption = TestConfirmationDefinition.Option, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(TestConfirmationDefinition.Option) assertThat(canConfirm).isTrue() } @Test - fun `On can confirm, should return false if definition is not the same type`() = runTest { + fun `On can confirm, should return false if definition is not the same type`() = test { val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), - definition = FakeConfirmationDefinition() + definition = definition, ) val canConfirm = mediator.canConfirm( - confirmationOption = ExternalPaymentMethodConfirmationOption( - type = "paypal", - billingDetails = null, - ), + confirmationOption = InvalidTestConfirmationOption, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(InvalidTestConfirmationOption) assertThat(canConfirm).isFalse() } @Test - fun `On register, should create launcher`() = runTest { - val definition = FakeConfirmationDefinition() + fun `On register, should create launcher`() = test { val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), definition = definition, @@ -73,31 +61,30 @@ class ConfirmationMediatorTest { onResult = {}, ) - val createLauncherCall = definition.createLauncherCalls.awaitItem() + val createLauncherCall = createLauncherCalls.awaitItem() assertThat(createLauncherCall.activityResultCaller).isEqualTo(activityResultCaller) } @Test - fun `On incorrect confirmation option provided on action, should return fail action`() = runTest { + fun `On incorrect confirmation option provided on action, should return fail action`() = test { val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), - definition = FakeConfirmationDefinition() + definition = definition ) val action = mediator.action( - option = ExternalPaymentMethodConfirmationOption( - type = "paypal", - billingDetails = null, - ), + option = InvalidTestConfirmationOption, intent = PaymentIntentFixtures.PI_SUCCEEDED, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(InvalidTestConfirmationOption) + val failAction = action.asFail() assertThat(failAction.cause).isInstanceOf(IllegalArgumentException::class.java) assertThat(failAction.cause.message).isEqualTo( - "Parameter type of 'ExternalPaymentMethodConfirmationOption' cannot be used with " + + "Parameter type of 'InvalidTestConfirmationOption' cannot be used with " + "ConfirmationMediator to read a result" ) assertThat(failAction.message).isEqualTo(R.string.stripe_something_went_wrong.resolvableString) @@ -105,84 +92,78 @@ class ConfirmationMediatorTest { } @Test - fun `On complete confirmation action, should return mediator complete action`() = runTest { - val definition = FakeConfirmationDefinition( - onAction = { confirmationOption, intent -> - ConfirmationDefinition.Action.Complete( - confirmationOption = confirmationOption, - intent = intent, - deferredIntentConfirmationType = DeferredIntentConfirmationType.Client, - ) - } - ) - + fun `On complete confirmation action, should return mediator complete action`() = test( + action = ConfirmationDefinition.Action.Complete( + confirmationOption = TestConfirmationDefinition.Option, + intent = INTENT, + deferredIntentConfirmationType = DeferredIntentConfirmationType.Client, + ), + ) { val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), definition = definition ) val action = mediator.action( - option = SAVED_CONFIRMATION_OPTION, + option = TestConfirmationDefinition.Option, intent = INTENT, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(TestConfirmationDefinition.Option) + + val actionCall = actionCalls.awaitItem() + + assertThat(actionCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(actionCall.intent).isEqualTo(INTENT) + val completeAction = action.asComplete() - assertThat(completeAction.confirmationOption).isEqualTo(SAVED_CONFIRMATION_OPTION) + assertThat(completeAction.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) assertThat(completeAction.intent).isEqualTo(INTENT) assertThat(completeAction.deferredIntentConfirmationType).isEqualTo(DeferredIntentConfirmationType.Client) } @Test - fun `On failed confirmation action, should return mediator fail action`() = runTest { - val exception = IllegalStateException("Failed!") - val message = R.string.stripe_something_went_wrong.resolvableString - val errorType = ConfirmationHandler.Result.Failed.ErrorType.Fatal - - val definition = FakeConfirmationDefinition( - onAction = { _, _ -> - ConfirmationDefinition.Action.Fail( - cause = exception, - message = R.string.stripe_something_went_wrong.resolvableString, - errorType = errorType, - ) - } - ) - + fun `On failed confirmation action, should return mediator fail action`() = test( + action = ConfirmationDefinition.Action.Fail( + cause = IllegalStateException("Failed!"), + message = R.string.stripe_something_went_wrong.resolvableString, + errorType = ConfirmationHandler.Result.Failed.ErrorType.Fatal, + ), + ) { val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), definition = definition ) val action = mediator.action( - option = SAVED_CONFIRMATION_OPTION, + option = TestConfirmationDefinition.Option, intent = INTENT, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(TestConfirmationDefinition.Option) + + val actionCall = actionCalls.awaitItem() + + assertThat(actionCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(actionCall.intent).isEqualTo(INTENT) + val failAction = action.asFail() assertThat(failAction.cause).isInstanceOf(IllegalStateException::class.java) assertThat(failAction.cause.message).isEqualTo("Failed!") - assertThat(failAction.message).isEqualTo(message) - assertThat(failAction.errorType).isEqualTo(errorType) + assertThat(failAction.message).isEqualTo(R.string.stripe_something_went_wrong.resolvableString) + assertThat(failAction.errorType).isEqualTo(ConfirmationHandler.Result.Failed.ErrorType.Fatal) } @Test - fun `On launch action, should call definition launch and persist parameters`() = runTest { - val launcherArguments = FakeConfirmationDefinition.LauncherArgs(amount = 5000) - val launcher = FakeConfirmationDefinition.Launcher() - - val definition = FakeConfirmationDefinition( - onAction = { _, _ -> - ConfirmationDefinition.Action.Launch( - launcherArguments = launcherArguments, - deferredIntentConfirmationType = DeferredIntentConfirmationType.Client, - receivesResultInProcess = false, - ) - }, - launcher = launcher, - ) - + fun `On launch action, should call definition launch and persist parameters`() = test( + action = ConfirmationDefinition.Action.Launch( + launcherArguments = TestConfirmationDefinition.LauncherArgs, + deferredIntentConfirmationType = DeferredIntentConfirmationType.Client, + receivesResultInProcess = false, + ), + ) { val savedStateHandle = SavedStateHandle() val mediator = ConfirmationMediator( @@ -195,48 +176,52 @@ class ConfirmationMediatorTest { ) } + assertThat(createLauncherCalls.awaitItem()).isNotNull() + val action = mediator.action( - option = SAVED_CONFIRMATION_OPTION, + option = TestConfirmationDefinition.Option, intent = INTENT, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(TestConfirmationDefinition.Option) + + val actionCall = actionCalls.awaitItem() + + assertThat(actionCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(actionCall.intent).isEqualTo(INTENT) + val launchAction = action.asLaunch() assertThat(launchAction.receivesResultInProcess).isFalse() launchAction.launch() - val launchCall = definition.launchCalls.awaitItem() + val launchCall = launchCalls.awaitItem() - assertThat(launchCall.confirmationOption).isEqualTo(SAVED_CONFIRMATION_OPTION) - assertThat(launchCall.arguments).isEqualTo(launcherArguments) + assertThat(launchCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(launchCall.arguments).isEqualTo(TestConfirmationDefinition.LauncherArgs) assertThat(launchCall.intent).isEqualTo(INTENT) - assertThat(launchCall.launcher).isEqualTo(launcher) + assertThat(launchCall.launcher).isEqualTo(TestConfirmationDefinition.Launcher) val parameters = savedStateHandle - .get>( + .get>( "TestParameters" ) - assertThat(parameters?.confirmationOption).isEqualTo(SAVED_CONFIRMATION_OPTION) + assertThat(parameters?.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) assertThat(parameters?.intent).isEqualTo(INTENT) assertThat(parameters?.deferredIntentConfirmationType).isEqualTo(DeferredIntentConfirmationType.Client) } @Test fun `On launch definition action where result is received in process, 'receivesResultInProcess' should be true`() = - runTest { - val definition = FakeConfirmationDefinition( - onAction = { _, _ -> - ConfirmationDefinition.Action.Launch( - launcherArguments = FakeConfirmationDefinition.LauncherArgs(amount = 5000), - deferredIntentConfirmationType = DeferredIntentConfirmationType.Client, - receivesResultInProcess = true, - ) - }, - launcher = FakeConfirmationDefinition.Launcher(), - ) - + test( + action = ConfirmationDefinition.Action.Launch( + launcherArguments = TestConfirmationDefinition.LauncherArgs, + deferredIntentConfirmationType = DeferredIntentConfirmationType.Client, + receivesResultInProcess = true, + ), + ) { val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), definition = definition @@ -247,60 +232,68 @@ class ConfirmationMediatorTest { ) } + assertThat(createLauncherCalls.awaitItem()).isNotNull() + val action = mediator.action( - option = SAVED_CONFIRMATION_OPTION, + option = TestConfirmationDefinition.Option, intent = INTENT, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(TestConfirmationDefinition.Option) + + val actionCall = actionCalls.awaitItem() + + assertThat(actionCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(actionCall.intent).isEqualTo(INTENT) + val launchAction = action.asLaunch() assertThat(launchAction.receivesResultInProcess).isTrue() } @Test - fun `On confirmation action without registering, should return fail action`() = runTest { - val definition = FakeConfirmationDefinition( - onAction = { _, _ -> - ConfirmationDefinition.Action.Launch( - launcherArguments = FakeConfirmationDefinition.LauncherArgs(amount = 5000), - deferredIntentConfirmationType = null, - receivesResultInProcess = false, - ) - }, - ) - + fun `On confirmation action without registering, should return fail action`() = test( + action = ConfirmationDefinition.Action.Launch( + launcherArguments = TestConfirmationDefinition.LauncherArgs, + deferredIntentConfirmationType = null, + receivesResultInProcess = false, + ), + ) { val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), definition = definition ) val action = mediator.action( - option = SAVED_CONFIRMATION_OPTION, + option = TestConfirmationDefinition.Option, intent = INTENT, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(TestConfirmationDefinition.Option) + + val actionCall = actionCalls.awaitItem() + + assertThat(actionCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(actionCall.intent).isEqualTo(INTENT) + val failAction = action.asFail() assertThat(failAction.cause).isInstanceOf(IllegalStateException::class.java) assertThat(failAction.cause.message).isEqualTo( - "No launcher for FakeConfirmationDefinition was found, did you call register?" + "No launcher for RecordingConfirmationDefinition was found, did you call register?" ) assertThat(failAction.message).isEqualTo(R.string.stripe_something_went_wrong.resolvableString) assertThat(failAction.errorType).isEqualTo(ConfirmationHandler.Result.Failed.ErrorType.Fatal) } @Test - fun `On confirmation action after un-registering, should return fail action`() = runTest { - val definition = FakeConfirmationDefinition( - onAction = { _, _ -> - ConfirmationDefinition.Action.Launch( - launcherArguments = FakeConfirmationDefinition.LauncherArgs(amount = 5000), - deferredIntentConfirmationType = null, - receivesResultInProcess = false, - ) - }, - ) - + fun `On confirmation action after un-registering, should return fail action`() = test( + action = ConfirmationDefinition.Action.Launch( + launcherArguments = TestConfirmationDefinition.LauncherArgs, + deferredIntentConfirmationType = null, + receivesResultInProcess = false, + ), + ) { val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), definition = definition @@ -312,60 +305,49 @@ class ConfirmationMediatorTest { ) mediator.unregister() + assertThat(createLauncherCalls.awaitItem()).isNotNull() + val action = mediator.action( - option = SAVED_CONFIRMATION_OPTION, + option = TestConfirmationDefinition.Option, intent = INTENT, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(TestConfirmationDefinition.Option) + + val actionCall = actionCalls.awaitItem() + + assertThat(actionCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(actionCall.intent).isEqualTo(INTENT) + val failAction = action.asFail() assertThat(failAction.cause).isInstanceOf(IllegalStateException::class.java) assertThat(failAction.cause.message).isEqualTo( - "No launcher for FakeConfirmationDefinition was found, did you call register?" + "No launcher for RecordingConfirmationDefinition was found, did you call register?" ) assertThat(failAction.message).isEqualTo(R.string.stripe_something_went_wrong.resolvableString) assertThat(failAction.errorType).isEqualTo(ConfirmationHandler.Result.Failed.ErrorType.Fatal) } @Test - fun `On result, should attempt to convert launcher result to confirmation result and return it`() = runTest { + fun `On result, should attempt to convert launcher result to confirmation result and return it`() = test( + action = ConfirmationDefinition.Action.Launch( + launcherArguments = TestConfirmationDefinition.LauncherArgs, + deferredIntentConfirmationType = DeferredIntentConfirmationType.Client, + receivesResultInProcess = false, + ), + result = ConfirmationDefinition.Result.Succeeded( + intent = INTENT, + deferredIntentConfirmationType = DeferredIntentConfirmationType.Client, + ), + ) { val waitForResultLatch = CountDownLatch(1) - val intent = SetupIntentFactory.create( - paymentMethod = PaymentMethodFactory.card(random = true) - ) - val deferredIntentConfirmationType = DeferredIntentConfirmationType.Client - val launcherResult = FakeConfirmationDefinition.LauncherResult(amount = 50) - val confirmationResult = ConfirmationDefinition.Result.Succeeded( - intent = intent, - deferredIntentConfirmationType = deferredIntentConfirmationType, - ) - - val definition = FakeConfirmationDefinition( - result = confirmationResult, - onAction = { _, _ -> - ConfirmationDefinition.Action.Launch( - launcherArguments = FakeConfirmationDefinition.LauncherArgs(amount = 5000), - deferredIntentConfirmationType = DeferredIntentConfirmationType.Client, - receivesResultInProcess = false, - ) - }, - ) - val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), definition = definition, ) - val confirmationOption = PaymentMethodConfirmationOption.Saved( - initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent( - clientSecret = "pi_123_secret_123", - ), - optionsParams = null, - shippingDetails = null, - paymentMethod = PaymentMethodFactory.card(), - ) - var receivedResult: ConfirmationDefinition.Result? = null mediator.register( @@ -377,38 +359,56 @@ class ConfirmationMediatorTest { }, ) - val createLauncherCall = definition.createLauncherCalls.awaitItem() + val createLauncherCall = createLauncherCalls.awaitItem() val action = mediator.action( - intent = intent, - option = confirmationOption, + intent = INTENT, + option = TestConfirmationDefinition.Option, ) + assertThat(optionCalls.awaitItem().option).isEqualTo(TestConfirmationDefinition.Option) + + val actionCall = actionCalls.awaitItem() + + assertThat(actionCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(actionCall.intent).isEqualTo(INTENT) + assertThat(action).isInstanceOf() val launchAction = action.asLaunch() launchAction.launch() - createLauncherCall.onResult(launcherResult) + val launchCall = launchCalls.awaitItem() + + assertThat(launchCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(launchCall.arguments).isEqualTo(TestConfirmationDefinition.LauncherArgs) + assertThat(launchCall.intent).isEqualTo(INTENT) + assertThat(launchCall.launcher).isEqualTo(TestConfirmationDefinition.Launcher) + + createLauncherCall.onResult(TestConfirmationDefinition.LauncherResult) waitForResultLatch.await(2, TimeUnit.SECONDS) - val toPaymentConfirmationResultCall = definition.toResultCalls.awaitItem() + val toPaymentConfirmationResultCall = toResultCalls.awaitItem() - assertThat(toPaymentConfirmationResultCall.confirmationOption).isEqualTo(confirmationOption) - assertThat(toPaymentConfirmationResultCall.intent).isEqualTo(intent) - assertThat(toPaymentConfirmationResultCall.result).isEqualTo(launcherResult) + assertThat(toPaymentConfirmationResultCall.confirmationOption).isEqualTo(TestConfirmationDefinition.Option) + assertThat(toPaymentConfirmationResultCall.intent).isEqualTo(INTENT) + assertThat(toPaymentConfirmationResultCall.result).isEqualTo(TestConfirmationDefinition.LauncherResult) assertThat(toPaymentConfirmationResultCall.deferredIntentConfirmationType) - .isEqualTo(deferredIntentConfirmationType) + .isEqualTo(DeferredIntentConfirmationType.Client) - assertThat(receivedResult).isEqualTo(confirmationResult) + assertThat(receivedResult).isInstanceOf() + + val successResult = receivedResult.asSucceeded() + + assertThat(successResult.intent).isEqualTo(INTENT) + assertThat(successResult.deferredIntentConfirmationType).isEqualTo(DeferredIntentConfirmationType.Client) } @Test - fun `On result with no persisted parameters, should return failed result`() = runTest { + fun `On result with no persisted parameters, should return failed result`() = test { val countDownLatch = CountDownLatch(1) - val definition = FakeConfirmationDefinition() val mediator = ConfirmationMediator( savedStateHandle = SavedStateHandle(), definition = definition, @@ -434,15 +434,33 @@ class ConfirmationMediatorTest { }, ) - val createLauncherCall = definition.createLauncherCalls.awaitItem() + val createLauncherCall = createLauncherCalls.awaitItem() - createLauncherCall.onResult( - FakeConfirmationDefinition.LauncherResult(amount = 50) - ) + createLauncherCall.onResult(TestConfirmationDefinition.LauncherResult) countDownLatch.await(2, TimeUnit.SECONDS) } + private fun test( + action: ConfirmationDefinition.Action = + ConfirmationDefinition.Action.Fail( + cause = IllegalStateException("Failed!"), + message = R.string.stripe_something_went_wrong.resolvableString, + errorType = ConfirmationHandler.Result.Failed.ErrorType.Internal, + ), + result: ConfirmationDefinition.Result = ConfirmationDefinition.Result.Canceled( + action = ConfirmationHandler.Result.Canceled.Action.InformCancellation, + ), + scenarioTest: suspend RecordingConfirmationDefinition.Scenario< + TestConfirmationDefinition.Option, + TestConfirmationDefinition.Launcher, + TestConfirmationDefinition.LauncherArgs, + TestConfirmationDefinition.LauncherResult, + >.() -> Unit + ) = runTest { + RecordingConfirmationDefinition.test(TestConfirmationDefinition(action, result), scenarioTest) + } + private fun ConfirmationHandler.Result.asFailed(): ConfirmationHandler.Result.Failed { return this as ConfirmationHandler.Result.Failed } @@ -459,14 +477,40 @@ class ConfirmationMediatorTest { return this as ConfirmationMediator.Action.Launch } - private companion object { - private val SAVED_CONFIRMATION_OPTION = PaymentMethodConfirmationOption.Saved( - initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent(clientSecret = "pi_123"), - shippingDetails = null, - paymentMethod = PaymentMethodFixtures.CARD_PAYMENT_METHOD, - optionsParams = null, - ) + private class TestConfirmationDefinition( + action: ConfirmationDefinition.Action, + result: ConfirmationDefinition.Result, + ) : FakeConfirmationDefinition< + TestConfirmationDefinition.Option, + TestConfirmationDefinition.Launcher, + TestConfirmationDefinition.LauncherArgs, + TestConfirmationDefinition.LauncherResult, + >( + launcher = Launcher, + action = action, + result = result, + ) { + override val key: String = "Test" + + override fun option(confirmationOption: ConfirmationHandler.Option): Option? { + return confirmationOption as? Option + } + + @Parcelize + data object Option : ConfirmationHandler.Option + + object Launcher + data object LauncherArgs + + @Parcelize + data object LauncherResult : Parcelable + } + + @Parcelize + private object InvalidTestConfirmationOption : ConfirmationHandler.Option + + private companion object { private val INTENT = PaymentIntentFixtures.PI_REQUIRES_PAYMENT_METHOD } } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/FakeConfirmationDefinition.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/FakeConfirmationDefinition.kt index 95034f27289..4c2fb4c7fc4 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/FakeConfirmationDefinition.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/FakeConfirmationDefinition.kt @@ -2,136 +2,61 @@ package com.stripe.android.paymentelement.confirmation import android.os.Parcelable import androidx.activity.result.ActivityResultCaller -import app.cash.turbine.ReceiveTurbine -import app.cash.turbine.Turbine -import com.stripe.android.common.exception.stripeErrorMessage +import com.stripe.android.core.strings.resolvableString import com.stripe.android.model.StripeIntent import com.stripe.android.paymentelement.confirmation.intent.DeferredIntentConfirmationType -import kotlinx.parcelize.Parcelize - -internal class FakeConfirmationDefinition( - private val onAction: ( - confirmationOption: PaymentMethodConfirmationOption.Saved, - intent: StripeIntent - ) -> ConfirmationDefinition.Action = { _, _ -> - val exception = IllegalStateException("Failed!") - - ConfirmationDefinition.Action.Fail( - cause = exception, - message = exception.stripeErrorMessage(), - errorType = ConfirmationHandler.Result.Failed.ErrorType.Internal, - ) - }, +import com.stripe.android.paymentsheet.R + +internal abstract class FakeConfirmationDefinition< + TConfirmationOption : ConfirmationHandler.Option, + TLauncher, + TLauncherArgs, + TLauncherResult : Parcelable, + >( + private val launcher: TLauncher, + private val action: ConfirmationDefinition.Action = ConfirmationDefinition.Action.Fail( + cause = IllegalStateException("Failed!"), + message = R.string.stripe_something_went_wrong.resolvableString, + errorType = ConfirmationHandler.Result.Failed.ErrorType.Internal, + ), private val result: ConfirmationDefinition.Result = ConfirmationDefinition.Result.Canceled( action = ConfirmationHandler.Result.Canceled.Action.InformCancellation, ), - private val launcher: Launcher = Launcher(), ) : ConfirmationDefinition< - PaymentMethodConfirmationOption.Saved, - FakeConfirmationDefinition.Launcher, - FakeConfirmationDefinition.LauncherArgs, - FakeConfirmationDefinition.LauncherResult + TConfirmationOption, + TLauncher, + TLauncherArgs, + TLauncherResult > { - private val _launchCalls = Turbine() - val launchCalls: ReceiveTurbine = _launchCalls - - private val _createLauncherCalls = Turbine() - val createLauncherCalls: ReceiveTurbine = _createLauncherCalls - - private val _toResultCalls = Turbine() - val toResultCalls: ReceiveTurbine = - _toResultCalls - - override val key: String = "Test" - - override fun option( - confirmationOption: ConfirmationHandler.Option - ): PaymentMethodConfirmationOption.Saved? { - return confirmationOption as? PaymentMethodConfirmationOption.Saved - } - override suspend fun action( - confirmationOption: PaymentMethodConfirmationOption.Saved, + confirmationOption: TConfirmationOption, intent: StripeIntent - ): ConfirmationDefinition.Action { - return onAction(confirmationOption, intent) + ): ConfirmationDefinition.Action { + return action } override fun launch( - launcher: Launcher, - arguments: LauncherArgs, - confirmationOption: PaymentMethodConfirmationOption.Saved, + launcher: TLauncher, + arguments: TLauncherArgs, + confirmationOption: TConfirmationOption, intent: StripeIntent ) { - _launchCalls.add( - LaunchCall( - launcher = launcher, - arguments = arguments, - confirmationOption = confirmationOption, - intent = intent, - ) - ) + // Do nothing } override fun createLauncher( activityResultCaller: ActivityResultCaller, - onResult: (LauncherResult) -> Unit - ): Launcher { - _createLauncherCalls.add( - CreateLauncherCall( - activityResultCaller = activityResultCaller, - onResult = onResult, - ) - ) - + onResult: (TLauncherResult) -> Unit + ): TLauncher { return launcher } override fun toResult( - confirmationOption: PaymentMethodConfirmationOption.Saved, + confirmationOption: TConfirmationOption, deferredIntentConfirmationType: DeferredIntentConfirmationType?, intent: StripeIntent, - result: LauncherResult + result: TLauncherResult ): ConfirmationDefinition.Result { - _toResultCalls.add( - ToResultCall( - confirmationOption = confirmationOption, - deferredIntentConfirmationType = deferredIntentConfirmationType, - intent = intent, - result = result, - ) - ) - return this.result } - - class LaunchCall( - val launcher: Launcher, - val arguments: LauncherArgs, - val confirmationOption: PaymentMethodConfirmationOption.Saved, - val intent: StripeIntent - ) - - class CreateLauncherCall( - val activityResultCaller: ActivityResultCaller, - val onResult: (LauncherResult) -> Unit - ) - - class ToResultCall( - val confirmationOption: PaymentMethodConfirmationOption.Saved, - val deferredIntentConfirmationType: DeferredIntentConfirmationType?, - val intent: StripeIntent, - val result: LauncherResult - ) - - class Launcher - - data class LauncherArgs( - val amount: Long, - ) - - @Parcelize - data class LauncherResult( - val amount: Long, - ) : Parcelable } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/RecordingConfirmationDefinition.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/RecordingConfirmationDefinition.kt new file mode 100644 index 00000000000..edb43cd7b6f --- /dev/null +++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/RecordingConfirmationDefinition.kt @@ -0,0 +1,149 @@ +package com.stripe.android.paymentelement.confirmation + +import android.os.Parcelable +import androidx.activity.result.ActivityResultCaller +import app.cash.turbine.ReceiveTurbine +import app.cash.turbine.Turbine +import com.stripe.android.model.StripeIntent +import com.stripe.android.paymentelement.confirmation.intent.DeferredIntentConfirmationType + +internal class RecordingConfirmationDefinition< + TConfirmationOption : ConfirmationHandler.Option, + TLauncher, + TLauncherArgs, + TLauncherResult : Parcelable, + > private constructor( + private val definition: ConfirmationDefinition +) : ConfirmationDefinition< + TConfirmationOption, + TLauncher, + TLauncherArgs, + TLauncherResult + > { + private val optionCalls = Turbine() + private val toResultCalls = Turbine>() + private val createLauncherCalls = Turbine>() + private val launchCalls = Turbine>() + private val actionCalls = Turbine>() + + override val key: String = definition.key + + override fun option(confirmationOption: ConfirmationHandler.Option): TConfirmationOption? { + optionCalls.add(OptionCall(confirmationOption)) + + return definition.option(confirmationOption) + } + + override fun toResult( + confirmationOption: TConfirmationOption, + deferredIntentConfirmationType: DeferredIntentConfirmationType?, + intent: StripeIntent, + result: TLauncherResult + ): ConfirmationDefinition.Result { + toResultCalls.add(ToResultCall(confirmationOption, deferredIntentConfirmationType, intent, result)) + + return definition.toResult(confirmationOption, deferredIntentConfirmationType, intent, result) + } + + override fun createLauncher( + activityResultCaller: ActivityResultCaller, + onResult: (TLauncherResult) -> Unit + ): TLauncher { + createLauncherCalls.add(CreateLauncherCall(activityResultCaller, onResult)) + + return definition.createLauncher(activityResultCaller, onResult) + } + + override fun launch( + launcher: TLauncher, + arguments: TLauncherArgs, + confirmationOption: TConfirmationOption, + intent: StripeIntent + ) { + launchCalls.add(LaunchCall(launcher, arguments, confirmationOption, intent)) + + definition.launch(launcher, arguments, confirmationOption, intent) + } + + override suspend fun action( + confirmationOption: TConfirmationOption, + intent: StripeIntent + ): ConfirmationDefinition.Action { + actionCalls.add(ActionCall(confirmationOption, intent)) + + return definition.action(confirmationOption, intent) + } + + class OptionCall( + val option: ConfirmationHandler.Option, + ) + + class ToResultCall( + val confirmationOption: TConfirmationOption, + val deferredIntentConfirmationType: DeferredIntentConfirmationType?, + val intent: StripeIntent, + val result: TLauncherResult, + ) + + class CreateLauncherCall( + val activityResultCaller: ActivityResultCaller, + val onResult: (TLauncherResult) -> Unit + ) + + class LaunchCall( + val launcher: TLauncher, + val arguments: TLauncherArgs, + val confirmationOption: TConfirmationOption, + val intent: StripeIntent, + ) + + class ActionCall( + val confirmationOption: TConfirmationOption, + val intent: StripeIntent, + ) + + class Scenario< + TConfirmationOption : ConfirmationHandler.Option, + TLauncher, + TLauncherArgs, + TLauncherResult : Parcelable, + >( + val definition: ConfirmationDefinition, + val optionCalls: ReceiveTurbine, + val toResultCalls: ReceiveTurbine>, + val createLauncherCalls: ReceiveTurbine>, + val launchCalls: ReceiveTurbine>, + val actionCalls: ReceiveTurbine>, + ) + + companion object { + suspend fun < + TConfirmationOption : ConfirmationHandler.Option, + TLauncher, + TLauncherArgs, + TLauncherResult : Parcelable, + > test( + definition: ConfirmationDefinition, + scenarioTest: suspend Scenario.() -> Unit + ) { + val recordingDefinition = RecordingConfirmationDefinition(definition) + + scenarioTest( + Scenario( + definition = recordingDefinition, + optionCalls = recordingDefinition.optionCalls, + toResultCalls = recordingDefinition.toResultCalls, + createLauncherCalls = recordingDefinition.createLauncherCalls, + launchCalls = recordingDefinition.launchCalls, + actionCalls = recordingDefinition.actionCalls, + ) + ) + + recordingDefinition.optionCalls.ensureAllEventsConsumed() + recordingDefinition.toResultCalls.ensureAllEventsConsumed() + recordingDefinition.createLauncherCalls.ensureAllEventsConsumed() + recordingDefinition.launchCalls.ensureAllEventsConsumed() + recordingDefinition.actionCalls.ensureAllEventsConsumed() + } + } +}