diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateMappingTests.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateMappingTests.kt new file mode 100644 index 0000000000..cce8879d4f --- /dev/null +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateMappingTests.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.appnav.loggedin + +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.CryptoSessionStateChange +import im.vector.app.features.analytics.plan.UserProperties +import io.element.android.libraries.matrix.api.encryption.RecoveryState +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus +import io.element.android.tests.testutils.WarmUpRule +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class AnalyticsVerificationStateMappingTests { + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `Test verification Mappings`() = runTest { + assertThat(SessionVerifiedStatus.Verified.toAnalyticsUserPropertyValue()) + .isEqualTo(UserProperties.VerificationState.Verified) + assertThat(SessionVerifiedStatus.NotVerified.toAnalyticsUserPropertyValue()) + .isEqualTo(UserProperties.VerificationState.NotVerified) + + assertThat(SessionVerifiedStatus.Verified.toAnalyticsStateChangeValue()) + .isEqualTo(CryptoSessionStateChange.VerificationState.Verified) + assertThat(SessionVerifiedStatus.NotVerified.toAnalyticsStateChangeValue()) + .isEqualTo(CryptoSessionStateChange.VerificationState.NotVerified) + } + + @Test + fun `Test recovery state Mappings`() = runTest { + assertThat(RecoveryState.UNKNOWN.toAnalyticsUserPropertyValue()) + .isNull() + assertThat(RecoveryState.WAITING_FOR_SYNC.toAnalyticsUserPropertyValue()) + .isNull() + assertThat(RecoveryState.INCOMPLETE.toAnalyticsUserPropertyValue()) + .isEqualTo(UserProperties.RecoveryState.Incomplete) + assertThat(RecoveryState.ENABLED.toAnalyticsUserPropertyValue()) + .isEqualTo(UserProperties.RecoveryState.Enabled) + assertThat(RecoveryState.DISABLED.toAnalyticsUserPropertyValue()) + .isEqualTo(UserProperties.RecoveryState.Disabled) + + assertThat(RecoveryState.UNKNOWN.toAnalyticsStateChangeValue()) + .isNull() + assertThat(RecoveryState.WAITING_FOR_SYNC.toAnalyticsStateChangeValue()) + .isNull() + assertThat(RecoveryState.INCOMPLETE.toAnalyticsStateChangeValue()) + .isEqualTo(CryptoSessionStateChange.RecoveryState.Incomplete) + assertThat(RecoveryState.ENABLED.toAnalyticsStateChangeValue()) + .isEqualTo(CryptoSessionStateChange.RecoveryState.Enabled) + assertThat(RecoveryState.DISABLED.toAnalyticsStateChangeValue()) + .isEqualTo(CryptoSessionStateChange.RecoveryState.Disabled) + } +} diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index d1b89fca77..2678c125e5 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -20,9 +20,13 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.CryptoSessionStateChange +import im.vector.app.features.analytics.plan.UserProperties import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.networkmonitor.test.FakeNetworkMonitor +import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService @@ -66,17 +70,53 @@ class LoggedInPresenterTest { } } + @Test + fun `present - report crypto status analytics`() = runTest { + val analyticsService = FakeAnalyticsService() + val verificationService = FakeSessionVerificationService() + val encryptionService = FakeEncryptionService() + val presenter = LoggedInPresenter( + matrixClient = FakeMatrixClient(encryptionService = encryptionService), + networkMonitor = FakeNetworkMonitor(NetworkStatus.Online), + pushService = FakePushService(), + sessionVerificationService = verificationService, + analyticsService = analyticsService, + encryptionService = encryptionService + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + encryptionService.emitRecoveryState(RecoveryState.UNKNOWN) + encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE) + verificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified) + + // Should only capture once (not report while checking state -like unknown-) + consumeItemsUntilPredicate { + analyticsService.capturedEvents.size == 1 && + analyticsService.capturedEvents[0] is CryptoSessionStateChange + } + consumeItemsUntilPredicate { + analyticsService.capturedUserProperties.size == 1 && + analyticsService.capturedUserProperties[0].recoveryState == UserProperties.RecoveryState.Incomplete && + analyticsService.capturedUserProperties[0].verificationState == UserProperties.VerificationState.Verified + } + cancelAndConsumeRemainingEvents() + } + } + private fun createLoggedInPresenter( roomListService: RoomListService = FakeRoomListService(), - networkStatus: NetworkStatus = NetworkStatus.Offline + networkStatus: NetworkStatus = NetworkStatus.Offline, + analyticsService: FakeAnalyticsService = FakeAnalyticsService(), + encryptionService: FakeEncryptionService = FakeEncryptionService(), ): LoggedInPresenter { return LoggedInPresenter( matrixClient = FakeMatrixClient(roomListService = roomListService), networkMonitor = FakeNetworkMonitor(networkStatus), pushService = FakePushService(), sessionVerificationService = FakeSessionVerificationService(), - analyticsService = FakeAnalyticsService(), - encryptionService = FakeEncryptionService() + analyticsService = analyticsService, + encryptionService = encryptionService ) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt index 64c7c8ab97..cde77d7c61 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt @@ -77,6 +77,10 @@ class FakeSessionVerificationService : SessionVerificationService { _sessionVerifiedStatus.value = status } + suspend fun emitVerifiedStatus(status: SessionVerifiedStatus) { + _sessionVerifiedStatus.emit(status) + } + fun givenVerificationFlowState(state: VerificationFlowState) { _verificationFlowState.value = state } diff --git a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt index 14ef329ae4..2d9f320422 100644 --- a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt +++ b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt @@ -33,6 +33,7 @@ class FakeAnalyticsService( val capturedEvents = mutableListOf() val screenEvents = mutableListOf() val trackedErrors = mutableListOf() + val capturedUserProperties = mutableListOf() override fun getAvailableAnalyticsProviders(): Set = emptySet() @@ -65,6 +66,7 @@ class FakeAnalyticsService( } override fun updateUserProperties(userProperties: UserProperties) { + capturedUserProperties += userProperties } override fun trackError(throwable: Throwable) { diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt index b594dcbee4..fb121aeb5e 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt @@ -87,7 +87,7 @@ class PosthogAnalyticsProvider @Inject constructor( userProperties.getProperties()?.let { pendingUserProperties?.putAll(it) } - // We are not currently using `identify` in EIX, if it was the case + // We are not currently using `identify` in EAX, if it was the case // we could have called identify to update the user properties. // For now, we have to store them, and they will be updated when the next call // to capture will happen.