Skip to content

Commit b8f1790

Browse files
@W-21933885: [MSDK Android] App Attestation Implementation (Updated Tests For Code Coverage And Behavioral Assertion In NativeLoginManagerTest.kt)
1 parent 5035087 commit b8f1790

1 file changed

Lines changed: 107 additions & 6 deletions

File tree

libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/NativeLoginManagerTest.kt

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.salesforce.androidsdk.accounts.UserAccountBuilder
1111
import com.salesforce.androidsdk.accounts.UserAccountManager
1212
import com.salesforce.androidsdk.accounts.UserAccountTest
1313
import com.salesforce.androidsdk.app.SalesforceSDKManager
14+
import com.salesforce.androidsdk.auth.OAuth2.OAUTH_AUTH_PATH
1415
import com.salesforce.androidsdk.rest.ClientManager
1516
import com.salesforce.androidsdk.rest.ClientManager.RestClientCallback
1617
import 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

Comments
 (0)