mirror of
https://github.com/bitwarden/android.git
synced 2024-11-26 19:36:18 +03:00
BIT-1687: Password reprompt for items (#936)
This commit is contained in:
parent
2e3200f53d
commit
c5e8faccc3
9 changed files with 252 additions and 3 deletions
|
@ -65,12 +65,19 @@ sealed class ListingItemOverflowAction : Parcelable {
|
|||
* Represents the vault actions.
|
||||
*/
|
||||
sealed class VaultAction : ListingItemOverflowAction() {
|
||||
/**
|
||||
* Whether the action requires a master password re-prompt if that
|
||||
* setting is enabled for the selected item.
|
||||
*/
|
||||
abstract val requiresPasswordReprompt: Boolean
|
||||
|
||||
/**
|
||||
* Click on the view cipher overflow option.
|
||||
*/
|
||||
@Parcelize
|
||||
data class ViewClick(val cipherId: String) : VaultAction() {
|
||||
override val title: Text get() = R.string.view.asText()
|
||||
override val requiresPasswordReprompt: Boolean get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,6 +86,7 @@ sealed class ListingItemOverflowAction : Parcelable {
|
|||
@Parcelize
|
||||
data class EditClick(val cipherId: String) : VaultAction() {
|
||||
override val title: Text get() = R.string.edit.asText()
|
||||
override val requiresPasswordReprompt: Boolean get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,6 +95,7 @@ sealed class ListingItemOverflowAction : Parcelable {
|
|||
@Parcelize
|
||||
data class CopyUsernameClick(val username: String) : VaultAction() {
|
||||
override val title: Text get() = R.string.copy_username.asText()
|
||||
override val requiresPasswordReprompt: Boolean get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,6 +104,7 @@ sealed class ListingItemOverflowAction : Parcelable {
|
|||
@Parcelize
|
||||
data class CopyPasswordClick(val password: String) : VaultAction() {
|
||||
override val title: Text get() = R.string.copy_password.asText()
|
||||
override val requiresPasswordReprompt: Boolean get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,6 +113,7 @@ sealed class ListingItemOverflowAction : Parcelable {
|
|||
@Parcelize
|
||||
data class CopyTotpClick(val totpCode: String) : VaultAction() {
|
||||
override val title: Text get() = R.string.copy_totp.asText()
|
||||
override val requiresPasswordReprompt: Boolean get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,6 +122,7 @@ sealed class ListingItemOverflowAction : Parcelable {
|
|||
@Parcelize
|
||||
data class CopyNumberClick(val number: String) : VaultAction() {
|
||||
override val title: Text get() = R.string.copy_number.asText()
|
||||
override val requiresPasswordReprompt: Boolean get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,6 +131,7 @@ sealed class ListingItemOverflowAction : Parcelable {
|
|||
@Parcelize
|
||||
data class CopySecurityCodeClick(val securityCode: String) : VaultAction() {
|
||||
override val title: Text get() = R.string.copy_security_code.asText()
|
||||
override val requiresPasswordReprompt: Boolean get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,6 +140,7 @@ sealed class ListingItemOverflowAction : Parcelable {
|
|||
@Parcelize
|
||||
data class CopyNoteClick(val notes: String) : VaultAction() {
|
||||
override val title: Text get() = R.string.copy_notes.asText()
|
||||
override val requiresPasswordReprompt: Boolean get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,6 +149,7 @@ sealed class ListingItemOverflowAction : Parcelable {
|
|||
@Parcelize
|
||||
data class LaunchClick(val url: String) : VaultAction() {
|
||||
override val title: Text get() = R.string.launch.asText()
|
||||
override val requiresPasswordReprompt: Boolean get() = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenGroupItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderTextWithSupportLabel
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.toIconResources
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.handlers.VaultHandlers
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
|
@ -28,6 +29,7 @@ import kotlinx.collections.immutable.toPersistentList
|
|||
fun VaultContent(
|
||||
state: VaultState.ViewState.Content,
|
||||
vaultHandlers: VaultHandlers,
|
||||
onOverflowOptionClick: (action: ListingItemOverflowAction.VaultAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
|
@ -86,7 +88,15 @@ fun VaultContent(
|
|||
supportingLabel = favoriteItem.supportingLabel?.invoke(),
|
||||
onClick = { vaultHandlers.vaultItemClick(favoriteItem) },
|
||||
overflowOptions = favoriteItem.overflowOptions,
|
||||
onOverflowOptionClick = vaultHandlers.overflowOptionClick,
|
||||
onOverflowOptionClick = { action ->
|
||||
if (favoriteItem.shouldShowMasterPasswordReprompt &&
|
||||
action.requiresPasswordReprompt
|
||||
) {
|
||||
onOverflowOptionClick(action)
|
||||
} else {
|
||||
vaultHandlers.overflowOptionClick(action)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
|
@ -245,7 +255,15 @@ fun VaultContent(
|
|||
supportingLabel = noFolderItem.supportingLabel?.invoke(),
|
||||
onClick = { vaultHandlers.vaultItemClick(noFolderItem) },
|
||||
overflowOptions = noFolderItem.overflowOptions,
|
||||
onOverflowOptionClick = vaultHandlers.overflowOptionClick,
|
||||
onOverflowOptionClick = { action ->
|
||||
if (noFolderItem.shouldShowMasterPasswordReprompt &&
|
||||
action.requiresPasswordReprompt
|
||||
) {
|
||||
onOverflowOptionClick(action)
|
||||
} else {
|
||||
vaultHandlers.overflowOptionClick(action)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
|
|
|
@ -43,6 +43,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMasterPasswordDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMediumTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
|
@ -55,6 +56,7 @@ import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
|
|||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalExitManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.handlers.VaultHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
@ -190,6 +192,21 @@ private fun VaultScreenScaffold(
|
|||
)
|
||||
}
|
||||
|
||||
var masterPasswordRepromptAction by remember {
|
||||
mutableStateOf<ListingItemOverflowAction.VaultAction?>(null)
|
||||
}
|
||||
masterPasswordRepromptAction?.let { action ->
|
||||
BitwardenMasterPasswordDialog(
|
||||
onConfirmClick = { password ->
|
||||
masterPasswordRepromptAction = null
|
||||
vaultHandlers.masterPasswordRepromptSubmit(action, password)
|
||||
},
|
||||
onDismissRequest = {
|
||||
masterPasswordRepromptAction = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
BitwardenScaffold(
|
||||
topBar = {
|
||||
BitwardenMediumTopAppBar(
|
||||
|
@ -276,6 +293,7 @@ private fun VaultScreenScaffold(
|
|||
is VaultState.ViewState.Content -> VaultContent(
|
||||
state = viewState,
|
||||
vaultHandlers = vaultHandlers,
|
||||
onOverflowOptionClick = { masterPasswordRepromptAction = it },
|
||||
modifier = innerModifier,
|
||||
)
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -144,6 +145,11 @@ class VaultViewModel @Inject constructor(
|
|||
is VaultAction.DialogDismiss -> handleDialogDismiss()
|
||||
is VaultAction.RefreshPull -> handleRefreshPull()
|
||||
is VaultAction.OverflowOptionClick -> handleOverflowOptionClick(action)
|
||||
|
||||
is VaultAction.MasterPasswordRepromptSubmit -> {
|
||||
handleMasterPasswordRepromptSubmit(action)
|
||||
}
|
||||
|
||||
is VaultAction.Internal -> handleInternalAction(action)
|
||||
}
|
||||
}
|
||||
|
@ -326,6 +332,20 @@ class VaultViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleMasterPasswordRepromptSubmit(
|
||||
action: VaultAction.MasterPasswordRepromptSubmit,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val result = authRepository.validatePassword(action.password)
|
||||
sendAction(
|
||||
VaultAction.Internal.ValidatePasswordResultReceive(
|
||||
overflowAction = action.overflowAction,
|
||||
result = result,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCopyNoteClick(action: ListingItemOverflowAction.VaultAction.CopyNoteClick) {
|
||||
clipboardManager.setText(action.notes)
|
||||
}
|
||||
|
@ -390,6 +410,10 @@ class VaultViewModel @Inject constructor(
|
|||
is VaultAction.Internal.IconLoadingSettingReceive -> handleIconLoadingSettingReceive(
|
||||
action,
|
||||
)
|
||||
|
||||
is VaultAction.Internal.ValidatePasswordResultReceive -> {
|
||||
handleValidatePasswordResultReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -525,6 +549,39 @@ class VaultViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleValidatePasswordResultReceive(
|
||||
action: VaultAction.Internal.ValidatePasswordResultReceive,
|
||||
) {
|
||||
when (val result = action.result) {
|
||||
ValidatePasswordResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is ValidatePasswordResult.Success -> {
|
||||
if (!result.isValid) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.invalid_master_password.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Complete the overflow action.
|
||||
trySendAction(VaultAction.OverflowOptionClick(action.overflowAction))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion VaultAction Handlers
|
||||
}
|
||||
|
||||
|
@ -721,6 +778,11 @@ data class VaultState(
|
|||
*/
|
||||
abstract val overflowOptions: List<ListingItemOverflowAction.VaultAction>
|
||||
|
||||
/**
|
||||
* Whether to prompt the user for their password when they select an overflow option.
|
||||
*/
|
||||
abstract val shouldShowMasterPasswordReprompt: Boolean
|
||||
|
||||
/**
|
||||
* Represents a login item within the vault.
|
||||
*
|
||||
|
@ -733,6 +795,7 @@ data class VaultState(
|
|||
override val startIcon: IconData = IconData.Local(R.drawable.ic_login_item),
|
||||
override val extraIconList: List<IconRes> = emptyList(),
|
||||
override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
|
||||
override val shouldShowMasterPasswordReprompt: Boolean,
|
||||
val username: Text?,
|
||||
) : VaultItem() {
|
||||
override val supportingLabel: Text? get() = username
|
||||
|
@ -751,6 +814,7 @@ data class VaultState(
|
|||
override val startIcon: IconData = IconData.Local(R.drawable.ic_card_item),
|
||||
override val extraIconList: List<IconRes> = emptyList(),
|
||||
override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
|
||||
override val shouldShowMasterPasswordReprompt: Boolean,
|
||||
val brand: Text? = null,
|
||||
val lastFourDigits: Text? = null,
|
||||
) : VaultItem() {
|
||||
|
@ -779,6 +843,7 @@ data class VaultState(
|
|||
override val startIcon: IconData = IconData.Local(R.drawable.ic_identity_item),
|
||||
override val extraIconList: List<IconRes> = emptyList(),
|
||||
override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
|
||||
override val shouldShowMasterPasswordReprompt: Boolean,
|
||||
val firstName: Text?,
|
||||
) : VaultItem() {
|
||||
override val supportingLabel: Text? get() = firstName
|
||||
|
@ -795,6 +860,7 @@ data class VaultState(
|
|||
override val startIcon: IconData = IconData.Local(R.drawable.ic_secure_note_item),
|
||||
override val extraIconList: List<IconRes> = emptyList(),
|
||||
override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
|
||||
override val shouldShowMasterPasswordReprompt: Boolean,
|
||||
) : VaultItem() {
|
||||
override val supportingLabel: Text? get() = null
|
||||
}
|
||||
|
@ -1033,6 +1099,15 @@ sealed class VaultAction {
|
|||
val overflowAction: ListingItemOverflowAction.VaultAction,
|
||||
) : VaultAction()
|
||||
|
||||
/**
|
||||
* User submitted their master password to authenticate before continuing with
|
||||
* the selected overflow action.
|
||||
*/
|
||||
data class MasterPasswordRepromptSubmit(
|
||||
val overflowAction: ListingItemOverflowAction.VaultAction,
|
||||
val password: String,
|
||||
) : VaultAction()
|
||||
|
||||
/**
|
||||
* Models actions that the [VaultViewModel] itself might send.
|
||||
*/
|
||||
|
@ -1070,6 +1145,14 @@ sealed class VaultAction {
|
|||
data class VaultDataReceive(
|
||||
val vaultData: DataState<VaultData>,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that a result for verifying the user's master password has been received.
|
||||
*/
|
||||
data class ValidatePasswordResultReceive(
|
||||
val overflowAction: ListingItemOverflowAction.VaultAction,
|
||||
val result: ValidatePasswordResult,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ data class VaultHandlers(
|
|||
val tryAgainClick: () -> Unit,
|
||||
val dialogDismiss: () -> Unit,
|
||||
val overflowOptionClick: (ListingItemOverflowAction.VaultAction) -> Unit,
|
||||
val masterPasswordRepromptSubmit: (ListingItemOverflowAction.VaultAction, String) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
|
@ -79,6 +80,14 @@ data class VaultHandlers(
|
|||
overflowOptionClick = {
|
||||
viewModel.trySendAction(VaultAction.OverflowOptionClick(it))
|
||||
},
|
||||
masterPasswordRepromptSubmit = { action, password ->
|
||||
viewModel.trySendAction(
|
||||
VaultAction.MasterPasswordRepromptSubmit(
|
||||
overflowAction = action,
|
||||
password = password,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
||||
|
||||
import android.net.Uri
|
||||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
|
@ -160,6 +161,7 @@ private fun CipherView.toVaultItemOrNull(
|
|||
),
|
||||
overflowOptions = toOverflowActions(),
|
||||
extraIconList = toLabelIcons(),
|
||||
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
|
||||
)
|
||||
|
||||
CipherType.SECURE_NOTE -> VaultState.ViewState.VaultItem.SecureNote(
|
||||
|
@ -167,6 +169,7 @@ private fun CipherView.toVaultItemOrNull(
|
|||
name = name.asText(),
|
||||
overflowOptions = toOverflowActions(),
|
||||
extraIconList = toLabelIcons(),
|
||||
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
|
||||
)
|
||||
|
||||
CipherType.CARD -> VaultState.ViewState.VaultItem.Card(
|
||||
|
@ -178,6 +181,7 @@ private fun CipherView.toVaultItemOrNull(
|
|||
?.asText(),
|
||||
overflowOptions = toOverflowActions(),
|
||||
extraIconList = toLabelIcons(),
|
||||
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
|
||||
)
|
||||
|
||||
CipherType.IDENTITY -> VaultState.ViewState.VaultItem.Identity(
|
||||
|
@ -186,6 +190,7 @@ private fun CipherView.toVaultItemOrNull(
|
|||
firstName = identity?.firstName?.asText(),
|
||||
overflowOptions = toOverflowActions(),
|
||||
extraIconList = toLabelIcons(),
|
||||
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -386,7 +386,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `MasterPasswordRepromptSubmit for a request Success with a valid password should should post to the AutofillSelectionManager`() =
|
||||
fun `MasterPasswordRepromptSubmit for a request Success with a valid password should post to the AutofillSelectionManager`() =
|
||||
runTest {
|
||||
setupMockUri()
|
||||
val cipherView = createMockCipherView(number = 1)
|
||||
|
|
|
@ -736,6 +736,7 @@ class VaultScreenTest : BaseComposeTest() {
|
|||
name = itemText.asText(),
|
||||
username = username.asText(),
|
||||
overflowOptions = emptyList(),
|
||||
shouldShowMasterPasswordReprompt = false,
|
||||
)
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
|
@ -857,6 +858,7 @@ class VaultScreenTest : BaseComposeTest() {
|
|||
name = itemText.asText(),
|
||||
username = userName.asText(),
|
||||
overflowOptions = emptyList(),
|
||||
shouldShowMasterPasswordReprompt = false,
|
||||
)
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -1328,6 +1329,104 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MasterPasswordRepromptSubmit for a request Error should show a generic error dialog`() =
|
||||
runTest {
|
||||
val password = "password"
|
||||
coEvery {
|
||||
authRepository.validatePassword(password = password)
|
||||
} returns ValidatePasswordResult.Error
|
||||
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(
|
||||
VaultAction.MasterPasswordRepromptSubmit(
|
||||
overflowAction = ListingItemOverflowAction.VaultAction.CopyPasswordClick(
|
||||
password = password,
|
||||
),
|
||||
password = password,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialog = VaultState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `MasterPasswordRepromptSubmit for a request Success with an invalid password should show an invalid password dialog`() =
|
||||
runTest {
|
||||
val password = "password"
|
||||
coEvery {
|
||||
authRepository.validatePassword(password = password)
|
||||
} returns ValidatePasswordResult.Success(isValid = false)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(
|
||||
VaultAction.MasterPasswordRepromptSubmit(
|
||||
overflowAction = ListingItemOverflowAction.VaultAction.CopyPasswordClick(
|
||||
password = password,
|
||||
),
|
||||
password = password,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialog = VaultState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.invalid_master_password.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `MasterPasswordRepromptSubmit for a request Success with a valid password should continue the action`() =
|
||||
runTest {
|
||||
val password = "password"
|
||||
coEvery {
|
||||
authRepository.validatePassword(password = password)
|
||||
} returns ValidatePasswordResult.Success(isValid = true)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(
|
||||
VaultAction.MasterPasswordRepromptSubmit(
|
||||
overflowAction = ListingItemOverflowAction.VaultAction.CopyPasswordClick(
|
||||
password = password,
|
||||
),
|
||||
password = password,
|
||||
),
|
||||
)
|
||||
|
||||
verify(exactly = 1) {
|
||||
clipboardManager.setText(password)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(): VaultViewModel =
|
||||
VaultViewModel(
|
||||
authRepository = authRepository,
|
||||
|
|
Loading…
Reference in a new issue