[PM-8137] Refactor FIDO 2 credential registration result object (#3445)

This commit is contained in:
Patrick Honkonen 2024-07-11 18:04:24 -04:00 committed by GitHub
parent c409132825
commit 9b240ddf5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 270 additions and 91 deletions

View file

@ -2,12 +2,12 @@ package com.x8bit.bitwarden.data.autofill.fido2.manager
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKeyCredentialCreationOptions
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
/**
* Responsible for managing FIDO 2 credential creation and authentication.
* Responsible for managing FIDO 2 credential registration and authentication.
*/
interface Fido2CredentialManager {
@ -32,5 +32,5 @@ interface Fido2CredentialManager {
userId: String,
fido2CredentialRequest: Fido2CredentialRequest,
selectedCipherView: CipherView,
): Fido2CreateCredentialResult
): Fido2RegisterCredentialResult
}

View file

@ -1,6 +1,5 @@
package com.x8bit.bitwarden.data.autofill.fido2.manager
import androidx.credentials.exceptions.CreateCredentialUnknownException
import androidx.credentials.provider.CallingAppInfo
import com.bitwarden.fido.ClientData
import com.bitwarden.sdk.CheckUserResult
@ -10,8 +9,8 @@ import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKeyCredentialCreationOptions
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.platform.manager.AssetManager
import com.x8bit.bitwarden.data.platform.util.asFailure
@ -46,15 +45,11 @@ class Fido2CredentialManagerImpl(
userId: String,
fido2CredentialRequest: Fido2CredentialRequest,
selectedCipherView: CipherView,
): Fido2CreateCredentialResult {
): Fido2RegisterCredentialResult {
val clientData = if (fido2CredentialRequest.callingAppInfo.isOriginPopulated()) {
fido2CredentialRequest.callingAppInfo.getAppSigningSignatureFingerprint()
?.let { ClientData.DefaultWithCustomHash(hash = it) }
?: return Fido2CreateCredentialResult.Error(
exception = CreateCredentialUnknownException(
errorMessage = "Application contains multiple signing certificates.",
),
)
?: return Fido2RegisterCredentialResult.Error
} else {
ClientData.DefaultWithExtraData(
androidPackageName = fido2CredentialRequest
@ -82,9 +77,9 @@ class Fido2CredentialManagerImpl(
.map { it.toAndroidAttestationResponse() }
.mapCatching { json.encodeToString(it) }
.fold(
onSuccess = { Fido2CreateCredentialResult.Success(it) },
onSuccess = { Fido2RegisterCredentialResult.Success(it) },
onFailure = {
Fido2CreateCredentialResult.Error(CreateCredentialUnknownException())
Fido2RegisterCredentialResult.Error
},
)
}

View file

@ -1,23 +0,0 @@
package com.x8bit.bitwarden.data.autofill.fido2.model
import androidx.credentials.exceptions.CreateCredentialException
/**
* Models the data returned from creating a FIDO 2 credential.
*/
sealed class Fido2CreateCredentialResult {
/**
* Models a successful response for creating a credential.
*/
data class Success(
val registrationResponse: String,
) : Fido2CreateCredentialResult()
/**
* Models an error response for creating a credential.
*/
data class Error(
val exception: CreateCredentialException,
) : Fido2CreateCredentialResult()
}

View file

@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.autofill.fido2.model
/**
* Models the data returned from creating a FIDO 2 credential.
*/
sealed class Fido2RegisterCredentialResult {
/**
* Indicates the credential has been successfully registered.
*/
data class Success(
val registrationResponse: String,
) : Fido2RegisterCredentialResult()
/**
* Indicates there was an error and the credential was not registered.
*/
data object Error : Fido2RegisterCredentialResult()
/**
* Indicates the user cancelled the request.
*/
data object Cancelled : Fido2RegisterCredentialResult()
}

View file

@ -1,6 +1,6 @@
package com.x8bit.bitwarden.ui.autofill.fido2.manager
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
/**
* A manager for completing the FIDO 2 creation process.
@ -8,7 +8,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
interface Fido2CompletionManager {
/**
* Completes the FIDO 2 creation process with the provided [result].
* Completes the FIDO 2 registration process with the provided [result].
*/
fun completeFido2Create(result: Fido2CreateCredentialResult)
fun completeFido2Registration(result: Fido2RegisterCredentialResult)
}

View file

@ -5,8 +5,10 @@ import android.content.Intent
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.credentials.CreatePublicKeyCredentialResponse
import androidx.credentials.exceptions.CreateCredentialCancellationException
import androidx.credentials.exceptions.CreateCredentialUnknownException
import androidx.credentials.provider.PendingIntentHandler
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
/**
@ -18,19 +20,19 @@ class Fido2CompletionManagerImpl(
) : Fido2CompletionManager {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun completeFido2Create(result: Fido2CreateCredentialResult) {
override fun completeFido2Registration(result: Fido2RegisterCredentialResult) {
activity.also {
val intent = Intent()
when (result) {
is Fido2CreateCredentialResult.Error -> {
is Fido2RegisterCredentialResult.Error -> {
PendingIntentHandler
.setCreateCredentialException(
intent = intent,
exception = result.exception,
exception = CreateCredentialUnknownException(),
)
}
is Fido2CreateCredentialResult.Success -> {
is Fido2RegisterCredentialResult.Success -> {
PendingIntentHandler
.setCreateCredentialResponse(
intent = intent,
@ -39,6 +41,14 @@ class Fido2CompletionManagerImpl(
),
)
}
is Fido2RegisterCredentialResult.Cancelled -> {
PendingIntentHandler
.setCreateCredentialException(
intent = intent,
exception = CreateCredentialCancellationException(),
)
}
}
it.setResult(Activity.RESULT_OK, intent)
it.finish()

View file

@ -112,8 +112,8 @@ fun VaultAddEditScreen(
)
}
is VaultAddEditEvent.CompleteFido2Create -> {
fido2CompletionManager.completeFido2Create(event.result)
is VaultAddEditEvent.CompleteFido2Registration -> {
fido2CompletionManager.completeFido2Registration(event.result)
}
}
}

View file

@ -1,7 +1,6 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit
import android.os.Parcelable
import androidx.credentials.exceptions.CreateCredentialUnknownException
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.vault.CipherView
@ -10,8 +9,8 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
@ -379,14 +378,12 @@ class VaultAddEditViewModel @Inject constructor(
?: run {
sendAction(
VaultAddEditAction.Internal.Fido2RegisterCredentialResultReceive(
result = Fido2CreateCredentialResult.Error(
exception = CreateCredentialUnknownException(),
),
result = Fido2RegisterCredentialResult.Error,
),
)
return@launch
}
val result: Fido2CreateCredentialResult =
val result: Fido2RegisterCredentialResult =
fido2CredentialManager.registerFido2Credential(
userId = activeUserId,
fido2CredentialRequest = request,
@ -1356,15 +1353,19 @@ class VaultAddEditViewModel @Inject constructor(
) {
clearDialogState()
when (action.result) {
is Fido2CreateCredentialResult.Error -> {
is Fido2RegisterCredentialResult.Error -> {
sendEvent(VaultAddEditEvent.ShowToast(R.string.an_error_has_occurred.asText()))
}
is Fido2CreateCredentialResult.Success -> {
is Fido2RegisterCredentialResult.Success -> {
sendEvent(VaultAddEditEvent.ShowToast(R.string.item_updated.asText()))
}
Fido2RegisterCredentialResult.Cancelled -> {
// no-op: The OS will handle re-displaying the system prompt.
}
}
sendEvent(VaultAddEditEvent.CompleteFido2Create(action.result))
sendEvent(VaultAddEditEvent.CompleteFido2Registration(action.result))
}
//endregion Internal Type Handlers
@ -1989,12 +1990,12 @@ sealed class VaultAddEditEvent {
) : VaultAddEditEvent()
/**
* Complete the current FIDO 2 credential creation process.
* Complete the current FIDO 2 credential registration process.
*
* @property result the result of FIDO 2 credential creation.
*/
data class CompleteFido2Create(
val result: Fido2CreateCredentialResult,
data class CompleteFido2Registration(
val result: Fido2RegisterCredentialResult,
) : VaultAddEditEvent()
}
@ -2484,7 +2485,7 @@ sealed class VaultAddEditAction {
* Indicates that the FIDO 2 registration result has been received.
*/
data class Fido2RegisterCredentialResultReceive(
val result: Fido2CreateCredentialResult,
val result: Fido2RegisterCredentialResult,
) : Internal()
}
}

View file

@ -134,8 +134,8 @@ fun VaultItemListingScreen(
onNavigateToVaultItemListing(VaultItemListingType.Collection(event.collectionId))
}
is VaultItemListingEvent.CompleteFido2Create -> {
fido2CompletionManager.completeFido2Create(event.result)
is VaultItemListingEvent.CompleteFido2Registration -> {
fido2CompletionManager.completeFido2Registration(event.result)
}
}
}

