mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-2046 Display passkey fields in Vault (#1143)
This commit is contained in:
parent
f9edd70beb
commit
be127f5d49
24 changed files with 582 additions and 128 deletions
|
@ -711,6 +711,7 @@ data class SyncResponseJson(
|
|||
* @property shouldAutofillOnPageLoad If autofill is used on page load (nullable).
|
||||
* @property uri The URI (nullable).
|
||||
* @property username The username (nullable).
|
||||
* @property fido2Credentials A list of FIDO 2 credentials (nullable).
|
||||
*/
|
||||
@Serializable
|
||||
data class Login(
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.semantics.semantics
|
|||
import androidx.compose.ui.semantics.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledTonalButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledTonalButtonWithIcon
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
|
||||
|
@ -81,6 +82,18 @@ fun LazyListScope.vaultAddEditLoginItems(
|
|||
)
|
||||
}
|
||||
|
||||
loginState.fido2CredentialCreationDateTime?.let { creationDateTime ->
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
PasskeyField(
|
||||
creationDateTime = creationDateTime,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
BitwardenListHeaderText(
|
||||
|
@ -476,3 +489,18 @@ private fun PasswordRow(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PasskeyField(
|
||||
creationDateTime: Text,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.passkey),
|
||||
value = creationDateTime.invoke(),
|
||||
onValueChange = { },
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Clock
|
||||
import java.util.Collections
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
@ -82,6 +83,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
private val settingsRepository: SettingsRepository,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val clock: Clock,
|
||||
) : BaseViewModel<VaultAddEditState, VaultAddEditEvent, VaultAddEditAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
|
@ -1164,6 +1166,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
isClone = isCloneMode,
|
||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||
resourceManager = resourceManager,
|
||||
clock = clock,
|
||||
) ?: viewState)
|
||||
.appendFolderAndOwnerData(
|
||||
folderViewList = vaultData.folderViewList,
|
||||
|
@ -1557,6 +1560,8 @@ data class VaultAddEditState(
|
|||
* @property totp The current TOTP (if applicable).
|
||||
* @property canViewPassword Indicates whether the current user can view and copy
|
||||
* passwords associated with the login item.
|
||||
* @property fido2CredentialCreationDateTime Date and time the FIDO 2 credential was
|
||||
* created.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Login(
|
||||
|
@ -1567,6 +1572,7 @@ data class VaultAddEditState(
|
|||
val uriList: List<UriItem> = listOf(
|
||||
UriItem(id = UUID.randomUUID().toString(), uri = "", match = null),
|
||||
),
|
||||
val fido2CredentialCreationDateTime: Text? = null,
|
||||
) : ItemType() {
|
||||
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.LOGIN
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.bitwarden.core.CipherRepromptType
|
|||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
import com.bitwarden.core.Fido2Credential
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.bitwarden.core.FolderView
|
||||
|
@ -14,6 +15,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
|
@ -23,8 +25,12 @@ import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
|||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType.Companion.fromId
|
||||
import com.x8bit.bitwarden.ui.vault.model.findVaultCardBrandWithNameOrNull
|
||||
import java.time.Clock
|
||||
import java.util.UUID
|
||||
|
||||
private const val PASSKEY_CREATION_DATE_PATTERN: String = "M/d/yy"
|
||||
private const val PASSKEY_CREATION_TIME_PATTERN: String = "hh:mm a"
|
||||
|
||||
/**
|
||||
* Transforms [CipherView] into [VaultAddEditState.ViewState].
|
||||
*/
|
||||
|
@ -32,6 +38,7 @@ fun CipherView.toViewState(
|
|||
isClone: Boolean,
|
||||
isIndividualVaultDisabled: Boolean,
|
||||
resourceManager: ResourceManager,
|
||||
clock: Clock,
|
||||
): VaultAddEditState.ViewState =
|
||||
VaultAddEditState.ViewState.Content(
|
||||
type = when (type) {
|
||||
|
@ -39,9 +46,13 @@ fun CipherView.toViewState(
|
|||
VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||
username = login?.username.orEmpty(),
|
||||
password = login?.password.orEmpty(),
|
||||
uriList = login?.uris.toUriItems(),
|
||||
totp = login?.totp,
|
||||
canViewPassword = this.viewPassword,
|
||||
uriList = login?.uris.toUriItems(),
|
||||
fido2CredentialCreationDateTime = login
|
||||
?.fido2Credentials
|
||||
.getPrimaryFido2CredentialOrNull(isClone)
|
||||
?.getCreationDateTime(clock),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -272,3 +283,24 @@ private fun List<LoginUriView>?.toUriItems(): List<UriItem> =
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cipher's primary (first) FIDO2 credential, or null if there is no FIDO2 credential
|
||||
* assigned.
|
||||
*/
|
||||
private fun List<Fido2Credential>?.getPrimaryFido2CredentialOrNull(
|
||||
isClone: Boolean,
|
||||
): Fido2Credential? {
|
||||
if (isNullOrEmpty() || isClone) return null
|
||||
|
||||
return first()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the creation date and time of the primary FIDO2 credential, formatted as
|
||||
* "M/d/yy, hh:mm a".
|
||||
*/
|
||||
private fun Fido2Credential.getCreationDateTime(clock: Clock) = R.string.created_xy.asText(
|
||||
creationDate.toFormattedPattern(pattern = PASSKEY_CREATION_DATE_PATTERN, clock = clock),
|
||||
creationDate.toFormattedPattern(pattern = PASSKEY_CREATION_TIME_PATTERN, clock = clock),
|
||||
)
|
||||
|
|
|
@ -100,6 +100,18 @@ fun VaultItemLoginContent(
|
|||
}
|
||||
}
|
||||
|
||||
loginItemState.fido2CredentialCreationDateText?.let { creationDate ->
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Fido2CredentialField(
|
||||
creationDate = creationDate.toString(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
loginItemState.totpCodeItemData?.let { totpCodeItemData ->
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
@ -247,6 +259,21 @@ fun VaultItemLoginContent(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Fido2CredentialField(
|
||||
creationDate: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.passkey),
|
||||
value = creationDate,
|
||||
onValueChange = { },
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotesField(
|
||||
notes: String,
|
||||
|
|
|
@ -137,13 +137,20 @@ fun VaultItemScreen(
|
|||
)
|
||||
}
|
||||
},
|
||||
onConfirmDeleteClick = remember {
|
||||
onConfirmDeleteClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
VaultItemAction.Common.ConfirmDeleteClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
onConfirmCloneWithoutFido2Credential = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
VaultItemAction.Common.ConfirmCloneWithoutFido2CredentialClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if (pendingRestoreCipher) {
|
||||
|
@ -291,6 +298,7 @@ private fun VaultItemDialogs(
|
|||
onDismissRequest: () -> Unit,
|
||||
onConfirmDeleteClick: () -> Unit,
|
||||
onSubmitMasterPassword: (masterPassword: String, action: PasswordRepromptAction) -> Unit,
|
||||
onConfirmCloneWithoutFido2Credential: () -> Unit,
|
||||
) {
|
||||
when (dialog) {
|
||||
is VaultItemState.DialogState.Generic -> BitwardenBasicDialog(
|
||||
|
@ -324,6 +332,18 @@ private fun VaultItemDialogs(
|
|||
)
|
||||
}
|
||||
|
||||
is VaultItemState.DialogState.Fido2CredentialCannotBeCopiedConfirmationPrompt -> {
|
||||
BitwardenTwoButtonDialog(
|
||||
title = stringResource(id = R.string.passkey_will_not_be_copied),
|
||||
message = dialog.message.invoke(),
|
||||
confirmButtonText = stringResource(id = R.string.yes),
|
||||
dismissButtonText = stringResource(id = R.string.no),
|
||||
onConfirmClick = onConfirmCloneWithoutFido2Credential,
|
||||
onDismissClick = onDismissRequest,
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,6 +151,9 @@ class VaultItemViewModel @Inject constructor(
|
|||
is VaultItemAction.Common.ConfirmDeleteClick -> handleConfirmDeleteClick()
|
||||
is VaultItemAction.Common.ConfirmRestoreClick -> handleConfirmRestoreClick()
|
||||
is VaultItemAction.Common.DeleteClick -> handleDeleteClick()
|
||||
is VaultItemAction.Common.ConfirmCloneWithoutFido2CredentialClick -> {
|
||||
handleConfirmCloneClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -373,9 +376,19 @@ class VaultItemViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun handleCloneClick() {
|
||||
onContent { content ->
|
||||
if (content.common.requiresReprompt) {
|
||||
if (content.common.requiresCloneConfirmation) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultItemState.DialogState.Fido2CredentialCannotBeCopiedConfirmationPrompt(
|
||||
message = R.string.the_passkey_will_not_be_copied_to_the_cloned_item_do_you_want_to_continue_cloning_this_item.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
return@onContent
|
||||
} else if (content.common.requiresReprompt) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = VaultItemState.DialogState.MasterPasswordDialog(
|
||||
|
@ -389,6 +402,23 @@ class VaultItemViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleConfirmCloneClick() {
|
||||
onContent { content ->
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = null,
|
||||
viewState = content.copy(
|
||||
common = content.common.copy(
|
||||
requiresCloneConfirmation = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
trySendAction(VaultItemAction.Common.CloneClick)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMoveToOrganizationClick() {
|
||||
onContent { content ->
|
||||
if (content.common.requiresReprompt) {
|
||||
|
@ -1030,6 +1060,8 @@ data class VaultItemState(
|
|||
* @property customFields A list of custom fields that user has added.
|
||||
* @property requiresReprompt Indicates if a master password prompt is required to view
|
||||
* secure fields.
|
||||
* @property requiresCloneConfirmation Indicates user confirmation is required when
|
||||
* cloning a cipher.
|
||||
* @property currentCipher The cipher that is currently being viewed (nullable).
|
||||
*/
|
||||
@Parcelize
|
||||
|
@ -1039,6 +1071,7 @@ data class VaultItemState(
|
|||
val notes: String?,
|
||||
val customFields: List<Custom>,
|
||||
val requiresReprompt: Boolean,
|
||||
val requiresCloneConfirmation: Boolean,
|
||||
@IgnoredOnParcel
|
||||
val currentCipher: CipherView? = null,
|
||||
val attachments: List<AttachmentItem>?,
|
||||
|
@ -1121,6 +1154,8 @@ data class VaultItemState(
|
|||
* @property totpCodeItemData The optional data related the TOTP code.
|
||||
* @property isPremiumUser Indicates if the user has subscribed to a premium
|
||||
* account.
|
||||
* @property fido2CredentialCreationDateText Optional creation date and time of the
|
||||
* FIDO2 credential associated with the login item.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Login(
|
||||
|
@ -1131,6 +1166,7 @@ data class VaultItemState(
|
|||
val passwordRevisionDate: String?,
|
||||
val totpCodeItemData: TotpCodeItemData?,
|
||||
val isPremiumUser: Boolean,
|
||||
val fido2CredentialCreationDateText: Text?,
|
||||
) : ItemType() {
|
||||
|
||||
/**
|
||||
|
@ -1246,6 +1282,14 @@ data class VaultItemState(
|
|||
data class DeleteConfirmationPrompt(
|
||||
val message: Text,
|
||||
) : DialogState()
|
||||
|
||||
/**
|
||||
* Displays the dialog for cloning without copying FIDO2 credentials to the user.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Fido2CredentialCannotBeCopiedConfirmationPrompt(
|
||||
val message: Text,
|
||||
) : DialogState()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1430,6 +1474,11 @@ sealed class VaultItemAction {
|
|||
* The user skipped selecting a location for the attachment file.
|
||||
*/
|
||||
data object NoAttachmentFileLocationReceive : Common()
|
||||
|
||||
/**
|
||||
* The user confirmed cloning a cipher without its FIDO 2 credentials.
|
||||
*/
|
||||
data object ConfirmCloneWithoutFido2CredentialClick : Common()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,11 +4,15 @@ import com.bitwarden.core.CardView
|
|||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.Fido2Credential
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.bitwarden.core.IdentityView
|
||||
import com.bitwarden.core.LoginUriView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.capitalize
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.nullIfAllEqual
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
|
||||
|
@ -22,7 +26,9 @@ import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
|||
import com.x8bit.bitwarden.ui.vault.model.findVaultCardBrandWithNameOrNull
|
||||
import java.time.Clock
|
||||
|
||||
private const val DATE_TIME_PATTERN: String = "M/d/yy hh:mm a"
|
||||
private const val LAST_UPDATED_DATE_TIME_PATTERN: String = "M/d/yy hh:mm a"
|
||||
private const val FIDO2_CREDENTIAL_CREATION_DATE_PATTERN: String = "M/d/yy"
|
||||
private const val FIDO2_CREDENTIAL_CREATION_TIME_PATTERN: String = "h:mm a"
|
||||
|
||||
/**
|
||||
* Transforms [VaultData] into [VaultState.ViewState].
|
||||
|
@ -40,10 +46,11 @@ fun CipherView.toViewState(
|
|||
requiresReprompt = reprompt == CipherRepromptType.PASSWORD,
|
||||
customFields = fields.orEmpty().map { it.toCustomField() },
|
||||
lastUpdated = revisionDate.toFormattedPattern(
|
||||
pattern = DATE_TIME_PATTERN,
|
||||
pattern = LAST_UPDATED_DATE_TIME_PATTERN,
|
||||
clock = clock,
|
||||
),
|
||||
notes = notes,
|
||||
requiresCloneConfirmation = login?.fido2Credentials?.any() ?: false,
|
||||
attachments = attachments
|
||||
?.mapNotNull {
|
||||
@Suppress("ComplexCondition")
|
||||
|
@ -87,12 +94,16 @@ fun CipherView.toViewState(
|
|||
passwordRevisionDate = loginValues
|
||||
.passwordRevisionDate
|
||||
?.toFormattedPattern(
|
||||
pattern = DATE_TIME_PATTERN,
|
||||
pattern = LAST_UPDATED_DATE_TIME_PATTERN,
|
||||
clock = clock,
|
||||
),
|
||||
passwordHistoryCount = passwordHistory?.count(),
|
||||
isPremiumUser = isPremiumUser,
|
||||
totpCodeItemData = totpCodeItemData,
|
||||
fido2CredentialCreationDateText = loginValues
|
||||
.fido2Credentials
|
||||
?.firstOrNull()
|
||||
?.getCreationDateText(clock),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -159,6 +170,20 @@ private fun LoginUriView.toUriData() =
|
|||
isLaunchable = !uri.isNullOrBlank(),
|
||||
)
|
||||
|
||||
private fun Fido2Credential?.getCreationDateText(clock: Clock): Text? =
|
||||
this?.let {
|
||||
R.string.created_xy.asText(
|
||||
creationDate.toFormattedPattern(
|
||||
pattern = FIDO2_CREDENTIAL_CREATION_DATE_PATTERN,
|
||||
clock = clock,
|
||||
),
|
||||
creationDate.toFormattedPattern(
|
||||
pattern = FIDO2_CREDENTIAL_CREATION_TIME_PATTERN,
|
||||
clock = clock,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private val IdentityView.identityAddress: String?
|
||||
get() = listOfNotNull(
|
||||
address1,
|
||||
|
|
|
@ -352,7 +352,7 @@ private const val CIPHER_JSON = """
|
|||
"userDisplayName": "mockUserDisplayName-1",
|
||||
"counter": "mockCounter-1",
|
||||
"discoverable": "mockDiscoverable-1",
|
||||
"creationDate": "2024-03-12T20:20:16.456Z"
|
||||
"creationDate": "2023-10-27T12:00:00.000Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -2,6 +2,11 @@ package com.x8bit.bitwarden.data.vault.datasource.network.model
|
|||
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Constant date time used for [ZonedDateTime] properties of mock objects.
|
||||
*/
|
||||
private val MOCK_ZONED_DATE_TIME = ZonedDateTime.parse("2023-10-27T12:00:00Z")
|
||||
|
||||
/**
|
||||
* Create a mock [SyncResponseJson.Cipher] with a given [number].
|
||||
*/
|
||||
|
@ -15,9 +20,9 @@ fun createMockCipher(number: Int, hasNullUri: Boolean = false): SyncResponseJson
|
|||
notes = "mockNotes-$number",
|
||||
type = CipherTypeJson.LOGIN,
|
||||
login = createMockLogin(number = number, hasNullUri = hasNullUri),
|
||||
creationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
deletedDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
creationDate = MOCK_ZONED_DATE_TIME,
|
||||
deletedDate = MOCK_ZONED_DATE_TIME,
|
||||
revisionDate = MOCK_ZONED_DATE_TIME,
|
||||
attachments = listOf(createMockAttachment(number = number)),
|
||||
card = createMockCard(number = number),
|
||||
fields = listOf(createMockField(number = number)),
|
||||
|
@ -89,7 +94,7 @@ fun createMockCard(number: Int): SyncResponseJson.Cipher.Card =
|
|||
fun createMockPasswordHistory(number: Int): SyncResponseJson.Cipher.PasswordHistory =
|
||||
SyncResponseJson.Cipher.PasswordHistory(
|
||||
password = "mockPassword-$number",
|
||||
lastUsedDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
lastUsedDate = MOCK_ZONED_DATE_TIME,
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -118,7 +123,7 @@ fun createMockLogin(number: Int, hasNullUri: Boolean = false): SyncResponseJson.
|
|||
SyncResponseJson.Cipher.Login(
|
||||
username = "mockUsername-$number",
|
||||
password = "mockPassword-$number",
|
||||
passwordRevisionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
passwordRevisionDate = MOCK_ZONED_DATE_TIME,
|
||||
shouldAutofillOnPageLoad = false,
|
||||
uri = if (hasNullUri) null else "mockUri-$number",
|
||||
uris = listOf(createMockUri(number = number)),
|
||||
|
@ -139,7 +144,7 @@ fun createMockFido2Credential(number: Int) = SyncResponseJson.Cipher.Fido2Creden
|
|||
userDisplayName = "mockUserDisplayName-$number",
|
||||
counter = "mockCounter-$number",
|
||||
discoverable = "mockDiscoverable-$number",
|
||||
creationDate = ZonedDateTime.parse("2024-03-12T20:20:16.456Z"),
|
||||
creationDate = MOCK_ZONED_DATE_TIME,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
@ -329,7 +329,7 @@ private const val CREATE_ATTACHMENT_SUCCESS_JSON = """
|
|||
"userDisplayName": "mockUserDisplayName-1",
|
||||
"counter": "mockCounter-1",
|
||||
"discoverable": "mockDiscoverable-1",
|
||||
"creationDate": "2024-03-12T20:20:16.456Z"
|
||||
"creationDate": "2023-10-27T12:00:00.00Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -439,7 +439,7 @@ private const val CREATE_UPDATE_CIPHER_SUCCESS_JSON = """
|
|||
"userDisplayName": "mockUserDisplayName-1",
|
||||
"counter": "mockCounter-1",
|
||||
"discoverable": "mockDiscoverable-1",
|
||||
"creationDate": "2024-03-12T20:20:16.456Z"
|
||||
"creationDate": "2023-10-27T12:00:00.00Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -245,7 +245,7 @@ private const val SYNC_SUCCESS_JSON = """
|
|||
"userDisplayName": "mockUserDisplayName-1",
|
||||
"counter": "mockCounter-1",
|
||||
"discoverable": "mockDiscoverable-1",
|
||||
"creationDate": "2024-03-12T20:20:16.456Z"
|
||||
"creationDate": "2023-10-27T12:00:00.00Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -15,8 +15,20 @@ import com.bitwarden.core.PasswordHistoryView
|
|||
import com.bitwarden.core.SecureNoteType
|
||||
import com.bitwarden.core.SecureNoteView
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Default date time used for [ZonedDateTime] properties of mock objects.
|
||||
*/
|
||||
private const val DEFAULT_TIMESTAMP = "2023-10-27T12:00:00Z"
|
||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||
Instant.parse(DEFAULT_TIMESTAMP),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a mock [CipherView].
|
||||
*
|
||||
|
@ -24,12 +36,14 @@ import java.time.ZonedDateTime
|
|||
* @param isDeleted whether or not the cipher has been deleted.
|
||||
* @param cipherType the type of cipher to create.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
fun createMockCipherView(
|
||||
number: Int,
|
||||
isDeleted: Boolean = false,
|
||||
cipherType: CipherType = CipherType.LOGIN,
|
||||
totp: String? = "mockTotp-$number",
|
||||
folderId: String? = "mockId-$number",
|
||||
clock: Clock = FIXED_CLOCK,
|
||||
): CipherView =
|
||||
CipherView(
|
||||
id = "mockId-$number",
|
||||
|
@ -43,21 +57,16 @@ fun createMockCipherView(
|
|||
login = createMockLoginView(
|
||||
number = number,
|
||||
totp = totp,
|
||||
clock = clock,
|
||||
)
|
||||
.takeIf { cipherType == CipherType.LOGIN },
|
||||
creationDate = ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant(),
|
||||
creationDate = clock.instant(),
|
||||
deletedDate = if (isDeleted) {
|
||||
ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant()
|
||||
clock.instant()
|
||||
} else {
|
||||
null
|
||||
},
|
||||
revisionDate = ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant(),
|
||||
revisionDate = clock.instant(),
|
||||
attachments = listOf(createMockAttachmentView(number = number)),
|
||||
card = createMockCardView(number = number).takeIf { cipherType == CipherType.CARD },
|
||||
fields = listOf(createMockFieldView(number = number)),
|
||||
|
@ -65,7 +74,7 @@ fun createMockCipherView(
|
|||
cipherType == CipherType.IDENTITY
|
||||
},
|
||||
favorite = false,
|
||||
passwordHistory = listOf(createMockPasswordHistoryView(number = number)),
|
||||
passwordHistory = listOf(createMockPasswordHistoryView(number = number, clock)),
|
||||
reprompt = CipherRepromptType.NONE,
|
||||
secureNote = createMockSecureNoteView().takeIf { cipherType == CipherType.SECURE_NOTE },
|
||||
edit = false,
|
||||
|
@ -80,40 +89,39 @@ fun createMockCipherView(
|
|||
fun createMockLoginView(
|
||||
number: Int,
|
||||
totp: String? = "mockTotp-$number",
|
||||
clock: Clock = FIXED_CLOCK,
|
||||
): LoginView =
|
||||
LoginView(
|
||||
username = "mockUsername-$number",
|
||||
password = "mockPassword-$number",
|
||||
passwordRevisionDate = ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant(),
|
||||
passwordRevisionDate = clock.instant(),
|
||||
autofillOnPageLoad = false,
|
||||
uris = listOf(createMockUriView(number = number)),
|
||||
totp = totp,
|
||||
fido2Credentials = createMockSdkFido2CredentialList(number),
|
||||
fido2Credentials = createMockSdkFido2CredentialList(number, clock),
|
||||
)
|
||||
|
||||
fun createMockSdkFido2CredentialList(number: Int) =
|
||||
listOf(createMockSdkFido2CredentialView(number))
|
||||
fun createMockSdkFido2CredentialList(number: Int, clock: Clock = FIXED_CLOCK) =
|
||||
listOf(createMockSdkFido2CredentialView(number, clock))
|
||||
|
||||
fun createMockSdkFido2CredentialView(number: Int) =
|
||||
Fido2Credential(
|
||||
credentialId = "mockCredentialId-$number",
|
||||
keyType = "mockKeyType-$number",
|
||||
keyAlgorithm = "mockKeyAlgorithm-$number",
|
||||
keyCurve = "mockKeyCurve-$number",
|
||||
keyValue = "mockKeyValue-$number",
|
||||
rpId = "mockRpId-$number",
|
||||
userHandle = "mockUserHandle-$number",
|
||||
userName = "mockUserName-$number",
|
||||
counter = "mockCounter-$number",
|
||||
rpName = "mockRpName-$number",
|
||||
userDisplayName = "mockUserDisplayName-$number",
|
||||
discoverable = "mockDiscoverable-$number",
|
||||
creationDate = ZonedDateTime
|
||||
.parse("2024-03-12T20:20:16.456Z")
|
||||
.toInstant(),
|
||||
)
|
||||
fun createMockSdkFido2CredentialView(
|
||||
number: Int,
|
||||
clock: Clock = FIXED_CLOCK,
|
||||
) = Fido2Credential(
|
||||
credentialId = "mockCredentialId-$number",
|
||||
keyType = "mockKeyType-$number",
|
||||
keyAlgorithm = "mockKeyAlgorithm-$number",
|
||||
keyCurve = "mockKeyCurve-$number",
|
||||
keyValue = "mockKeyValue-$number",
|
||||
rpId = "mockRpId-$number",
|
||||
userHandle = "mockUserHandle-$number",
|
||||
userName = "mockUserName-$number",
|
||||
counter = "mockCounter-$number",
|
||||
rpName = "mockRpName-$number",
|
||||
userDisplayName = "mockUserDisplayName-$number",
|
||||
discoverable = "mockDiscoverable-$number",
|
||||
creationDate = clock.instant(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a mock [LoginUriView] with a given [number].
|
||||
|
@ -189,12 +197,10 @@ fun createMockIdentityView(number: Int): IdentityView =
|
|||
/**
|
||||
* Create a mock [PasswordHistoryView] with a given [number].
|
||||
*/
|
||||
fun createMockPasswordHistoryView(number: Int): PasswordHistoryView =
|
||||
fun createMockPasswordHistoryView(number: Int, clock: Clock = FIXED_CLOCK): PasswordHistoryView =
|
||||
PasswordHistoryView(
|
||||
password = "mockPassword-$number",
|
||||
lastUsedDate = ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant(),
|
||||
lastUsedDate = clock.instant(),
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,12 +14,24 @@ import com.bitwarden.core.PasswordHistory
|
|||
import com.bitwarden.core.SecureNote
|
||||
import com.bitwarden.core.SecureNoteType
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Default date time used for [ZonedDateTime] properties of mock objects.
|
||||
*/
|
||||
private const val DEFAULT_TIMESTAMP = "2023-10-27T12:00:00Z"
|
||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||
Instant.parse(DEFAULT_TIMESTAMP),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a mock [Cipher] with a given [number].
|
||||
*/
|
||||
fun createMockSdkCipher(number: Int): Cipher =
|
||||
fun createMockSdkCipher(number: Int, clock: Clock = FIXED_CLOCK): Cipher =
|
||||
Cipher(
|
||||
id = "mockId-$number",
|
||||
organizationId = "mockOrganizationId-$number",
|
||||
|
@ -29,22 +41,16 @@ fun createMockSdkCipher(number: Int): Cipher =
|
|||
name = "mockName-$number",
|
||||
notes = "mockNotes-$number",
|
||||
type = CipherType.LOGIN,
|
||||
login = createMockSdkLogin(number = number),
|
||||
creationDate = ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant(),
|
||||
deletedDate = ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant(),
|
||||
revisionDate = ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant(),
|
||||
login = createMockSdkLogin(number = number, clock = clock),
|
||||
creationDate = clock.instant(),
|
||||
deletedDate = clock.instant(),
|
||||
revisionDate = clock.instant(),
|
||||
attachments = listOf(createMockSdkAttachment(number = number)),
|
||||
card = createMockSdkCard(number = number),
|
||||
fields = listOf(createMockSdkField(number = number)),
|
||||
identity = createMockSdkIdentity(number = number),
|
||||
favorite = false,
|
||||
passwordHistory = listOf(createMockSdkPasswordHistory(number = number)),
|
||||
passwordHistory = listOf(createMockSdkPasswordHistory(number = number, clock = clock)),
|
||||
reprompt = CipherRepromptType.NONE,
|
||||
secureNote = createMockSdkSecureNote(),
|
||||
edit = false,
|
||||
|
@ -64,12 +70,10 @@ fun createMockSdkSecureNote(): SecureNote =
|
|||
/**
|
||||
* Create a mock [PasswordHistory] with a given [number].
|
||||
*/
|
||||
fun createMockSdkPasswordHistory(number: Int): PasswordHistory =
|
||||
fun createMockSdkPasswordHistory(number: Int, clock: Clock): PasswordHistory =
|
||||
PasswordHistory(
|
||||
password = "mockPassword-$number",
|
||||
lastUsedDate = ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant(),
|
||||
lastUsedDate = clock.instant(),
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -137,17 +141,15 @@ fun createMockSdkAttachment(number: Int): Attachment =
|
|||
/**
|
||||
* Create a mock [Login] with a given [number].
|
||||
*/
|
||||
fun createMockSdkLogin(number: Int): Login =
|
||||
fun createMockSdkLogin(number: Int, clock: Clock): Login =
|
||||
Login(
|
||||
username = "mockUsername-$number",
|
||||
password = "mockPassword-$number",
|
||||
passwordRevisionDate = ZonedDateTime
|
||||
.parse("2023-10-27T12:00:00Z")
|
||||
.toInstant(),
|
||||
passwordRevisionDate = clock.instant(),
|
||||
autofillOnPageLoad = false,
|
||||
uris = listOf(createMockSdkUri(number = number)),
|
||||
totp = "mockTotp-$number",
|
||||
fido2Credentials = createMockSdkFido2CredentialList(number),
|
||||
fido2Credentials = createMockSdkFido2CredentialList(number, clock),
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
@ -62,7 +62,7 @@ class TotpCodeManagerTest {
|
|||
)
|
||||
|
||||
val cipherView = createMockCipherView(1).copy(
|
||||
login = createMockLoginView(1).copy(
|
||||
login = createMockLoginView(number = 1, clock = clock).copy(
|
||||
totp = null,
|
||||
),
|
||||
)
|
||||
|
@ -82,7 +82,7 @@ class TotpCodeManagerTest {
|
|||
)
|
||||
|
||||
val cipherView = createMockCipherView(1).copy(
|
||||
login = createMockLoginView(1).copy(
|
||||
login = createMockLoginView(number = 1, clock = clock).copy(
|
||||
totp = null,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -280,7 +280,7 @@ class VaultRepositoryTest {
|
|||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(
|
||||
userId = userId,
|
||||
cipherList = listOf(createMockSdkCipher(1)),
|
||||
cipherList = listOf(createMockSdkCipher(1, clock)),
|
||||
)
|
||||
} returns listOf(createMockCipherView(number = 1)).asSuccess()
|
||||
coEvery {
|
||||
|
@ -1834,7 +1834,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.createCipher(
|
||||
body = createMockCipherJsonRequest(number = 1, hasNullUri = true),
|
||||
|
@ -1861,7 +1861,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
val mockCipher = createMockCipher(number = 1)
|
||||
coEvery {
|
||||
ciphersService.createCipher(
|
||||
|
@ -1931,7 +1931,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.createCipherInOrganization(
|
||||
body = CreateCipherInOrganizationJsonRequest(
|
||||
|
@ -1964,7 +1964,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
val mockCipher = createMockCipher(number = 1)
|
||||
coEvery {
|
||||
ciphersService.createCipherInOrganization(
|
||||
|
@ -2043,7 +2043,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.updateCipher(
|
||||
cipherId = cipherId,
|
||||
|
@ -2072,7 +2072,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.updateCipher(
|
||||
cipherId = cipherId,
|
||||
|
@ -2111,7 +2111,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = mockCipherView,
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
val mockCipher = createMockCipher(number = 1)
|
||||
coEvery {
|
||||
ciphersService.updateCipher(
|
||||
|
@ -2221,7 +2221,7 @@ class VaultRepositoryTest {
|
|||
runTest {
|
||||
mockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
every {
|
||||
createMockSdkCipher(number = 1).toEncryptedNetworkCipherResponse()
|
||||
createMockSdkCipher(number = 1, clock = clock).toEncryptedNetworkCipherResponse()
|
||||
} returns createMockCipher(number = 1)
|
||||
val fixedInstant = Instant.parse("2021-01-01T00:00:00Z")
|
||||
val userId = "mockId-1"
|
||||
|
@ -2234,7 +2234,7 @@ class VaultRepositoryTest {
|
|||
deletedDate = fixedInstant,
|
||||
),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery { ciphersService.softDeleteCipher(cipherId = cipherId) } returns Unit.asSuccess()
|
||||
coEvery {
|
||||
|
@ -2303,7 +2303,7 @@ class VaultRepositoryTest {
|
|||
runTest {
|
||||
mockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
every {
|
||||
createMockSdkCipher(number = 1).toEncryptedNetworkCipherResponse()
|
||||
createMockSdkCipher(number = 1, clock = clock).toEncryptedNetworkCipherResponse()
|
||||
} returns createMockCipher(number = 1)
|
||||
val fixedInstant = Instant.parse("2021-01-01T00:00:00Z")
|
||||
val userId = "mockId-1"
|
||||
|
@ -2316,7 +2316,7 @@ class VaultRepositoryTest {
|
|||
attachments = emptyList(),
|
||||
),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery {
|
||||
ciphersService.deleteCipherAttachment(
|
||||
|
@ -2385,7 +2385,7 @@ class VaultRepositoryTest {
|
|||
runTest {
|
||||
mockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
every {
|
||||
createMockSdkCipher(number = 1).toEncryptedNetworkCipherResponse()
|
||||
createMockSdkCipher(number = 1, clock = clock).toEncryptedNetworkCipherResponse()
|
||||
} returns createMockCipher(number = 1)
|
||||
val fixedInstant = Instant.parse("2021-01-01T00:00:00Z")
|
||||
val userId = "mockId-1"
|
||||
|
@ -2398,7 +2398,7 @@ class VaultRepositoryTest {
|
|||
deletedDate = null,
|
||||
),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery { ciphersService.restoreCipher(cipherId = cipherId) } returns Unit.asSuccess()
|
||||
coEvery {
|
||||
|
@ -2957,7 +2957,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.shareCipher(
|
||||
cipherId = "mockId-1",
|
||||
|
@ -2998,7 +2998,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.shareCipher(
|
||||
cipherId = "mockId-1",
|
||||
|
@ -3085,7 +3085,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.updateCipherCollections(
|
||||
cipherId = "mockId-1",
|
||||
|
@ -3120,7 +3120,7 @@ class VaultRepositoryTest {
|
|||
userId = userId,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
} returns createMockSdkCipher(number = 1, clock = clock).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.updateCipherCollections(
|
||||
cipherId = "mockId-1",
|
||||
|
@ -3233,7 +3233,7 @@ class VaultRepositoryTest {
|
|||
val cipherId = "cipherId-1"
|
||||
val mockUri = setupMockUri(url = "www.test.com")
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
|
||||
val mockFileName = "mockFileName-1"
|
||||
val mockFileSize = "1"
|
||||
val mockAttachmentView = createMockAttachmentView(number = 1).copy(
|
||||
|
@ -3278,7 +3278,7 @@ class VaultRepositoryTest {
|
|||
val cipherId = "cipherId-1"
|
||||
val mockUri = setupMockUri(url = "www.test.com")
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
|
||||
val mockFileName = "mockFileName-1"
|
||||
val mockFileSize = "1"
|
||||
coEvery {
|
||||
|
@ -3308,7 +3308,7 @@ class VaultRepositoryTest {
|
|||
val cipherId = "cipherId-1"
|
||||
val mockUri = setupMockUri(url = "www.test.com")
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
|
||||
val mockFileName = "mockFileName-1"
|
||||
val mockFileSize = "1"
|
||||
val mockAttachmentView = createMockAttachmentView(number = 1).copy(
|
||||
|
@ -3364,7 +3364,7 @@ class VaultRepositoryTest {
|
|||
val cipherId = "cipherId-1"
|
||||
val mockUri = setupMockUri(url = "www.test.com")
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
|
||||
val mockFileName = "mockFileName-1"
|
||||
val mockFileSize = "1"
|
||||
val mockAttachmentView = createMockAttachmentView(number = 1).copy(
|
||||
|
@ -3427,7 +3427,7 @@ class VaultRepositoryTest {
|
|||
val cipherId = "cipherId-1"
|
||||
val mockUri = setupMockUri(url = "www.test.com")
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
|
||||
val mockFileName = "mockFileName-1"
|
||||
val mockFileSize = "1"
|
||||
val mockAttachmentView = createMockAttachmentView(number = 1).copy(
|
||||
|
@ -3503,7 +3503,7 @@ class VaultRepositoryTest {
|
|||
val cipherId = "cipherId-1"
|
||||
val mockUri = setupMockUri(url = "www.test.com")
|
||||
val mockCipherView = createMockCipherView(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1)
|
||||
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
|
||||
val mockFileName = "mockFileName-1"
|
||||
val mockFileSize = "1"
|
||||
val mockAttachmentView = createMockAttachmentView(number = 1).copy(
|
||||
|
@ -4456,7 +4456,7 @@ class VaultRepositoryTest {
|
|||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherList = listOf(createMockSdkCipher(number = number)),
|
||||
cipherList = listOf(createMockSdkCipher(number = number, clock = clock)),
|
||||
)
|
||||
} returns listOf(cipherView).asSuccess()
|
||||
|
||||
|
@ -4499,7 +4499,7 @@ class VaultRepositoryTest {
|
|||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherList = listOf(createMockSdkCipher(number = number)),
|
||||
cipherList = listOf(createMockSdkCipher(number = number, clock = clock)),
|
||||
)
|
||||
} returns listOf(cipherView).asSuccess()
|
||||
val collectionView = createMockCollectionView(number = number)
|
||||
|
@ -4678,7 +4678,7 @@ class VaultRepositoryTest {
|
|||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherList = listOf(createMockSdkCipher(number = number)),
|
||||
cipherList = listOf(createMockSdkCipher(number = number, clock = clock)),
|
||||
)
|
||||
} returns listOf(cipherView).asSuccess()
|
||||
|
||||
|
@ -4734,7 +4734,7 @@ class VaultRepositoryTest {
|
|||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherList = listOf(createMockSdkCipher(number = number)),
|
||||
cipherList = listOf(createMockSdkCipher(number = number, clock = clock)),
|
||||
)
|
||||
} returns listOf(cipherView).asSuccess()
|
||||
|
||||
|
@ -4888,7 +4888,7 @@ class VaultRepositoryTest {
|
|||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherList = listOf(createMockSdkCipher(number = number)),
|
||||
cipherList = listOf(createMockSdkCipher(number = number, clock = clock)),
|
||||
)
|
||||
} returns listOf(cipherView).asSuccess()
|
||||
|
||||
|
@ -5734,7 +5734,7 @@ class VaultRepositoryTest {
|
|||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(
|
||||
userId = userId,
|
||||
cipherList = listOf(createMockSdkCipher(1)),
|
||||
cipherList = listOf(createMockSdkCipher(1, clock)),
|
||||
)
|
||||
} returns listOf(createMockCipherView(1)).asSuccess()
|
||||
coEvery {
|
||||
|
|
|
@ -30,12 +30,25 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSecureNo
|
|||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkUri
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Default date time used for [ZonedDateTime] properties of mock objects.
|
||||
*/
|
||||
private const val DEFAULT_TIMESTAMP = "2023-10-27T12:00:00Z"
|
||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||
Instant.parse(DEFAULT_TIMESTAMP),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
class VaultSdkCipherExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toEncryptedNetworkCipherResponse should convert an Sdk Cipher to a cipher`() {
|
||||
val sdkCipher = createMockSdkCipher(number = 1)
|
||||
val sdkCipher = createMockSdkCipher(number = 1, clock = FIXED_CLOCK)
|
||||
|
||||
val result = sdkCipher.toEncryptedNetworkCipherResponse()
|
||||
|
||||
|
@ -50,7 +63,7 @@ class VaultSdkCipherExtensionsTest {
|
|||
|
||||
@Test
|
||||
fun `toEncryptedNetworkCipher should convert an Sdk Cipher to a Network Cipher`() {
|
||||
val sdkCipher = createMockSdkCipher(number = 1)
|
||||
val sdkCipher = createMockSdkCipher(number = 1, clock = FIXED_CLOCK)
|
||||
val syncCipher = sdkCipher.toEncryptedNetworkCipher()
|
||||
assertEquals(
|
||||
createMockCipherJsonRequest(
|
||||
|
@ -70,8 +83,8 @@ class VaultSdkCipherExtensionsTest {
|
|||
val sdkCiphers = syncCiphers.toEncryptedSdkCipherList()
|
||||
assertEquals(
|
||||
listOf(
|
||||
createMockSdkCipher(number = 1),
|
||||
createMockSdkCipher(number = 2),
|
||||
createMockSdkCipher(number = 1, clock = FIXED_CLOCK),
|
||||
createMockSdkCipher(number = 2, clock = FIXED_CLOCK),
|
||||
),
|
||||
sdkCiphers,
|
||||
)
|
||||
|
@ -82,7 +95,7 @@ class VaultSdkCipherExtensionsTest {
|
|||
val syncCipher = createMockCipher(number = 1)
|
||||
val sdkCipher = syncCipher.toEncryptedSdkCipher()
|
||||
assertEquals(
|
||||
createMockSdkCipher(number = 1),
|
||||
createMockSdkCipher(number = 1, clock = FIXED_CLOCK),
|
||||
sdkCipher,
|
||||
)
|
||||
}
|
||||
|
@ -92,7 +105,7 @@ class VaultSdkCipherExtensionsTest {
|
|||
val syncLogin = createMockLogin(number = 1)
|
||||
val sdkLogin = syncLogin.toSdkLogin()
|
||||
assertEquals(
|
||||
createMockSdkLogin(number = 1),
|
||||
createMockSdkLogin(number = 1, clock = FIXED_CLOCK),
|
||||
sdkLogin,
|
||||
)
|
||||
}
|
||||
|
@ -215,8 +228,8 @@ class VaultSdkCipherExtensionsTest {
|
|||
val sdkPasswordHistories = syncPasswordHistories.toSdkPasswordHistoryList()
|
||||
assertEquals(
|
||||
listOf(
|
||||
createMockSdkPasswordHistory(number = 1),
|
||||
createMockSdkPasswordHistory(number = 2),
|
||||
createMockSdkPasswordHistory(number = 1, FIXED_CLOCK),
|
||||
createMockSdkPasswordHistory(number = 2, FIXED_CLOCK),
|
||||
),
|
||||
sdkPasswordHistories,
|
||||
)
|
||||
|
@ -227,7 +240,7 @@ class VaultSdkCipherExtensionsTest {
|
|||
val syncPasswordHistory = createMockPasswordHistory(number = 1)
|
||||
val sdkPasswordHistory = syncPasswordHistory.toSdkPasswordHistory()
|
||||
assertEquals(
|
||||
createMockSdkPasswordHistory(number = 1),
|
||||
createMockSdkPasswordHistory(number = 1, clock = FIXED_CLOCK),
|
||||
sdkPasswordHistory,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -210,7 +210,7 @@ class SearchViewModelTest : BaseViewModelTest() {
|
|||
val cipherId = CIPHER_ID
|
||||
val errorMessage = "Server error"
|
||||
val updatedCipherView = cipherView.copy(
|
||||
login = createMockLoginView(1).copy(
|
||||
login = createMockLoginView(number = 1, clock = clock).copy(
|
||||
uris = listOf(createMockUriView(number = 1)) +
|
||||
LoginUriView(
|
||||
uri = AUTOFILL_URI,
|
||||
|
@ -261,7 +261,7 @@ class SearchViewModelTest : BaseViewModelTest() {
|
|||
val cipherView = setupForAutofill()
|
||||
val cipherId = CIPHER_ID
|
||||
val updatedCipherView = cipherView.copy(
|
||||
login = createMockLoginView(1).copy(
|
||||
login = createMockLoginView(number = 1, clock = clock).copy(
|
||||
uris = listOf(createMockUriView(number = 1)) +
|
||||
LoginUriView(
|
||||
uri = AUTOFILL_URI,
|
||||
|
@ -420,7 +420,7 @@ class SearchViewModelTest : BaseViewModelTest() {
|
|||
val cipherId = CIPHER_ID
|
||||
val password = "password"
|
||||
val updatedCipherView = cipherView.copy(
|
||||
login = createMockLoginView(1).copy(
|
||||
login = createMockLoginView(number = 1, clock = clock).copy(
|
||||
uris = listOf(createMockUriView(number = 1)) +
|
||||
LoginUriView(
|
||||
uri = AUTOFILL_URI,
|
||||
|
|
|
@ -37,6 +37,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
|
|||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||
|
@ -70,11 +71,18 @@ import org.junit.jupiter.api.Assertions.assertNull
|
|||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val fixedClock: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
every { initialAutofillDialogShown = any() } just runs
|
||||
every { initialAutofillDialogShown } returns true
|
||||
|
@ -476,6 +484,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
uri = listOf(UriItem("testId", "www.mockuri1.com", UriMatchType.HOST)),
|
||||
totpCode = "mockTotp-1",
|
||||
canViewPassword = true,
|
||||
fido2CredentialCreationDateTime = R.string.created_xy.asText(
|
||||
"10/27/23",
|
||||
"12:00 PM",
|
||||
),
|
||||
)
|
||||
.copy(totp = "mockTotp-1"),
|
||||
),
|
||||
|
@ -752,6 +764,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
|
@ -781,6 +794,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
)
|
||||
vaultRepository.updateCipher(DEFAULT_EDIT_ITEM_ID, any())
|
||||
}
|
||||
|
@ -813,6 +827,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
coEvery {
|
||||
|
@ -873,6 +888,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
coEvery {
|
||||
|
@ -1857,6 +1873,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
authRepository = authRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
clock = fixedClock,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2369,12 +2386,14 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
availableOwners = availableOwners,
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun createLoginTypeContentViewState(
|
||||
username: String = "",
|
||||
password: String = "",
|
||||
uri: List<UriItem> = listOf(UriItem("testId", "", null)),
|
||||
totpCode: String? = null,
|
||||
canViewPassword: Boolean = true,
|
||||
fido2CredentialCreationDateTime: Text? = null,
|
||||
): VaultAddEditState.ViewState.Content.ItemType.Login =
|
||||
VaultAddEditState.ViewState.Content.ItemType.Login(
|
||||
username = username,
|
||||
|
@ -2382,6 +2401,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
uriList = uri,
|
||||
totp = totpCode,
|
||||
canViewPassword = canViewPassword,
|
||||
fido2CredentialCreationDateTime = fido2CredentialCreationDateTime,
|
||||
)
|
||||
|
||||
private fun createSavedStateHandleWithState(
|
||||
|
@ -2400,12 +2420,14 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
set("vault_edit_id", (vaultAddEditType as? VaultAddEditType.EditItem)?.vaultItemId)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun createAddVaultItemViewModel(
|
||||
savedStateHandle: SavedStateHandle = loginInitialSavedStateHandle,
|
||||
bitwardenClipboardManager: BitwardenClipboardManager = clipboardManager,
|
||||
vaultRepo: VaultRepository = vaultRepository,
|
||||
generatorRepo: GeneratorRepository = generatorRepository,
|
||||
bitwardenResourceManager: ResourceManager = resourceManager,
|
||||
clock: Clock = fixedClock,
|
||||
): VaultAddEditViewModel =
|
||||
VaultAddEditViewModel(
|
||||
savedStateHandle = savedStateHandle,
|
||||
|
@ -2417,6 +2439,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = bitwardenResourceManager,
|
||||
authRepository = authRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
clock = clock,
|
||||
)
|
||||
|
||||
private fun createVaultData(
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.bitwarden.core.CardView
|
|||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.Fido2Credential
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.bitwarden.core.IdentityView
|
||||
|
@ -20,7 +21,6 @@ import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
|||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
|
@ -37,7 +37,9 @@ import org.junit.jupiter.api.AfterEach
|
|||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
class CipherViewExtensionsTest {
|
||||
|
@ -66,6 +68,7 @@ class CipherViewExtensionsTest {
|
|||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -110,6 +113,7 @@ class CipherViewExtensionsTest {
|
|||
isClone = false,
|
||||
isIndividualVaultDisabled = true,
|
||||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -159,6 +163,7 @@ class CipherViewExtensionsTest {
|
|||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -189,6 +194,10 @@ class CipherViewExtensionsTest {
|
|||
uriList = listOf(UriItem(TEST_ID, "www.example.com", null)),
|
||||
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
|
||||
canViewPassword = false,
|
||||
fido2CredentialCreationDateTime = R.string.created_xy.asText(
|
||||
"10/27/23",
|
||||
"12:00 PM",
|
||||
),
|
||||
),
|
||||
),
|
||||
result,
|
||||
|
@ -203,6 +212,7 @@ class CipherViewExtensionsTest {
|
|||
isClone = false,
|
||||
isIndividualVaultDisabled = true,
|
||||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -236,6 +246,7 @@ class CipherViewExtensionsTest {
|
|||
isClone = true,
|
||||
isIndividualVaultDisabled = false,
|
||||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -423,6 +434,11 @@ class CipherViewExtensionsTest {
|
|||
)
|
||||
}
|
||||
|
||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
|
||||
id = "id1234",
|
||||
organizationId = null,
|
||||
|
@ -472,12 +488,12 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
|
|||
passwordHistory = listOf(
|
||||
PasswordHistoryView(
|
||||
password = "old_password",
|
||||
lastUsedDate = Instant.ofEpochSecond(1_000L),
|
||||
lastUsedDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
),
|
||||
creationDate = Instant.ofEpochSecond(1_000L),
|
||||
creationDate = FIXED_CLOCK.instant(),
|
||||
deletedDate = null,
|
||||
revisionDate = Instant.ofEpochSecond(1_000L),
|
||||
revisionDate = FIXED_CLOCK.instant(),
|
||||
)
|
||||
|
||||
private val DEFAULT_CARD_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.copy(
|
||||
|
@ -521,7 +537,7 @@ private val DEFAULT_LOGIN_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.cop
|
|||
login = LoginView(
|
||||
username = "username",
|
||||
password = "password",
|
||||
passwordRevisionDate = Instant.ofEpochSecond(1_000L),
|
||||
passwordRevisionDate = FIXED_CLOCK.instant(),
|
||||
uris = listOf(
|
||||
LoginUriView(
|
||||
uri = "www.example.com",
|
||||
|
@ -530,7 +546,23 @@ private val DEFAULT_LOGIN_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.cop
|
|||
),
|
||||
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
|
||||
autofillOnPageLoad = false,
|
||||
fido2Credentials = createMockSdkFido2CredentialList(number = 1),
|
||||
fido2Credentials = listOf(
|
||||
Fido2Credential(
|
||||
credentialId = "mockCredentialId",
|
||||
keyType = "mockKeyType",
|
||||
keyAlgorithm = "mockKeyAlgorithm",
|
||||
keyCurve = "mockKeyCurve",
|
||||
keyValue = "mockKeyValue",
|
||||
rpId = "mockRpId",
|
||||
userHandle = "mockUserHandle",
|
||||
userName = "mockUserName",
|
||||
counter = "mockCounter",
|
||||
rpName = "mockRpName",
|
||||
userDisplayName = "mockUserDisplayName",
|
||||
discoverable = "mockDiscoverable",
|
||||
creationDate = FIXED_CLOCK.instant(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.test.onNodeWithContentDescription
|
|||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.onSiblings
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.core.net.toUri
|
||||
import com.x8bit.bitwarden.R
|
||||
|
@ -1214,6 +1215,7 @@ class VaultItemScreenTest : BaseComposeTest() {
|
|||
passwordRevisionDate = null,
|
||||
isPremiumUser = true,
|
||||
totpCodeItemData = null,
|
||||
fido2CredentialCreationDateText = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -1301,6 +1303,53 @@ class VaultItemScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in login state, the Passkey field should exist based on the state`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
viewState = EMPTY_LOGIN_VIEW_STATE.copy(
|
||||
type = EMPTY_LOGIN_TYPE.copy(
|
||||
fido2CredentialCreationDateText = DEFAULT_PASSKEY,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNode(isProgressBar)
|
||||
.assertDoesNotExist()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Passkey")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in login state, the Passkey field should not exist based on state`() {
|
||||
mutableStateFlow.update { it }
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Passkey")
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in login state, the Passkey field text should display creation date`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
viewState = EMPTY_LOGIN_VIEW_STATE.copy(
|
||||
type = EMPTY_LOGIN_TYPE.copy(
|
||||
fido2CredentialCreationDateText = DEFAULT_PASSKEY,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = DEFAULT_PASSKEY.toString(), substring = true)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in login state, the TOTP field should exist based on the state`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
|
@ -1338,6 +1387,7 @@ class VaultItemScreenTest : BaseComposeTest() {
|
|||
|
||||
composeTestRule
|
||||
.onNode(isProgressBar)
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
|
@ -1355,6 +1405,7 @@ class VaultItemScreenTest : BaseComposeTest() {
|
|||
|
||||
composeTestRule
|
||||
.onNode(isProgressBar)
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
|
@ -2034,8 +2085,8 @@ private val DEFAULT_STATE: VaultItemState = VaultItemState(
|
|||
|
||||
private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
|
||||
VaultItemState.ViewState.Content.Common(
|
||||
lastUpdated = "12/31/69 06:16 PM",
|
||||
name = "cipher",
|
||||
lastUpdated = "12/31/69 06:16 PM",
|
||||
notes = "Lots of notes",
|
||||
customFields = listOf(
|
||||
VaultItemState.ViewState.Content.Common.Custom.TextField(
|
||||
|
@ -2055,6 +2106,7 @@ private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
|
|||
),
|
||||
),
|
||||
requiresReprompt = true,
|
||||
requiresCloneConfirmation = false,
|
||||
attachments = listOf(
|
||||
VaultItemState.ViewState.Content.Common.AttachmentItem(
|
||||
id = "attachment-id",
|
||||
|
@ -2067,6 +2119,11 @@ private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
|
|||
),
|
||||
)
|
||||
|
||||
private val DEFAULT_PASSKEY = R.string.created_xy.asText(
|
||||
"3/13/24",
|
||||
"3:56 PM",
|
||||
)
|
||||
|
||||
private val DEFAULT_LOGIN: VaultItemState.ViewState.Content.ItemType.Login =
|
||||
VaultItemState.ViewState.Content.ItemType.Login(
|
||||
passwordHistoryCount = 1,
|
||||
|
@ -2091,6 +2148,7 @@ private val DEFAULT_LOGIN: VaultItemState.ViewState.Content.ItemType.Login =
|
|||
verificationCode = "123456",
|
||||
totpCode = "testCode",
|
||||
),
|
||||
fido2CredentialCreationDateText = null,
|
||||
)
|
||||
|
||||
private val DEFAULT_IDENTITY: VaultItemState.ViewState.Content.ItemType.Identity =
|
||||
|
@ -2122,6 +2180,7 @@ private val EMPTY_COMMON: VaultItemState.ViewState.Content.Common =
|
|||
notes = null,
|
||||
customFields = emptyList(),
|
||||
requiresReprompt = true,
|
||||
requiresCloneConfirmation = false,
|
||||
attachments = emptyList(),
|
||||
)
|
||||
|
||||
|
@ -2134,6 +2193,7 @@ private val EMPTY_LOGIN_TYPE: VaultItemState.ViewState.Content.ItemType.Login =
|
|||
passwordRevisionDate = null,
|
||||
totpCodeItemData = null,
|
||||
isPremiumUser = true,
|
||||
fido2CredentialCreationDateText = null,
|
||||
)
|
||||
|
||||
private val EMPTY_IDENTITY_TYPE: VaultItemState.ViewState.Content.ItemType.Identity =
|
||||
|
|
|
@ -932,6 +932,103 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on CloneClick should show confirmation when cipher contains a passkey`() = runTest {
|
||||
val loginViewState = DEFAULT_VIEW_STATE.copy(
|
||||
common = DEFAULT_COMMON.copy(
|
||||
requiresReprompt = false,
|
||||
requiresCloneConfirmation = true,
|
||||
),
|
||||
)
|
||||
val loginState = DEFAULT_STATE.copy(viewState = loginViewState)
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every {
|
||||
toViewState(
|
||||
isPremiumUser = true,
|
||||
totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
|
||||
|
||||
assertEquals(loginState, viewModel.stateFlow.value)
|
||||
|
||||
viewModel.trySendAction(VaultItemAction.Common.CloneClick)
|
||||
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Fido2CredentialCannotBeCopiedConfirmationPrompt(
|
||||
R.string.the_passkey_will_not_be_copied_to_the_cloned_item_do_you_want_to_continue_cloning_this_item.asText(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
|
||||
verify(exactly = 1) {
|
||||
mockCipherView.toViewState(
|
||||
isPremiumUser = true,
|
||||
totpCodeItemData = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on CloneClick should show confirmation before re-prompt when both are required`() {
|
||||
val loginViewState = DEFAULT_VIEW_STATE.copy(
|
||||
common = DEFAULT_COMMON.copy(
|
||||
requiresReprompt = true,
|
||||
requiresCloneConfirmation = true,
|
||||
),
|
||||
)
|
||||
val loginState = DEFAULT_STATE.copy(
|
||||
viewState = loginViewState,
|
||||
)
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every {
|
||||
toViewState(
|
||||
isPremiumUser = true,
|
||||
totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
|
||||
|
||||
assertEquals(loginState, viewModel.stateFlow.value)
|
||||
viewModel.trySendAction(VaultItemAction.Common.CloneClick)
|
||||
|
||||
// Assert clone confirmation dialog is triggered
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.Fido2CredentialCannotBeCopiedConfirmationPrompt(
|
||||
R.string.the_passkey_will_not_be_copied_to_the_cloned_item_do_you_want_to_continue_cloning_this_item.asText(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
|
||||
// Simulate confirmation click.
|
||||
viewModel.trySendAction(VaultItemAction.Common.ConfirmCloneWithoutFido2CredentialClick)
|
||||
|
||||
// Assert MP dialog is triggered.
|
||||
assertEquals(
|
||||
loginState.copy(
|
||||
dialog = VaultItemState.DialogState.MasterPasswordDialog(
|
||||
action = PasswordRepromptAction.CloneClick,
|
||||
),
|
||||
viewState = loginViewState.copy(
|
||||
common = loginViewState.common.copy(
|
||||
requiresCloneConfirmation = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on CloneClick should show password dialog when re-prompt is required`() = runTest {
|
||||
val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
|
||||
|
@ -1946,6 +2043,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
timeLeftSeconds = 15,
|
||||
periodSeconds = 30,
|
||||
),
|
||||
fido2CredentialCreationDateText = null,
|
||||
)
|
||||
|
||||
private val DEFAULT_CARD_TYPE: VaultItemState.ViewState.Content.ItemType.Card =
|
||||
|
@ -1988,6 +2086,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
),
|
||||
requiresReprompt = true,
|
||||
requiresCloneConfirmation = false,
|
||||
currentCipher = createMockCipherView(number = 1),
|
||||
attachments = listOf(
|
||||
VaultItemState.ViewState.Content.Common.AttachmentItem(
|
||||
|
|
|
@ -4,13 +4,15 @@ import com.bitwarden.core.AttachmentView
|
|||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.Fido2Credential
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.bitwarden.core.IdentityView
|
||||
import com.bitwarden.core.LoginUriView
|
||||
import com.bitwarden.core.LoginView
|
||||
import com.bitwarden.core.PasswordHistoryView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.model.TotpCodeItemData
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
|
@ -43,7 +45,23 @@ fun createLoginView(isEmpty: Boolean): LoginView =
|
|||
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
|
||||
.takeUnless { isEmpty },
|
||||
autofillOnPageLoad = false,
|
||||
fido2Credentials = createMockSdkFido2CredentialList(number = 1),
|
||||
fido2Credentials = listOf(
|
||||
Fido2Credential(
|
||||
credentialId = "mockCredentialId",
|
||||
keyType = "mockKeyType",
|
||||
keyAlgorithm = "mockKeyAlgorithm",
|
||||
keyCurve = "mockKeyCurve",
|
||||
keyValue = "mockKeyValue",
|
||||
rpId = "mockRpId",
|
||||
userHandle = "mockUserHandle",
|
||||
userName = "mockUserName",
|
||||
counter = "mockCounter",
|
||||
rpName = "mockRpName",
|
||||
userDisplayName = "mockUserDisplayName",
|
||||
discoverable = "mockDiscoverable",
|
||||
creationDate = Instant.ofEpochSecond(1_000L),
|
||||
),
|
||||
).takeUnless { isEmpty },
|
||||
)
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
|
@ -156,6 +174,7 @@ fun createCommonContent(
|
|||
notes = null,
|
||||
customFields = emptyList(),
|
||||
requiresReprompt = true,
|
||||
requiresCloneConfirmation = false,
|
||||
attachments = emptyList(),
|
||||
)
|
||||
} else {
|
||||
|
@ -189,6 +208,7 @@ fun createCommonContent(
|
|||
),
|
||||
),
|
||||
requiresReprompt = true,
|
||||
requiresCloneConfirmation = true,
|
||||
attachments = listOf(
|
||||
VaultItemState.ViewState.Content.Common.AttachmentItem(
|
||||
id = "attachment-id",
|
||||
|
@ -232,6 +252,11 @@ fun createLoginContent(isEmpty: Boolean): VaultItemState.ViewState.Content.ItemT
|
|||
totpCode = "testCode",
|
||||
)
|
||||
.takeUnless { isEmpty },
|
||||
fido2CredentialCreationDateText = R.string.created_xy.asText(
|
||||
"1/1/70",
|
||||
"12:16 AM",
|
||||
)
|
||||
.takeUnless { isEmpty },
|
||||
)
|
||||
|
||||
fun createIdentityContent(
|
||||
|
|
|
@ -57,6 +57,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
password = "mockPassword-1",
|
||||
uriList = listOf(UriItem("testId", "mockUri-1", UriMatchType.DOMAIN)),
|
||||
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
|
||||
fido2CredentialCreationDateTime = null,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue