Merge "Implement basic Fingerprint functionality." into udc-qpr-dev am: 1412f391d5
am: 22b67802f5
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/23480676 Change-Id: I981accee2b7f0f3742c98d1b352d3076d421dede Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -21,6 +21,7 @@ android_test {
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"androidx.arch.core_core-testing",
|
||||
"androidx.test.core",
|
||||
"androidx.test.rules",
|
||||
"androidx.test.espresso.core",
|
||||
|
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.fingerprint2.domain.interactor
|
||||
|
||||
import android.hardware.biometrics.SensorProperties
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
/** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
|
||||
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||
|
||||
var enrollableFingerprints: Int = 5
|
||||
var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf()
|
||||
var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
|
||||
var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1)
|
||||
var pressToAuthEnabled = true
|
||||
|
||||
var sensorProps =
|
||||
listOf(
|
||||
FingerprintSensorPropertiesInternal(
|
||||
0 /* sensorId */,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
5 /* maxEnrollmentsPerUser */,
|
||||
emptyList() /* ComponentInfoInternal */,
|
||||
TYPE_POWER_BUTTON,
|
||||
true /* resetLockoutRequiresHardwareAuthToken */
|
||||
)
|
||||
)
|
||||
|
||||
override suspend fun authenticate(): FingerprintAuthAttemptViewModel {
|
||||
return authenticateAttempt
|
||||
}
|
||||
|
||||
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> {
|
||||
return challengeToGenerate
|
||||
}
|
||||
override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
|
||||
emit(enrolledFingerprintsInternal)
|
||||
}
|
||||
|
||||
override fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean> = flow {
|
||||
emit(numFingerprints < enrollableFingerprints)
|
||||
}
|
||||
|
||||
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
|
||||
|
||||
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
|
||||
return enrolledFingerprintsInternal.remove(fp)
|
||||
}
|
||||
|
||||
override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {}
|
||||
|
||||
override suspend fun hasSideFps(): Boolean {
|
||||
return sensorProps.any { it.isAnySidefpsType }
|
||||
}
|
||||
|
||||
override suspend fun pressToAuthEnabled(): Boolean {
|
||||
return pressToAuthEnabled
|
||||
}
|
||||
|
||||
override suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal> =
|
||||
sensorProps
|
||||
}
|
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.hardware.fingerprint.Fingerprint
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.hardware.fingerprint.FingerprintManager.CryptoObject
|
||||
import android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
|
||||
import android.os.CancellationSignal
|
||||
import android.os.Handler
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.ArgumentMatchers.anyLong
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.ArgumentMatchers.nullable
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
|
||||
@RunWith(MockitoJUnitRunner::class)
|
||||
class FingerprintManagerInteractorTest {
|
||||
|
||||
@JvmField @Rule var rule = MockitoJUnit.rule()
|
||||
private lateinit var underTest: FingerprintManagerInteractor
|
||||
private var context: Context = ApplicationProvider.getApplicationContext()
|
||||
private var backgroundDispatcher = StandardTestDispatcher()
|
||||
@Mock private lateinit var fingerprintManager: FingerprintManager
|
||||
@Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
|
||||
|
||||
private var testScope = TestScope(backgroundDispatcher)
|
||||
private var pressToAuthProvider = { true }
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
underTest =
|
||||
FingerprintManagerInteractorImpl(
|
||||
context,
|
||||
backgroundDispatcher,
|
||||
fingerprintManager,
|
||||
gateKeeperPasswordProvider,
|
||||
pressToAuthProvider,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyFingerprints() =
|
||||
testScope.runTest {
|
||||
Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt()))
|
||||
.thenReturn(emptyList())
|
||||
|
||||
val emptyFingerprintList: List<Fingerprint> = emptyList()
|
||||
assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOneFingerprint() =
|
||||
testScope.runTest {
|
||||
val expected = Fingerprint("Finger 1,", 2, 3L)
|
||||
val fingerprintList: List<Fingerprint> = listOf(expected)
|
||||
Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt()))
|
||||
.thenReturn(fingerprintList)
|
||||
|
||||
val list = underTest.enrolledFingerprints.last()
|
||||
assertThat(list.size).isEqualTo(fingerprintList.size)
|
||||
val actual = list[0]
|
||||
assertThat(actual.name).isEqualTo(expected.name)
|
||||
assertThat(actual.fingerId).isEqualTo(expected.biometricId)
|
||||
assertThat(actual.deviceId).isEqualTo(expected.deviceId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCanEnrollFingerprint() =
|
||||
testScope.runTest {
|
||||
val mockContext = Mockito.mock(Context::class.java)
|
||||
val resources = Mockito.mock(Resources::class.java)
|
||||
Mockito.`when`(mockContext.resources).thenReturn(resources)
|
||||
Mockito.`when`(resources.getInteger(anyInt())).thenReturn(3)
|
||||
underTest =
|
||||
FingerprintManagerInteractorImpl(
|
||||
mockContext,
|
||||
backgroundDispatcher,
|
||||
fingerprintManager,
|
||||
gateKeeperPasswordProvider,
|
||||
pressToAuthProvider,
|
||||
)
|
||||
|
||||
assertThat(underTest.canEnrollFingerprints(2).last()).isTrue()
|
||||
assertThat(underTest.canEnrollFingerprints(3).last()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGenerateChallenge() =
|
||||
testScope.runTest {
|
||||
val byteArray = byteArrayOf(5, 3, 2)
|
||||
val challenge = 100L
|
||||
val intent = Intent()
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, challenge)
|
||||
Mockito.`when`(
|
||||
gateKeeperPasswordProvider.requestGatekeeperHat(
|
||||
any(Intent::class.java),
|
||||
anyLong(),
|
||||
anyInt()
|
||||
)
|
||||
)
|
||||
.thenReturn(byteArray)
|
||||
|
||||
val generateChallengeCallback: ArgumentCaptor<FingerprintManager.GenerateChallengeCallback> =
|
||||
ArgumentCaptor.forClass(FingerprintManager.GenerateChallengeCallback::class.java)
|
||||
|
||||
var result: Pair<Long, ByteArray?>? = null
|
||||
val job = testScope.launch { result = underTest.generateChallenge(1L) }
|
||||
runCurrent()
|
||||
|
||||
Mockito.verify(fingerprintManager)
|
||||
.generateChallenge(anyInt(), capture(generateChallengeCallback))
|
||||
generateChallengeCallback.value.onChallengeGenerated(1, 2, challenge)
|
||||
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
|
||||
assertThat(result?.first).isEqualTo(challenge)
|
||||
assertThat(result?.second).isEqualTo(byteArray)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemoveFingerprint_succeeds() =
|
||||
testScope.runTest {
|
||||
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
|
||||
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
|
||||
|
||||
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
|
||||
ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
|
||||
|
||||
var result: Boolean? = null
|
||||
val job =
|
||||
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
|
||||
runCurrent()
|
||||
|
||||
Mockito.verify(fingerprintManager)
|
||||
.remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
|
||||
removalCallback.value.onRemovalSucceeded(fingerprintToRemove, 1)
|
||||
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemoveFingerprint_fails() =
|
||||
testScope.runTest {
|
||||
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
|
||||
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
|
||||
|
||||
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
|
||||
ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
|
||||
|
||||
var result: Boolean? = null
|
||||
val job =
|
||||
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
|
||||
runCurrent()
|
||||
|
||||
Mockito.verify(fingerprintManager)
|
||||
.remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
|
||||
removalCallback.value.onRemovalError(
|
||||
fingerprintToRemove,
|
||||
100,
|
||||
"Oh no, we couldn't find that one"
|
||||
)
|
||||
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRenameFingerprint_succeeds() =
|
||||
testScope.runTest {
|
||||
val fingerprintToRename = FingerprintViewModel("Finger 2", 1, 2L)
|
||||
|
||||
underTest.renameFingerprint(fingerprintToRename, "Woo")
|
||||
|
||||
Mockito.verify(fingerprintManager)
|
||||
.rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAuth_succeeds() =
|
||||
testScope.runTest {
|
||||
val fingerprint = Fingerprint("Woooo", 100, 101L)
|
||||
|
||||
var result: FingerprintAuthAttemptViewModel? = null
|
||||
val job = launch { result = underTest.authenticate() }
|
||||
|
||||
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
|
||||
ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
|
||||
|
||||
runCurrent()
|
||||
|
||||
Mockito.verify(fingerprintManager)
|
||||
.authenticate(
|
||||
nullable(CryptoObject::class.java),
|
||||
any(CancellationSignal::class.java),
|
||||
capture(authCallback),
|
||||
nullable(Handler::class.java),
|
||||
anyInt()
|
||||
)
|
||||
authCallback.value.onAuthenticationSucceeded(
|
||||
FingerprintManager.AuthenticationResult(null, fingerprint, 1, false)
|
||||
)
|
||||
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
assertThat(result).isEqualTo(FingerprintAuthAttemptViewModel.Success(fingerprint.biometricId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAuth_lockout() =
|
||||
testScope.runTest {
|
||||
var result: FingerprintAuthAttemptViewModel? = null
|
||||
val job = launch { result = underTest.authenticate() }
|
||||
|
||||
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
|
||||
ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
|
||||
|
||||
runCurrent()
|
||||
|
||||
Mockito.verify(fingerprintManager)
|
||||
.authenticate(
|
||||
nullable(CryptoObject::class.java),
|
||||
any(CancellationSignal::class.java),
|
||||
capture(authCallback),
|
||||
nullable(Handler::class.java),
|
||||
anyInt()
|
||||
)
|
||||
authCallback.value.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
|
||||
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
assertThat(result)
|
||||
.isEqualTo(
|
||||
FingerprintAuthAttemptViewModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
|
||||
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
|
||||
}
|
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.fingerprint2.viewmodel
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsNavigationViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettings
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettingsWithResult
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchConfirmDeviceCredential
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.NextStepViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.ShowSettings
|
||||
import com.android.settings.fingerprint2.domain.interactor.FakeFingerprintManagerInteractor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
|
||||
@RunWith(MockitoJUnitRunner::class)
|
||||
class FingerprintSettingsNavigationViewModelTest {
|
||||
|
||||
@JvmField @Rule var rule = MockitoJUnit.rule()
|
||||
|
||||
@get:Rule val instantTaskRule = InstantTaskExecutorRule()
|
||||
|
||||
private lateinit var underTest: FingerprintSettingsNavigationViewModel
|
||||
private val defaultUserId = 0
|
||||
private var backgroundDispatcher = StandardTestDispatcher()
|
||||
private var testScope = TestScope(backgroundDispatcher)
|
||||
private lateinit var fakeFingerprintManagerInteractor: FakeFingerprintManagerInteractor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
fakeFingerprintManagerInteractor = FakeFingerprintManagerInteractor()
|
||||
backgroundDispatcher = StandardTestDispatcher()
|
||||
testScope = TestScope(backgroundDispatcher)
|
||||
Dispatchers.setMain(backgroundDispatcher)
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
.create(FingerprintSettingsNavigationViewModel::class.java)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoGateKeeper_launchesConfirmDeviceCredential() =
|
||||
testScope.runTest {
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
runCurrent()
|
||||
assertThat(nextStep).isEqualTo(LaunchConfirmDeviceCredential(defaultUserId))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConfirmDevice_fails() =
|
||||
testScope.runTest {
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
underTest.onConfirmDevice(false, null)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun confirmDeviceSuccess_noGateKeeper() =
|
||||
testScope.runTest {
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
underTest.onConfirmDevice(true, null)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun confirmDeviceSuccess_launchesEnrollment_ifNoPreviousEnrollments() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(EnrollFirstFingerprint(defaultUserId, 10L, null, null))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollment_fails() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirstFailure("We failed!!")
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollment_failsWithReason() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
val failStr = "We failed!!"
|
||||
val failReason = 101
|
||||
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirstFailure(failStr, failReason)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(FinishSettingsWithResult(failReason, failStr))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollmentSucceeds_noToken() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirst(null, null)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(FinishSettings("Error, empty token"))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollmentSucceeds_noKeyChallenge() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
val byteArray = ByteArray(1) { 3 }
|
||||
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirst(byteArray, null)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(FinishSettings("Error, empty keyChallenge"))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollment_succeeds() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = testScope.launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
val byteArray = ByteArray(1) { 3 }
|
||||
val keyChallenge = 89L
|
||||
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirst(byteArray, keyChallenge)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(ShowSettings)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enrollAdditionalFingerprints_fails() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
fakeFingerprintManagerInteractor.challengeToGenerate = Pair(4L, byteArrayOf(3, 3, 1))
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
runCurrent()
|
||||
underTest.onEnrollAdditionalFailure()
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enrollAdditional_success() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollSuccess()
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(ShowSettings)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
fakeFingerprintManagerInteractor.challengeToGenerate = Pair(10L, byteArrayOf(1, 2, 3))
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(ShowSettings)
|
||||
job.cancel()
|
||||
}
|
||||
}
|
@@ -16,317 +16,232 @@
|
||||
|
||||
package com.android.settings.fingerprint2.viewmodel
|
||||
|
||||
import android.hardware.fingerprint.Fingerprint
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettings
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettingsWithResult
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchConfirmDeviceCredential
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.NextStepViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.ShowSettings
|
||||
import android.hardware.biometrics.SensorProperties
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsNavigationViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.PreferenceViewModel
|
||||
import com.android.settings.fingerprint2.domain.interactor.FakeFingerprintManagerInteractor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@RunWith(MockitoJUnitRunner::class)
|
||||
class FingerprintSettingsViewModelTest {
|
||||
|
||||
@JvmField
|
||||
@Rule
|
||||
var rule = MockitoJUnit.rule()
|
||||
@JvmField @Rule var rule = MockitoJUnit.rule()
|
||||
|
||||
@Mock
|
||||
private lateinit var fingerprintManager: FingerprintManager
|
||||
private lateinit var underTest: FingerprintSettingsViewModel
|
||||
private val defaultUserId = 0
|
||||
@get:Rule val instantTaskRule = InstantTaskExecutorRule()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// @formatter:off
|
||||
underTest = FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
private lateinit var underTest: FingerprintSettingsViewModel
|
||||
private lateinit var navigationViewModel: FingerprintSettingsNavigationViewModel
|
||||
private val defaultUserId = 0
|
||||
private var backgroundDispatcher = StandardTestDispatcher()
|
||||
private var testScope = TestScope(backgroundDispatcher)
|
||||
private lateinit var fakeFingerprintManagerInteractor: FakeFingerprintManagerInteractor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
fakeFingerprintManagerInteractor = FakeFingerprintManagerInteractor()
|
||||
backgroundDispatcher = StandardTestDispatcher()
|
||||
testScope = TestScope(backgroundDispatcher)
|
||||
Dispatchers.setMain(backgroundDispatcher)
|
||||
|
||||
navigationViewModel =
|
||||
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
.create(FingerprintSettingsNavigationViewModel::class.java)
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticate_DoesNotRun_ifOptical() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.sensorProps =
|
||||
listOf(
|
||||
FingerprintSensorPropertiesInternal(
|
||||
0 /* sensorId */,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
5 /* maxEnrollmentsPerUser */,
|
||||
emptyList() /* ComponentInfoInternal */,
|
||||
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
|
||||
true /* resetLockoutRequiresHardwareAuthToken */
|
||||
)
|
||||
)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fingerprintManager,
|
||||
).create(FingerprintSettingsViewModel::class.java)
|
||||
// @formatter:on
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
|
||||
var authAttempt: FingerprintAuthAttemptViewModel? = null
|
||||
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
|
||||
|
||||
underTest.shouldAuthenticate(true)
|
||||
// Ensure we are showing settings
|
||||
navigationViewModel.onConfirmDevice(true, 10L)
|
||||
|
||||
runCurrent()
|
||||
advanceTimeBy(400)
|
||||
|
||||
assertThat(authAttempt).isNull()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoGateKeeper_launchesConfirmDeviceCredential() = runTest {
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
|
||||
runCurrent()
|
||||
assertThat(nextStep).isEqualTo(LaunchConfirmDeviceCredential(defaultUserId))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConfirmDevice_fails() = runTest {
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(false, null)
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun confirmDeviceSuccess_noGateKeeper() = runTest {
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, null)
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun confirmDeviceSuccess_launchesEnrollment_ifNoPreviousEnrollments() = runTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(EnrollFirstFingerprint(defaultUserId, 10L, null, null))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollment_fails() = runTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirstFailure("We failed!!")
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollment_failsWithReason() = runTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
|
||||
val failStr = "We failed!!"
|
||||
val failReason = 101
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirstFailure(failStr, failReason)
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(FinishSettingsWithResult(failReason, failStr))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollmentSucceeds_noToken() = runTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirst(null, null)
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(FinishSettings("Error, empty token"))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollmentSucceeds_noKeyChallenge() = runTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
|
||||
val byteArray = ByteArray(1) {
|
||||
3
|
||||
}
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirst(byteArray, null)
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(FinishSettings("Error, empty keyChallenge"))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstEnrollment_succeeds() = runTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
|
||||
val byteArray = ByteArray(1) {
|
||||
3
|
||||
}
|
||||
val keyChallenge = 89L
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollFirst(byteArray, keyChallenge)
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(ShowSettings(defaultUserId))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() = runTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
|
||||
listOf(
|
||||
Fingerprint(
|
||||
"a", 1, 2, 3L
|
||||
)
|
||||
)
|
||||
@Test
|
||||
fun authenticate_DoesNotRun_ifUltrasonic() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.sensorProps =
|
||||
listOf(
|
||||
FingerprintSensorPropertiesInternal(
|
||||
0 /* sensorId */,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
5 /* maxEnrollmentsPerUser */,
|
||||
emptyList() /* ComponentInfoInternal */,
|
||||
FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
|
||||
true /* resetLockoutRequiresHardwareAuthToken */
|
||||
)
|
||||
)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
var authAttempt: FingerprintAuthAttemptViewModel? = null
|
||||
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
|
||||
|
||||
runCurrent()
|
||||
underTest.shouldAuthenticate(true)
|
||||
navigationViewModel.onConfirmDevice(true, 10L)
|
||||
advanceTimeBy(400)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isEqualTo(ShowSettings(defaultUserId))
|
||||
job.cancel()
|
||||
assertThat(authAttempt).isNull()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enrollAdditionalFingerprints_fails() = runTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
|
||||
listOf(
|
||||
Fingerprint(
|
||||
"a", 1, 2, 3L
|
||||
)
|
||||
)
|
||||
@Test
|
||||
fun authenticate_DoesRun_ifNotUdfps() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.sensorProps =
|
||||
listOf(
|
||||
FingerprintSensorPropertiesInternal(
|
||||
0 /* sensorId */,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
5 /* maxEnrollmentsPerUser */,
|
||||
emptyList() /* ComponentInfoInternal */,
|
||||
FingerprintSensorProperties.TYPE_POWER_BUTTON,
|
||||
true /* resetLockoutRequiresHardwareAuthToken */
|
||||
)
|
||||
)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
val success = FingerprintAuthAttemptViewModel.Success(1)
|
||||
fakeFingerprintManagerInteractor.authenticateAttempt = success
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollAdditionalFailure()
|
||||
var authAttempt: FingerprintAuthAttemptViewModel? = null
|
||||
|
||||
runCurrent()
|
||||
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
|
||||
underTest.shouldAuthenticate(true)
|
||||
navigationViewModel.onConfirmDevice(true, 10L)
|
||||
advanceTimeBy(400)
|
||||
runCurrent()
|
||||
|
||||
assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
|
||||
job.cancel()
|
||||
assertThat(authAttempt).isEqualTo(success)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enrollAdditional_success() = runTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
|
||||
listOf(
|
||||
Fingerprint(
|
||||
"a", 1, 2, 3L
|
||||
)
|
||||
)
|
||||
@Test
|
||||
fun deleteDialog_showAndDismiss() = runTest {
|
||||
val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf(fingerprintToDelete)
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch {
|
||||
underTest.nextStep.collect {
|
||||
nextStep = it
|
||||
}
|
||||
}
|
||||
var dialog: PreferenceViewModel? = null
|
||||
val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
|
||||
|
||||
underTest.updateTokenAndChallenge(null, null)
|
||||
underTest.onConfirmDevice(true, 10L)
|
||||
underTest.onEnrollSuccess()
|
||||
// Move to the ShowSettings state
|
||||
navigationViewModel.onConfirmDevice(true, 10L)
|
||||
runCurrent()
|
||||
underTest.onDeleteClicked(fingerprintToDelete)
|
||||
runCurrent()
|
||||
|
||||
runCurrent()
|
||||
assertThat(dialog is PreferenceViewModel.DeleteDialog)
|
||||
assertThat(dialog).isEqualTo(PreferenceViewModel.DeleteDialog(fingerprintToDelete))
|
||||
|
||||
assertThat(nextStep).isEqualTo(ShowSettings(defaultUserId))
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
underTest.deleteFingerprint(fingerprintToDelete)
|
||||
underTest.onDeleteDialogFinished()
|
||||
runCurrent()
|
||||
|
||||
assertThat(dialog).isNull()
|
||||
|
||||
dialogJob.cancel()
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user