View file

@ -1,15 +1,14 @@
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
import android.os.Parcelable
import androidx.credentials.exceptions.CreateCredentialUnknownException
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
@ -408,10 +407,8 @@ class VaultItemListingViewModel @Inject constructor(
private fun handleDismissFido2ErrorDialogClick() {
sendEvent(
VaultItemListingEvent.CompleteFido2Create(
result = Fido2CreateCredentialResult.Error(
exception = CreateCredentialUnknownException(),
),
VaultItemListingEvent.CompleteFido2Registration(
result = Fido2RegisterCredentialResult.Error,
),
)
}
@ -766,15 +763,19 @@ class VaultItemListingViewModel @Inject constructor(
) {
mutableStateFlow.update { it.copy(dialogState = null) }
when (action.result) {
is Fido2CreateCredentialResult.Error -> {
is Fido2RegisterCredentialResult.Error -> {
sendEvent(VaultItemListingEvent.ShowToast(R.string.an_error_has_occurred.asText()))
}
is Fido2CreateCredentialResult.Success -> {
is Fido2RegisterCredentialResult.Success -> {
sendEvent(VaultItemListingEvent.ShowToast(R.string.item_updated.asText()))
}
Fido2RegisterCredentialResult.Cancelled -> {
// no-op: The OS will handle re-displaying the system prompt.
}
}
sendEvent(VaultItemListingEvent.CompleteFido2Create(action.result))
sendEvent(VaultItemListingEvent.CompleteFido2Registration(action.result))
}
private fun handleValidateFido2OriginResultReceive(
@ -1346,8 +1347,8 @@ sealed class VaultItemListingEvent {
*
* @property result the result of FIDO 2 credential creation.
*/
data class CompleteFido2Create(
val result: Fido2CreateCredentialResult,
data class CompleteFido2Registration(
val result: Fido2RegisterCredentialResult,
) : VaultItemListingEvent()
}
@ -1529,7 +1530,7 @@ sealed class VaultItemListingsAction {
* Indicates that a result for FIDO 2 credential registration has been received.
*/
data class Fido2RegisterCredentialResultReceive(
val result: Fido2CreateCredentialResult,
val result: Fido2RegisterCredentialResult,
) : Internal()
}
}

View file

@ -10,8 +10,8 @@ import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalA
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKeyCredentialCreationOptions
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2AttestationResponse
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRequest
import com.x8bit.bitwarden.data.platform.manager.AssetManager
@ -486,7 +486,7 @@ class Fido2CredentialManagerTest {
)
assertTrue(
result is Fido2CreateCredentialResult.Error,
result is Fido2RegisterCredentialResult.Error,
)
}
@ -530,7 +530,7 @@ class Fido2CredentialManagerTest {
)
assertTrue(
result is Fido2CreateCredentialResult.Error,
result is Fido2RegisterCredentialResult.Error,
)
}
}

View file

@ -33,7 +33,7 @@ import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performTouchInput
import androidx.core.net.toUri
import com.bitwarden.vault.UriMatchType
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
import com.x8bit.bitwarden.ui.autofill.fido2.manager.Fido2CompletionManager
@ -96,7 +96,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
every { launchUri(any()) } just runs
}
private val fido2CompletionManager: Fido2CompletionManager = mockk {
every { completeFido2Create(any()) } just runs
every { completeFido2Registration(any()) } just runs
}
@Before
@ -194,11 +194,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
@Test
fun `on CompleteFido2Create even should invoke Fido2CompletionManager`() {
val result = Fido2CreateCredentialResult.Success(
val result = Fido2RegisterCredentialResult.Success(
registrationResponse = "mockRegistrationResponse",
)
mutableEventFlow.tryEmit(VaultAddEditEvent.CompleteFido2Create(result = result))
verify { fido2CompletionManager.completeFido2Create(result) }
mutableEventFlow.tryEmit(VaultAddEditEvent.CompleteFido2Registration(result = result))
verify { fido2CompletionManager.completeFido2Registration(result) }
}
@Test

View file

@ -1,7 +1,6 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit
import android.content.pm.SigningInfo
import androidx.credentials.exceptions.CreateCredentialUnknownException
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import app.cash.turbine.turbineScope
@ -17,8 +16,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.Organization
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
@ -725,7 +724,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
),
)
val mockCreateResult = Fido2CreateCredentialResult.Success(
val mockCreateResult = Fido2RegisterCredentialResult.Success(
registrationResponse = "mockRegistrationResponse",
)
coEvery {
@ -750,7 +749,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
eventTurbine.awaitItem(),
)
assertEquals(
VaultAddEditEvent.CompleteFido2Create(result = mockCreateResult),
VaultAddEditEvent.CompleteFido2Registration(result = mockCreateResult),
eventTurbine.awaitItem(),
)
assertEquals(stateWithName, stateTurbine.awaitItem())
@ -767,7 +766,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
@Suppress("MaxLineLength")
@Test
fun `in add mode during fido2, SaveClick should show dialog, register credential, show toast on error, and emit ExitApp`() =
fun `in add mode during fido2, SaveClick should show dialog, register credential, show toast on error, and emit ExitApp when result is Error`() =
runTest {
val fido2CredentialRequest = Fido2CredentialRequest(
userId = "mockUserId",
@ -807,9 +806,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
),
)
val mockCreateResult = Fido2CreateCredentialResult.Error(
exception = CreateCredentialUnknownException(),
)
val mockCreateResult = Fido2RegisterCredentialResult.Error
coEvery {
fido2CredentialManager.registerFido2Credential(
userId = "mockUserId",
@ -832,7 +829,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
eventTurbine.awaitItem(),
)
assertEquals(
VaultAddEditEvent.CompleteFido2Create(result = mockCreateResult),
VaultAddEditEvent.CompleteFido2Registration(result = mockCreateResult),
eventTurbine.awaitItem(),
)
assertEquals(stateWithName, stateTurbine.awaitItem())
@ -847,6 +844,86 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `in add mode during fido2, SaveClick should show saving dialog, register credential, dismiss dialog, show toast on error, and emit ExitApp when activeUserId is null`() =
runTest {
val fido2CredentialRequest = Fido2CredentialRequest(
userId = "mockUserId",
requestJson = "mockRequestJson",
packageName = "mockPackageName",
signingInfo = mockk<SigningInfo>(),
origin = null,
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
fido2CredentialRequest = fido2CredentialRequest,
)
val stateWithDialog = createVaultAddItemState(
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
dialogState = VaultAddEditState.DialogState.Loading(
R.string.saving.asText(),
),
commonContentViewState = createCommonContentViewState(
name = "mockName-1",
),
)
.copy(shouldExitOnSave = true)
val stateWithName = createVaultAddItemState(
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
commonContentViewState = createCommonContentViewState(
name = "mockName-1",
),
)
.copy(shouldExitOnSave = true)
mutableVaultDataFlow.value = DataState.Loaded(
createVaultData(),
)
val viewModel = createAddVaultItemViewModel(
createSavedStateHandleWithState(
state = stateWithName,
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
),
)
val mockCreateResult = Fido2RegisterCredentialResult.Error
coEvery {
fido2CredentialManager.registerFido2Credential(
userId = "mockUserId",
selectedCipherView = any(),
fido2CredentialRequest = fido2CredentialRequest,
)
} returns mockCreateResult
every { authRepository.activeUserId } returns null
turbineScope {
val stateTurbine = viewModel.stateFlow.testIn(backgroundScope)
val eventTurbine = viewModel.eventFlow.testIn(backgroundScope)
viewModel.trySendAction(VaultAddEditAction.Common.SaveClick)
assertEquals(stateWithName, stateTurbine.awaitItem())
assertEquals(stateWithDialog, stateTurbine.awaitItem())
assertEquals(
VaultAddEditEvent.ShowToast(R.string.an_error_has_occurred.asText()),
eventTurbine.awaitItem(),
)
assertEquals(
VaultAddEditEvent.CompleteFido2Registration(result = mockCreateResult),
eventTurbine.awaitItem(),
)
assertEquals(stateWithName, stateTurbine.awaitItem())
coVerify(exactly = 0) {
fido2CredentialManager.registerFido2Credential(
userId = "mockUserId",
selectedCipherView = any(),
fido2CredentialRequest = fido2CredentialRequest,
)
}
}
}
@Test
fun `in add mode, createCipherInOrganization success should ShowToast and NavigateBack`() =
runTest {

View file

@ -82,7 +82,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
every { launchUri(any()) } just runs
}
private val fido2CompletionManager: Fido2CompletionManager = mockk {
every { completeFido2Create(any()) } just runs
every { completeFido2Registration(any()) } just runs
}
private val mutableEventFlow = bufferedMutableSharedFlow<VaultItemListingEvent>()
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)

View file

@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
@ -66,6 +67,7 @@ import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.Clock
@ -1736,6 +1738,98 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
)
}
@Suppress("MaxLineLength")
@Test
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() =
runTest {
val mockResult = Fido2RegisterCredentialResult.Error
val viewModel = createVaultItemListingViewModel()
viewModel.trySendAction(
VaultItemListingsAction.Internal.Fido2RegisterCredentialResultReceive(
mockResult,
),
)
viewModel.eventFlow.test {
assertEquals(
VaultItemListingEvent.ShowToast(R.string.an_error_has_occurred.asText()),
awaitItem(),
)
assertEquals(
VaultItemListingEvent.CompleteFido2Registration(mockResult),
awaitItem(),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `Fido2RegisterCredentialResult Success should show toast and emit CompleteFido2Registration result`() =
runTest {
val mockResult = Fido2RegisterCredentialResult.Success(
registrationResponse = "mockResponse",
)
val viewModel = createVaultItemListingViewModel()
viewModel.trySendAction(
VaultItemListingsAction.Internal.Fido2RegisterCredentialResultReceive(
mockResult,
),
)
viewModel.eventFlow.test {
assertEquals(
VaultItemListingEvent.ShowToast(R.string.item_updated.asText()),
awaitItem(),
)
assertEquals(
VaultItemListingEvent.CompleteFido2Registration(mockResult),
awaitItem(),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `Fido2RegisterCredentialResult Cancelled should emit CompleteFido2Registration result`() =
runTest {
val mockResult = Fido2RegisterCredentialResult.Cancelled
val viewModel = createVaultItemListingViewModel()
viewModel.trySendAction(
VaultItemListingsAction.Internal.Fido2RegisterCredentialResultReceive(
mockResult,
),
)
viewModel.eventFlow.test {
assertEquals(
VaultItemListingEvent.CompleteFido2Registration(mockResult),
awaitItem(),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `DismissFido2ErrorDialogClick should clear the dialog state then complete FIDO 2 create`() =
runTest {
val viewModel = createVaultItemListingViewModel()
viewModel.trySendAction(VaultItemListingsAction.DismissFido2CreationErrorDialogClick)
viewModel.eventFlow.test {
assertNull(viewModel.stateFlow.value.dialogState)
assertEquals(
VaultItemListingEvent.CompleteFido2Registration(
result = Fido2RegisterCredentialResult.Error,
),
awaitItem(),
)
}
}
@Suppress("CyclomaticComplexMethod")
private fun createSavedStateHandleWithVaultItemListingType(
vaultItemListingType: VaultItemListingType,