diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderImpl.kt index 45289a15ec..587488d675 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderImpl.kt @@ -76,15 +76,13 @@ class FillResponseBuilderImpl : FillResponseBuilder { */ private fun FilledPartition.toAuthIntentSenderOrNull( autofillAppInfo: AutofillAppInfo, -): IntentSender? { - val isTotpEnabled = this.autofillCipher.isTotpEnabled - val cipherId = this.autofillCipher.cipherId - return if (isTotpEnabled && cipherId != null) { - createTotpCopyIntentSender( - cipherId = cipherId, - context = autofillAppInfo.context, - ) - } else { - null - } -} +): IntentSender? = + autofillCipher + .cipherId + ?.let { cipherId -> + // We always do this even if there is no TOTP code because we want to log the events + createTotpCopyIntentSender( + cipherId = cipherId, + context = autofillAppInfo.context, + ) + } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt index 09b7b9e839..a416331c24 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt @@ -23,6 +23,7 @@ import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager +import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.vault.repository.VaultRepository import dagger.Module @@ -59,6 +60,7 @@ object AutofillModule { dispatcherManager: DispatcherManager, settingsRepository: SettingsRepository, vaultRepository: VaultRepository, + organizationEventManager: OrganizationEventManager, ): AutofillCompletionManager = AutofillCompletionManagerImpl( authRepository = authRepository, @@ -67,6 +69,7 @@ object AutofillModule { dispatcherManager = dispatcherManager, settingsRepository = settingsRepository, vaultRepository = vaultRepository, + organizationEventManager = organizationEventManager, ) @Provides diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillCompletionManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillCompletionManagerImpl.kt index e7ff46bbf5..4a49f0b510 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillCompletionManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/manager/AutofillCompletionManagerImpl.kt @@ -18,6 +18,8 @@ import com.x8bit.bitwarden.data.autofill.util.toAutofillAppInfo import com.x8bit.bitwarden.data.autofill.util.toAutofillCipherProvider import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager +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.SettingsRepository import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult @@ -37,6 +39,7 @@ class AutofillCompletionManagerImpl( { createSingleItemFilledDataBuilder(cipherView = it) }, private val settingsRepository: SettingsRepository, private val vaultRepository: VaultRepository, + private val organizationEventManager: OrganizationEventManager, ) : AutofillCompletionManager { private val mainScope = CoroutineScope(dispatcherManager.main) @@ -85,6 +88,11 @@ class AutofillCompletionManagerImpl( ) val resultIntent = createAutofillSelectionResultIntent(dataset) activity.setResultAndFinish(resultIntent = resultIntent) + cipherView.id?.let { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientAutoFilled(cipherId = it), + ) + } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt index 55997c8d14..ffeb0a4017 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt @@ -12,6 +12,8 @@ import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager +import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository @@ -65,6 +67,7 @@ class SearchViewModel @Inject constructor( private val clipboardManager: BitwardenClipboardManager, private val policyManager: PolicyManager, private val autofillSelectionManager: AutofillSelectionManager, + private val organizationEventManager: OrganizationEventManager, private val vaultRepo: VaultRepository, private val authRepo: AuthRepository, environmentRepo: EnvironmentRepository, @@ -343,12 +346,18 @@ class SearchViewModel @Inject constructor( action: ListingItemOverflowAction.VaultAction.CopyPasswordClick, ) { clipboardManager.setText(action.password) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedPassword(cipherId = action.cipherId), + ) } private fun handleCopySecurityCodeClick( action: ListingItemOverflowAction.VaultAction.CopySecurityCodeClick, ) { clipboardManager.setText(action.securityCode) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedCardCode(cipherId = action.cipherId), + ) } private fun handleCopyUsernameClick( 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 719703fec7..dda1ba5956 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 @@ -12,6 +12,8 @@ import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager +import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull @@ -88,6 +90,7 @@ class VaultAddEditViewModel @Inject constructor( private val specialCircumstanceManager: SpecialCircumstanceManager, private val resourceManager: ResourceManager, private val clock: Clock, + private val organizationEventManager: OrganizationEventManager, ) : BaseViewModel( // We load the state from the savedStateHandle for testing purposes. initialState = savedStateHandle[KEY_STATE] @@ -159,6 +162,11 @@ class VaultAddEditViewModel @Inject constructor( //region Initialization and Overrides init { + onEdit { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientViewed(cipherId = it.vaultItemId), + ) + } vaultRepository .vaultDataStateFlow .takeUntilLoaded() 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 1d1c9c5952..88bdfb217e 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 @@ -11,6 +11,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +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.model.DataState import com.x8bit.bitwarden.data.platform.repository.util.combineDataStates import com.x8bit.bitwarden.data.platform.repository.util.mapNullable @@ -53,6 +55,7 @@ class VaultItemViewModel @Inject constructor( private val authRepository: AuthRepository, private val vaultRepository: VaultRepository, private val fileManager: FileManager, + private val organizationEventManager: OrganizationEventManager, ) : BaseViewModel( // We load the state from the savedStateHandle for testing purposes. initialState = savedStateHandle[KEY_STATE] ?: VaultItemState( @@ -72,6 +75,9 @@ class VaultItemViewModel @Inject constructor( //region Initialization and Overrides init { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientViewed(cipherId = state.vaultItemId), + ) combine( vaultRepository.getVaultItemStateFlow(state.vaultItemId), authRepository.userStateFlow, @@ -244,6 +250,11 @@ class VaultItemViewModel @Inject constructor( return@onContent } clipboardManager.setText(text = action.field) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedHiddenField( + cipherId = state.vaultItemId, + ), + ) } } @@ -285,6 +296,13 @@ class VaultItemViewModel @Inject constructor( ), ) } + if (action.isVisible) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledHiddenFieldVisible( + cipherId = state.vaultItemId, + ), + ) + } } } @@ -572,6 +590,9 @@ class VaultItemViewModel @Inject constructor( return@onLoginContent } clipboardManager.setText(text = password) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedPassword(cipherId = state.vaultItemId), + ) } } @@ -642,6 +663,13 @@ class VaultItemViewModel @Inject constructor( ), ) } + if (action.isVisible) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledPasswordVisible( + cipherId = state.vaultItemId, + ), + ) + } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index 62c2546ca1..e46a59b5d8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -17,6 +17,8 @@ import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager +import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository @@ -82,6 +84,7 @@ class VaultItemListingViewModel @Inject constructor( private val specialCircumstanceManager: SpecialCircumstanceManager, private val policyManager: PolicyManager, private val fido2CredentialManager: Fido2CredentialManager, + private val organizationEventManager: OrganizationEventManager, ) : BaseViewModel( initialState = run { val userState = requireNotNull(authRepository.userStateFlow.value) @@ -342,12 +345,18 @@ class VaultItemListingViewModel @Inject constructor( action: ListingItemOverflowAction.VaultAction.CopyPasswordClick, ) { clipboardManager.setText(action.password) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedPassword(cipherId = action.cipherId), + ) } private fun handleCopySecurityCodeClick( action: ListingItemOverflowAction.VaultAction.CopySecurityCodeClick, ) { clipboardManager.setText(action.securityCode) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedCardCode(cipherId = action.cipherId), + ) } private fun handleCopyTotpClick( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/model/ListingItemOverflowAction.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/model/ListingItemOverflowAction.kt index 4ddefc3713..aa1b6720ae 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/model/ListingItemOverflowAction.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/model/ListingItemOverflowAction.kt @@ -105,6 +105,7 @@ sealed class ListingItemOverflowAction : Parcelable { */ @Parcelize data class CopyPasswordClick( + val cipherId: String, val password: String, override val requiresPasswordReprompt: Boolean, ) : VaultAction() { @@ -137,6 +138,7 @@ sealed class ListingItemOverflowAction : Parcelable { @Parcelize data class CopySecurityCodeClick( val securityCode: String, + val cipherId: String, override val requiresPasswordReprompt: Boolean, ) : VaultAction() { override val title: Text get() = R.string.copy_security_code.asText() diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensions.kt index f27a56cc2b..607cc7694f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensions.kt @@ -28,6 +28,7 @@ fun CipherView.toOverflowActions( this.login?.password ?.let { ListingItemOverflowAction.VaultAction.CopyPasswordClick( + cipherId = cipherId, password = it, requiresPasswordReprompt = hasMasterPassword, ) @@ -45,6 +46,7 @@ fun CipherView.toOverflowActions( this.card?.code?.let { ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( securityCode = it, + cipherId = cipherId, requiresPasswordReprompt = hasMasterPassword, ) }, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt index 505885aeb2..0138527575 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt @@ -10,6 +10,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager +import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl @@ -52,11 +54,12 @@ import javax.inject.Inject /** * Manages [VaultState], handles [VaultAction], and launches [VaultEvent] for the [VaultScreen]. */ -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LongParameterList") @HiltViewModel class VaultViewModel @Inject constructor( private val authRepository: AuthRepository, private val clipboardManager: BitwardenClipboardManager, + private val organizationEventManager: OrganizationEventManager, private val clock: Clock, private val policyManager: PolicyManager, private val settingsRepository: SettingsRepository, @@ -364,12 +367,18 @@ class VaultViewModel @Inject constructor( action: ListingItemOverflowAction.VaultAction.CopyPasswordClick, ) { clipboardManager.setText(action.password) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedPassword(cipherId = action.cipherId), + ) } private fun handleCopySecurityCodeClick( action: ListingItemOverflowAction.VaultAction.CopySecurityCodeClick, ) { clipboardManager.setText(action.securityCode) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedCardCode(cipherId = action.cipherId), + ) } private fun handleCopyTotpClick( diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt index ef1ddbd0de..23008835d3 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt @@ -45,15 +45,9 @@ class FillResponseBuilderTest { ) private val autofillCipherValid: AutofillCipher = mockk { every { cipherId } returns CIPHER_ID - every { isTotpEnabled } returns true } private val autofillCipherNoId: AutofillCipher = mockk { every { cipherId } returns null - every { isTotpEnabled } returns true - } - private val autofillCipherTotpDisabled: AutofillCipher = mockk { - every { cipherId } returns CIPHER_ID - every { isTotpEnabled } returns false } private val filledPartitionOne: FilledPartition = mockk { every { this@mockk.filledItems } returns listOf(mockk()) @@ -66,10 +60,6 @@ class FillResponseBuilderTest { every { this@mockk.filledItems } returns listOf(mockk()) every { this@mockk.autofillCipher } returns autofillCipherNoId } - private val filledPartitionFour: FilledPartition = mockk { - every { this@mockk.filledItems } returns listOf(mockk()) - every { this@mockk.autofillCipher } returns autofillCipherTotpDisabled - } private val saveInfo: SaveInfo = mockk() @BeforeEach @@ -141,7 +131,6 @@ class FillResponseBuilderTest { filledPartitionOne, filledPartitionTwo, filledPartitionThree, - filledPartitionFour, ) val filledData = FilledData( filledPartitions = filledPartitions, @@ -175,12 +164,6 @@ class FillResponseBuilderTest { autofillAppInfo = appInfo, ) } returns dataset - every { - filledPartitionFour.buildDataset( - authIntentSender = null, - autofillAppInfo = appInfo, - ) - } returns dataset every { filledData.buildVaultItemDataset( autofillAppInfo = appInfo, @@ -219,10 +202,6 @@ class FillResponseBuilderTest { authIntentSender = null, autofillAppInfo = appInfo, ) - filledPartitionFour.buildDataset( - authIntentSender = null, - autofillAppInfo = appInfo, - ) filledData.buildVaultItemDataset( autofillAppInfo = appInfo, ) @@ -233,7 +212,7 @@ class FillResponseBuilderTest { ) anyConstructed().setSaveInfo(saveInfo) } - verify(exactly = 3) { + verify(exactly = 2) { anyConstructed().addDataset(dataset) } } @@ -252,7 +231,6 @@ class FillResponseBuilderTest { filledPartitionOne, filledPartitionTwo, filledPartitionThree, - filledPartitionFour, ) val filledData = FilledData( filledPartitions = filledPartitions, @@ -286,12 +264,6 @@ class FillResponseBuilderTest { autofillAppInfo = appInfo, ) } returns dataset - every { - filledPartitionFour.buildDataset( - authIntentSender = null, - autofillAppInfo = appInfo, - ) - } returns dataset every { filledData.buildVaultItemDataset( autofillAppInfo = appInfo, @@ -327,10 +299,6 @@ class FillResponseBuilderTest { authIntentSender = null, autofillAppInfo = appInfo, ) - filledPartitionFour.buildDataset( - authIntentSender = null, - autofillAppInfo = appInfo, - ) filledData.buildVaultItemDataset( autofillAppInfo = appInfo, ) @@ -340,7 +308,7 @@ class FillResponseBuilderTest { ignoredAutofillIdTwo, ) } - verify(exactly = 3) { + verify(exactly = 2) { anyConstructed().addDataset(dataset) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillCompletionManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillCompletionManagerTest.kt index 678f34feef..773f4f004f 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillCompletionManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/manager/AutofillCompletionManagerTest.kt @@ -22,6 +22,8 @@ import com.x8bit.bitwarden.data.autofill.util.getAutofillAssistStructureOrNull import com.x8bit.bitwarden.data.autofill.util.toAutofillAppInfo import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +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.SettingsRepository import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult @@ -54,7 +56,9 @@ class AutofillCompletionManagerTest { } private val autofillAppInfo: AutofillAppInfo = mockk() private val autofillParser: AutofillParser = mockk() - private val cipherView: CipherView = mockk() + private val cipherView: CipherView = mockk { + every { id } returns "cipherId" + } private val clipboardManager: BitwardenClipboardManager = mockk { every { setText(any()) } just runs } @@ -70,6 +74,9 @@ class AutofillCompletionManagerTest { every { show() } just runs } private val vaultRepository: VaultRepository = mockk() + private val organizationEventManager = mockk { + every { trackEvent(event = any()) } just runs + } private val autofillCompletionManager: AutofillCompletionManager = AutofillCompletionManagerImpl( @@ -80,6 +87,7 @@ class AutofillCompletionManagerTest { filledDataBuilderProvider = { filledDataBuilder }, settingsRepository = settingsRepository, vaultRepository = vaultRepository, + organizationEventManager = organizationEventManager, ) @BeforeEach @@ -290,6 +298,9 @@ class AutofillCompletionManagerTest { Toast.LENGTH_LONG, ) toast.show() + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientAutoFilled(cipherId = "cipherId"), + ) } coVerify { filledDataBuilder.build(autofillRequest = fillableRequest) @@ -358,6 +369,9 @@ class AutofillCompletionManagerTest { ) settingsRepository.isAutoCopyTotpDisabled createAutofillSelectionResultIntent(dataset = dataset) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientAutoFilled(cipherId = "cipherId"), + ) } coVerify { filledDataBuilder.build(autofillRequest = fillableRequest) @@ -421,6 +435,9 @@ class AutofillCompletionManagerTest { ) settingsRepository.isAutoCopyTotpDisabled createAutofillSelectionResultIntent(dataset = dataset) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientAutoFilled(cipherId = "cipherId"), + ) } coVerify { filledDataBuilder.build(autofillRequest = fillableRequest) @@ -479,6 +496,9 @@ class AutofillCompletionManagerTest { ) settingsRepository.isAutoCopyTotpDisabled createAutofillSelectionResultIntent(dataset = dataset) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientAutoFilled(cipherId = "cipherId"), + ) } coVerify { filledDataBuilder.build(autofillRequest = fillableRequest) @@ -537,6 +557,9 @@ class AutofillCompletionManagerTest { ) settingsRepository.isAutoCopyTotpDisabled createAutofillSelectionResultIntent(dataset = dataset) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientAutoFilled(cipherId = "cipherId"), + ) } coVerify { filledDataBuilder.build(autofillRequest = fillableRequest) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt index e10829d40e..bdee83adbf 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt @@ -581,6 +581,7 @@ class SearchScreenTest : BaseComposeTest() { overflowAction = ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = "mockPassword-1", requiresPasswordReprompt = true, + cipherId = "mockId-1", ), ), ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt index 1a5d80ba39..4ebdce278b 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt @@ -17,6 +17,8 @@ import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager +import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository @@ -106,6 +108,9 @@ class SearchViewModelTest : BaseViewModelTest() { } private val specialCircumstanceManager: SpecialCircumstanceManager = SpecialCircumstanceManagerImpl() + private val organizationEventManager = mockk { + every { trackEvent(event = any()) } just runs + } @BeforeEach fun setup() { @@ -727,17 +732,22 @@ class SearchViewModelTest : BaseViewModelTest() { fun `OverflowOptionClick Vault CopyPasswordClick should call setText on the ClipboardManager`() = runTest { val password = "passTheWord" + val cipherId = "mockId-1" val viewModel = createViewModel() viewModel.trySendAction( SearchAction.OverflowOptionClick( ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = password, requiresPasswordReprompt = true, + cipherId = cipherId, ), ), ) verify(exactly = 1) { clipboardManager.setText(password) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedPassword(cipherId = cipherId), + ) } } @@ -746,17 +756,22 @@ class SearchViewModelTest : BaseViewModelTest() { fun `OverflowOptionClick Vault CopySecurityCodeClick should call setText on the ClipboardManager`() = runTest { val securityCode = "234" + val cipherId = "cipherId" val viewModel = createViewModel() viewModel.trySendAction( SearchAction.OverflowOptionClick( ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( securityCode = securityCode, + cipherId = cipherId, requiresPasswordReprompt = true, ), ), ) verify(exactly = 1) { clipboardManager.setText(securityCode) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedCardCode(cipherId = cipherId), + ) } } @@ -1304,6 +1319,7 @@ class SearchViewModelTest : BaseViewModelTest() { policyManager = policyManager, specialCircumstanceManager = specialCircumstanceManager, autofillSelectionManager = autofillSelectionManager, + organizationEventManager = organizationEventManager, ) /** diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt index c094a56d82..75f19f002c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt @@ -52,6 +52,7 @@ fun createMockDisplayItemForCipher( ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = "mockPassword-$number", requiresPasswordReprompt = true, + cipherId = "mockId-$number", ), ListingItemOverflowAction.VaultAction.CopyTotpClick( totpCode = "mockTotp-$number", @@ -136,6 +137,7 @@ fun createMockDisplayItemForCipher( ), ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( securityCode = "mockCode-$number", + cipherId = "mockId-$number", requiresPasswordReprompt = true, ), ), 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 fe4aefc238..abe753d848 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 @@ -23,6 +23,8 @@ import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager +import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.model.DataState @@ -126,6 +128,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { SpecialCircumstanceManagerImpl() private val generatorRepository: GeneratorRepository = FakeGeneratorRepository() + private val organizationEventManager = mockk { + every { trackEvent(event = any()) } just runs + } @BeforeEach fun setup() { @@ -179,6 +184,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { verify(exactly = 1) { vaultRepository.vaultDataStateFlow } + verify(exactly = 0) { + organizationEventManager.trackEvent(event = any()) + } } @Test @@ -351,6 +359,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ) verify(exactly = 1) { vaultRepository.vaultDataStateFlow + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientViewed(cipherId = DEFAULT_EDIT_ITEM_ID), + ) } } @@ -371,6 +382,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { verify(exactly = 1) { vaultRepository.vaultDataStateFlow } + verify(exactly = 0) { + organizationEventManager.trackEvent(event = any()) + } } @Test @@ -1934,6 +1948,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { fido2CredentialManager = fido2CredentialManager, settingsRepository = settingsRepository, clock = fixedClock, + organizationEventManager = organizationEventManager, ) } @@ -2514,6 +2529,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { authRepository = authRepository, settingsRepository = settingsRepository, clock = clock, + organizationEventManager = organizationEventManager, ) private fun createVaultData( 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 e4405b7504..ab385266e0 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 @@ -11,6 +11,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +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.model.DataState import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView @@ -68,6 +70,10 @@ class VaultItemViewModelTest : BaseViewModelTest() { private val mockFileManager: FileManager = mockk() + private val organizationEventManager = mockk { + every { trackEvent(event = any()) } just runs + } + @BeforeEach fun setup() { mockkStatic(CipherView::toViewState) @@ -82,6 +88,11 @@ class VaultItemViewModelTest : BaseViewModelTest() { fun `initial state should be correct when not set`() { val viewModel = createViewModel(state = null) assertEquals(DEFAULT_STATE, viewModel.stateFlow.value) + verify(exactly = 1) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientViewed(cipherId = VAULT_ITEM_ID), + ) + } } @Test @@ -98,6 +109,11 @@ class VaultItemViewModelTest : BaseViewModelTest() { val state = DEFAULT_STATE.copy(vaultItemId = differentVaultItemId) val viewModel = createViewModel(state = state) assertEquals(state, viewModel.stateFlow.value) + verify(exactly = 1) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientViewed(cipherId = differentVaultItemId), + ) + } } @Nested @@ -764,6 +780,11 @@ class VaultItemViewModelTest : BaseViewModelTest() { hasMasterPassword = true, totpCodeItemData = null, ) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedHiddenField( + cipherId = VAULT_ITEM_ID, + ), + ) } } @@ -888,6 +909,11 @@ class VaultItemViewModelTest : BaseViewModelTest() { hasMasterPassword = true, totpCodeItemData = null, ) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledHiddenFieldVisible( + cipherId = VAULT_ITEM_ID, + ), + ) } } @@ -1855,6 +1881,11 @@ class VaultItemViewModelTest : BaseViewModelTest() { hasMasterPassword = true, totpCodeItemData = createTotpCodeData(), ) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledPasswordVisible( + cipherId = VAULT_ITEM_ID, + ), + ) } } } @@ -2187,6 +2218,7 @@ class VaultItemViewModelTest : BaseViewModelTest() { authRepository: AuthRepository = authRepo, vaultRepository: VaultRepository = vaultRepo, fileManager: FileManager = mockFileManager, + eventManager: OrganizationEventManager = organizationEventManager, tempAttachmentFile: File? = null, ): VaultItemViewModel = VaultItemViewModel( savedStateHandle = SavedStateHandle().apply { @@ -2198,6 +2230,7 @@ class VaultItemViewModelTest : BaseViewModelTest() { authRepository = authRepository, vaultRepository = vaultRepository, fileManager = fileManager, + organizationEventManager = eventManager, ) private fun createViewState( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index 491af4672a..b3958c46d9 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -20,6 +20,8 @@ import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager +import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository @@ -130,6 +132,10 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { coEvery { validateOrigin(any()) } returns Fido2ValidateOriginResult.Success } + private val organizationEventManager = mockk { + every { trackEvent(event = any()) } just runs + } + private val initialState = createVaultItemListingState() private val initialSavedStateHandle = createSavedStateHandleWithVaultItemListingType( vaultItemListingType = VaultItemListingType.Login, @@ -743,17 +749,22 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { fun `OverflowOptionClick Vault CopyPasswordClick should call setText on the ClipboardManager`() = runTest { val password = "passTheWord" + val cipherId = "cipherId" val viewModel = createVaultItemListingViewModel() viewModel.trySendAction( VaultItemListingsAction.OverflowOptionClick( ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = password, requiresPasswordReprompt = true, + cipherId = cipherId, ), ), ) verify(exactly = 1) { clipboardManager.setText(password) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedPassword(cipherId = cipherId), + ) } } @@ -762,17 +773,22 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { fun `OverflowOptionClick Vault CopySecurityCodeClick should call setText on the ClipboardManager`() = runTest { val securityCode = "234" + val cipherId = "cipherId" val viewModel = createVaultItemListingViewModel() viewModel.trySendAction( VaultItemListingsAction.OverflowOptionClick( ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( securityCode = securityCode, + cipherId = cipherId, requiresPasswordReprompt = true, ), ), ) verify(exactly = 1) { clipboardManager.setText(securityCode) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedCardCode(cipherId = cipherId), + ) } } @@ -1767,6 +1783,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { specialCircumstanceManager = specialCircumstanceManager, policyManager = policyManager, fido2CredentialManager = fido2CredentialManager, + organizationEventManager = organizationEventManager, ) @Suppress("MaxLineLength") diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt index 8d59397a61..333f8e4382 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt @@ -53,6 +53,7 @@ fun createMockDisplayItemForCipher( ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = "mockPassword-$number", requiresPasswordReprompt = true, + cipherId = "mockId-$number", ), ListingItemOverflowAction.VaultAction.CopyTotpClick( totpCode = "mockTotp-$number", @@ -139,6 +140,7 @@ fun createMockDisplayItemForCipher( ), ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( securityCode = "mockCode-$number", + cipherId = "mockId-$number", requiresPasswordReprompt = true, ), ), diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensionsTest.kt index a8f10a2411..7518873bbe 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensionsTest.kt @@ -44,6 +44,7 @@ class CipherViewExtensionsTest { ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = password, requiresPasswordReprompt = false, + cipherId = id, ), ListingItemOverflowAction.VaultAction.CopyTotpClick(totpCode = totpCode), ListingItemOverflowAction.VaultAction.LaunchClick(url = uri), @@ -140,6 +141,7 @@ class CipherViewExtensionsTest { ), ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( securityCode = securityCode, + cipherId = id, requiresPasswordReprompt = true, ), ), diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt index 4c8e13fae8..ed6e64078d 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt @@ -9,6 +9,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager +import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.platform.repository.model.Environment @@ -98,6 +100,10 @@ class VaultViewModelTest : BaseViewModelTest() { every { lockVault(any()) } just runs } + private val organizationEventManager = mockk { + every { trackEvent(event = any()) } just runs + } + @Test fun `initial state should be correct and should trigger a syncIfNecessary call`() { val viewModel = createViewModel() @@ -1204,17 +1210,22 @@ class VaultViewModelTest : BaseViewModelTest() { fun `OverflowOptionClick Vault CopyPasswordClick should call setText on the ClipboardManager`() = runTest { val password = "passTheWord" + val cipherId = "cipherId" val viewModel = createViewModel() viewModel.trySendAction( VaultAction.OverflowOptionClick( ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = password, requiresPasswordReprompt = true, + cipherId = cipherId, ), ), ) verify(exactly = 1) { clipboardManager.setText(password) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedPassword(cipherId = cipherId), + ) } } @@ -1268,17 +1279,22 @@ class VaultViewModelTest : BaseViewModelTest() { fun `OverflowOptionClick Vault CopySecurityCodeClick should call setText on the ClipboardManager`() = runTest { val securityCode = "234" + val cipherId = "cipherId" val viewModel = createViewModel() viewModel.trySendAction( VaultAction.OverflowOptionClick( ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( securityCode = securityCode, + cipherId = cipherId, requiresPasswordReprompt = true, ), ), ) verify(exactly = 1) { clipboardManager.setText(securityCode) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedCardCode(cipherId = cipherId), + ) } } @@ -1365,6 +1381,7 @@ class VaultViewModelTest : BaseViewModelTest() { overflowAction = ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = password, requiresPasswordReprompt = true, + cipherId = "cipherId", ), password = password, ), @@ -1403,6 +1420,7 @@ class VaultViewModelTest : BaseViewModelTest() { overflowAction = ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = password, requiresPasswordReprompt = true, + cipherId = "cipherId", ), password = password, ), @@ -1425,6 +1443,7 @@ class VaultViewModelTest : BaseViewModelTest() { fun `MasterPasswordRepromptSubmit for a request Success with a valid password should continue the action`() = runTest { val password = "password" + val cipherId = "cipherId" coEvery { authRepository.validatePassword(password = password) } returns ValidatePasswordResult.Success(isValid = true) @@ -1436,6 +1455,7 @@ class VaultViewModelTest : BaseViewModelTest() { overflowAction = ListingItemOverflowAction.VaultAction.CopyPasswordClick( password = password, requiresPasswordReprompt = true, + cipherId = cipherId, ), password = password, ), @@ -1443,6 +1463,9 @@ class VaultViewModelTest : BaseViewModelTest() { verify(exactly = 1) { clipboardManager.setText(password) + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientCopiedPassword(cipherId = cipherId), + ) } } @@ -1454,6 +1477,7 @@ class VaultViewModelTest : BaseViewModelTest() { clock = clock, settingsRepository = settingsRepository, vaultRepository = vaultRepository, + organizationEventManager = organizationEventManager, ) }