Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.autofill.model.AutofillCipher
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager
import com.x8bit.bitwarden.data.platform.util.firstWithTimeoutOrNull
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.data.platform.util.subtitle
import com.x8bit.bitwarden.data.vault.manager.model.GetCipherResult
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
Expand Down Expand Up @@ -66,10 +67,8 @@ class AutofillCipherProviderImpl(
.takeIf {
// Must be card type.
it.type is CipherListViewType.Card &&
// Must not be deleted.
it.deletedDate == null &&
// Must not be archived.
it.archivedDate == null &&
// Must still be active.
it.isActive &&
// Must not require a reprompt.
it.reprompt == CipherRepromptType.NONE &&
// Must not be restricted by organization.
Expand Down Expand Up @@ -106,10 +105,8 @@ class AutofillCipherProviderImpl(
.filter {
// Must be login type
it.type is CipherListViewType.Login &&
// Must not be deleted.
it.deletedDate == null &&
// Must not be archived.
it.archivedDate == null &&
// Must still be active.
it.isActive &&
// Must not require a reprompt.
it.reprompt == CipherRepromptType.NONE
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ import com.bitwarden.vault.CipherListView
import com.bitwarden.vault.CipherListViewType
import com.bitwarden.vault.CopyableCipherFields
import com.bitwarden.vault.LoginListView
import com.x8bit.bitwarden.data.platform.util.isActive

/**
* Returns true when the cipher is not archived, not deleted and contains at least one FIDO 2
* credential.
*/
val CipherListView.isActiveWithFido2Credentials: Boolean
get() = archivedDate == null && deletedDate == null && login?.hasFido2 ?: false
get() = isActive && login?.hasFido2 ?: false

/**
* Returns true when the cipher type is not archived, not deleted and contains a copyable password.
*/
val CipherListView.isActiveWithCopyablePassword: Boolean
get() = archivedDate == null &&
deletedDate == null &&
copyableFields.contains(CopyableCipherFields.LOGIN_PASSWORD)
get() = isActive && copyableFields.contains(CopyableCipherFields.LOGIN_PASSWORD)

/**
* Returns the [LoginListView] if the cipher is of type [CipherListViewType.Login], otherwise null.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.autofill.util
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.autofill.model.AutofillCipher
import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.data.platform.util.subtitle

/**
Expand Down Expand Up @@ -52,13 +53,11 @@ fun CipherView.toAutofillCipherProvider(): AutofillCipherProvider =
* credential.
*/
val CipherView.isActiveWithFido2Credentials: Boolean
get() = archivedDate == null &&
deletedDate == null &&
!(login?.fido2Credentials.isNullOrEmpty())
get() = isActive && !(login?.fido2Credentials.isNullOrEmpty())

/**
* Returns true when the cipher is not archived, not deleted and contains at least one Password
* credential.
*/
val CipherView.isActiveWithPasswordCredentials: Boolean
get() = archivedDate == null && deletedDate == null && !(login?.password.isNullOrEmpty())
get() = isActive && !(login?.password.isNullOrEmpty())
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow
import com.x8bit.bitwarden.data.billing.repository.BillingRepository
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -116,7 +117,7 @@ private fun DataState<VaultData>.activeVaultItemCount(): Int =
data
?.decryptCipherListResult
?.successes
?.count { it.deletedDate == null && it.archivedDate == null }
?.count { it.isActive }
?: 0

private const val PREMIUM_UPGRADE_MINIMUM_VAULT_ITEMS: Int = 5
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.x8bit.bitwarden.data.platform.util

import com.bitwarden.vault.CipherListView

/**
* Indicates if this [CipherListView] is active based on its deleted or archived status.
*/
val CipherListView.isActive: Boolean
get() = this.archivedDate == null && this.deletedDate == null
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ private const val AMEX_DIGITS_DISPLAYED: Int = 5
*/
private const val CARD_DIGITS_DISPLAYED: Int = 4

/**
* Indicates if this [CipherView] is active based on its deleted or archived status.
*/
val CipherView.isActive: Boolean
get() = this.archivedDate == null && this.deletedDate == null

/**
* The subtitle for a [CipherView] used to give extra information about a particular instance.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
import com.x8bit.bitwarden.data.autofill.util.login
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.manager.CipherManager
Expand Down Expand Up @@ -191,8 +192,7 @@ class VaultRepositoryImpl(
.filter {
it.type is CipherListViewType.Login &&
!it.login?.totp.isNullOrBlank() &&
it.deletedDate == null &&
it.archivedDate == null
it.isActive
}
.toFilteredList(vaultFilterType)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.bitwarden.vault.CipherView
import com.bitwarden.vault.FolderView
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
import com.x8bit.bitwarden.data.autofill.util.login
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.ui.platform.feature.search.SearchState
import com.x8bit.bitwarden.ui.platform.feature.search.SearchTypeData
import com.x8bit.bitwarden.ui.platform.feature.search.model.AutofillSelectionOption
Expand Down Expand Up @@ -111,46 +112,20 @@ private fun CipherListView.filterBySearchType(
searchTypeData: SearchTypeData.Vault,
): Boolean =
when (searchTypeData) {
SearchTypeData.Vault.All -> deletedDate == null && archivedDate == null
SearchTypeData.Vault.All -> isActive
SearchTypeData.Vault.Archive -> archivedDate != null && deletedDate == null
is SearchTypeData.Vault.Cards -> {
type is CipherListViewType.Card && deletedDate == null && archivedDate == null
}

is SearchTypeData.Vault.Cards -> type is CipherListViewType.Card && isActive
is SearchTypeData.Vault.Collection -> {
searchTypeData.collectionId in this.collectionIds &&
deletedDate == null &&
archivedDate == null
}

is SearchTypeData.Vault.Folder -> {
folderId == searchTypeData.folderId && deletedDate == null && archivedDate == null
}

SearchTypeData.Vault.NoFolder -> {
folderId == null && deletedDate == null && archivedDate == null
}

is SearchTypeData.Vault.Identities -> {
type is CipherListViewType.Identity && deletedDate == null && archivedDate == null
}

is SearchTypeData.Vault.Logins -> {
type is CipherListViewType.Login && deletedDate == null && archivedDate == null
}

is SearchTypeData.Vault.SecureNotes -> {
type is CipherListViewType.SecureNote && deletedDate == null && archivedDate == null
}

is SearchTypeData.Vault.SshKeys -> {
type is CipherListViewType.SshKey && deletedDate == null && archivedDate == null
}

is SearchTypeData.Vault.VerificationCodes -> {
login?.totp != null && deletedDate == null && archivedDate == null
searchTypeData.collectionId in this.collectionIds && isActive
}

is SearchTypeData.Vault.Folder -> folderId == searchTypeData.folderId && isActive
SearchTypeData.Vault.NoFolder -> folderId == null && isActive
is SearchTypeData.Vault.Identities -> type is CipherListViewType.Identity && isActive
is SearchTypeData.Vault.Logins -> type is CipherListViewType.Login && isActive
is SearchTypeData.Vault.SecureNotes -> type is CipherListViewType.SecureNote && isActive
is SearchTypeData.Vault.SshKeys -> type is CipherListViewType.SshKey && isActive
is SearchTypeData.Vault.VerificationCodes -> login?.totp != null && isActive
is SearchTypeData.Vault.Trash -> deletedDate != null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
import com.x8bit.bitwarden.data.platform.manager.util.toImportCredentialsRequestDataOrNull
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
Expand Down Expand Up @@ -94,7 +95,7 @@ class ReviewExportViewModel @Inject constructor(
.value
.data
?.successes
?.filter { it.deletedDate == null && it.archivedDate == null }
?.filter { it.isActive }
.filterRestrictedItemsIfNecessary(),
)
.fold(
Expand Down Expand Up @@ -241,7 +242,7 @@ class ReviewExportViewModel @Inject constructor(
var secureNoteItemCount = 0
this@toItemTypeCounts
?.successes
?.filter { it.deletedDate == null && it.archivedDate == null }
?.filter { it.isActive }
.orEmpty()
.forEach {
when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.ArchiveCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
Expand Down Expand Up @@ -1516,7 +1517,7 @@ data class VaultItemState(
viewState.asContentOrNull()
?.common
?.currentCipher
?.let { it.archivedDate == null && it.deletedDate == null } == true
?.isActive == true

/**
* Helper to determine if the UI should display the unarchive button.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
import com.x8bit.bitwarden.data.autofill.util.login
import com.x8bit.bitwarden.data.credentials.model.CreateCredentialRequest
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.data.vault.repository.util.toFailureCipherListView
import com.x8bit.bitwarden.ui.tools.feature.send.util.toLabelIcons
Expand Down Expand Up @@ -50,33 +51,31 @@ fun CipherListView.determineListingPredicate(
): Boolean =
when (itemListingType) {
is VaultItemListingState.ItemListingType.Vault.Card -> {
type is CipherListViewType.Card && deletedDate == null && archivedDate == null
type is CipherListViewType.Card && isActive
}

is VaultItemListingState.ItemListingType.Vault.Collection -> {
itemListingType.collectionId in this.collectionIds &&
deletedDate == null &&
archivedDate == null
itemListingType.collectionId in this.collectionIds && isActive
}

is VaultItemListingState.ItemListingType.Vault.Folder -> {
folderId == itemListingType.folderId && deletedDate == null && archivedDate == null
folderId == itemListingType.folderId && isActive
}

is VaultItemListingState.ItemListingType.Vault.Identity -> {
type is CipherListViewType.Identity && deletedDate == null && archivedDate == null
type is CipherListViewType.Identity && isActive
}

is VaultItemListingState.ItemListingType.Vault.Login -> {
type is CipherListViewType.Login && deletedDate == null && archivedDate == null
type is CipherListViewType.Login && isActive
}

is VaultItemListingState.ItemListingType.Vault.SecureNote -> {
type is CipherListViewType.SecureNote && deletedDate == null && archivedDate == null
type is CipherListViewType.SecureNote && isActive
}

is VaultItemListingState.ItemListingType.Vault.SshKey -> {
type is CipherListViewType.SshKey && deletedDate == null && archivedDate == null
type is CipherListViewType.SshKey && isActive
}

is VaultItemListingState.ItemListingType.Vault.Trash -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.bitwarden.vault.CipherListViewType
import com.bitwarden.vault.CipherView
import com.bitwarden.vault.CopyableCipherFields
import com.x8bit.bitwarden.data.autofill.util.login
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import com.x8bit.bitwarden.ui.vault.model.VaultTrailingIcon
import com.x8bit.bitwarden.ui.vault.util.toSdkCipherType
Expand Down Expand Up @@ -88,9 +89,7 @@ fun CipherListView.toOverflowActions(
ListingItemOverflowAction.VaultAction.LaunchClick(url = it)
},
ListingItemOverflowAction.VaultAction.ArchiveClick(cipherId = cipherId)
.takeIf {
this.archivedDate == null && deletedDate == null && isArchiveEnabled
},
.takeIf { this.isActive && isArchiveEnabled },
ListingItemOverflowAction.VaultAction.UnarchiveClick(cipherId = cipherId)
.takeIf {
this.archivedDate != null && deletedDate == null && isArchiveEnabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.bitwarden.vault.FolderView
import com.bitwarden.vault.LoginUriView
import com.x8bit.bitwarden.data.autofill.util.card
import com.x8bit.bitwarden.data.autofill.util.login
import com.x8bit.bitwarden.data.platform.util.isActive
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.data.vault.repository.util.toFailureCipherListView
import com.x8bit.bitwarden.ui.vault.feature.util.getFilteredCollections
Expand Down Expand Up @@ -64,9 +65,7 @@ fun VaultData.toViewState(
excludeDeleted = false,
)

val activeCipherViews = allCipherViews
.filter { it.deletedDate == null && it.archivedDate == null }

val activeCipherViews = allCipherViews.filter { it.isActive }
val activeDecryptedCipherViews = decryptCipherListResult
.successes
.applyFilters(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.x8bit.bitwarden.data.platform.util

import com.bitwarden.vault.CipherListView
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.Clock
import java.time.Instant
import java.time.ZoneOffset

class CipherListViewExtensionsTest {

private val clock: Clock = Clock.fixed(
Instant.parse("2023-10-27T12:00:00Z"),
ZoneOffset.UTC,
)

@Test
fun `isActive should return true when item is not archived and not deleted`() {
val cipherListView = mockk<CipherListView> {
every { archivedDate } returns null
every { deletedDate } returns null
}

assertTrue(cipherListView.isActive)
}

@Test
fun `isActive should return false when item is archived and not deleted`() {
val cipherListView = mockk<CipherListView> {
every { archivedDate } returns clock.instant()
every { deletedDate } returns null
}

assertFalse(cipherListView.isActive)
}

@Test
fun `isActive should return false when item is not archived and is deleted`() {
val cipherListView = mockk<CipherListView> {
every { archivedDate } returns null
every { deletedDate } returns clock.instant()
}

assertFalse(cipherListView.isActive)
}

@Test
Comment on lines +40 to +50
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ IMPORTANT: Test names contradict assertions -- both say "return true" but assert false

Details and fix

Line 40: isActive should return true when item is not archived and is deleted asserts assertFalse.
Line 50: isActive should return true when item is archived and is deleted asserts assertFalse.

Both test names should say "return false" to match the actual assertions:

Suggested change
@Test
fun `isActive should return true when item is not archived and is deleted`() {
val cipherListView = mockk<CipherListView> {
every { archivedDate } returns null
every { deletedDate } returns clock.instant()
}
assertFalse(cipherListView.isActive)
}
@Test
@Test
fun `isActive should return false when item is not archived and is deleted`() {
val cipherListView = mockk<CipherListView> {
every { archivedDate } returns null
every { deletedDate } returns clock.instant()
}
assertFalse(cipherListView.isActive)
}
@Test
fun `isActive should return false when item is archived and is deleted`() {
val cipherListView = mockk<CipherListView> {
every { archivedDate } returns clock.instant()
every { deletedDate } returns clock.instant()
}
assertFalse(cipherListView.isActive)
}

Misleading test names cause confusion when reading test reports or debugging failures.

fun `isActive should return false when item is archived and is deleted`() {
val cipherListView = mockk<CipherListView> {
every { archivedDate } returns clock.instant()
every { deletedDate } returns clock.instant()
}

assertFalse(cipherListView.isActive)
}
}
Loading
Loading