mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
[PM-13360] Respect manage permission to assign collections
This commit prevents users from assigning items to collections if the item is already in a read-only collection where the user does not have "manage" permission. This change ensures that users with limited permissions cannot modify items in a way that violates the collection's access controls.
This commit is contained in:
parent
98506076d3
commit
76954d456e
15 changed files with 564 additions and 44 deletions
|
@ -235,7 +235,7 @@ fun VaultAddEditScreen(
|
|||
},
|
||||
)
|
||||
|
||||
if (pendingDeleteCipher) {
|
||||
if (pendingDeleteCipher && state.canDelete) {
|
||||
BitwardenTwoButtonDialog(
|
||||
title = stringResource(id = R.string.delete),
|
||||
message = stringResource(id = R.string.do_you_really_want_to_soft_delete_cipher),
|
||||
|
@ -309,7 +309,11 @@ fun VaultAddEditScreen(
|
|||
}
|
||||
},
|
||||
)
|
||||
.takeUnless { state.isAddItemMode || !state.isCipherInCollection },
|
||||
.takeUnless {
|
||||
state.isAddItemMode ||
|
||||
(!state.isCipherInCollection ||
|
||||
!state.canAssociateToCollections)
|
||||
},
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.delete),
|
||||
onClick = { pendingDeleteCipher = true },
|
||||
|
|
|
@ -1621,16 +1621,29 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
currentAccount = userData?.activeAccount,
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
) { currentAccount, cipherView ->
|
||||
|
||||
// Deletion is not allowed when the item is in a collection that the user
|
||||
// does not have "manage" permission for.
|
||||
val canDelete = vaultData.collectionViewList
|
||||
.none {
|
||||
val itemIsInCollection = cipherView
|
||||
val isItemInCollection = cipherView
|
||||
?.collectionIds
|
||||
?.contains(it.id) == true
|
||||
|
||||
val canManageCollection = it.manage
|
||||
|
||||
itemIsInCollection && !canManageCollection
|
||||
isItemInCollection && !it.manage
|
||||
}
|
||||
|
||||
// Assigning to a collection is not allowed when the item is in a collection
|
||||
// that the user does not have "manage" and "edit" permission for.
|
||||
val canAssignToCollections = vaultData.collectionViewList
|
||||
.none {
|
||||
val isItemInCollection = cipherView
|
||||
?.collectionIds
|
||||
?.contains(it.id) == true
|
||||
|
||||
isItemInCollection && (!it.manage || it.readOnly)
|
||||
}
|
||||
|
||||
// Derive the view state from the current Cipher for Edit mode
|
||||
// or use the current state for Add
|
||||
(cipherView
|
||||
|
@ -1641,6 +1654,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
resourceManager = resourceManager,
|
||||
clock = clock,
|
||||
canDelete = canDelete,
|
||||
canAssignToCollections = canAssignToCollections,
|
||||
)
|
||||
?: viewState)
|
||||
.appendFolderAndOwnerData(
|
||||
|
@ -2078,6 +2092,12 @@ data class VaultAddEditState(
|
|||
?.canDelete
|
||||
?: false
|
||||
|
||||
val canAssociateToCollections: Boolean
|
||||
get() = (viewState as? ViewState.Content)
|
||||
?.common
|
||||
?.canAssignToCollections
|
||||
?: false
|
||||
|
||||
/**
|
||||
* Enum representing the main type options for the vault, such as LOGIN, CARD, etc.
|
||||
*
|
||||
|
@ -2153,6 +2173,7 @@ data class VaultAddEditState(
|
|||
val selectedOwnerId: String? = null,
|
||||
val availableOwners: List<Owner> = emptyList(),
|
||||
val canDelete: Boolean = true,
|
||||
val canAssignToCollections: Boolean = true,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
|
|
|
@ -43,6 +43,7 @@ fun CipherView.toViewState(
|
|||
resourceManager: ResourceManager,
|
||||
clock: Clock,
|
||||
canDelete: Boolean,
|
||||
canAssignToCollections: Boolean,
|
||||
): VaultAddEditState.ViewState =
|
||||
VaultAddEditState.ViewState.Content(
|
||||
type = when (type) {
|
||||
|
@ -109,6 +110,7 @@ fun CipherView.toViewState(
|
|||
availableOwners = emptyList(),
|
||||
customFieldData = this.fields.orEmpty().map { it.toCustomField() },
|
||||
canDelete = canDelete,
|
||||
canAssignToCollections = canAssignToCollections,
|
||||
),
|
||||
isIndividualVaultDisabled = isIndividualVaultDisabled,
|
||||
)
|
||||
|
|
|
@ -163,7 +163,7 @@ fun VaultItemScreen(
|
|||
{ viewModel.trySendAction(VaultItemAction.Common.CloseClick) }
|
||||
},
|
||||
actions = {
|
||||
if (state.isCipherDeleted) {
|
||||
if (state.isCipherDeleted && state.canDelete) {
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.restore),
|
||||
onClick = remember(viewModel) {
|
||||
|
@ -216,7 +216,10 @@ fun VaultItemScreen(
|
|||
}
|
||||
},
|
||||
)
|
||||
.takeIf { state.isCipherInCollection },
|
||||
.takeIf {
|
||||
state.isCipherInCollection &&
|
||||
state.canAssignToCollections
|
||||
},
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.delete),
|
||||
onClick = remember(viewModel) {
|
||||
|
|
|
@ -111,9 +111,19 @@ class VaultItemViewModel @Inject constructor(
|
|||
?.collectionIds
|
||||
?.contains(it.id) == true
|
||||
|
||||
val canManageCollection = it.manage
|
||||
itemIsInCollection && !it.manage
|
||||
}
|
||||
?: true
|
||||
|
||||
itemIsInCollection && !canManageCollection
|
||||
// Assigning to a collection is not allowed when the item is in a collection
|
||||
// that the user does not have "manage" and "edit" permission for.
|
||||
val canAssignToCollections = collectionsState.data
|
||||
?.none {
|
||||
val itemIsInCollection = cipherViewState.data
|
||||
?.collectionIds
|
||||
?.contains(it.id) == true
|
||||
|
||||
itemIsInCollection && !it.manage && it.readOnly
|
||||
}
|
||||
?: true
|
||||
|
||||
|
@ -121,6 +131,7 @@ class VaultItemViewModel @Inject constructor(
|
|||
cipher = cipherViewState.data,
|
||||
totpCodeItemData = totpCodeData,
|
||||
canDelete = canDelete,
|
||||
canAssociateToCollections = canAssignToCollections,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
@ -917,6 +928,7 @@ class VaultItemViewModel @Inject constructor(
|
|||
hasMasterPassword = account.hasMasterPassword,
|
||||
totpCodeItemData = this.data?.totpCodeItemData,
|
||||
canDelete = this.data?.canDelete == true,
|
||||
canAssignToCollections = this.data?.canAssociateToCollections == true,
|
||||
)
|
||||
?: VaultItemState.ViewState.Error(message = errorText)
|
||||
|
||||
|
@ -1163,6 +1175,12 @@ data class VaultItemState(
|
|||
?.common
|
||||
?.canDelete == true
|
||||
|
||||
val canAssignToCollections: Boolean
|
||||
get() = viewState.asContentOrNull()
|
||||
?.common
|
||||
?.canAssignToCollections
|
||||
?: false
|
||||
|
||||
/**
|
||||
* The text to display on the deletion confirmation dialog.
|
||||
*/
|
||||
|
@ -1214,6 +1232,10 @@ data class VaultItemState(
|
|||
* @property requiresCloneConfirmation Indicates user confirmation is required when
|
||||
* cloning a cipher.
|
||||
* @property currentCipher The cipher that is currently being viewed (nullable).
|
||||
* @property attachments A list of attachments associated with the cipher.
|
||||
* @property canDelete Indicates if the cipher can be deleted.
|
||||
* @property canAssignToCollections Indicates if the cipher can be assigned to
|
||||
* collections.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Common(
|
||||
|
@ -1227,6 +1249,7 @@ data class VaultItemState(
|
|||
val currentCipher: CipherView? = null,
|
||||
val attachments: List<AttachmentItem>?,
|
||||
val canDelete: Boolean,
|
||||
val canAssignToCollections: Boolean,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,9 +7,12 @@ import com.bitwarden.vault.CipherView
|
|||
*
|
||||
* @property cipher The cipher view for the item.
|
||||
* @property totpCodeItemData The data for the totp code.
|
||||
* @property canDelete Whether the item can be deleted.
|
||||
* @property canAssociateToCollections Whether the item can be associated to a collection.
|
||||
*/
|
||||
data class VaultItemStateData(
|
||||
val cipher: CipherView?,
|
||||
val totpCodeItemData: TotpCodeItemData?,
|
||||
val canDelete: Boolean,
|
||||
val canAssociateToCollections: Boolean,
|
||||
)
|
||||
|
|
|
@ -40,6 +40,7 @@ fun CipherView.toViewState(
|
|||
totpCodeItemData: TotpCodeItemData?,
|
||||
clock: Clock = Clock.systemDefaultZone(),
|
||||
canDelete: Boolean,
|
||||
canAssignToCollections: Boolean,
|
||||
): VaultItemState.ViewState =
|
||||
VaultItemState.ViewState.Content(
|
||||
common = VaultItemState.ViewState.Content.Common(
|
||||
|
@ -81,6 +82,7 @@ fun CipherView.toViewState(
|
|||
}
|
||||
.orEmpty(),
|
||||
canDelete = canDelete,
|
||||
canAssignToCollections = canAssignToCollections,
|
||||
),
|
||||
type = when (type) {
|
||||
CipherType.LOGIN -> {
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.bitwarden.vault.CollectionView
|
|||
fun createMockCollectionView(
|
||||
number: Int,
|
||||
name: String? = null,
|
||||
readOnly: Boolean = false,
|
||||
manage: Boolean = true,
|
||||
): CollectionView =
|
||||
CollectionView(
|
||||
|
@ -16,6 +17,6 @@ fun createMockCollectionView(
|
|||
hidePasswords = false,
|
||||
name = name ?: "mockName-$number",
|
||||
externalId = "mockExternalId-$number",
|
||||
readOnly = false,
|
||||
readOnly = readOnly,
|
||||
manage = manage,
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.test.assertIsOn
|
|||
import androidx.compose.ui.test.assertTextContains
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.click
|
||||
import androidx.compose.ui.test.filter
|
||||
import androidx.compose.ui.test.filterToOne
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.hasContentDescription
|
||||
|
@ -3023,6 +3024,57 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Menu Collections should display correctly according to state`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
vaultAddEditType = VaultAddEditType.EditItem(vaultItemId = "mockId-1"),
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = createMockCipherView(1),
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes,
|
||||
isIndividualVaultDisabled = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
// Confirm overflow is closed on initial load
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Collections")
|
||||
.filter(hasAnyAncestor(isPopup()))
|
||||
.assertCountEquals(0)
|
||||
|
||||
// Open the overflow menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.performClick()
|
||||
|
||||
// Confirm Collections option is present
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Collections")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
// Confirm Collections option is not present when canAssignToCollections is false
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
vaultAddEditType = VaultAddEditType.EditItem(vaultItemId = "mockId-1"),
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = createMockCipherView(1),
|
||||
canAssignToCollections = false,
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes,
|
||||
isIndividualVaultDisabled = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Collections")
|
||||
.filter(hasAnyAncestor(isPopup()))
|
||||
.assertCountEquals(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Menu should display correct items when cipher is not in a collection`() {
|
||||
mutableStateFlow.update {
|
||||
|
|
|
@ -1227,6 +1227,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = false,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
|
||||
|
@ -1257,6 +1258,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = false,
|
||||
canAssignToCollections = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1281,6 +1283,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
notes = "mockNotes-1",
|
||||
canDelete = true,
|
||||
canAssociateToCollections = true,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -1292,6 +1295,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
|
||||
|
@ -1302,6 +1306,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
createMockCollectionView(
|
||||
number = 1,
|
||||
manage = true,
|
||||
readOnly = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1322,6 +1327,214 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in edit mode, canAssociateToCollections should be false when cipher is in a collection the user cannot manage or edit`() =
|
||||
runTest {
|
||||
val cipherView = createMockCipherView(1)
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName-1",
|
||||
originalCipher = cipherView,
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.HiddenField(
|
||||
itemId = "testId",
|
||||
name = "mockName-1",
|
||||
value = "mockValue-1",
|
||||
),
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
canDelete = false,
|
||||
canAssociateToCollections = false,
|
||||
),
|
||||
)
|
||||
|
||||
every {
|
||||
cipherView.toViewState(
|
||||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
totpData = null,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = false,
|
||||
canAssignToCollections = false,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
data = createVaultData(
|
||||
cipherView = cipherView,
|
||||
collectionViewList = listOf(
|
||||
createMockCollectionView(
|
||||
number = 1,
|
||||
readOnly = true,
|
||||
manage = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = stateWithName,
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
),
|
||||
)
|
||||
|
||||
verify {
|
||||
cipherView.toViewState(
|
||||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
totpData = null,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = false,
|
||||
canAssignToCollections = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in edit mode, canAssociateToCollections should be false when cipher is in a collection the user cannot manage but can edit`() =
|
||||
runTest {
|
||||
val cipherView = createMockCipherView(1)
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName-1",
|
||||
originalCipher = cipherView,
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.HiddenField(
|
||||
itemId = "testId",
|
||||
name = "mockName-1",
|
||||
value = "mockValue-1",
|
||||
),
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
canDelete = false,
|
||||
canAssociateToCollections = false,
|
||||
),
|
||||
)
|
||||
|
||||
every {
|
||||
cipherView.toViewState(
|
||||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
totpData = null,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = false,
|
||||
canAssignToCollections = false,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
data = createVaultData(
|
||||
cipherView = cipherView,
|
||||
collectionViewList = listOf(
|
||||
createMockCollectionView(
|
||||
number = 1,
|
||||
manage = false,
|
||||
readOnly = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = stateWithName,
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
),
|
||||
)
|
||||
|
||||
verify {
|
||||
cipherView.toViewState(
|
||||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
totpData = null,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = false,
|
||||
canAssignToCollections = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in edit mode, canAssociateToCollections should be false when cipher is in a collection the user can manage but cannot edit`() =
|
||||
runTest {
|
||||
val cipherView = createMockCipherView(1)
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName-1",
|
||||
originalCipher = cipherView,
|
||||
customFieldData = listOf(
|
||||
VaultAddEditState.Custom.HiddenField(
|
||||
itemId = "testId",
|
||||
name = "mockName-1",
|
||||
value = "mockValue-1",
|
||||
),
|
||||
),
|
||||
notes = "mockNotes-1",
|
||||
canDelete = true,
|
||||
canAssociateToCollections = false,
|
||||
),
|
||||
)
|
||||
|
||||
every {
|
||||
cipherView.toViewState(
|
||||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
totpData = null,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = false,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
data = createVaultData(
|
||||
cipherView = cipherView,
|
||||
collectionViewList = listOf(
|
||||
createMockCollectionView(
|
||||
number = 1,
|
||||
manage = true,
|
||||
readOnly = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = stateWithName,
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
),
|
||||
)
|
||||
|
||||
verify {
|
||||
cipherView.toViewState(
|
||||
isClone = false,
|
||||
isIndividualVaultDisabled = false,
|
||||
totpData = null,
|
||||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1442,6 +1655,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
|
@ -1474,6 +1688,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
vaultRepository.updateCipher(DEFAULT_EDIT_ITEM_ID, any())
|
||||
}
|
||||
|
@ -1509,6 +1724,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
coEvery {
|
||||
|
@ -1572,6 +1788,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
coEvery {
|
||||
|
@ -1638,6 +1855,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
|
@ -1695,6 +1913,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
every { fido2CredentialManager.isUserVerified } returns true
|
||||
|
@ -1757,6 +1976,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
resourceManager = resourceManager,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns stateWithName.viewState
|
||||
every { fido2CredentialManager.isUserVerified } returns false
|
||||
|
@ -4165,6 +4385,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
availableOwners: List<VaultAddEditState.Owner> = createOwnerList(),
|
||||
selectedOwnerId: String? = null,
|
||||
canDelete: Boolean = true,
|
||||
canAssociateToCollections: Boolean = true,
|
||||
): VaultAddEditState.ViewState.Content.Common =
|
||||
VaultAddEditState.ViewState.Content.Common(
|
||||
name = name,
|
||||
|
@ -4178,6 +4399,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
availableFolders = availableFolders,
|
||||
availableOwners = availableOwners,
|
||||
canDelete = canDelete,
|
||||
canAssignToCollections = canAssociateToCollections,
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
|
@ -76,6 +76,7 @@ class CipherViewExtensionsTest {
|
|||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -123,6 +124,7 @@ class CipherViewExtensionsTest {
|
|||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -175,6 +177,7 @@ class CipherViewExtensionsTest {
|
|||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -236,6 +239,7 @@ class CipherViewExtensionsTest {
|
|||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -294,6 +298,7 @@ class CipherViewExtensionsTest {
|
|||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -330,6 +335,7 @@ class CipherViewExtensionsTest {
|
|||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -375,6 +381,7 @@ class CipherViewExtensionsTest {
|
|||
resourceManager = resourceManager,
|
||||
clock = FIXED_CLOCK,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
|
|
@ -1105,6 +1105,50 @@ class VaultItemScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `menu Collection option should be displayed based on state`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = DEFAULT_IDENTITY_VIEW_STATE
|
||||
.copy(
|
||||
common = DEFAULT_COMMON
|
||||
.copy(currentCipher = createMockCipherView(1)),
|
||||
),
|
||||
)
|
||||
}
|
||||
// Confirm overflow is closed on initial load
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Collections")
|
||||
.filter(hasAnyAncestor(isPopup()))
|
||||
.assertCountEquals(0)
|
||||
|
||||
// Open the overflow menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.performClick()
|
||||
|
||||
// Confirm Collections option is present
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Collections")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
// Confirm Collections option is not present when canDelete is false
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = DEFAULT_IDENTITY_VIEW_STATE
|
||||
.copy(
|
||||
common = DEFAULT_COMMON
|
||||
.copy(canAssignToCollections = false),
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Collections")
|
||||
.filter(hasAnyAncestor(isPopup()))
|
||||
.assertCountEquals(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Collections menu click should send CollectionsClick action`() {
|
||||
mutableStateFlow.update {
|
||||
|
@ -2387,6 +2431,7 @@ private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
|
|||
),
|
||||
),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
private val DEFAULT_PASSKEY = R.string.created_xy.asText(
|
||||
|
@ -2469,6 +2514,7 @@ private val EMPTY_COMMON: VaultItemState.ViewState.Content.Common =
|
|||
requiresCloneConfirmation = false,
|
||||
attachments = emptyList(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
private val EMPTY_LOGIN_TYPE: VaultItemState.ViewState.Content.ItemType.Login =
|
||||
|
|
|
@ -166,6 +166,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -179,6 +180,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -196,6 +198,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = false,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -216,6 +219,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = false,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -231,6 +235,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -251,6 +256,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -267,6 +273,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -292,6 +299,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -311,7 +319,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canDelete = true, canAssignToCollections = true,
|
||||
)
|
||||
} returns loginState
|
||||
}
|
||||
|
@ -357,6 +365,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns loginState
|
||||
}
|
||||
|
@ -391,6 +400,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -436,6 +446,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -484,6 +495,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -524,6 +536,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -559,6 +572,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns viewState
|
||||
}
|
||||
|
@ -598,6 +612,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -640,6 +655,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -691,6 +707,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -725,6 +742,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -743,6 +761,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
assertEquals(
|
||||
|
@ -769,7 +788,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -832,7 +851,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -887,7 +906,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -953,7 +972,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -978,7 +997,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -994,7 +1013,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns createViewState(common = DEFAULT_COMMON.copy(requiresReprompt = false))
|
||||
}
|
||||
|
@ -1013,7 +1032,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
organizationEventManager.trackEvent(
|
||||
event = OrganizationEvent.CipherClientCopiedHiddenField(
|
||||
|
@ -1053,7 +1072,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -1086,7 +1105,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1120,6 +1139,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -1152,7 +1172,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
organizationEventManager.trackEvent(
|
||||
event = OrganizationEvent.CipherClientToggledHiddenFieldVisible(
|
||||
|
@ -1173,7 +1193,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -1198,7 +1218,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1218,7 +1238,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -1254,7 +1274,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -1281,7 +1301,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1305,7 +1325,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -1355,7 +1375,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -1380,7 +1400,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1400,7 +1420,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -1433,7 +1453,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -1458,7 +1478,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1478,7 +1498,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -1497,6 +1517,74 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `canAssignToCollections should be true when item is in a collection the user can manage and edit`() {
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every {
|
||||
toViewState(
|
||||
previousState = null,
|
||||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
|
||||
mutableCollectionsStateFlow.value = DataState.Loaded(emptyList())
|
||||
verify {
|
||||
mockCipherView.toViewState(
|
||||
previousState = null,
|
||||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `canAssignToCollections should be false when item is not in a collection the user can manage and edit`() {
|
||||
val mockCipherView = mockk<CipherView> {
|
||||
every { collectionIds } returns listOf("mockId-1", "mockId-2")
|
||||
every {
|
||||
toViewState(
|
||||
previousState = null,
|
||||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = false,
|
||||
canAssignToCollections = false,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
|
||||
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
|
||||
mutableCollectionsStateFlow.value = DataState.Loaded(
|
||||
data = listOf(
|
||||
createMockCollectionView(number = 1)
|
||||
.copy(manage = false, readOnly = true),
|
||||
createMockCollectionView(number = 2)
|
||||
.copy(manage = true),
|
||||
),
|
||||
)
|
||||
verify {
|
||||
mockCipherView.toViewState(
|
||||
previousState = null,
|
||||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = false,
|
||||
canAssignToCollections = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on CollectionsClick should emit NavigateToCollections`() = runTest {
|
||||
val viewModel = createViewModel(state = DEFAULT_STATE)
|
||||
|
@ -1522,7 +1610,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -1580,7 +1668,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -1647,7 +1735,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = null,
|
||||
canAssignToCollections = true, totpCodeItemData = null,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -1827,7 +1915,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canAssignToCollections = true, totpCodeItemData = createTotpCodeData(),
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -1869,7 +1957,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canAssignToCollections = true, totpCodeItemData = createTotpCodeData(),
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 1) {
|
||||
|
@ -1888,7 +1976,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canAssignToCollections = true, totpCodeItemData = createTotpCodeData(),
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -1916,7 +2004,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canAssignToCollections = true, totpCodeItemData = createTotpCodeData(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1931,7 +2019,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canAssignToCollections = true, totpCodeItemData = createTotpCodeData(),
|
||||
)
|
||||
} returns createViewState(common = DEFAULT_COMMON.copy(requiresReprompt = false))
|
||||
}
|
||||
|
@ -1951,7 +2039,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canAssignToCollections = true, totpCodeItemData = createTotpCodeData(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1993,7 +2081,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
isPremiumUser = true,
|
||||
hasMasterPassword = true,
|
||||
canDelete = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canAssignToCollections = true, totpCodeItemData = createTotpCodeData(),
|
||||
)
|
||||
} returns createViewState(common = DEFAULT_COMMON.copy(requiresReprompt = false))
|
||||
}
|
||||
|
@ -2012,6 +2100,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2037,6 +2126,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -2063,6 +2153,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2079,6 +2170,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
.returns(
|
||||
|
@ -2107,6 +2199,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2124,6 +2217,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns DEFAULT_VIEW_STATE
|
||||
}
|
||||
|
@ -2154,6 +2248,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2174,6 +2269,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns loginViewState
|
||||
}
|
||||
|
@ -2207,6 +2303,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = createTotpCodeData(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
organizationEventManager.trackEvent(
|
||||
event = OrganizationEvent.CipherClientToggledPasswordVisible(
|
||||
|
@ -2242,6 +2339,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns CARD_VIEW_STATE
|
||||
}
|
||||
|
@ -2269,6 +2367,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2284,6 +2383,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns createViewState(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
|
@ -2305,6 +2405,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2321,6 +2422,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns CARD_VIEW_STATE
|
||||
}
|
||||
|
@ -2348,6 +2450,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2363,6 +2466,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns createViewState(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
|
@ -2390,6 +2494,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2406,6 +2511,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns CARD_VIEW_STATE
|
||||
}
|
||||
|
@ -2433,6 +2539,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2448,6 +2555,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns createViewState(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
|
@ -2469,6 +2577,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2485,6 +2594,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns CARD_VIEW_STATE
|
||||
}
|
||||
|
@ -2512,6 +2622,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2527,6 +2638,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns createViewState(
|
||||
common = DEFAULT_COMMON.copy(requiresReprompt = false),
|
||||
|
@ -2554,6 +2666,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2588,6 +2701,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns SSH_KEY_VIEW_STATE
|
||||
}
|
||||
|
@ -2646,6 +2760,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns viewState
|
||||
}
|
||||
|
@ -2685,6 +2800,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns viewState
|
||||
}
|
||||
|
@ -2725,6 +2841,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns viewState
|
||||
}
|
||||
|
@ -2762,6 +2879,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
hasMasterPassword = true,
|
||||
totpCodeItemData = null,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} returns viewState
|
||||
}
|
||||
|
@ -2977,6 +3095,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
private val DEFAULT_VIEW_STATE: VaultItemState.ViewState.Content =
|
||||
|
|
|
@ -46,6 +46,7 @@ class CipherViewExtensionsTest {
|
|||
),
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -82,6 +83,7 @@ class CipherViewExtensionsTest {
|
|||
),
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -111,6 +113,7 @@ class CipherViewExtensionsTest {
|
|||
),
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -140,6 +143,7 @@ class CipherViewExtensionsTest {
|
|||
),
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -175,6 +179,7 @@ class CipherViewExtensionsTest {
|
|||
),
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -200,6 +205,7 @@ class CipherViewExtensionsTest {
|
|||
totpCodeItemData = null,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -223,6 +229,7 @@ class CipherViewExtensionsTest {
|
|||
totpCodeItemData = null,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -245,6 +252,7 @@ class CipherViewExtensionsTest {
|
|||
totpCodeItemData = null,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -277,6 +285,7 @@ class CipherViewExtensionsTest {
|
|||
totpCodeItemData = null,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -314,6 +323,7 @@ class CipherViewExtensionsTest {
|
|||
totpCodeItemData = null,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -353,6 +363,7 @@ class CipherViewExtensionsTest {
|
|||
totpCodeItemData = null,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
|
@ -376,6 +387,7 @@ class CipherViewExtensionsTest {
|
|||
totpCodeItemData = null,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
|
||||
val expectedState = VaultItemState.ViewState.Content(
|
||||
|
@ -397,6 +409,7 @@ class CipherViewExtensionsTest {
|
|||
totpCodeItemData = null,
|
||||
clock = fixedClock,
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
assertEquals(
|
||||
VaultItemState.ViewState.Content(
|
||||
|
|
|
@ -171,6 +171,7 @@ fun createCommonContent(
|
|||
requiresCloneConfirmation = false,
|
||||
attachments = emptyList(),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
} else {
|
||||
VaultItemState.ViewState.Content.Common(
|
||||
|
@ -215,6 +216,7 @@ fun createCommonContent(
|
|||
),
|
||||
),
|
||||
canDelete = true,
|
||||
canAssignToCollections = true,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue