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