1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.authentication.domain.interactor
18 
19 import android.app.admin.DevicePolicyManager
20 import androidx.test.ext.junit.runners.AndroidJUnit4
21 import androidx.test.filters.SmallTest
22 import com.android.internal.widget.LockPatternUtils
23 import com.android.systemui.SysuiTestCase
24 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
25 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
26 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
27 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
28 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
29 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
30 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
31 import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
32 import com.android.systemui.coroutines.collectLastValue
33 import com.android.systemui.kosmos.testScope
34 import com.android.systemui.testKosmos
35 import com.android.systemui.user.data.repository.FakeUserRepository
36 import com.android.systemui.user.data.repository.fakeUserRepository
37 import com.google.common.truth.Truth.assertThat
38 import kotlin.time.Duration.Companion.seconds
39 import kotlinx.coroutines.ExperimentalCoroutinesApi
40 import kotlinx.coroutines.test.advanceTimeBy
41 import kotlinx.coroutines.test.currentTime
42 import kotlinx.coroutines.test.runCurrent
43 import kotlinx.coroutines.test.runTest
44 import org.junit.Test
45 import org.junit.runner.RunWith
46 
47 @OptIn(ExperimentalCoroutinesApi::class)
48 @SmallTest
49 @RunWith(AndroidJUnit4::class)
50 class AuthenticationInteractorTest : SysuiTestCase() {
51 
52     private val kosmos = testKosmos()
53     private val testScope = kosmos.testScope
54     private val underTest = kosmos.authenticationInteractor
55 
56     private val onAuthenticationResult by
57         testScope.collectLastValue(underTest.onAuthenticationResult)
58     private val failedAuthenticationAttempts by
59         testScope.collectLastValue(underTest.failedAuthenticationAttempts)
60 
61     @Test
62     fun authenticationMethod() =
63         testScope.runTest {
64             val authMethod by collectLastValue(underTest.authenticationMethod)
65             runCurrent()
66             assertThat(authMethod).isEqualTo(Pin)
67             assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin)
68 
69             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
70 
71             assertThat(authMethod).isEqualTo(Password)
72             assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password)
73         }
74 
75     @Test
76     fun authenticationMethod_none_whenLockscreenDisabled() =
77         testScope.runTest {
78             val authMethod by collectLastValue(underTest.authenticationMethod)
79             runCurrent()
80 
81             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(None)
82 
83             assertThat(authMethod).isEqualTo(None)
84             assertThat(underTest.getAuthenticationMethod()).isEqualTo(None)
85         }
86 
87     @Test
88     fun authenticate_withCorrectPin_succeeds() =
89         testScope.runTest {
90             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
91 
92             assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
93         }
94 
95     @Test
96     fun authenticate_withIncorrectPin_fails() =
97         testScope.runTest {
98             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
99 
100             assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
101         }
102 
103     @Test(expected = IllegalArgumentException::class)
104     fun authenticate_withEmptyPin_throwsException() =
105         testScope.runTest {
106             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
107             underTest.authenticate(listOf())
108         }
109 
110     @Test
111     fun authenticate_withCorrectMaxLengthPin_succeeds() =
112         testScope.runTest {
113             val correctMaxLengthPin = List(16) { 9 }
114             kosmos.fakeAuthenticationRepository.apply {
115                 setAuthenticationMethod(Pin)
116                 overrideCredential(correctMaxLengthPin)
117             }
118 
119             assertSucceeded(underTest.authenticate(correctMaxLengthPin))
120         }
121 
122     @Test
123     fun authenticate_withCorrectTooLongPin_fails() =
124         testScope.runTest {
125             // Max pin length is 16 digits. To avoid issues with overflows, this test ensures that
126             // all pins > 16 decimal digits are rejected.
127 
128             // If the policy changes, there is work to do in SysUI.
129             assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
130 
131             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
132 
133             assertFailed(underTest.authenticate(List(17) { 9 }))
134         }
135 
136     @Test
137     fun authenticate_withCorrectPassword_succeeds() =
138         testScope.runTest {
139             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
140 
141             assertSucceeded(underTest.authenticate("password".toList()))
142         }
143 
144     @Test
145     fun authenticate_withIncorrectPassword_fails() =
146         testScope.runTest {
147             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
148 
149             assertFailed(underTest.authenticate("alohomora".toList()))
150         }
151 
152     @Test
153     fun authenticate_withCorrectPattern_succeeds() =
154         testScope.runTest {
155             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
156 
157             assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
158         }
159 
160     @Test
161     fun authenticate_withIncorrectPattern_fails() =
162         testScope.runTest {
163             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
164             val wrongPattern =
165                 listOf(
166                     AuthenticationPatternCoordinate(x = 2, y = 0),
167                     AuthenticationPatternCoordinate(x = 2, y = 1),
168                     AuthenticationPatternCoordinate(x = 2, y = 2),
169                     AuthenticationPatternCoordinate(x = 1, y = 2),
170                 )
171 
172             assertFailed(underTest.authenticate(wrongPattern))
173         }
174 
175     @Test
176     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
177         testScope.runTest {
178             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
179             kosmos.fakeAuthenticationRepository.apply {
180                 setAuthenticationMethod(Pin)
181                 setAutoConfirmFeatureEnabled(true)
182             }
183             assertThat(isAutoConfirmEnabled).isTrue()
184             val defaultPin = FakeAuthenticationRepository.DEFAULT_PIN.toMutableList()
185 
186             assertSkipped(
187                 underTest.authenticate(
188                     defaultPin.subList(0, defaultPin.size - 1),
189                     tryAutoConfirm = true
190                 )
191             )
192             assertThat(underTest.lockoutEndTimestamp).isNull()
193             assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
194         }
195 
196     @Test
197     fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() =
198         testScope.runTest {
199             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
200             kosmos.fakeAuthenticationRepository.apply {
201                 setAuthenticationMethod(Pin)
202                 setAutoConfirmFeatureEnabled(true)
203             }
204             assertThat(isAutoConfirmEnabled).isTrue()
205 
206             val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
207 
208             assertFailed(
209                 underTest.authenticate(wrongPin, tryAutoConfirm = true),
210             )
211         }
212 
213     @Test
214     fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() =
215         testScope.runTest {
216             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
217             kosmos.fakeAuthenticationRepository.apply {
218                 setAuthenticationMethod(Pin)
219                 setAutoConfirmFeatureEnabled(true)
220             }
221             assertThat(isAutoConfirmEnabled).isTrue()
222 
223             val longerPin = FakeAuthenticationRepository.DEFAULT_PIN + listOf(7)
224 
225             assertFailed(
226                 underTest.authenticate(longerPin, tryAutoConfirm = true),
227             )
228         }
229 
230     @Test
231     fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() =
232         testScope.runTest {
233             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
234             kosmos.fakeAuthenticationRepository.apply {
235                 setAuthenticationMethod(Pin)
236                 setAutoConfirmFeatureEnabled(true)
237             }
238             assertThat(isAutoConfirmEnabled).isTrue()
239 
240             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
241 
242             assertSucceeded(underTest.authenticate(correctPin, tryAutoConfirm = true))
243         }
244 
245     @Test
246     fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() =
247         testScope.runTest {
248             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
249             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
250             kosmos.fakeAuthenticationRepository.apply {
251                 setAuthenticationMethod(Pin)
252                 setAutoConfirmFeatureEnabled(true)
253                 reportLockoutStarted(42)
254             }
255 
256             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
257 
258             assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true))
259             assertThat(isAutoConfirmEnabled).isFalse()
260             assertThat(hintedPinLength).isNull()
261             assertThat(underTest.lockoutEndTimestamp).isNotNull()
262         }
263 
264     @Test
265     fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() =
266         testScope.runTest {
267             kosmos.fakeAuthenticationRepository.apply {
268                 setAuthenticationMethod(Pin)
269                 setAutoConfirmFeatureEnabled(false)
270             }
271 
272             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
273 
274             assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true))
275         }
276 
277     @Test
278     fun tryAutoConfirm_withoutCorrectPassword_returnsNull() =
279         testScope.runTest {
280             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
281 
282             assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true))
283         }
284 
285     @Test
286     fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
287         testScope.runTest {
288             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
289             kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
290 
291             assertThat(isAutoConfirmEnabled).isFalse()
292         }
293 
294     @Test
295     fun isAutoConfirmEnabled_featureEnabled_returnsTrue() =
296         testScope.runTest {
297             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
298             kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
299 
300             assertThat(isAutoConfirmEnabled).isTrue()
301         }
302 
303     @Test
304     fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
305         testScope.runTest {
306             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
307             kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
308 
309             // The feature is enabled.
310             assertThat(isAutoConfirmEnabled).isTrue()
311 
312             // Make many wrong attempts to trigger lockout.
313             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
314                 assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN
315             }
316             assertThat(underTest.lockoutEndTimestamp).isNotNull()
317             assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
318 
319             // Lockout disabled auto-confirm.
320             assertThat(isAutoConfirmEnabled).isFalse()
321 
322             // Move the clock forward one more second, to completely finish the lockout period:
323             advanceTimeBy(
324                 FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds.plus(1.seconds)
325             )
326             assertThat(underTest.lockoutEndTimestamp).isNull()
327 
328             // Auto-confirm is still disabled, because lockout occurred at least once in this
329             // session.
330             assertThat(isAutoConfirmEnabled).isFalse()
331 
332             // Correct PIN and unlocks successfully, resetting the 'session'.
333             assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
334 
335             // Auto-confirm is re-enabled.
336             assertThat(isAutoConfirmEnabled).isTrue()
337         }
338 
339     @Test
340     fun failedAuthenticationAttempts() =
341         testScope.runTest {
342             val failedAuthenticationAttempts by
343                 collectLastValue(underTest.failedAuthenticationAttempts)
344 
345             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
346             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
347 
348             assertSucceeded(underTest.authenticate(correctPin))
349             assertThat(failedAuthenticationAttempts).isEqualTo(0)
350 
351             // Make many wrong attempts, leading to lockout:
352             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { index ->
353                 underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
354                 assertThat(failedAuthenticationAttempts).isEqualTo(index + 1)
355             }
356 
357             // Correct PIN, but locked out, so doesn't attempt it:
358             assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
359             assertThat(failedAuthenticationAttempts)
360                 .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT)
361 
362             // Move the clock forward to finish the lockout period:
363             advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds)
364             assertThat(failedAuthenticationAttempts)
365                 .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT)
366 
367             // Correct PIN and no longer locked out so unlocks successfully:
368             assertSucceeded(underTest.authenticate(correctPin))
369             assertThat(failedAuthenticationAttempts).isEqualTo(0)
370         }
371 
372     @Test
373     fun lockoutEndTimestamp() =
374         testScope.runTest {
375             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
376             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
377 
378             underTest.authenticate(correctPin)
379             assertThat(underTest.lockoutEndTimestamp).isNull()
380 
381             // Make many wrong attempts, but just shy of what's needed to get locked out:
382             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
383                 underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
384                 assertThat(underTest.lockoutEndTimestamp).isNull()
385             }
386 
387             // Make one more wrong attempt, leading to lockout:
388             underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
389 
390             val expectedLockoutEndTimestamp =
391                 testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS
392             assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
393             assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
394 
395             // Correct PIN, but locked out, so doesn't attempt it:
396             assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
397             assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
398 
399             // Move the clock forward to ALMOST skip the lockout, leaving one second to go:
400             repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) {
401                 advanceTimeBy(1.seconds)
402                 assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
403             }
404 
405             // Move the clock forward one more second, to completely finish the lockout period:
406             advanceTimeBy(1.seconds)
407             assertThat(underTest.lockoutEndTimestamp).isNull()
408 
409             // Correct PIN and no longer locked out so unlocks successfully:
410             assertSucceeded(underTest.authenticate(correctPin))
411             assertThat(underTest.lockoutEndTimestamp).isNull()
412         }
413 
414     @Test
415     fun upcomingWipe() =
416         testScope.runTest {
417             val upcomingWipe by collectLastValue(underTest.upcomingWipe)
418             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
419             val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
420             val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
421             kosmos.fakeUserRepository.asMainUser()
422             kosmos.fakeAuthenticationRepository.profileWithMinFailedUnlockAttemptsForWipe =
423                 FakeUserRepository.MAIN_USER_ID
424 
425             underTest.authenticate(correctPin)
426             assertThat(upcomingWipe).isNull()
427 
428             var expectedFailedAttempts = 0
429             var remainingFailedAttempts =
430                 kosmos.fakeAuthenticationRepository.getMaxFailedUnlockAttemptsForWipe()
431             assertThat(remainingFailedAttempts)
432                 .isGreaterThan(LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE)
433 
434             // Make many wrong attempts, until wipe is triggered:
435             repeat(remainingFailedAttempts) { attemptIndex ->
436                 underTest.authenticate(wrongPin)
437                 expectedFailedAttempts++
438                 remainingFailedAttempts--
439                 if (underTest.lockoutEndTimestamp != null) {
440                     // If there's a lockout, wait it out:
441                     advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds)
442                 }
443 
444                 if (attemptIndex < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
445                     // No risk of wipe.
446                     assertThat(upcomingWipe).isNull()
447                 } else {
448                     // Wipe grace period started; Make additional wrong attempts, confirm the
449                     // warning is shown each time:
450                     assertThat(upcomingWipe)
451                         .isEqualTo(
452                             AuthenticationWipeModel(
453                                 wipeTarget = AuthenticationWipeModel.WipeTarget.WholeDevice,
454                                 failedAttempts = expectedFailedAttempts,
455                                 remainingAttempts = remainingFailedAttempts
456                             )
457                         )
458                 }
459             }
460 
461             // Unlock successfully, no more risk of upcoming wipe:
462             assertSucceeded(underTest.authenticate(correctPin))
463             assertThat(upcomingWipe).isNull()
464         }
465 
466     @Test
467     fun hintedPinLength_withoutAutoConfirm_isNull() =
468         testScope.runTest {
469             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
470             kosmos.fakeAuthenticationRepository.apply {
471                 setAuthenticationMethod(Pin)
472                 setAutoConfirmFeatureEnabled(false)
473             }
474 
475             assertThat(hintedPinLength).isNull()
476         }
477 
478     @Test
479     fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
480         testScope.runTest {
481             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
482             kosmos.fakeAuthenticationRepository.apply {
483                 setAuthenticationMethod(Pin)
484                 overrideCredential(
485                     buildList {
486                         repeat(kosmos.fakeAuthenticationRepository.hintedPinLength - 1) {
487                             add(it + 1)
488                         }
489                     }
490                 )
491                 setAutoConfirmFeatureEnabled(true)
492             }
493 
494             assertThat(hintedPinLength).isNull()
495         }
496 
497     @Test
498     fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
499         testScope.runTest {
500             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
501             kosmos.fakeAuthenticationRepository.apply {
502                 setAuthenticationMethod(Pin)
503                 setAutoConfirmFeatureEnabled(true)
504                 overrideCredential(
505                     buildList {
506                         repeat(kosmos.fakeAuthenticationRepository.hintedPinLength) { add(it + 1) }
507                     }
508                 )
509             }
510 
511             assertThat(hintedPinLength)
512                 .isEqualTo(kosmos.fakeAuthenticationRepository.hintedPinLength)
513         }
514 
515     @Test
516     fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
517         testScope.runTest {
518             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
519             kosmos.fakeAuthenticationRepository.apply {
520                 setAuthenticationMethod(Pin)
521                 overrideCredential(
522                     buildList {
523                         repeat(kosmos.fakeAuthenticationRepository.hintedPinLength + 1) {
524                             add(it + 1)
525                         }
526                     }
527                 )
528                 setAutoConfirmFeatureEnabled(true)
529             }
530 
531             assertThat(hintedPinLength).isNull()
532         }
533 
534     @Test
535     fun authenticate_withTooShortPassword() =
536         testScope.runTest {
537             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
538 
539             val tooShortPassword = buildList {
540                 repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time ->
541                     add("$time")
542                 }
543             }
544             assertSkipped(underTest.authenticate(tooShortPassword))
545         }
546 
547     private fun assertSucceeded(authenticationResult: AuthenticationResult) {
548         assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED)
549         assertThat(onAuthenticationResult).isTrue()
550         assertThat(underTest.lockoutEndTimestamp).isNull()
551         assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
552         assertThat(failedAuthenticationAttempts).isEqualTo(0)
553     }
554 
555     private fun assertFailed(
556         authenticationResult: AuthenticationResult,
557     ) {
558         assertThat(authenticationResult).isEqualTo(AuthenticationResult.FAILED)
559         assertThat(onAuthenticationResult).isFalse()
560     }
561 
562     private fun assertSkipped(
563         authenticationResult: AuthenticationResult,
564         assertNoResultEvents: Boolean = true,
565     ) {
566         assertThat(authenticationResult).isEqualTo(AuthenticationResult.SKIPPED)
567         if (assertNoResultEvents) {
568             assertThat(onAuthenticationResult).isNull()
569         } else {
570             assertThat(onAuthenticationResult).isNotNull()
571         }
572     }
573 }
574