From 2b6caa826bcb7b3ab791df08a4972c7bfb1ee86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mu=C3=B1oz?= <99293320+carlosmuvi-stripe@users.noreply.github.com> Date: Wed, 22 Jan 2025 05:57:12 -0800 Subject: [PATCH] Adds attestation analytics events. (#9831) --- .../FinancialConnectionsSheetViewModel.kt | 45 ++++++++++------- .../FinancialConnectionsAnalyticsEvent.kt | 48 +++++++++++++++++++ .../FinancialConnectionsAnalyticsTracker.kt | 1 + .../domain/LookupAccount.kt | 11 +++-- .../domain/RequestIntegrityToken.kt | 37 ++++++++++++++ .../NetworkingLinkLoginWarmupViewModel.kt | 1 + .../networkinglinksignup/LinkSignupHandler.kt | 17 +++++-- .../NetworkingLinkSignupViewModel.kt | 1 + .../NetworkingLinkLoginWarmupViewModelTest.kt | 5 +- .../LinkSignupHandlerForInstantDebitsTest.kt | 10 ++-- .../LinkSignupHandlerForNetworkingTest.kt | 10 ++-- .../NetworkingLinkSignupViewModelTest.kt | 28 +++++------ 12 files changed, 164 insertions(+), 50 deletions(-) create mode 100644 financial-connections/src/main/java/com/stripe/android/financialconnections/domain/RequestIntegrityToken.kt diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModel.kt index 979fcc5c8f5..efc0e5b12c5 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModel.kt @@ -21,7 +21,8 @@ import com.stripe.android.financialconnections.FinancialConnectionsSheetViewEffe import com.stripe.android.financialconnections.FinancialConnectionsSheetViewEffect.OpenAuthFlowWithUrl import com.stripe.android.financialconnections.FinancialConnectionsSheetViewEffect.OpenNativeAuthFlow import com.stripe.android.financialconnections.FinancialConnectionsSheetViewModel.Companion.QUERY_PARAM_PAYMENT_METHOD -import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent +import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.AttestationInitFailed +import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.AttestationInitSkipped import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent.ErrorCode import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent.Metadata @@ -119,11 +120,22 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor( private fun initAuthFlow() { viewModelScope.launch { kotlin.runCatching { - val attestationInitialized = prepareStandardRequestManager() - getOrFetchSync( + val attestationInitResult = prepareStandardRequestManager() + val syncResponse = getOrFetchSync( refetchCondition = Always, - attestationInitialized = attestationInitialized + attestationInitialized = attestationInitResult.initialized ) + val pane = syncResponse.manifest.nextPane + when (attestationInitResult) { + // We'll just emit failure events to reduce event emissions + AttestationInitResult.Success -> null + AttestationInitResult.Skipped -> AttestationInitSkipped(pane) + is AttestationInitResult.Failure -> AttestationInitFailed( + pane = pane, + error = attestationInitResult.error + ) + }?.let(analyticsTracker::track) + syncResponse }.onFailure { finishWithResult(stateFlow.value, Failed(it)) }.onSuccess { @@ -132,23 +144,16 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor( } } - private suspend fun prepareStandardRequestManager(): Boolean { + private suspend fun prepareStandardRequestManager(): AttestationInitResult { // If previously within the application session an integrity check failed // do not initialize the request manager and directly launch the web flow. if (integrityVerdictManager.verdictFailed()) { - return false - } - val result = integrityRequestManager.prepare() - result.onFailure { - analyticsTracker.track( - FinancialConnectionsAnalyticsEvent.Error( - extraMessage = "Failed to warm up the IntegrityStandardRequestManager", - pane = Pane.CONSENT, - exception = it - ) - ) + return AttestationInitResult.Skipped } - return result.isSuccess + return integrityRequestManager.prepare().fold( + onSuccess = { AttestationInitResult.Success }, + onFailure = { AttestationInitResult.Failure(it) } + ) } /** @@ -582,6 +587,12 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor( } } + private sealed class AttestationInitResult(val initialized: Boolean) { + data object Success : AttestationInitResult(initialized = true) + data object Skipped : AttestationInitResult(initialized = false) + data class Failure(val error: Throwable) : AttestationInitResult(initialized = false) + } + companion object { val Factory = viewModelFactory { diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/analytics/FinancialConnectionsAnalyticsEvent.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/analytics/FinancialConnectionsAnalyticsEvent.kt index 429d68809d3..0b5d89f24b4 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/analytics/FinancialConnectionsAnalyticsEvent.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/analytics/FinancialConnectionsAnalyticsEvent.kt @@ -6,6 +6,7 @@ import com.stripe.android.financialconnections.exception.FinancialConnectionsErr import com.stripe.android.financialconnections.exception.WebAuthFlowFailedException import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane import com.stripe.android.financialconnections.utils.filterNotNullValues +import com.stripe.attestation.AttestationError /** * Event definitions for Financial Connections. @@ -410,6 +411,48 @@ internal sealed class FinancialConnectionsAnalyticsEvent( ).filterNotNullValues() ) + class AttestationInitSkipped(pane: Pane) : FinancialConnectionsAnalyticsEvent( + name = "attestation.init_skipped", + mapOf( + "pane" to pane.analyticsValue, + ).filterNotNullValues() + ) + + class AttestationInitFailed( + pane: Pane, + error: Throwable + ) : FinancialConnectionsAnalyticsEvent( + name = "attestation.init_failed", + mapOf( + "pane" to pane.analyticsValue, + "error_reason" to if (error is AttestationError) error.errorType.name else "unknown" + ).filterNotNullValues() + ) + + class AttestationRequestSucceeded( + pane: Pane, + endpoint: AttestationEndpoint + ) : FinancialConnectionsAnalyticsEvent( + name = "attestation.request_token_succeeded", + mapOf( + "pane" to pane.analyticsValue, + "api" to endpoint.analyticsValue + ).filterNotNullValues() + ) + + class AttestationRequestFailed( + pane: Pane, + endpoint: AttestationEndpoint, + error: Throwable, + ) : FinancialConnectionsAnalyticsEvent( + name = "attestation.request_token_failed", + mapOf( + "pane" to pane.analyticsValue, + "api" to endpoint.analyticsValue, + "error_reason" to if (error is AttestationError) error.errorType.name else "unknown" + ) + ) + internal val Pane.analyticsValue get() = when (this) { // We want to log partner_auth regardless of the pane being shown full-screen or as a drawer. @@ -443,6 +486,11 @@ internal sealed class FinancialConnectionsAnalyticsEvent( result = 31 * result + eventName.hashCode() return result } + + enum class AttestationEndpoint(val analyticsValue: String) { + LOOKUP("consumer_session_lookup"), + SIGNUP("link_sign_up"), + } } private const val EVENT_PREFIX = "linked_accounts" diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/analytics/FinancialConnectionsAnalyticsTracker.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/analytics/FinancialConnectionsAnalyticsTracker.kt index 6e4ded85101..562030f67e5 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/analytics/FinancialConnectionsAnalyticsTracker.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/analytics/FinancialConnectionsAnalyticsTracker.kt @@ -122,6 +122,7 @@ internal class FinancialConnectionsAnalyticsTrackerImpl( "is_stripe_direct" to manifest.isStripeDirect.toString(), "single_account" to manifest.singleAccount.toString(), "allow_manual_entry" to manifest.allowManualEntry.toString(), + "app_verification_enabled" to manifest.appVerificationEnabled.toString(), "account_holder_id" to manifest.accountholderToken, ) } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupAccount.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupAccount.kt index 3562b69833a..f7f4aacc03a 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupAccount.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupAccount.kt @@ -2,15 +2,16 @@ package com.stripe.android.financialconnections.domain import android.app.Application import com.stripe.android.financialconnections.FinancialConnectionsSheet +import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.AttestationEndpoint +import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane import com.stripe.android.financialconnections.repository.FinancialConnectionsConsumerSessionRepository import com.stripe.android.model.ConsumerSessionLookup import com.stripe.android.model.EmailSource -import com.stripe.attestation.IntegrityRequestManager import javax.inject.Inject internal class LookupAccount @Inject constructor( private val application: Application, - private val integrityRequestManager: IntegrityRequestManager, + private val requestIntegrityToken: RequestIntegrityToken, private val consumerSessionRepository: FinancialConnectionsConsumerSessionRepository, val configuration: FinancialConnectionsSheet.Configuration, ) { @@ -19,14 +20,16 @@ internal class LookupAccount @Inject constructor( email: String, emailSource: EmailSource, verifiedFlow: Boolean, - sessionId: String + sessionId: String, + pane: Pane ): ConsumerSessionLookup { return if (verifiedFlow) { + val token = requestIntegrityToken(pane = pane, endpoint = AttestationEndpoint.LOOKUP) requireNotNull( consumerSessionRepository.mobileLookupConsumerSession( email = email.lowercase().trim(), emailSource = emailSource, - verificationToken = integrityRequestManager.requestToken().getOrThrow(), + verificationToken = token, appId = application.packageName, sessionId = sessionId ) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/RequestIntegrityToken.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/RequestIntegrityToken.kt new file mode 100644 index 00000000000..a5d1d4437b8 --- /dev/null +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/RequestIntegrityToken.kt @@ -0,0 +1,37 @@ +package com.stripe.android.financialconnections.domain + +import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.AttestationEndpoint +import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.AttestationRequestFailed +import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.AttestationRequestSucceeded +import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker +import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest +import com.stripe.attestation.IntegrityRequestManager +import jakarta.inject.Inject + +internal class RequestIntegrityToken @Inject constructor( + private val integrityRequestManager: IntegrityRequestManager, + private val analyticsTracker: FinancialConnectionsAnalyticsTracker +) { + suspend operator fun invoke( + endpoint: AttestationEndpoint, + pane: FinancialConnectionsSessionManifest.Pane + ): String = integrityRequestManager.requestToken() + .onSuccess { + analyticsTracker.track( + AttestationRequestSucceeded( + pane = pane, + endpoint = endpoint + ) + ) + } + .onFailure { + analyticsTracker.track( + AttestationRequestFailed( + pane = pane, + endpoint = endpoint, + error = it + ) + ) + } + .getOrThrow() +} diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinkloginwarmup/NetworkingLinkLoginWarmupViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinkloginwarmup/NetworkingLinkLoginWarmupViewModel.kt index ee125f32269..63d4fb4d5d3 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinkloginwarmup/NetworkingLinkLoginWarmupViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinkloginwarmup/NetworkingLinkLoginWarmupViewModel.kt @@ -95,6 +95,7 @@ internal class NetworkingLinkLoginWarmupViewModel @AssistedInject constructor( eventTracker.track(Click("click.continue", PANE)) // Trigger a lookup call to ensure we cache a consumer session for posterior verification. lookupAccount( + pane = PANE, email = payload.email, emailSource = EmailSource.CUSTOMER_OBJECT, sessionId = payload.sessionId, diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandler.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandler.kt index dfcf0df5aa5..7e900360d8a 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandler.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandler.kt @@ -1,6 +1,7 @@ package com.stripe.android.financialconnections.features.networkinglinksignup import com.stripe.android.core.Logger +import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.AttestationEndpoint.SIGNUP import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.Click import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker import com.stripe.android.financialconnections.analytics.logError @@ -10,6 +11,7 @@ import com.stripe.android.financialconnections.domain.GetCachedAccounts import com.stripe.android.financialconnections.domain.GetOrFetchSync import com.stripe.android.financialconnections.domain.GetOrFetchSync.RefetchCondition.Always import com.stripe.android.financialconnections.domain.HandleError +import com.stripe.android.financialconnections.domain.RequestIntegrityToken import com.stripe.android.financialconnections.domain.SaveAccountToLink import com.stripe.android.financialconnections.features.common.isDataFlow import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane @@ -20,7 +22,6 @@ import com.stripe.android.financialconnections.navigation.Destination.Networking import com.stripe.android.financialconnections.navigation.Destination.Success import com.stripe.android.financialconnections.navigation.NavigationManager import com.stripe.android.financialconnections.repository.FinancialConnectionsConsumerSessionRepository -import com.stripe.attestation.IntegrityRequestManager import javax.inject.Inject import javax.inject.Named @@ -40,7 +41,7 @@ internal interface LinkSignupHandler { internal class LinkSignupHandlerForInstantDebits @Inject constructor( private val consumerRepository: FinancialConnectionsConsumerSessionRepository, private val attachConsumerToLinkAccountSession: AttachConsumerToLinkAccountSession, - private val integrityRequestManager: IntegrityRequestManager, + private val requestIntegrityToken: RequestIntegrityToken, private val getOrFetchSync: GetOrFetchSync, private val navigationManager: NavigationManager, @Named(APPLICATION_ID) private val applicationId: String, @@ -54,7 +55,10 @@ internal class LinkSignupHandlerForInstantDebits @Inject constructor( val manifest = getOrFetchSync().manifest val signup = if (manifest.appVerificationEnabled) { - val token = integrityRequestManager.requestToken().getOrThrow() + val token = requestIntegrityToken( + endpoint = SIGNUP, + pane = LINK_LOGIN + ) consumerRepository.mobileSignUp( email = state.validEmail!!, phoneNumber = state.validPhone!!, @@ -96,7 +100,7 @@ internal class LinkSignupHandlerForNetworking @Inject constructor( private val consumerRepository: FinancialConnectionsConsumerSessionRepository, private val getOrFetchSync: GetOrFetchSync, private val getCachedAccounts: GetCachedAccounts, - private val integrityRequestManager: IntegrityRequestManager, + private val requestIntegrityToken: RequestIntegrityToken, private val saveAccountToLink: SaveAccountToLink, private val eventTracker: FinancialConnectionsAnalyticsTracker, private val navigationManager: NavigationManager, @@ -117,7 +121,10 @@ internal class LinkSignupHandlerForNetworking @Inject constructor( // ** New signup flow on verified flows: 2 requests ** // 1. Mobile signup endpoint providing email + phone number // 2. Separately call SaveAccountToLink with the newly created account. - val token = integrityRequestManager.requestToken().getOrThrow() + val token = requestIntegrityToken( + endpoint = SIGNUP, + pane = NETWORKING_LINK_SIGNUP_PANE + ) val signup = consumerRepository.mobileSignUp( email = state.validEmail!!, phoneNumber = state.validPhone!!, diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModel.kt index e576be5b68b..604bb999d64 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModel.kt @@ -213,6 +213,7 @@ internal class NetworkingLinkSignupViewModel @AssistedInject constructor( delay(getLookupDelayMs(validEmail)) val payload = stateFlow.value.payload() lookupAccount( + pane = pane, email = validEmail, emailSource = if (payload?.prefilledEmail == validEmail) CUSTOMER_OBJECT else USER_ACTION, sessionId = payload?.sessionId ?: "", diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinkloginwarmup/NetworkingLinkLoginWarmupViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinkloginwarmup/NetworkingLinkLoginWarmupViewModelTest.kt index bada03132cb..c599d8e10f4 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinkloginwarmup/NetworkingLinkLoginWarmupViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinkloginwarmup/NetworkingLinkLoginWarmupViewModelTest.kt @@ -82,7 +82,8 @@ class NetworkingLinkLoginWarmupViewModelTest { email = anyOrNull(), emailSource = anyOrNull(), verifiedFlow = anyOrNull(), - sessionId = anyOrNull() + sessionId = anyOrNull(), + pane = any() ) ).thenReturn( ConsumerSessionLookup( @@ -95,7 +96,7 @@ class NetworkingLinkLoginWarmupViewModelTest { val viewModel = buildViewModel(NetworkingLinkLoginWarmupState()) viewModel.onContinueClick() - verify(lookupAccount).invoke(any(), any(), any(), any()) + verify(lookupAccount).invoke(any(), any(), any(), any(), any()) navigationManager.assertNavigatedTo( destination = Destination.NetworkingLinkVerification, pane = Pane.NETWORKING_LINK_LOGIN_WARMUP diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandlerForInstantDebitsTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandlerForInstantDebitsTest.kt index 613ca04dcd4..0a5bb6d399e 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandlerForInstantDebitsTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandlerForInstantDebitsTest.kt @@ -6,12 +6,12 @@ import com.stripe.android.financialconnections.ApiKeyFixtures.syncResponse import com.stripe.android.financialconnections.domain.AttachConsumerToLinkAccountSession import com.stripe.android.financialconnections.domain.GetOrFetchSync import com.stripe.android.financialconnections.domain.HandleError +import com.stripe.android.financialconnections.domain.RequestIntegrityToken import com.stripe.android.financialconnections.features.networkinglinksignup.NetworkingLinkSignupState.Payload import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane import com.stripe.android.financialconnections.navigation.NavigationManager import com.stripe.android.financialconnections.presentation.Async import com.stripe.android.financialconnections.repository.FinancialConnectionsConsumerSessionRepository -import com.stripe.android.financialconnections.utils.TestIntegrityRequestManager import kotlinx.coroutines.test.runTest import org.junit.Before import org.mockito.kotlin.any @@ -29,7 +29,7 @@ class LinkSignupHandlerForInstantDebitsTest { private val consumerRepository = mock() private val getOrFetchSync = mock() private val attachConsumerToLinkAccountSession = mock() - private val integrityRequestManager = TestIntegrityRequestManager() + private val requestIntegrityToken = mock() private val navigationManager = mock() private val handleError = mock() @@ -38,7 +38,7 @@ class LinkSignupHandlerForInstantDebitsTest { handler = LinkSignupHandlerForInstantDebits( consumerRepository, attachConsumerToLinkAccountSession, - integrityRequestManager, + requestIntegrityToken, getOrFetchSync, navigationManager, "applicationId", @@ -61,6 +61,8 @@ class LinkSignupHandlerForInstantDebitsTest { @Test fun `performSignup should navigate to next pane on success`() = runTest { + val expectedToken = "token" + val expectedPane = Pane.INSTITUTION_PICKER val testState = NetworkingLinkSignupState( validEmail = "test@example.com", validPhone = "+123456789", @@ -68,7 +70,7 @@ class LinkSignupHandlerForInstantDebitsTest { payload = Async.Success(validPayload) ) - val expectedPane = Pane.INSTITUTION_PICKER + whenever(requestIntegrityToken(anyOrNull(), anyOrNull())).thenReturn(expectedToken) whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse( sessionManifest().copy( diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandlerForNetworkingTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandlerForNetworkingTest.kt index df17b4d1cd8..6ebac93f4de 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandlerForNetworkingTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/LinkSignupHandlerForNetworkingTest.kt @@ -9,12 +9,12 @@ import com.stripe.android.financialconnections.analytics.logError import com.stripe.android.financialconnections.domain.CachedPartnerAccount import com.stripe.android.financialconnections.domain.GetCachedAccounts import com.stripe.android.financialconnections.domain.GetOrFetchSync +import com.stripe.android.financialconnections.domain.RequestIntegrityToken import com.stripe.android.financialconnections.domain.SaveAccountToLink import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane import com.stripe.android.financialconnections.navigation.Destination import com.stripe.android.financialconnections.presentation.Async import com.stripe.android.financialconnections.repository.FinancialConnectionsConsumerSessionRepository -import com.stripe.android.financialconnections.utils.TestIntegrityRequestManager import com.stripe.android.financialconnections.utils.TestNavigationManager import kotlinx.coroutines.test.runTest import org.junit.Before @@ -35,7 +35,7 @@ class LinkSignupHandlerForNetworkingTest { private val getOrFetchSync = mock() private val getCachedAccounts = mock() private val saveAccountToLink = mock() - private val integrityRequestManager = TestIntegrityRequestManager() + private val requestIntegrityToken = mock() private val navigationManager = TestNavigationManager() private val eventTracker = mock() private val logger = mock() @@ -46,7 +46,7 @@ class LinkSignupHandlerForNetworkingTest { consumerRepository, getOrFetchSync, getCachedAccounts, - integrityRequestManager, + requestIntegrityToken, saveAccountToLink, eventTracker, navigationManager, @@ -70,6 +70,7 @@ class LinkSignupHandlerForNetworkingTest { @Test fun `performSignup on verified flows should save account and return success pane on success`() = runTest { + val expectedToken = "token" val testState = NetworkingLinkSignupState( validEmail = "test@networking.com", validPhone = "+1987654321", @@ -79,6 +80,7 @@ class LinkSignupHandlerForNetworkingTest { val expectedPane = Pane.SUCCESS + whenever(requestIntegrityToken(anyOrNull(), anyOrNull())).thenReturn(expectedToken) whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse(sessionManifest().copy(appVerificationEnabled = true)) ) @@ -99,7 +101,7 @@ class LinkSignupHandlerForNetworkingTest { email = eq("test@networking.com"), phoneNumber = eq("+1987654321"), country = eq("US"), - verificationToken = eq(integrityRequestManager.requestTokenResult.getOrThrow()), + verificationToken = eq(expectedToken), appId = eq("applicationId") ) verify(saveAccountToLink).existing(any(), any(), any()) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModelTest.kt index 1cd3d15dd8a..4b1670d855c 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModelTest.kt @@ -96,7 +96,7 @@ class NetworkingLinkSignupViewModelTest { ) ) ) - whenever(lookupAccount(any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) + whenever(lookupAccount(any(), any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel(NetworkingLinkSignupState()) val state = viewModel.stateFlow.value @@ -117,7 +117,7 @@ class NetworkingLinkSignupViewModelTest { ) ) ) - whenever(lookupAccount(any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) + whenever(lookupAccount(any(), any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel( state = NetworkingLinkSignupState(), @@ -201,7 +201,7 @@ class NetworkingLinkSignupViewModelTest { ) ) whenever(getOrFetchSync().manifest).thenReturn(manifest) - whenever(lookupAccount(any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = true)) + whenever(lookupAccount(any(), any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = true)) val viewModel = buildViewModel(NetworkingLinkSignupState()) @@ -236,7 +236,7 @@ class NetworkingLinkSignupViewModelTest { ) ) - whenever(lookupAccount(any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = true)) + whenever(lookupAccount(any(), any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = true)) val viewModel = buildViewModel( state = NetworkingLinkSignupState(isInstantDebits = true), @@ -274,7 +274,7 @@ class NetworkingLinkSignupViewModelTest { ) ) whenever(getOrFetchSync().manifest).thenReturn(manifest) - whenever(lookupAccount(any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = true)) + whenever(lookupAccount(any(), any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = true)) val viewModel = buildViewModel( state = NetworkingLinkSignupState(), @@ -368,7 +368,7 @@ class NetworkingLinkSignupViewModelTest { ) whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(syncResponse) - whenever(lookupAccount(any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) + whenever(lookupAccount(any(), any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel( state = NetworkingLinkSignupState(isInstantDebits = false), @@ -402,7 +402,7 @@ class NetworkingLinkSignupViewModelTest { ) whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) - whenever(lookupAccount(any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) + whenever(lookupAccount(any(), any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel( state = NetworkingLinkSignupState(isInstantDebits = true), @@ -436,7 +436,7 @@ class NetworkingLinkSignupViewModelTest { ) whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(syncResponse) - whenever(lookupAccount(any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) + whenever(lookupAccount(any(), any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel( state = NetworkingLinkSignupState(isInstantDebits = false), @@ -470,7 +470,7 @@ class NetworkingLinkSignupViewModelTest { ) whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) - whenever(lookupAccount(any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) + whenever(lookupAccount(any(), any(), any(), any(), any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel( state = NetworkingLinkSignupState(isInstantDebits = true), @@ -502,7 +502,7 @@ class NetworkingLinkSignupViewModelTest { val permissionException = PermissionException(stripeError = StripeError()) whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) - whenever(lookupAccount(any(), any(), any(), any())).then { + whenever(lookupAccount(any(), any(), any(), any(), any())).then { throw permissionException } @@ -534,7 +534,7 @@ class NetworkingLinkSignupViewModelTest { val apiException = APIConnectionException() whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) - whenever(lookupAccount(any(), any(), any(), any())).then { + whenever(lookupAccount(any(), any(), any(), any(), any())).then { throw apiException } @@ -566,7 +566,7 @@ class NetworkingLinkSignupViewModelTest { val permissionException = PermissionException(stripeError = StripeError()) whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) - whenever(lookupAccount(any(), any(), any(), any())).then { + whenever(lookupAccount(any(), any(), any(), any(), any())).then { throw permissionException } @@ -624,7 +624,7 @@ class NetworkingLinkSignupViewModelTest { saveAccountToLink = saveAccountToLink, eventTracker = eventTracker, navigationManager = navigationManager, - integrityRequestManager = mock(), + requestIntegrityToken = mock(), applicationId = "test", logger = Logger.noop(), ) @@ -659,7 +659,7 @@ class NetworkingLinkSignupViewModelTest { }, navigationManager = navigationManager, handleError = handleError, - integrityRequestManager = mock(), + requestIntegrityToken = mock(), applicationId = "test", ) }