Skip to content

Commit dc3c8fb

Browse files
committed
clean up MFA tests per review feedback
1 parent c1a8b57 commit dc3c8fb

File tree

3 files changed

+22
-222
lines changed

3 files changed

+22
-222
lines changed

auth0/src/test/java/com/auth0/android/authentication/MfaApiClientTest.kt

Lines changed: 22 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import com.auth0.android.authentication.mfa.MfaApiClient
55
import com.auth0.android.authentication.mfa.MfaEnrollmentType
66
import com.auth0.android.authentication.mfa.MfaVerificationType
77
import com.auth0.android.authentication.mfa.MfaException.*
8-
import com.auth0.android.callback.Callback
98
import com.auth0.android.request.internal.ThreadSwitcherShadow
109
import com.auth0.android.result.Authenticator
1110
import com.auth0.android.result.Challenge
1211
import com.auth0.android.result.Credentials
1312
import com.auth0.android.result.EnrollmentChallenge
1413
import com.auth0.android.result.MfaEnrollmentChallenge
1514
import com.auth0.android.result.TotpEnrollmentChallenge
15+
import com.auth0.android.util.CallbackMatcher
16+
import com.auth0.android.util.MockCallback
1617
import com.auth0.android.util.SSLTestUtils
1718
import com.google.gson.Gson
1819
import com.google.gson.GsonBuilder
@@ -32,8 +33,6 @@ import org.junit.runner.RunWith
3233
import org.robolectric.RobolectricTestRunner
3334
import org.robolectric.annotation.Config
3435
import org.robolectric.shadows.ShadowLooper
35-
import java.util.concurrent.CountDownLatch
36-
import java.util.concurrent.TimeUnit
3736

3837
@RunWith(RobolectricTestRunner::class)
3938
@Config(shadows = [ThreadSwitcherShadow::class])
@@ -624,119 +623,67 @@ public class MfaApiClientTest {
624623
val json = """[{"id": "sms|dev_123", "authenticator_type": "oob", "active": true}]"""
625624
enqueueMockResponse(json)
626625

627-
val latch = CountDownLatch(1)
628-
var callbackResult: List<Authenticator>? = null
629-
var callbackError: MfaListAuthenticatorsException? = null
626+
val callback = MockCallback<List<Authenticator>, MfaListAuthenticatorsException>()
630627

631628
mfaClient.getAuthenticators(listOf("oob"))
632-
.start(object : Callback<List<Authenticator>, MfaListAuthenticatorsException> {
633-
override fun onSuccess(result: List<Authenticator>) {
634-
callbackResult = result
635-
latch.countDown()
636-
}
637-
638-
override fun onFailure(error: MfaListAuthenticatorsException) {
639-
callbackError = error
640-
latch.countDown()
641-
}
642-
})
629+
.start(callback)
643630

644631
ShadowLooper.idleMainLooper()
645-
latch.await(5, TimeUnit.SECONDS)
646632

647-
assertThat(callbackResult, `is`(notNullValue()))
648-
assertThat(callbackResult, hasSize(1))
649-
assertThat(callbackError, `is`(nullValue()))
633+
assertThat(callback.getPayload(), `is`(notNullValue()))
634+
assertThat(callback.getPayload(), hasSize(1))
635+
assertThat(callback.getError(), `is`(nullValue()))
650636
}
651637

