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