From 76954d456e6563d592b2d6756dc628c0adb106f7 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Wed, 23 Oct 2024 13:47:55 -0400 Subject: [PATCH] [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. --- .../feature/addedit/VaultAddEditScreen.kt | 8 +- .../feature/addedit/VaultAddEditViewModel.kt | 27 ++- .../addedit/util/CipherViewExtensions.kt | 2 + .../ui/vault/feature/item/VaultItemScreen.kt | 7 +- .../vault/feature/item/VaultItemViewModel.kt | 27 ++- .../feature/item/model/VaultItemStateData.kt | 3 + .../feature/item/util/CipherViewExtensions.kt | 2 + .../sdk/model/CollectionViewUtil.kt | 3 +- .../feature/addedit/VaultAddEditScreenTest.kt | 52 ++++ .../addedit/VaultAddEditViewModelTest.kt | 222 ++++++++++++++++++ .../addedit/util/CipherViewExtensionsTest.kt | 7 + .../vault/feature/item/VaultItemScreenTest.kt | 46 ++++ .../feature/item/VaultItemViewModelTest.kt | 185 ++++++++++++--- .../item/util/CipherViewExtensionsTest.kt | 13 + .../feature/item/util/VaultItemTestUtil.kt | 2 + 15 files changed, 563 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt index 23a778d30b2..75eb44c11a3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt @@ -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 }, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index f6ddefd2310..24a83b25eba 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -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 + 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 - itemIsInCollection && !canManageCollection + 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 = emptyList(), val canDelete: Boolean = true, + val canAssignToCollections: Boolean = true, ) : Parcelable { /** diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt index e9587a53c12..7ed9411b697 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt @@ -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, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt index c16a9a3de44..dc46ed4d6bb 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt @@ -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) { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt index 460be16e7ec..1ade718e05c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt @@ -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?, val canDelete: Boolean, + val canAssignToCollections: Boolean, ) : Parcelable { /** diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/model/VaultItemStateData.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/model/VaultItemStateData.kt index 1f7c9d43b3f..5554c56b0c1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/model/VaultItemStateData.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/model/VaultItemStateData.kt @@ -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, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt index 565f600a962..55b95dc90a8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt @@ -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 -> { diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CollectionViewUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CollectionViewUtil.kt index 5d3e216f45f..9e3813745ad 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CollectionViewUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CollectionViewUtil.kt @@ -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, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index 1956f980a03..7d38e4d5d0a 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -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 { diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index 7300ee2387c..e80e5054e6f 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -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 = 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") diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt index 074d32c2a5b..ef44ae27ded 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt @@ -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( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt index a3a42c155ec..695437eb45a 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt @@ -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 = diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt index 911f80edcbe..eba63eaaaeb 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt @@ -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 { + 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 { + 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 = diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt index 05d86866ad1..4bb7186bd15 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt @@ -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( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/VaultItemTestUtil.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/VaultItemTestUtil.kt index 982044e60c9..cba10b2af9b 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/VaultItemTestUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/VaultItemTestUtil.kt @@ -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, ) }