[PM-20127] Only prompt passkey user verification once (#5026)

This commit is contained in:
Patrick Honkonen
2025-04-16 15:35:25 +00:00
committed by GitHub
parent 9dd71eaea2
commit ff72efe0ed
15 changed files with 530 additions and 104 deletions
@@ -691,6 +691,7 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel()
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = DEFAULT_USER_STATE.activeUserId,
isUserPreVerified = false,
requestData = bundleOf(),
)
val fido2Intent = createMockIntent(
@@ -715,7 +716,10 @@ class MainViewModelTest : BaseViewModelTest() {
@Test
fun `on ReceiveFirstIntent with fido2 create request data should set the user verification based on request`() {
val viewModel = createViewModel()
val createCredentialRequest = createMockFido2CreateCredentialRequest(number = 1)
val createCredentialRequest = createMockFido2CreateCredentialRequest(
number = 1,
isUserPreVerified = true,
)
val fido2Intent = createMockIntent(
mockFido2CreateCredentialRequest = createCredentialRequest,
)
@@ -739,6 +743,7 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel()
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "selectedUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
val mockIntent = createMockIntent(
@@ -769,6 +774,7 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel()
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = DEFAULT_USER_STATE.activeUserId,
isUserPreVerified = false,
requestData = bundleOf(),
)
val mockIntent = createMockIntent(
@@ -10,5 +10,6 @@ fun createMockFido2CredentialAssertionRequest(
userId = userId,
cipherId = "mockCipherId-$number",
credentialId = "mockCredentialId-$number",
isUserPreVerified = false,
requestData = bundleOf(),
)
@@ -8,8 +8,10 @@ import androidx.core.os.bundleOf
*/
fun createMockFido2CreateCredentialRequest(
number: Int,
isUserPreVerified: Boolean = false,
requestData: Bundle = bundleOf(),
): Fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId-$number",
isUserPreVerified = isUserPreVerified,
requestData = requestData,
)
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.autofill.fido2.util
import android.content.Intent
import androidx.core.os.bundleOf
import androidx.credentials.provider.BeginGetCredentialRequest
import androidx.credentials.provider.BiometricPromptResult
import androidx.credentials.provider.PendingIntentHandler
import androidx.credentials.provider.ProviderCreateCredentialRequest
import androidx.credentials.provider.ProviderGetCredentialRequest
@@ -18,6 +19,7 @@ import io.mockk.unmockkObject
import io.mockk.unmockkStatic
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.BeforeEach
@@ -58,7 +60,11 @@ class Fido2IntentUtilsTest {
every { ProviderCreateCredentialRequest.asBundle(any()) } returns bundleOf()
every {
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
} returns mockk()
} returns mockk(relaxed = true) {
every { biometricPromptResult } returns mockk(relaxed = true) {
every { isSuccessful } returns false
}
}
val createRequest = intent.getFido2CreateCredentialRequestOrNull()
assertEquals(
@@ -67,6 +73,39 @@ class Fido2IntentUtilsTest {
)
}
@Suppress("MaxLineLength")
@Test
fun `getFido2CreateCredentialRequestOrNull should set user verification based on biometric prompt result`() {
val intent = mockk<Intent> {
every { getStringExtra(EXTRA_KEY_USER_ID) } returns "mockUserId"
}
val mockBiometricPromptResult = mockk<BiometricPromptResult>(relaxed = true) {
every { isSuccessful } returns false
}
val mockProviderCreateCredentialRequest =
mockk<ProviderCreateCredentialRequest>(relaxed = true) {
every { biometricPromptResult } returns mockBiometricPromptResult
}
every { ProviderCreateCredentialRequest.asBundle(any()) } returns bundleOf()
every {
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
} returns mockProviderCreateCredentialRequest
// Verify false is returned when biometric prompt is unsuccessful
var createRequest = intent.getFido2CreateCredentialRequestOrNull()
assertFalse(createRequest!!.isUserPreVerified)
// Verify true is returned when biometric prompt is successful
every { mockBiometricPromptResult.isSuccessful } returns true
createRequest = intent.getFido2CreateCredentialRequestOrNull()
assert(createRequest!!.isUserPreVerified)
// Verify false is returned when biometric prompt result is null
every { mockProviderCreateCredentialRequest.biometricPromptResult } returns null
createRequest = intent.getFido2CreateCredentialRequestOrNull()
assertFalse(createRequest!!.isUserPreVerified)
}
@Suppress("MaxLineLength")
@Test
fun `getFido2CreateCredentialRequestOrNull should return null when build version is below 34`() {
@@ -108,7 +147,11 @@ class Fido2IntentUtilsTest {
every { ProviderGetCredentialRequest.asBundle(any()) } returns bundleOf()
every {
PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
} returns mockk()
} returns mockk(relaxed = true) {
every { biometricPromptResult } returns mockk(relaxed = true) {
every { isSuccessful } returns false
}
}
val assertionRequest = intent.getFido2AssertionRequestOrNull()
@@ -116,6 +159,42 @@ class Fido2IntentUtilsTest {
assertEquals("mockUserId", assertionRequest?.userId)
assertEquals("mockCipherId", assertionRequest?.cipherId)
assertEquals("mockCredentialId", assertionRequest?.credentialId)
assertEquals(false, assertionRequest?.isUserPreVerified)
}
@Suppress("MaxLineLength")
@Test
fun `getFido2AssertionRequestOrNull should set user verification based on biometric prompt result`() {
val intent = mockk<Intent> {
every { getStringExtra(EXTRA_KEY_USER_ID) } returns "mockUserId"
every { getStringExtra(EXTRA_KEY_CIPHER_ID) } returns "mockCipherId"
every { getStringExtra(EXTRA_KEY_CREDENTIAL_ID) } returns "mockCredentialId"
}
val mockBiometricPromptResult = mockk<BiometricPromptResult>(relaxed = true) {
every { isSuccessful } returns false
}
val mockGetCredentialRequest =
mockk<ProviderGetCredentialRequest>(relaxed = true) {
every { biometricPromptResult } returns mockBiometricPromptResult
}
every { ProviderCreateCredentialRequest.asBundle(any()) } returns bundleOf()
every {
PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
} returns mockGetCredentialRequest
// Verify false is returned when biometric prompt is unsuccessful
var assertionRequest = intent.getFido2AssertionRequestOrNull()
assertFalse(assertionRequest!!.isUserPreVerified)
// Verify true is returned when biometric prompt is successful
every { mockBiometricPromptResult.isSuccessful } returns true
assertionRequest = intent.getFido2AssertionRequestOrNull()
assert(assertionRequest!!.isUserPreVerified)
// Verify false is returned when biometric prompt result is null
every { mockGetCredentialRequest.biometricPromptResult } returns null
assertionRequest = intent.getFido2AssertionRequestOrNull()
assertFalse(assertionRequest!!.isUserPreVerified)
}
@Test
@@ -148,6 +148,7 @@ class SpecialCircumstanceExtensionsTest {
fun `toFido2RequestOrNull should return a non-null value for Fido2Save`() {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
assertEquals(
@@ -664,6 +664,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
fun `when the active user has an unlocked vault but there is a Fido2Save special circumstance the nav state should be VaultUnlockedForFido2Save`() {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "activeUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
specialCircumstanceManager.specialCircumstance =
@@ -396,6 +396,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
} returns mockk(relaxed = true)
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId-1",
isUserPreVerified = false,
requestData = bundleOf(),
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
@@ -880,6 +881,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
runTest {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
specialCircumstanceManager.specialCircumstance =
@@ -959,6 +961,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
val mockUserId = "mockUserId"
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = mockUserId,
isUserPreVerified = false,
requestData = bundleOf(),
)
specialCircumstanceManager.specialCircumstance =
@@ -59,6 +59,7 @@ class Fido2CredentialRequestExtensionsTest {
),
Fido2CreateCredentialRequest(
userId = "mockUserId-1",
isUserPreVerified = false,
requestData = bundleOf(),
)
.toDefaultAddTypeContent(
@@ -96,6 +97,7 @@ class Fido2CredentialRequestExtensionsTest {
),
Fido2CreateCredentialRequest(
userId = "mockUserId-1",
isUserPreVerified = false,
requestData = bundleOf(),
)
.toDefaultAddTypeContent(
@@ -4,6 +4,8 @@ import android.net.Uri
import androidx.core.os.bundleOf
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.GetPublicKeyCredentialOption
import androidx.credentials.provider.BeginGetCredentialRequest
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
import androidx.credentials.provider.ProviderCreateCredentialRequest
import androidx.credentials.provider.ProviderGetCredentialRequest
import androidx.lifecycle.SavedStateHandle
@@ -37,6 +39,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CreateCredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialAssertionRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2GetCredentialsRequest
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
@@ -68,6 +71,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.ui.autofill.fido2.manager.model.AssertFido2CredentialResult
import com.x8bit.bitwarden.ui.autofill.fido2.manager.model.GetFido2CredentialsResult
import com.x8bit.bitwarden.ui.autofill.fido2.manager.model.RegisterFido2CredentialResult
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
@@ -217,17 +221,30 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
mockk<GetPublicKeyCredentialOption>(relaxed = true),
)
}
private val mockBeginGetPublicKeyCredentialOption =
mockk<BeginGetPublicKeyCredentialOption>(relaxed = true)
private val mockBeginGetCredentialRequest = mockk<BeginGetCredentialRequest>(relaxed = true) {
every {
beginGetCredentialOptions
} returns listOf(
mockBeginGetPublicKeyCredentialOption,
)
}
@BeforeEach
fun setUp() {
mockkObject(
ProviderCreateCredentialRequest.Companion,
ProviderGetCredentialRequest.Companion,
BeginGetCredentialRequest.Companion,
)
every { ProviderCreateCredentialRequest.fromBundle(any()) } returns mockk(relaxed = true)
every {
ProviderGetCredentialRequest.fromBundle(any())
} returns mockProviderGetCredentialRequest
every {
BeginGetCredentialRequest.fromBundle(any())
} returns mockBeginGetCredentialRequest
}
@AfterEach
@@ -235,6 +252,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
unmockkObject(
ProviderCreateCredentialRequest.Companion,
ProviderGetCredentialRequest.Companion,
BeginGetCredentialRequest.Companion,
)
}
@@ -252,7 +270,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
fun `initial dialog state should be correct when Fido2CreateCredentialRequest is present`() =
runTest {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
"mockUserId",
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
@@ -1859,6 +1878,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "activeUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
@@ -2553,10 +2573,12 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
)
}
//region FIDO2 process handling
@Test
fun `Fido2CreateCredentialRequest should be evaluated before observing vault data`() {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
@@ -2590,6 +2612,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
fun `Fido2ValidateOriginResult should update dialog state on Unknown error`() = runTest {
val mockFido2CreateRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
@@ -2628,6 +2651,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
runTest {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
@@ -2666,6 +2690,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
runTest {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
@@ -2704,6 +2729,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
runTest {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
@@ -2742,6 +2768,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
runTest {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
@@ -2780,6 +2807,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
runTest {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
@@ -2818,6 +2846,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
runTest {
val fido2CreateCredentialRequest = Fido2CreateCredentialRequest(
userId = "mockUserId",
isUserPreVerified = false,
requestData = bundleOf(),
)
@@ -2990,6 +3019,261 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
)
}
@Suppress("MaxLineLength")
@Test
fun `Fido2GetCredentialsRequest should validate request and emit CompleteFido2GetCredentialsRequest event`() =
runTest {
setupMockUri()
val mockGetCredentialsRequest = createMockFido2GetCredentialsRequest(number = 1)
val mockFido2CredentialsList = createMockSdkFido2CredentialList(number = 1)
val mockCipherView = createMockCipherView(
number = 1,
fido2Credentials = mockFido2CredentialsList,
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2GetCredentials(
mockGetCredentialsRequest,
)
every {
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(any())
} returns mockk(relaxed = true)
coEvery {
fido2OriginManager.validateOrigin(
callingAppInfo = any(),
relyingPartyId = any(),
)
} returns Fido2ValidateOriginResult.Success("mockOrigin")
every {
vaultRepository
.ciphersStateFlow
.value
.data
} returns listOf(mockCipherView)
val dataState = DataState.Loaded(
data = VaultData(
cipherViewList = listOf(mockCipherView),
folderViewList = listOf(createMockFolderView(number = 1)),
collectionViewList = listOf(createMockCollectionView(number = 1)),
sendViewList = listOf(createMockSendView(number = 1)),
),
)
val viewModel = createVaultItemListingViewModel()
mutableVaultDataStateFlow.value = dataState
viewModel.eventFlow.test {
assertEquals(
VaultItemListingEvent.CompleteFido2GetCredentialsRequest(
result = GetFido2CredentialsResult.Success(
userId = "mockUserId-1",
option = mockBeginGetPublicKeyCredentialOption,
credentials = emptyList(),
),
),
awaitItem(),
)
}
}
@Test
fun `Fido2GetCredentialsRequest should display error dialog when request is invalid`() =
runTest {
setupMockUri()
val mockGetCredentialsRequest = createMockFido2GetCredentialsRequest(number = 1)
val mockFido2CredentialsList = createMockSdkFido2CredentialList(number = 1)
val mockCipherView = createMockCipherView(
number = 1,
fido2Credentials = mockFido2CredentialsList,
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2GetCredentials(
mockGetCredentialsRequest,
)
every {
vaultRepository
.ciphersStateFlow
.value
.data
} returns listOf(mockCipherView)
every {
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(any())
} returns mockk(relaxed = true)
every {
mockBeginGetCredentialRequest.beginGetCredentialOptions
} returns emptyList()
val dataState = DataState.Loaded(
data = VaultData(
cipherViewList = listOf(mockCipherView),
folderViewList = listOf(createMockFolderView(number = 1)),
collectionViewList = listOf(createMockCollectionView(number = 1)),
sendViewList = listOf(createMockSendView(number = 1)),
),
)
val viewModel = createVaultItemListingViewModel()
mutableVaultDataStateFlow.value = dataState
assertEquals(
VaultItemListingState.DialogState.Fido2OperationFail(
title = R.string.an_error_has_occurred.asText(),
message =
R.string.passkey_operation_failed_because_the_request_is_invalid.asText(),
),
viewModel.stateFlow.value.dialogState,
)
}
@Suppress("MaxLineLength")
@Test
fun `Fido2GetCredentialsRequest should display error dialog when relyingParty cannot be identified`() =
runTest {
setupMockUri()
val mockGetCredentialsRequest = createMockFido2GetCredentialsRequest(number = 1)
val mockFido2CredentialsList = createMockSdkFido2CredentialList(number = 1)
val mockCipherView = createMockCipherView(
number = 1,
fido2Credentials = mockFido2CredentialsList,
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2GetCredentials(
mockGetCredentialsRequest,
)
every {
vaultRepository
.ciphersStateFlow
.value
.data
} returns listOf(mockCipherView)
every {
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(any())
} returns mockk(relaxed = true) {
every { relyingPartyId } returns null
}
val dataState = DataState.Loaded(
data = VaultData(
cipherViewList = listOf(mockCipherView),
folderViewList = listOf(createMockFolderView(number = 1)),
collectionViewList = listOf(createMockCollectionView(number = 1)),
sendViewList = listOf(createMockSendView(number = 1)),
),
)
val viewModel = createVaultItemListingViewModel()
mutableVaultDataStateFlow.value = dataState
assertEquals(
VaultItemListingState.DialogState.Fido2OperationFail(
title = R.string.an_error_has_occurred.asText(),
message =
R.string.passkey_operation_failed_because_relying_party_cannot_be_identified
.asText(),
),
viewModel.stateFlow.value.dialogState,
)
}
@Suppress("MaxLineLength")
@Test
fun `Fido2GetCredentialsRequest should display error dialog when callingApp cannot be verified`() =
runTest {
setupMockUri()
val mockGetCredentialsRequest = createMockFido2GetCredentialsRequest(number = 1)
val mockFido2CredentialsList = createMockSdkFido2CredentialList(number = 1)
val mockCipherView = createMockCipherView(
number = 1,
fido2Credentials = mockFido2CredentialsList,
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2GetCredentials(
mockGetCredentialsRequest,
)
every {
vaultRepository
.ciphersStateFlow
.value
.data
} returns listOf(mockCipherView)
every {
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(any())
} returns mockk(relaxed = true)
every {
mockBeginGetCredentialRequest.callingAppInfo
} returns null
val dataState = DataState.Loaded(
data = VaultData(
cipherViewList = listOf(mockCipherView),
folderViewList = listOf(createMockFolderView(number = 1)),
collectionViewList = listOf(createMockCollectionView(number = 1)),
sendViewList = listOf(createMockSendView(number = 1)),
),
)
val viewModel = createVaultItemListingViewModel()
mutableVaultDataStateFlow.value = dataState
assertEquals(
VaultItemListingState.DialogState.Fido2OperationFail(
title = R.string.an_error_has_occurred.asText(),
message =
R.string.passkey_operation_failed_because_app_could_not_be_verified
.asText(),
),
viewModel.stateFlow.value.dialogState,
)
}
@Test
fun `Fido2GetCredentialsRequest should display error dialog when origin validation fails`() =
runTest {
setupMockUri()
val mockGetCredentialsRequest = createMockFido2GetCredentialsRequest(number = 1)
val mockFido2CredentialsList = createMockSdkFido2CredentialList(number = 1)
val mockCipherView = createMockCipherView(
number = 1,
fido2Credentials = mockFido2CredentialsList,
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2GetCredentials(
mockGetCredentialsRequest,
)
every {
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(any())
} returns mockk(relaxed = true)
coEvery {
fido2OriginManager.validateOrigin(
callingAppInfo = any(),
relyingPartyId = any(),
)
} returns Fido2ValidateOriginResult.Error.Unknown
val dataState = DataState.Loaded(
data = VaultData(
cipherViewList = listOf(mockCipherView),
folderViewList = listOf(createMockFolderView(number = 1)),
collectionViewList = listOf(createMockCollectionView(number = 1)),
sendViewList = listOf(createMockSendView(number = 1)),
),
)
val viewModel = createVaultItemListingViewModel()
mutableVaultDataStateFlow.value = dataState
assertEquals(
VaultItemListingState.DialogState.Fido2OperationFail(
title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(),
),
viewModel.stateFlow.value.dialogState,
)
}
@Suppress("MaxLineLength")
@Test
fun `Fido2AssertionRequest should display loading dialog then request user verification when user is not verified and verification is REQUIRED`() =
@@ -3651,6 +3935,28 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `UserVerificationCancelled should clear dialog state, set isUserVerified to false, and emit CompleteFido2Assertion with cancelled result`() =
runTest {
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
createMockFido2CredentialAssertionRequest(number = 1),
)
val viewModel = createVaultItemListingViewModel()
viewModel.trySendAction(VaultItemListingsAction.UserVerificationCancelled)
verify { fido2CredentialManager.isUserVerified = false }
assertNull(viewModel.stateFlow.value.dialogState)
viewModel.eventFlow.test {
assertEquals(
VaultItemListingEvent.CompleteFido2Assertion(
result = AssertFido2CredentialResult.Cancelled,
),
awaitItem(),
)
}
}
@Test
fun `UserVerificationFail should display Fido2ErrorDialog and set isUserVerified to false`() {
val viewModel = createVaultItemListingViewModel()
@@ -4511,6 +4817,8 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
)
}
//endregion FIDO2 process handling
@Test
fun `InternetConnectionErrorReceived should show network error if no internet connection`() =
runTest {
@@ -887,6 +887,7 @@ class VaultItemListingDataExtensionsTest {
autofillSelectionData = null,
fido2CreationData = Fido2CreateCredentialRequest(
userId = "userId",
isUserPreVerified = false,
requestData = bundleOf(),
),
fido2CredentialAutofillViews = null,