@@ -11,6 +11,7 @@ import com.salesforce.androidsdk.accounts.UserAccountBuilder
1111import com.salesforce.androidsdk.accounts.UserAccountManager
1212import com.salesforce.androidsdk.accounts.UserAccountTest
1313import com.salesforce.androidsdk.app.SalesforceSDKManager
14+ import com.salesforce.androidsdk.auth.OAuth2.OAUTH_AUTH_PATH
1415import com.salesforce.androidsdk.rest.ClientManager
1516import com.salesforce.androidsdk.rest.ClientManager.RestClientCallback
1617import com.salesforce.androidsdk.rest.RestClient
@@ -49,6 +50,7 @@ class NativeLoginManagerTest {
4950 fun tearDown () {
5051 SalesforceSDKManager .getInstance().userAccountManager
5152 .signoutCurrentUser(null , true , OAuth2 .LogoutReason .USER_LOGOUT )
53+ SalesforceSDKManager .getInstance().appAttestationClient = null
5254 unmockkAll()
5355 }
5456
@@ -342,7 +344,59 @@ class NativeLoginManagerTest {
342344
343345 verify(exactly = 1 ) {
344346 restClient.sendAsync(match {
345- it.path.contains(" ?attestation=__TEST_APP_ATTESTATION__" )
347+ it.path == " loginUrl$OAUTH_AUTH_PATH ?attestation=__TEST_APP_ATTESTATION__"
348+ }, any())
349+ }
350+ }
351+
352+ /* *
353+ * Tests that native login does not include app attestation during login
354+ * when the app attestation client is set but
355+ * [AppAttestationClient.createAppAttestation] returns null (for example,
356+ * because the Google Play Integrity API could not produce a token). This
357+ * test can be removed when a comprehensive test of native login is created
358+ * so long as that test covers the exclusion of the attestation parameter.
359+ */
360+ @OptIn(ExperimentalCoroutinesApi ::class )
361+ @Test
362+ fun nativeLoginManager_login_doesNotCollectAppAttestationWhenCreateAppAttestationReturnsNull () = runTest {
363+
364+ val appAttestationClient = mockk<AppAttestationClient >(relaxed = true )
365+ every { appAttestationClient.fetchMobileAppAttestationChallenge() } returns " __TEST_CHALLENGE_VALUE__"
366+ coEvery {
367+ appAttestationClient.createAppAttestation(
368+ appAttestationChallenge = " __TEST_CHALLENGE_VALUE__"
369+ )
370+ } returns null
371+
372+ val salesforceSdkManager = SalesforceSDKManager .getInstance()
373+ salesforceSdkManager.appAttestationClient = appAttestationClient
374+
375+ val restClient = mockk<RestClient >(relaxed = true )
376+ val mockResponse = mockk<RestResponse >(relaxed = true )
377+ every { mockResponse.isSuccess } returns false
378+ every {
379+ restClient.sendAsync(any(), any())
380+ } answers {
381+ val callback = secondArg<RestClient .AsyncRequestCallback >()
382+ callback.onSuccess(firstArg(), mockResponse)
383+ mockk<Call >(relaxed = true )
384+ }
385+
386+ mgr = NativeLoginManager (
387+ clientId = " clientId" ,
388+ redirectUri = " redirect" ,
389+ loginUrl = " loginUrl" ,
390+ restClient = restClient,
391+ )
392+
393+ mgr.login(" TestUser@Example.com" , " test123456" )
394+
395+ advanceUntilIdle()
396+
397+ verify(exactly = 1 ) {
398+ restClient.sendAsync(match {
399+ it.path == " loginUrl$OAUTH_AUTH_PATH "
346400 }, any())
347401 }
348402 }
@@ -381,11 +435,58 @@ class NativeLoginManagerTest {
381435
382436 verify(exactly = 1 ) {
383437 restClient.sendAsync(match {
384- runCatching {
385- val buffer = okio.Buffer ()
386- it.requestBody?.writeTo(buffer)
387- ! buffer.readUtf8().contains(" attestation=" )
388- }.getOrDefault(false )
438+ it.path == " loginUrl$OAUTH_AUTH_PATH "
439+ }, any())
440+ }
441+ }
442+
443+ /* *
444+ * Tests that native login URL-encodes the app attestation value when it
445+ * contains URL-unsafe characters. This gates the [Uri.encode] call on the
446+ * attestation parameter and can be removed when a comprehensive test of
447+ * native login is created so long as that test covers URL encoding of the
448+ * attestation parameter.
449+ */
450+ @OptIn(ExperimentalCoroutinesApi ::class )
451+ @Test
452+ fun nativeLoginManager_login_urlEncodesAppAttestationValue () = runTest {
453+
454+ val appAttestationClient = mockk<AppAttestationClient >(relaxed = true )
455+ every { appAttestationClient.fetchMobileAppAttestationChallenge() } returns " __TEST_CHALLENGE_VALUE__"
456+ coEvery {
457+ appAttestationClient.createAppAttestation(
458+ appAttestationChallenge = " __TEST_CHALLENGE_VALUE__"
459+ )
460+ } returns " foo bar+baz=qux/"
461+
462+ val salesforceSdkManager = SalesforceSDKManager .getInstance()
463+ salesforceSdkManager.appAttestationClient = appAttestationClient
464+
465+ val restClient = mockk<RestClient >(relaxed = true )
466+ val mockResponse = mockk<RestResponse >(relaxed = true )
467+ every { mockResponse.isSuccess } returns false
468+ every {
469+ restClient.sendAsync(any(), any())
470+ } answers {
471+ val callback = secondArg<RestClient .AsyncRequestCallback >()
472+ callback.onSuccess(firstArg(), mockResponse)
473+ mockk<Call >(relaxed = true )
474+ }
475+
476+ mgr = NativeLoginManager (
477+ clientId = " clientId" ,
478+ redirectUri = " redirect" ,
479+ loginUrl = " loginUrl" ,
480+ restClient = restClient,
481+ )
482+
483+ mgr.login(" TestUser@Example.com" , " test123456" )
484+
485+ advanceUntilIdle()
486+
487+ verify(exactly = 1 ) {
488+ restClient.sendAsync(match {
489+ it.path == " loginUrl$OAUTH_AUTH_PATH ?attestation=foo%20bar%2Bbaz%3Dqux%2F"
389490 }, any())
390491 }
391492 }
0 commit comments