652638
@Test
653639
public fun shouldEnrollPhoneWithCallback(): Unit {
654640
val json = """{"id": "sms|dev_123", "auth_session": "session_abc"}"""
655641
enqueueMockResponse(json)
656642

657-
val latch = CountDownLatch(1)
658-
var callbackResult: EnrollmentChallenge? = null
659-
var callbackError: MfaEnrollmentException? = null
643+
val callback = MockCallback<EnrollmentChallenge, MfaEnrollmentException>()
660644

661645
mfaClient.enroll(MfaEnrollmentType.Phone("+12025550135"))
662-
.start(object : Callback<EnrollmentChallenge, MfaEnrollmentException> {
663-
override fun onSuccess(result: EnrollmentChallenge) {
664-
callbackResult = result
665-
latch.countDown()
666-
}
667-
668-
override fun onFailure(error: MfaEnrollmentException) {
669-
callbackError = error
670-
latch.countDown()
671-
}
672-
})
646+
.start(callback)
673647

674648
ShadowLooper.idleMainLooper()
675-
latch.await(5, TimeUnit.SECONDS)
676649

677-
assertThat(callbackResult, `is`(notNullValue()))
678-
assertThat(callbackResult!!.id, `is`("sms|dev_123"))
679-
assertThat(callbackError, `is`(nullValue()))
650+
assertThat(callback.getPayload(), `is`(notNullValue()))
651+
assertThat(callback.getPayload().id, `is`("sms|dev_123"))
652+
assertThat(callback.getError(), `is`(nullValue()))
680653
}
681654

682655
@Test
683656
public fun shouldChallengeWithCallback(): Unit {
684657
val json = """{"challenge_type": "oob", "oob_code": "oob_123"}"""
685658
enqueueMockResponse(json)
686659

687-
val latch = CountDownLatch(1)
688-
var callbackResult: Challenge? = null
689-
var callbackError: MfaChallengeException? = null
660+
val callback = MockCallback<Challenge, MfaChallengeException>()
690661

691662
mfaClient.challenge("sms|dev_123")
692-
.start(object : Callback<Challenge, MfaChallengeException> {
693-
override fun onSuccess(result: Challenge) {
694-
callbackResult = result
695-
latch.countDown()
696-
}
697-
698-
override fun onFailure(error: MfaChallengeException) {
699-
callbackError = error
700-
latch.countDown()
701-
}
702-
})
663+
.start(callback)
703664

704665
ShadowLooper.idleMainLooper()
705-
latch.await(5, TimeUnit.SECONDS)
706666

707-
assertThat(callbackResult, `is`(notNullValue()))
708-
assertThat(callbackResult!!.challengeType, `is`("oob"))
709-
assertThat(callbackError, `is`(nullValue()))
667+
assertThat(callback.getPayload(), `is`(notNullValue()))
668+
assertThat(callback.getPayload().challengeType, `is`("oob"))
669+
assertThat(callback.getError(), `is`(nullValue()))
710670
}
711671

