@@ -661,117 +661,101 @@ class PaymentLauncherViewModelTest {
661661
662662 @Test
663663 fun `verify only one finished event is sent when result is already set` () = runTest {
664- // Setup: Confirm succeeds without requiring action
665664 whenever(paymentIntent.requiresAction()).thenReturn(false )
666665 val viewModel = createViewModel()
667666
668- // First confirm call - should send finished event
669667 viewModel.confirmStripeIntent(confirmPaymentIntentParams, authHost)
670-
671- // Verify the result was set
672668 assertThat(viewModel.internalPaymentResult.value).isInstanceOf(InternalPaymentResult .Completed ::class .java)
673669
674- // Verify finished event was sent once
675- verify(analyticsRequestFactory, times(1 )).createRequest(
676- eq(PaymentAnalyticsEvent .PaymentLauncherConfirmFinished ),
677- additionalParams = argThat { params ->
678- params[" status" ] == " succeeded"
679- }
680- )
681-
682- // Try to process a payment flow result after the result is already set
683- // This should NOT send another finished event due to the guard
684- val paymentFlowResult = mock<PaymentFlowResult .Unvalidated >()
685- whenever(paymentIntentFlowResultProcessor.processResult(eq(paymentFlowResult)))
686- .thenReturn(Result .success(succeededPaymentResult))
687- viewModel.onPaymentFlowResult(paymentFlowResult)
688-
689- // Verify finished event was still only sent once (guard prevented duplicate)
690- verify(analyticsRequestFactory, times(1 )).createRequest(
691- eq(PaymentAnalyticsEvent .PaymentLauncherConfirmFinished ),
692- additionalParams = argThat { params ->
693- params[" status" ] == " succeeded"
694- }
670+ verifyGuardPreventsDuplicateFinishedEvents(
671+ viewModel = viewModel,
672+ expectedEvent = PaymentAnalyticsEvent .PaymentLauncherConfirmFinished ,
673+ expectedStatus = " succeeded" ,
674+ expectedResultType = InternalPaymentResult .Completed ::class .java,
675+ secondResult = succeededPaymentResult
695676 )
696-
697- // Verify the result value didn't change
698- assertThat(viewModel.internalPaymentResult.value).isInstanceOf(InternalPaymentResult .Completed ::class .java)
699677 }
700678
701679 @Test
702- fun `verify guard prevents duplicate events for failed results` () = runTest {
703- // Setup: Confirm fails
704- whenever(stripeApiRepository.confirmPaymentIntent(any(), any(), any()))
705- .thenReturn(Result .failure(APIConnectionException ()))
680+ fun `verify guard blocks different result types` () = runTest {
681+ whenever(paymentIntent.requiresAction()).thenReturn(false )
706682 val viewModel = createViewModel()
707683
708- // Confirm call - should send finished event with failed status
709684 viewModel.confirmStripeIntent(confirmPaymentIntentParams, authHost)
685+ assertThat(viewModel.internalPaymentResult.value).isInstanceOf(InternalPaymentResult .Completed ::class .java)
710686
711- // Verify failed result was set
712- assertThat(viewModel.internalPaymentResult.value).isInstanceOf(InternalPaymentResult .Failed ::class .java)
713-
714- // Verify finished event was sent once with failed status
715- verify(analyticsRequestFactory, times(1 )).createRequest(
716- eq(PaymentAnalyticsEvent .PaymentLauncherConfirmFinished ),
717- additionalParams = argThat { params ->
718- params[" status" ] == " failed"
719- }
720- )
721-
722- // Try to send another result - should be blocked by guard
723- val paymentFlowResult = mock<PaymentFlowResult .Unvalidated >()
724- whenever(paymentIntentFlowResultProcessor.processResult(eq(paymentFlowResult)))
725- .thenReturn(Result .success(canceledPaymentResult))
726- viewModel.onPaymentFlowResult(paymentFlowResult)
727-
728- // Verify no additional finished events were sent
729- verify(analyticsRequestFactory, times(1 )).createRequest(
730- eq(PaymentAnalyticsEvent .PaymentLauncherConfirmFinished ),
731- additionalParams = any()
687+ verifyGuardPreventsDuplicateFinishedEvents(
688+ viewModel = viewModel,
689+ expectedEvent = PaymentAnalyticsEvent .PaymentLauncherConfirmFinished ,
690+ expectedStatus = " succeeded" ,
691+ expectedResultType = InternalPaymentResult .Completed ::class .java,
692+ secondResult = failedPaymentResult
732693 )
733-
734- // Verify the result value remained as the first failure
735- assertThat(viewModel.internalPaymentResult.value).isInstanceOf(InternalPaymentResult .Failed ::class .java)
736694 }
737695
738696 @Test
739697 fun `verify guard works for next action flows` () = runTest {
740698 val savedStateHandle = SavedStateHandle ()
741699 val viewModel = createViewModel(savedStateHandle = savedStateHandle)
742700
743- // Start next action handling
744701 viewModel.handleNextActionForStripeIntent(CLIENT_SECRET , authHost)
745702
746- // First result callback - should send finished event
747- val paymentFlowResult1 = mock<PaymentFlowResult .Unvalidated >()
748- whenever(paymentIntentFlowResultProcessor.processResult(eq(paymentFlowResult1)))
703+ val paymentFlowResult = mock<PaymentFlowResult .Unvalidated >()
704+ whenever(paymentIntentFlowResultProcessor.processResult(eq(paymentFlowResult)))
749705 .thenReturn(Result .success(succeededPaymentResult))
750- viewModel.onPaymentFlowResult(paymentFlowResult1)
751-
752- // Verify result was set
706+ viewModel.onPaymentFlowResult(paymentFlowResult)
753707 assertThat(viewModel.internalPaymentResult.value).isInstanceOf(InternalPaymentResult .Completed ::class .java)
754708
755- // Verify finished event was sent once
709+ verifyGuardPreventsDuplicateFinishedEvents(
710+ viewModel = viewModel,
711+ expectedEvent = PaymentAnalyticsEvent .PaymentLauncherNextActionFinished ,
712+ expectedStatus = " succeeded" ,
713+ expectedResultType = InternalPaymentResult .Completed ::class .java,
714+ secondResult = succeededPaymentResult
715+ )
716+ }
717+
718+ private fun verifyGuardPreventsDuplicateFinishedEvents (
719+ viewModel : PaymentLauncherViewModel ,
720+ expectedEvent : PaymentAnalyticsEvent ,
721+ expectedStatus : String ,
722+ expectedResultType : Class <out InternalPaymentResult >,
723+ secondResult : StripeIntentResult <* >
724+ ) {
725+ // Verify initial finished event was sent once
756726 verify(analyticsRequestFactory, times(1 )).createRequest(
757- eq(PaymentAnalyticsEvent . PaymentLauncherNextActionFinished ),
727+ eq(expectedEvent ),
758728 additionalParams = argThat { params ->
759- params[" status" ] == " succeeded "
729+ params[" status" ] == expectedStatus
760730 }
761731 )
762732
763- // Second result callback (simulating multiple callbacks from a buggy handler)
764- // Should be blocked by guard
765- val paymentFlowResult2 = mock<PaymentFlowResult .Unvalidated >()
766- whenever(paymentIntentFlowResultProcessor.processResult(eq(paymentFlowResult2)))
767- .thenReturn(Result .success(succeededPaymentResult))
768- viewModel.onPaymentFlowResult(paymentFlowResult2)
733+ // Try to send another result - should be blocked by guard
734+ val paymentFlowResult = mock<PaymentFlowResult .Unvalidated >()
735+ whenever(paymentIntentFlowResultProcessor.processResult(eq(paymentFlowResult)))
736+ .thenReturn(Result .success(secondResult))
737+ viewModel.onPaymentFlowResult(paymentFlowResult)
769738
770739 // Verify still only one finished event was sent
771740 verify(analyticsRequestFactory, times(1 )).createRequest(
772- eq(PaymentAnalyticsEvent . PaymentLauncherNextActionFinished ),
741+ eq(expectedEvent ),
773742 additionalParams = any()
774743 )
744+
745+ // Verify the result value didn't change
746+ assertThat(viewModel.internalPaymentResult.value).isInstanceOf(expectedResultType)
747+ }
748+
749+ private fun finishedAnalyticsEventSent (
750+ status : String = "succeeded",
751+ times : Int = 1
752+ ) {
753+ verify(analyticsRequestFactory, times(times)).createRequest(
754+ eq(PaymentAnalyticsEvent .PaymentLauncherConfirmFinished ),
755+ additionalParams = argThat { params ->
756+ params[" status" ] == status
757+ }
758+ )
775759 }
776760
777761 companion object {
0 commit comments