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 9280b6f2d93..bb0c6991117 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 @@ -116,8 +116,11 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor( private fun initAuthFlow() { viewModelScope.launch { kotlin.runCatching { - prepareStandardRequestManager() - getOrFetchSync(refetchCondition = Always) + val attestationInitialized = prepareStandardRequestManager() + getOrFetchSync( + refetchCondition = Always, + attestationInitialized = attestationInitialized + ) }.onFailure { finishWithResult(stateFlow.value, Failed(it)) }.onSuccess { diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/GetOrFetchSync.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/GetOrFetchSync.kt index 14675925814..7942915afff 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/GetOrFetchSync.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/GetOrFetchSync.kt @@ -18,12 +18,14 @@ internal class GetOrFetchSync @Inject constructor( ) { suspend operator fun invoke( - refetchCondition: RefetchCondition = RefetchCondition.None + refetchCondition: RefetchCondition = RefetchCondition.None, + attestationInitialized: Boolean = false ): SynchronizeSessionResponse { return repository.getOrSynchronizeFinancialConnectionsSession( clientSecret = configuration.financialConnectionsSessionClientSecret, applicationId = applicationId, reFetchCondition = refetchCondition::shouldReFetch, + attestationInitialized = attestationInitialized ) } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsManifestRepository.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsManifestRepository.kt index a7d99acb296..7ff344ea0ce 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsManifestRepository.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsManifestRepository.kt @@ -40,6 +40,7 @@ internal interface FinancialConnectionsManifestRepository { suspend fun getOrSynchronizeFinancialConnectionsSession( clientSecret: String, applicationId: String, + attestationInitialized: Boolean, reFetchCondition: (SynchronizeSessionResponse) -> Boolean ): SynchronizeSessionResponse @@ -206,15 +207,17 @@ private class FinancialConnectionsManifestRepositoryImpl( override suspend fun getOrSynchronizeFinancialConnectionsSession( clientSecret: String, applicationId: String, + attestationInitialized: Boolean, reFetchCondition: (SynchronizeSessionResponse) -> Boolean ): SynchronizeSessionResponse = mutex.withLock { val cachedSync = cachedSynchronizeSessionResponse?.takeUnless(reFetchCondition) - return cachedSync ?: synchronize(applicationId, clientSecret) + return cachedSync ?: synchronize(applicationId, clientSecret, attestationInitialized) } private suspend fun synchronize( applicationId: String, clientSecret: String, + attestationInitialized: Boolean, ): SynchronizeSessionResponse = requestExecutor.execute( apiRequestFactory.createPost( url = synchronizeSessionUrl, @@ -228,6 +231,8 @@ private class FinancialConnectionsManifestRepositoryImpl( "forced_authflow_version" to "v3", PARAMS_FULLSCREEN to true, PARAMS_HIDE_CLOSE_BUTTON to true, + PARAMS_SUPPORT_APP_VERIFICATION to attestationInitialized, + PARAMS_VERIFY_APP_ID to applicationId.takeIf { attestationInitialized }, NetworkConstants.PARAMS_APPLICATION_ID to applicationId ), NetworkConstants.PARAMS_CLIENT_SECRET to clientSecret @@ -523,6 +528,8 @@ private class FinancialConnectionsManifestRepositoryImpl( companion object { internal const val PARAMS_FULLSCREEN = "fullscreen" internal const val PARAMS_HIDE_CLOSE_BUTTON = "hide_close_button" + internal const val PARAMS_SUPPORT_APP_VERIFICATION = "supports_app_verification" + internal const val PARAMS_VERIFY_APP_ID = "verified_app_id" internal const val synchronizeSessionUrl: String = "${ApiRequest.API_HOST}/v1/financial_connections/sessions/synchronize" diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt index ae728bef74a..a79cbc7ff12 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt @@ -34,8 +34,10 @@ import com.stripe.android.financialconnections.model.FinancialConnectionsAccount import com.stripe.android.financialconnections.model.FinancialConnectionsSession import com.stripe.android.financialconnections.model.FinancialConnectionsSession.StatusDetails import com.stripe.android.financialconnections.presentation.withState +import com.stripe.android.financialconnections.utils.TestIntegrityRequestManager import com.stripe.android.model.IncentiveEligibilitySession import com.stripe.android.model.LinkMode +import com.stripe.attestation.IntegrityRequestManager import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -44,6 +46,7 @@ import org.junit.Test import org.junit.rules.TestRule import org.junit.runner.RunWith import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -88,12 +91,31 @@ class FinancialConnectionsSheetViewModelTest { } @Test - fun `init - if manifest not present in initial state, fetchManifest triggered`() = + fun `init - if manifest not present in initial state and attestation ready, fetchManifest triggered`() = runTest { - whenever(getOrFetchSync(any())).thenReturn(syncResponse) - createViewModel(defaultInitialState) + whenever(getOrFetchSync(any(), anyOrNull())).thenReturn(syncResponse) + createViewModel( + defaultInitialState, + integrityRequestManager = TestIntegrityRequestManager( + prepareResult = Result.success(Unit) + ) + ) + + verify(getOrFetchSync).invoke(refetchCondition = Always, attestationInitialized = true) + } + + @Test + fun `init - if manifest not present in initial state and attestation fails, fetchManifest triggered`() = + runTest { + whenever(getOrFetchSync(any(), anyOrNull())).thenReturn(syncResponse) + createViewModel( + defaultInitialState, + integrityRequestManager = TestIntegrityRequestManager( + prepareResult = Result.failure(Exception()) + ) + ) - verify(getOrFetchSync).invoke(refetchCondition = Always) + verify(getOrFetchSync).invoke(refetchCondition = Always, attestationInitialized = false) } @Test @@ -109,7 +131,7 @@ class FinancialConnectionsSheetViewModelTest { fun `init - When no browser available, AuthFlow closes and logs error`() = runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(false) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) // When val viewModel = createViewModel(defaultInitialState) @@ -139,7 +161,7 @@ class FinancialConnectionsSheetViewModelTest { runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) // When @@ -175,7 +197,7 @@ class FinancialConnectionsSheetViewModelTest { runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) // When @@ -210,7 +232,7 @@ class FinancialConnectionsSheetViewModelTest { fun `init - when instant debits flow, hosted auth url doesn't contain link_mode if unknown`() = runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) // When @@ -245,7 +267,7 @@ class FinancialConnectionsSheetViewModelTest { fun `init - when instant debits flow, hosted auth url contains incentive info if eligible`() = runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) // When @@ -281,7 +303,7 @@ class FinancialConnectionsSheetViewModelTest { fun `init - when instant debits flow, hosted auth url does not contain incentive info if not eligible`() = runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) // When @@ -317,7 +339,7 @@ class FinancialConnectionsSheetViewModelTest { fun `init - hosted auth url contains prefill details`() = runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) // When @@ -354,7 +376,7 @@ class FinancialConnectionsSheetViewModelTest { fun `init - hosted auth url contains billing details`() = runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) // When @@ -408,7 +430,7 @@ class FinancialConnectionsSheetViewModelTest { fun `init - when data flow and non-native, hosted auth url without query params is launched`() = runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(nativeRouter.nativeAuthFlowEnabled(any())).thenReturn(false) // When @@ -429,7 +451,7 @@ class FinancialConnectionsSheetViewModelTest { fun `handleOnNewIntent - wrong intent should fire analytics event and set fail result`() = runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) val viewModel = createViewModel(defaultInitialState) // When @@ -444,7 +466,7 @@ class FinancialConnectionsSheetViewModelTest { fun `handleOnNewIntent - on Link flows with invalid account, error is thrown`() { runTest { // Given - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) val viewModel = createViewModel( defaultInitialState.copy(initialArgs = ForInstantDebits(configuration)) @@ -474,7 +496,7 @@ class FinancialConnectionsSheetViewModelTest { whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) whenever(fetchFinancialConnectionsSession(any())) .thenReturn(financialConnectionsSessionWithNoMoreAccounts) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) val viewModel = createViewModel(defaultInitialState) val cancelIntent = cancelIntent() @@ -495,7 +517,7 @@ class FinancialConnectionsSheetViewModelTest { whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) whenever(fetchFinancialConnectionsSession(any())) .thenReturn(financialConnectionsSessionWithNoMoreAccounts) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) val viewModel = createViewModel(defaultInitialState) // When @@ -520,7 +542,7 @@ class FinancialConnectionsSheetViewModelTest { ) ) whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(fetchFinancialConnectionsSession(any())).thenReturn(expectedSession) val viewModel = createViewModel(defaultInitialState) @@ -541,7 +563,7 @@ class FinancialConnectionsSheetViewModelTest { fun `handleOnNewIntent - when intent with unknown received, then finish with Result#Failed`() = runTest { // Given - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) val viewModel = createViewModel(defaultInitialState) val errorIntent = Intent() @@ -563,7 +585,7 @@ class FinancialConnectionsSheetViewModelTest { // Given val expectedSession = financialConnectionsSession() whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(fetchFinancialConnectionsSession(any())).thenReturn(expectedSession) val viewModel = createViewModel(defaultInitialState) @@ -590,7 +612,7 @@ class FinancialConnectionsSheetViewModelTest { "skip_aggregation=true&referral_source=APP&client_redirect_url=123" val nativeRedirectUrl = "stripe-auth://native-redirect/com.example.app/$aggregatorUrl" val expectedSession = financialConnectionsSession() - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(fetchFinancialConnectionsSession(any())).thenReturn(expectedSession) val viewModel = createViewModel(defaultInitialState) @@ -616,7 +638,7 @@ class FinancialConnectionsSheetViewModelTest { "stripe-auth://link-accounts/com.example.app/authentication_return#$returnUrlQueryParams" val expectedSession = financialConnectionsSession() whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(fetchFinancialConnectionsSession(any())).thenReturn(expectedSession) val viewModel = createViewModel(defaultInitialState) @@ -641,7 +663,7 @@ class FinancialConnectionsSheetViewModelTest { // Given val apiException = APIException() whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(fetchFinancialConnectionsSession.invoke(any())).thenAnswer { throw apiException } val viewModel = createViewModel(defaultInitialState) @@ -662,7 +684,7 @@ class FinancialConnectionsSheetViewModelTest { runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) whenever(fetchFinancialConnectionsSession(any())).thenAnswer { throw APIException() } val viewModel = createViewModel(defaultInitialState) // When @@ -682,7 +704,7 @@ class FinancialConnectionsSheetViewModelTest { runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())) + whenever(getOrFetchSync(any(), any())) .thenReturn( syncResponse.copy( manifest = sessionManifest().copy( @@ -772,7 +794,7 @@ class FinancialConnectionsSheetViewModelTest { runTest { // Given whenever(browserManager.canOpenHttpsUrl()).thenReturn(true) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), any())).thenReturn(syncResponse) // When val viewModel = createViewModel(defaultInitialState) @@ -807,7 +829,8 @@ class FinancialConnectionsSheetViewModelTest { ) private fun createViewModel( - initialState: FinancialConnectionsSheetState + initialState: FinancialConnectionsSheetState, + integrityRequestManager: IntegrityRequestManager = TestIntegrityRequestManager() ): FinancialConnectionsSheetViewModel { return FinancialConnectionsSheetViewModel( applicationId = "com.example.app", @@ -821,7 +844,7 @@ class FinancialConnectionsSheetViewModelTest { browserManager = browserManager, savedStateHandle = SavedStateHandle(), nativeAuthFlowCoordinator = mock(), - integrityRequestManager = mock(), + integrityRequestManager = integrityRequestManager, logger = Logger.noop() ) } diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/linkstepupverification/LinkStepUpVerificationViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/linkstepupverification/LinkStepUpVerificationViewModelTest.kt index 64a257b5d57..30be11f8012 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/linkstepupverification/LinkStepUpVerificationViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/linkstepupverification/LinkStepUpVerificationViewModelTest.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -137,7 +138,7 @@ class LinkStepUpVerificationViewModelTest { private suspend fun givenGetSyncReturns( syncResponse: SynchronizeSessionResponse = syncResponse() ) { - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(any(), anyOrNull())).thenReturn(syncResponse) } private suspend fun markLinkVerifiedReturns( 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 9de51911f84..ff13e3f4943 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 @@ -17,6 +17,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -49,7 +50,7 @@ class NetworkingLinkLoginWarmupViewModelTest { @Test fun `init - payload error navigates to error screen`() = runTest { val error = RuntimeException("Failed to fetch manifest") - whenever(getOrFetchSync(any())).thenAnswer { throw error } + whenever(getOrFetchSync(any(), anyOrNull())).thenAnswer { throw error } buildViewModel(NetworkingLinkLoginWarmupState()) 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 f1805ed6c10..93c4e805ef4 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 @@ -42,6 +42,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @@ -86,7 +87,7 @@ class NetworkingLinkSignupViewModelTest { accountholderCustomerEmailAddress = "test@test.com" ) - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse().copy( manifest = manifest, text = TextUpdate( @@ -107,7 +108,7 @@ class NetworkingLinkSignupViewModelTest { fun `init - creates controllers with Elements billing details`() = runTest { val manifest = sessionManifest() - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse().copy( manifest = manifest, text = TextUpdate( @@ -148,7 +149,7 @@ class NetworkingLinkSignupViewModelTest { accountholderCustomerEmailAddress = "", ) - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse().copy( manifest = manifest.copy(isLinkWithStripe = true), text = TextUpdate( @@ -171,7 +172,7 @@ class NetworkingLinkSignupViewModelTest { accountholderCustomerEmailAddress = "", ) - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse().copy( manifest = manifest.copy(isLinkWithStripe = false), text = TextUpdate( @@ -191,7 +192,7 @@ class NetworkingLinkSignupViewModelTest { fun `Redirects to save-to-link verification screen if entering returning user email`() = runTest { val manifest = sessionManifest() - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse().copy( text = TextUpdate( consent = null, @@ -225,7 +226,7 @@ class NetworkingLinkSignupViewModelTest { isLinkWithStripe = true, ) - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse().copy( manifest = manifest, text = TextUpdate( @@ -264,7 +265,7 @@ class NetworkingLinkSignupViewModelTest { fun `Enables Save To Link button if we encounter a returning user`() = runTest { val manifest = sessionManifest() - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse().copy( text = TextUpdate( consent = null, @@ -315,7 +316,7 @@ class NetworkingLinkSignupViewModelTest { isLinkWithStripe = false, ) - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse().copy( manifest = manifest, text = TextUpdate( @@ -340,7 +341,7 @@ class NetworkingLinkSignupViewModelTest { isLinkWithStripe = true, ) - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse().copy( manifest = manifest, text = TextUpdate( @@ -366,7 +367,7 @@ class NetworkingLinkSignupViewModelTest { text = TextUpdate(networkingLinkSignupPane = networkingLinkSignupPane()), ) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(syncResponse) whenever(lookupAccount(any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel( @@ -400,7 +401,7 @@ class NetworkingLinkSignupViewModelTest { text = TextUpdate(linkLoginPane = linkLoginPane()), ) - whenever(getOrFetchSync(any())).thenReturn(initialSyncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) whenever(lookupAccount(any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel( @@ -434,7 +435,7 @@ class NetworkingLinkSignupViewModelTest { text = TextUpdate(networkingLinkSignupPane = networkingLinkSignupPane()), ) - whenever(getOrFetchSync(any())).thenReturn(syncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(syncResponse) whenever(lookupAccount(any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel( @@ -468,7 +469,7 @@ class NetworkingLinkSignupViewModelTest { text = TextUpdate(linkLoginPane = linkLoginPane()), ) - whenever(getOrFetchSync(any())).thenReturn(initialSyncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) whenever(lookupAccount(any())).thenReturn(ConsumerSessionLookup(exists = false)) val viewModel = buildViewModel( @@ -500,7 +501,7 @@ class NetworkingLinkSignupViewModelTest { val permissionException = PermissionException(stripeError = StripeError()) - whenever(getOrFetchSync(any())).thenReturn(initialSyncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) whenever(lookupAccount(any())).then { throw permissionException } @@ -532,7 +533,7 @@ class NetworkingLinkSignupViewModelTest { val apiException = APIConnectionException() - whenever(getOrFetchSync(any())).thenReturn(initialSyncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) whenever(lookupAccount(any())).then { throw apiException } @@ -564,7 +565,7 @@ class NetworkingLinkSignupViewModelTest { val permissionException = PermissionException(stripeError = StripeError()) - whenever(getOrFetchSync(any())).thenReturn(initialSyncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(initialSyncResponse) whenever(lookupAccount(any())).then { throw permissionException } @@ -593,7 +594,7 @@ class NetworkingLinkSignupViewModelTest { ) val getOrFetchSync = mock { - onBlocking { invoke(any()) } doReturn syncResponse().copy( + onBlocking { invoke(any(), anyOrNull()) } doReturn syncResponse().copy( manifest = manifest, text = TextUpdate( consent = null, @@ -636,7 +637,7 @@ class NetworkingLinkSignupViewModelTest { ) val getOrFetchSync = mock { - onBlocking { invoke(any()) } doReturn syncResponse().copy( + onBlocking { invoke(any(), anyOrNull()) } doReturn syncResponse().copy( manifest = manifest, text = TextUpdate( consent = null, diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinkverification/NetworkingLinkVerificationViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinkverification/NetworkingLinkVerificationViewModelTest.kt index a35f1487679..5f5024741fb 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinkverification/NetworkingLinkVerificationViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/networkinglinkverification/NetworkingLinkVerificationViewModelTest.kt @@ -311,7 +311,7 @@ class NetworkingLinkVerificationViewModelTest { val onStartVerificationCaptor = argumentCaptor Unit>() val onVerificationStartedCaptor = argumentCaptor Unit>() - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(any(), anyOrNull())).thenReturn( syncResponse(sessionManifest().copy(accountholderCustomerEmailAddress = consumerSession.emailAddress)) ) whenever(attachConsumerToLinkAccountSession.invoke(any())).thenReturn(Unit) @@ -356,7 +356,7 @@ class NetworkingLinkVerificationViewModelTest { val onStartVerificationCaptor = argumentCaptor Unit>() val onVerificationStartedCaptor = argumentCaptor Unit>() - whenever(getOrFetchSync(any())).thenReturn( + whenever(getOrFetchSync(any(), anyOrNull())).thenReturn( syncResponse(sessionManifest().copy(accountholderCustomerEmailAddress = consumerSession.emailAddress)) ) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/SupportabilityViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/SupportabilityViewModelTest.kt index 0f2fc80a07c..6332fed939c 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/SupportabilityViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/SupportabilityViewModelTest.kt @@ -69,7 +69,7 @@ internal class SupportabilityViewModelTest { showManualEntry = false, stripeException = APIException() ) - whenever(getOrFetchSync(anyOrNull())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse(sessionManifest().copy(activeInstitution = institution())) ) whenever(createAuthorizationSession(any(), any())).thenAnswer { @@ -94,7 +94,7 @@ internal class SupportabilityViewModelTest { activeInstitution = activeInstitution, activeAuthSession = activeAuthSession ) - whenever(getOrFetchSync(anyOrNull())).thenReturn(syncResponse(manifest)) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(syncResponse(manifest)) val viewModel = createViewModel(SharedPartnerAuthState(Pane.PARTNER_AUTH_DRAWER)) @@ -124,7 +124,7 @@ internal class SupportabilityViewModelTest { publicToken = "123456" ) - whenever(getOrFetchSync(anyOrNull())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse( sessionManifest().copy(activeAuthSession = activeAuthSession) ) @@ -149,7 +149,7 @@ internal class SupportabilityViewModelTest { val activeAuthSession = authorizationSession() val viewModel = createViewModel() - whenever(getOrFetchSync(anyOrNull())).thenReturn( + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn( syncResponse( manifest = sessionManifest().copy(activeAuthSession = activeAuthSession) ) @@ -169,7 +169,7 @@ internal class SupportabilityViewModelTest { val activeAuthSession = authorizationSession().copy(_isOAuth = false) val viewModel = createViewModel() - whenever(getOrFetchSync(anyOrNull())) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())) .thenReturn( syncResponse( sessionManifest().copy( @@ -203,7 +203,7 @@ internal class SupportabilityViewModelTest { activeInstitution = activeInstitution ) val syncResponse = syncResponse(manifest) - whenever(getOrFetchSync(anyOrNull())).thenReturn(syncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(syncResponse) whenever(createAuthorizationSession.invoke(any(), any())).thenReturn(activeAuthSession) // simulate that auth session succeeded in abstract auth: whenever(retrieveAuthorizationSession.invoke(any())) @@ -232,7 +232,7 @@ internal class SupportabilityViewModelTest { ) ) val syncResponse = syncResponse(manifest) - whenever(getOrFetchSync(anyOrNull())).thenReturn(syncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(syncResponse) whenever(createAuthorizationSession.invoke(any(), any())).thenReturn(activeAuthSession) // simulate that auth session succeeded in abstract auth: whenever(retrieveAuthorizationSession.invoke(any())) @@ -270,7 +270,7 @@ internal class SupportabilityViewModelTest { activeInstitution = activeInstitution ) val syncResponse = syncResponse(manifest) - whenever(getOrFetchSync(anyOrNull())).thenReturn(syncResponse) + whenever(getOrFetchSync(anyOrNull(), anyOrNull())).thenReturn(syncResponse) whenever(createAuthorizationSession.invoke(any(), any())).thenReturn(activeAuthSession) // simulate that auth session succeeded in abstract auth: whenever(retrieveAuthorizationSession.invoke(any())) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/networking/AbsFinancialConnectionsManifestRepository.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/networking/AbsFinancialConnectionsManifestRepository.kt index c07782e3fb4..e701ae2a4c5 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/networking/AbsFinancialConnectionsManifestRepository.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/networking/AbsFinancialConnectionsManifestRepository.kt @@ -94,6 +94,7 @@ internal abstract class AbsFinancialConnectionsManifestRepository : FinancialCon override suspend fun getOrSynchronizeFinancialConnectionsSession( clientSecret: String, applicationId: String, + attestationInitialized: Boolean, reFetchCondition: (SynchronizeSessionResponse) -> Boolean ): SynchronizeSessionResponse { TODO("Not yet implemented") diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/networking/FakeFinancialConnectionsManifestRepository.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/networking/FakeFinancialConnectionsManifestRepository.kt index 8656f9c6768..e92cb0b423f 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/networking/FakeFinancialConnectionsManifestRepository.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/networking/FakeFinancialConnectionsManifestRepository.kt @@ -27,6 +27,7 @@ internal class FakeFinancialConnectionsManifestRepository : FinancialConnections override suspend fun getOrSynchronizeFinancialConnectionsSession( clientSecret: String, applicationId: String, + attestationInitialized: Boolean, reFetchCondition: (SynchronizeSessionResponse) -> Boolean ): SynchronizeSessionResponse = getSynchronizeSessionResponseProvider() diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsManifestRepositoryImplTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsManifestRepositoryImplTest.kt index 93641eaeec5..1b9e685f750 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsManifestRepositoryImplTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/repository/FinancialConnectionsManifestRepositoryImplTest.kt @@ -55,8 +55,22 @@ internal class FinancialConnectionsManifestRepositoryImplTest { // simulates to concurrent accesses to manifest. awaitAll( - async { repository.getOrSynchronizeFinancialConnectionsSession("", "", None::shouldReFetch) }, - async { repository.getOrSynchronizeFinancialConnectionsSession("", "", None::shouldReFetch) } + async { + repository.getOrSynchronizeFinancialConnectionsSession( + clientSecret = "", + applicationId = "", + attestationInitialized = false, + reFetchCondition = None::shouldReFetch + ) + }, + async { + repository.getOrSynchronizeFinancialConnectionsSession( + clientSecret = "", + applicationId = "", + attestationInitialized = false, + reFetchCondition = None::shouldReFetch + ) + } ) verify(mockRequestExecutor, times(1)).execute(any(), any>()) @@ -69,7 +83,12 @@ internal class FinancialConnectionsManifestRepositoryImplTest { val repository = buildRepository(initialSync = initialSync) val returnedManifest = - repository.getOrSynchronizeFinancialConnectionsSession("", "", None::shouldReFetch) + repository.getOrSynchronizeFinancialConnectionsSession( + clientSecret = "", + applicationId = "", + attestationInitialized = false, + reFetchCondition = None::shouldReFetch + ) assertThat(returnedManifest).isEqualTo(initialSync) verifyNoInteractions(mockRequestExecutor) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/utils/TestIntegrityRequestManager.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/utils/TestIntegrityRequestManager.kt new file mode 100644 index 00000000000..a922a9f9e04 --- /dev/null +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/utils/TestIntegrityRequestManager.kt @@ -0,0 +1,12 @@ +package com.stripe.android.financialconnections.utils + +import com.stripe.attestation.IntegrityRequestManager + +internal class TestIntegrityRequestManager( + val prepareResult: Result = Result.success(Unit), + val requestTokenResult: Result = Result.success("token") +) : IntegrityRequestManager { + override suspend fun prepare(): Result = prepareResult + + override suspend fun requestToken(requestIdentifier: String?): Result = requestTokenResult +}