712672
@Test
713673
public fun shouldVerifyOtpWithCallback(): Unit {
714674
val json = """{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
715675
enqueueMockResponse(json)
716676

717-
val latch = CountDownLatch(1)
718-
var callbackResult: Credentials? = null
719-
var callbackError: MfaVerifyException? = null
677+
val callback = MockCallback<Credentials, MfaVerifyException>()
720678

721679
mfaClient.verify(MfaVerificationType.Otp("123456"))
722-
.start(object : Callback<Credentials, MfaVerifyException> {
723-
override fun onSuccess(result: Credentials) {
724-
callbackResult = result
725-
latch.countDown()
726-
}
727-
728-
override fun onFailure(error: MfaVerifyException) {
729-
callbackError = error
730-
latch.countDown()
731-
}
732-
})
680+
.start(callback)
733681

734682
ShadowLooper.idleMainLooper()
735-
latch.await(5, TimeUnit.SECONDS)
736683

737-
assertThat(callbackResult, `is`(notNullValue()))
738-
assertThat(callbackResult!!.accessToken, `is`(ACCESS_TOKEN))
739-
assertThat(callbackError, `is`(nullValue()))
684+
assertThat(callback.getPayload(), `is`(notNullValue()))
685+
assertThat(callback.getPayload().accessToken, `is`(ACCESS_TOKEN))
686+
assertThat(callback.getError(), `is`(nullValue()))
740687
}
741688

742689

auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,7 +1953,6 @@ public class CredentialsManagerTest {
19531953

19541954
@Test
19551955
public fun shouldFailWithMfaRequiredWhenRenewingExpiredCredentials() {
1956-
// Arrange: Set up expired credentials that need renewal
19571956
Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken")
19581957
Mockito.`when`(storage.retrieveString("com.auth0.access_token")).thenReturn("accessToken")
19591958
Mockito.`when`(storage.retrieveString("com.auth0.refresh_token")).thenReturn("refreshToken")
@@ -1964,7 +1963,6 @@ public class CredentialsManagerTest {
19641963
Mockito.`when`(storage.retrieveString("com.auth0.scope")).thenReturn("scope")
19651964
Mockito.`when`(client.renewAuth("refreshToken")).thenReturn(request)
19661965

1967-
// Create an AuthenticationException that simulates MFA required response
19681966
val mfaRequiredValues: MutableMap<String, Any> = mutableMapOf(
19691967
"error" to "mfa_required",
19701968
"error_description" to "Multifactor authentication required",
@@ -1978,24 +1976,19 @@ public class CredentialsManagerTest {
19781976
)
19791977
val mfaRequiredException = AuthenticationException(mfaRequiredValues, 403)
19801978

1981-
// Verify the exception is correctly configured
19821979
MatcherAssert.assertThat(mfaRequiredException.isMultifactorRequired, Is.`is`(true))
19831980
MatcherAssert.assertThat(mfaRequiredException.getCode(), Is.`is`("mfa_required"))
19841981

19851982
Mockito.`when`(request.execute()).thenThrow(mfaRequiredException)
19861983

1987-
// Act: Try to get credentials, which triggers renewal
19881984
manager.getCredentials(callback)
19891985

1990-
// Assert: Verify the callback receives MFA_REQUIRED exception with payload
19911986
verify(callback).onFailure(exceptionCaptor.capture())
19921987
val exception = exceptionCaptor.firstValue
19931988
MatcherAssert.assertThat(exception, Is.`is`(Matchers.notNullValue()))
1994-
// Note: CredentialsManager uses error.message which is the DEFAULT_MESSAGE from AuthenticationException
19951989
MatcherAssert.assertThat(exception.message, Matchers.containsString("authenticate"))
19961990
MatcherAssert.assertThat(exception.cause, Is.`is`(mfaRequiredException))
19971991

1998-
// Verify MFA payload is properly passed through
19991992
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload, Is.`is`(Matchers.notNullValue()))
20001993
MatcherAssert.assertThat(exception.mfaToken, Is.`is`("test-mfa-token-12345"))
20011994
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload?.mfaRequirements?.challenge, Is.`is`(Matchers.notNullValue()))
@@ -2004,7 +1997,6 @@ public class CredentialsManagerTest {
20041997

20051998
@Test
20061999
public fun shouldFailWithMfaRequiredWithEnrollmentOptionsWhenRenewingCredentials() {
2007-
// Arrange: Set up expired credentials that need renewal
20082000
Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken")
20092001
Mockito.`when`(storage.retrieveString("com.auth0.access_token")).thenReturn("accessToken")
20102002
Mockito.`when`(storage.retrieveString("com.auth0.refresh_token")).thenReturn("refreshToken")
@@ -2015,7 +2007,6 @@ public class CredentialsManagerTest {
20152007
Mockito.`when`(storage.retrieveString("com.auth0.scope")).thenReturn("scope")
20162008
Mockito.`when`(client.renewAuth("refreshToken")).thenReturn(request)
20172009

2018-
// Create an AuthenticationException with enrollment options (user needs to enroll MFA)
20192010
val mfaRequiredValues = mapOf(
20202011
"error" to "mfa_required",
20212012
"error_description" to "Multifactor authentication required",
@@ -2031,13 +2022,10 @@ public class CredentialsManagerTest {
20312022
val mfaRequiredException = AuthenticationException(mfaRequiredValues, 403)
20322023
Mockito.`when`(request.execute()).thenThrow(mfaRequiredException)
20332024

2034-
// Act: Try to get credentials
20352025
manager.getCredentials(callback)
20362026

2037-
// Assert: Verify MFA required with enrollment options
20382027
verify(callback).onFailure(exceptionCaptor.capture())
20392028
val exception = exceptionCaptor.firstValue
2040-
// Note: CredentialsManager uses error.message which is the DEFAULT_MESSAGE from AuthenticationException
20412029
MatcherAssert.assertThat(exception.message, Matchers.containsString("authenticate"))
20422030
MatcherAssert.assertThat(exception.mfaToken, Is.`is`("enroll-mfa-token"))
20432031
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload?.mfaRequirements?.enroll, Is.`is`(Matchers.notNullValue()))
@@ -2047,7 +2035,6 @@ public class CredentialsManagerTest {
20472035

20482036
@Test
20492037
public fun shouldNotStoreMfaPayloadWhenNonMfaApiErrorOccurs() {
2050-
// Arrange: Set up expired credentials that need renewal
20512038
Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken")
20522039
Mockito.`when`(storage.retrieveString("com.auth0.access_token")).thenReturn("accessToken")
20532040
Mockito.`when`(storage.retrieveString("com.auth0.refresh_token")).thenReturn("refreshToken")
@@ -2068,13 +2055,10 @@ public class CredentialsManagerTest {
20682055
)
20692056
Mockito.`when`(request.execute()).thenThrow(regularApiError)
20702057

2071-
// Act: Try to get credentials
20722058
manager.getCredentials(callback)
20732059

2074-
// Assert: Verify no MFA payload is present for non-MFA errors
20752060
verify(callback).onFailure(exceptionCaptor.capture())
20762061
val exception = exceptionCaptor.firstValue
2077-
// For non-MFA API errors, message is "An error occurred while processing the request."
20782062
MatcherAssert.assertThat(exception.message, Matchers.containsString("processing the request"))
20792063
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload, Is.`is`(Matchers.nullValue()))
20802064
MatcherAssert.assertThat(exception.mfaToken, Is.`is`(Matchers.nullValue()))
@@ -2083,7 +2067,6 @@ public class CredentialsManagerTest {
20832067
@Test
20842068
@ExperimentalCoroutinesApi
20852069
public fun shouldThrowMfaRequiredExceptionWhenAwaitingExpiredCredentials(): Unit = runTest {
2086-
// Arrange: Set up expired credentials that need renewal
20872070
Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken")
20882071
Mockito.`when`(storage.retrieveString("com.auth0.access_token")).thenReturn("accessToken")
20892072
Mockito.`when`(storage.retrieveString("com.auth0.refresh_token")).thenReturn("refreshToken")
@@ -2094,7 +2077,6 @@ public class CredentialsManagerTest {
20942077
Mockito.`when`(storage.retrieveString("com.auth0.scope")).thenReturn("scope")
20952078
Mockito.`when`(client.renewAuth("refreshToken")).thenReturn(request)
20962079

2097-
// Create an AuthenticationException that simulates MFA required response
20982080
val mfaRequiredValues = mapOf(
20992081
"error" to "mfa_required",
21002082
"error_description" to "Multifactor authentication required",
@@ -2108,7 +2090,6 @@ public class CredentialsManagerTest {
21082090
val mfaRequiredException = AuthenticationException(mfaRequiredValues, 403)
21092091
Mockito.`when`(request.execute()).thenThrow(mfaRequiredException)
21102092

2111-
// Act & Assert: Verify awaitCredentials throws CredentialsManagerException with MFA payload
21122093
val exception = assertThrows(CredentialsManagerException::class.java) {
21132094
runBlocking { manager.awaitCredentials() }
21142095
}
@@ -2118,58 +2099,8 @@ public class CredentialsManagerTest {
21182099
MatcherAssert.assertThat(exception.mfaToken, Is.`is`("await-mfa-token-12345"))
21192100
}
21202101

2121-
@Test
2122-
public fun shouldFailWithMfaRequiredContainingBothChallengeAndEnrollOptions() {
2123-
// Arrange: Set up expired credentials that need renewal
2124-
Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken")
2125-
Mockito.`when`(storage.retrieveString("com.auth0.access_token")).thenReturn("accessToken")
2126-
Mockito.`when`(storage.retrieveString("com.auth0.refresh_token")).thenReturn("refreshToken")
2127-
Mockito.`when`(storage.retrieveString("com.auth0.token_type")).thenReturn("type")
2128-
val expirationTime = CredentialsMock.CURRENT_TIME_MS // Expired
2129-
Mockito.`when`(storage.retrieveLong("com.auth0.expires_at")).thenReturn(expirationTime)
2130-
Mockito.`when`(storage.retrieveLong("com.auth0.cache_expires_at")).thenReturn(expirationTime)
2131-
Mockito.`when`(storage.retrieveString("com.auth0.scope")).thenReturn("scope")
2132-
Mockito.`when`(client.renewAuth("refreshToken")).thenReturn(request)
2133-
2134-
// Create MFA required with BOTH challenge (existing factors) AND enroll (can add more factors)
2135-
val mfaRequiredValues = mapOf(
2136-
"error" to "mfa_required",
2137-
"error_description" to "Multifactor authentication required",
2138-
"mfa_token" to "combined-mfa-token",
2139-
"mfa_requirements" to mapOf(
2140-
"challenge" to listOf(
2141-
mapOf("type" to "otp")
2142-
),
2143-
"enroll" to listOf(
2144-
mapOf("type" to "sms"),
2145-
mapOf("type" to "push-notification")
2146-
)
2147-
)
2148-
)
2149-
val mfaRequiredException = AuthenticationException(mfaRequiredValues, 403)
2150-
Mockito.`when`(request.execute()).thenThrow(mfaRequiredException)
2151-
2152-
// Act: Try to get credentials
2153-
manager.getCredentials(callback)
2154-
2155-
// Assert: Verify both challenge and enroll are present in the payload
2156-
verify(callback).onFailure(exceptionCaptor.capture())
2157-
val exception = exceptionCaptor.firstValue
2158-
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload, Is.`is`(Matchers.notNullValue()))
2159-
MatcherAssert.assertThat(exception.mfaToken, Is.`is`("combined-mfa-token"))
2160-
2161-
// Verify challenge factors
2162-
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload?.mfaRequirements?.challenge, Is.`is`(Matchers.notNullValue()))
2163-
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload?.mfaRequirements?.challenge?.size, Is.`is`(1))
2164-
2165-
// Verify enroll factors
2166-
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload?.mfaRequirements?.enroll, Is.`is`(Matchers.notNullValue()))
2167-
MatcherAssert.assertThat(exception.mfaRequiredErrorPayload?.mfaRequirements?.enroll?.size, Is.`is`(2))
2168-
}
2169-
21702102
@Test
21712103
public fun shouldPreserveOriginalAuthenticationExceptionAsCauseForMfaRequired() {
2172-
// Arrange: Set up expired credentials that need renewal
21732104
Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken")
21742105
Mockito.`when`(storage.retrieveString("com.auth0.access_token")).thenReturn("accessToken")
21752106
Mockito.`when`(storage.retrieveString("com.auth0.refresh_token")).thenReturn("refreshToken")
@@ -2188,14 +2119,11 @@ public class CredentialsManagerTest {
21882119
val originalException = AuthenticationException(mfaRequiredValues, 403)
21892120
Mockito.`when`(request.execute()).thenThrow(originalException)
21902121

2191-
// Act
21922122
manager.getCredentials(callback)
21932123

2194-
// Assert: Verify the original exception is preserved as cause
21952124
verify(callback).onFailure(exceptionCaptor.capture())
21962125
val exception = exceptionCaptor.firstValue
21972126

2198-
// The cause should be the original AuthenticationException
21992127
MatcherAssert.assertThat(exception.cause, Is.`is`(Matchers.notNullValue()))
22002128
MatcherAssert.assertThat(exception.cause, IsInstanceOf.instanceOf(AuthenticationException::class.java))
22012129

0 commit comments

Comments
 (0)