Add organization event tracking (#3346)

This commit is contained in:
David Perez 2024-06-24 12:44:31 -05:00 committed by GitHub
parent 6bd628c346
commit 9f5a27c06c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 228 additions and 48 deletions

View file

@ -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,
)
}

View file

@ -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

View file

@ -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),
)
}
}
}

View file

@ -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(

View file

@ -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<VaultAddEditState, VaultAddEditEvent, VaultAddEditAction>(
// 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()

View file

@ -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<VaultItemState, VaultItemEvent, VaultItemAction>(
// 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,
),
)
}
}
}

View file

@ -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<VaultItemListingState, VaultItemListingEvent, VaultItemListingsAction>(
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(

View file

@ -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()

View file

@ -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,
)
},

View file

@ -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(

View file

@ -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<FillResponse.Builder>().setSaveInfo(saveInfo)
}
verify(exactly = 3) {
verify(exactly = 2) {
anyConstructed<FillResponse.Builder>().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<FillResponse.Builder>().addDataset(dataset)
}
}

View file

@ -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<String>()) } just runs
}
@ -70,6 +74,9 @@ class AutofillCompletionManagerTest {
every { show() } just runs
}
private val vaultRepository: VaultRepository = mockk()
private val organizationEventManager = mockk<OrganizationEventManager> {
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)

View file

@ -581,6 +581,7 @@ class SearchScreenTest : BaseComposeTest() {
overflowAction = ListingItemOverflowAction.VaultAction.CopyPasswordClick(
password = "mockPassword-1",
requiresPasswordReprompt = true,
cipherId = "mockId-1",
),
),
)

View file

@ -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<OrganizationEventManager> {
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,
)
/**

View file

@ -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,
),
),

View file

@ -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<OrganizationEventManager> {
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(

View file

@ -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<OrganizationEventManager> {
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(

View file

@ -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<OrganizationEventManager> {
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")

View file

@ -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,
),
),

View file

@ -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,
),
),

View file

@ -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<OrganizationEventManager> {
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,
)
}