@@ -35,12 +35,16 @@ import com.google.android.play.core.integrity.StandardIntegrityManager.StandardI
3535import com.google.android.play.core.integrity.StandardIntegrityManager.StandardIntegrityTokenProvider
3636import com.google.android.play.core.integrity.model.StandardIntegrityErrorCode.INTEGRITY_TOKEN_PROVIDER_INVALID
3737import com.google.android.play.core.integrity.model.StandardIntegrityErrorCode.INTERNAL_ERROR
38+ import com.salesforce.androidsdk.rest.AppAttestationChallengeApiException
3839import com.salesforce.androidsdk.rest.RestClient
40+ import com.salesforce.androidsdk.rest.RestRequest
3941import com.salesforce.androidsdk.rest.RestResponse
42+ import io.mockk.CapturingSlot
4043import io.mockk.coEvery
4144import io.mockk.every
4245import io.mockk.mockk
4346import io.mockk.mockkStatic
47+ import io.mockk.slot
4448import io.mockk.verify
4549import kotlinx.coroutines.ExperimentalCoroutinesApi
4650import kotlinx.coroutines.tasks.await
@@ -49,6 +53,8 @@ import kotlinx.coroutines.test.runTest
4953import kotlinx.serialization.json.Json
5054import org.junit.Assert.assertEquals
5155import org.junit.Assert.assertNull
56+ import org.junit.Assert.assertThrows
57+ import org.junit.Assert.assertTrue
5258import org.junit.Test
5359import org.junit.runner.RunWith
5460
@@ -465,6 +471,61 @@ class AppAttestationClientTest {
465471 assertEquals(" eyJhdHRlc3RhdGlvbklkIjoiMTIzNDU2IiwiYXR0ZXN0YXRpb25EYXRhIjoiWDE5VVJWTlVYMGxPVkVWSFVrbFVXVjlVVDB0RlRsOWYifQ" , result)
466472 }
467473
474+ @Test
475+ fun appAttestationClient_fetchMobileAppAttestationChallenge_OnSuccess_ReturnsChallenge () {
476+
477+ val requestSlot = slot<RestRequest >()
478+ val restClient = createRestClientReturning(
479+ restResponse = createRestResponse(body = TEST_CHALLENGE_VALUE , success = true ),
480+ requestSlot = requestSlot,
481+ )
482+ val appAttestationClient = createAppAttestationClientForTest(restClient = restClient)
483+
484+ val result = appAttestationClient.fetchMobileAppAttestationChallenge()
485+
486+ assertEquals(TEST_CHALLENGE_VALUE , result)
487+ val requestedPath = requestSlot.captured.path
488+ assertTrue(
489+ " Request URL should target the attestation challenge endpoint at '$TEST_API_HOST_NAME ' but was '$requestedPath '." ,
490+ requestedPath.startsWith(" https://$TEST_API_HOST_NAME /mobile/attest/challenge" ),
491+ )
492+ assertTrue(
493+ " Request URL should contain 'attestationId=$TEST_DEVICE_ID ' but was '$requestedPath '." ,
494+ requestedPath.contains(" attestationId=$TEST_DEVICE_ID " ),
495+ )
496+ assertTrue(
497+ " Request URL should contain 'consumerKey=$TEST_REMOTE_ACCESS_CONSUMER_KEY ' but was '$requestedPath '." ,
498+ requestedPath.contains(" consumerKey=$TEST_REMOTE_ACCESS_CONSUMER_KEY " ),
499+ )
500+ verify(exactly = 1 ) { restClient.sendSync(any()) }
501+ }
502+
503+ @Test
504+ fun appAttestationClient_fetchMobileAppAttestationChallenge_OnFailureResponse_ThrowsException () {
505+
506+ val restClient = createRestClientReturning(
507+ restResponse = createRestResponse(body = " __ERROR_BODY__" , success = false ),
508+ )
509+ val appAttestationClient = createAppAttestationClientForTest(restClient = restClient)
510+
511+ assertThrows(AppAttestationChallengeApiException ::class .java) {
512+ appAttestationClient.fetchMobileAppAttestationChallenge()
513+ }
514+ }
515+
516+ @Test
517+ fun appAttestationClient_fetchMobileAppAttestationChallenge_OnNullResponseBody_ThrowsException () {
518+
519+ val restClient = createRestClientReturning(
520+ restResponse = createRestResponse(body = null , success = true ),
521+ )
522+ val appAttestationClient = createAppAttestationClientForTest(restClient = restClient)
523+
524+ assertThrows(AppAttestationChallengeApiException ::class .java) {
525+ appAttestationClient.fetchMobileAppAttestationChallenge()
526+ }
527+ }
528+
468529 @Test
469530 fun oAuthAuthorizationAttestation_encode_returnsSuccessfully () {
470531
@@ -494,4 +555,51 @@ class AppAttestationClientTest {
494555 fun oAuthAuthorizationAttestation_serializerDescriptor_hasCorrectElementCount () {
495556 assertEquals(2 , OAuthAuthorizationAttestation .serializer().descriptor.elementsCount)
496557 }
558+
559+ // region Helpers
560+
561+ private fun createAppAttestationClientForTest (
562+ restClient : RestClient ,
563+ ): AppAttestationClient {
564+ val integrityTokenProviderTask = mockk<Task <StandardIntegrityTokenProvider >>(relaxed = true )
565+ every { integrityTokenProviderTask.addOnSuccessListener(any()) } returns integrityTokenProviderTask
566+ every { integrityTokenProviderTask.addOnFailureListener(any()) } returns integrityTokenProviderTask
567+ val integrityManager = mockk<StandardIntegrityManager >(relaxed = true )
568+ every { integrityManager.prepareIntegrityToken(any()) } returns integrityTokenProviderTask
569+
570+ return AppAttestationClient (
571+ apiHostName = TEST_API_HOST_NAME ,
572+ context = mockk<Context >(relaxed = true ),
573+ deviceId = TEST_DEVICE_ID ,
574+ googleCloudProjectId = TEST_GOOGLE_CLOUD_PROJECT_ID ,
575+ integrityManager = integrityManager,
576+ remoteAccessConsumerKey = TEST_REMOTE_ACCESS_CONSUMER_KEY ,
577+ restClient = restClient,
578+ )
579+ }
580+
581+ private fun createRestResponse (
582+ body : String? ,
583+ success : Boolean ,
584+ ): RestResponse = mockk<RestResponse >(relaxed = true ).also { response ->
585+ every { response.asString() } returns body
586+ every { response.isSuccess } returns success
587+ }
588+
589+ private fun createRestClientReturning (
590+ restResponse : RestResponse ,
591+ requestSlot : CapturingSlot <RestRequest > = slot(),
592+ ): RestClient = mockk<RestClient >(relaxed = true ).also { client ->
593+ every { client.sendSync(capture(requestSlot)) } returns restResponse
594+ }
595+
596+ // endregion Helpers
597+
598+ private companion object {
599+ const val TEST_API_HOST_NAME = " login.example.com"
600+ const val TEST_DEVICE_ID = " 123456"
601+ const val TEST_GOOGLE_CLOUD_PROJECT_ID = 654321L
602+ const val TEST_REMOTE_ACCESS_CONSUMER_KEY = " 13579"
603+ const val TEST_CHALLENGE_VALUE = " __TEST_CHALLENGE_VALUE__"
604+ }
497605}
0 commit comments