You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/test/java/org/mozilla/fenix/components/metrics/DefaultMetricsStorageTest.kt

506 lines
20 KiB
Kotlin

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.components.metrics
import android.app.Activity
import android.app.Application
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.utils.Settings
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class DefaultMetricsStorageTest {
private val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.US)
private val calendarStart = Calendar.getInstance(Locale.US)
private val dayMillis: Long = 1000 * 60 * 60 * 24
private val usageThresholdMillis: Long = 340 * 1000
private var checkDefaultBrowser = false
private val doCheckDefaultBrowser = { checkDefaultBrowser }
private var shouldSendGenerally = true
private val doShouldSendGenerally = { shouldSendGenerally }
private var installTime = 0L
private val doGetInstallTime = { installTime }
private val settings = mockk<Settings>()
private val dispatcher = StandardTestDispatcher()
private lateinit var storage: DefaultMetricsStorage
@Before
fun setup() {
checkDefaultBrowser = false
shouldSendGenerally = true
installTime = System.currentTimeMillis()
every { settings.firstWeekDaysOfUseGrowthData } returns setOf()
every { settings.firstWeekDaysOfUseGrowthData = any() } returns Unit
storage = DefaultMetricsStorage(mockk(), settings, doCheckDefaultBrowser, doShouldSendGenerally, doGetInstallTime, dispatcher)
}
@Test
fun `GIVEN that events should not be generally sent WHEN event would be tracked THEN it is not`() = runTest(dispatcher) {
shouldSendGenerally = false
checkDefaultBrowser = true
every { settings.setAsDefaultGrowthSent } returns false
val result = storage.shouldTrack(Event.GrowthData.SetAsDefault)
assertFalse(result)
}
@Test
fun `GIVEN set as default has not been sent and app is not default WHEN checked for sending THEN will not be sent`() = runTest(dispatcher) {
every { settings.setAsDefaultGrowthSent } returns false
checkDefaultBrowser = false
val result = storage.shouldTrack(Event.GrowthData.SetAsDefault)
assertFalse(result)
}
@Test
fun `GIVEN set as default has not been sent and app is default WHEN checked for sending THEN will be sent`() = runTest(dispatcher) {
every { settings.setAsDefaultGrowthSent } returns false
checkDefaultBrowser = true
val result = storage.shouldTrack(Event.GrowthData.SetAsDefault)
assertTrue(result)
}
@Test
fun `GIVEN set as default has been sent and app is default WHEN checked for sending THEN will be not sent`() = runTest(dispatcher) {
every { settings.setAsDefaultGrowthSent } returns true
checkDefaultBrowser = true
val result = storage.shouldTrack(Event.GrowthData.SetAsDefault)
assertFalse(result)
}
@Test
fun `WHEN set as default updated THEN settings will be updated accordingly`() = runTest(dispatcher) {
val updateSlot = slot<Boolean>()
every { settings.setAsDefaultGrowthSent = capture(updateSlot) } returns Unit
storage.updateSentState(Event.GrowthData.SetAsDefault)
assertTrue(updateSlot.captured)
}
@Test
fun `GIVEN that app has been used for less than 3 days in a row WHEN checked for first week activity THEN event will not be sent`() = runTest(dispatcher) {
val tomorrow = calendarStart.createNextDay()
every { settings.firstWeekDaysOfUseGrowthData = any() } returns Unit
every { settings.firstWeekDaysOfUseGrowthData } returns setOf(calendarStart, tomorrow).toStrings()
every { settings.firstWeekSeriesGrowthSent } returns false
val result = storage.shouldTrack(Event.GrowthData.FirstWeekSeriesActivity)
assertFalse(result)
}
@Test
fun `GIVEN that app has only been used for 3 days in a row WHEN checked for first week activity THEN event will be sent`() = runTest(dispatcher) {
val tomorrow = calendarStart.createNextDay()
val thirdDay = tomorrow.createNextDay()
every { settings.firstWeekDaysOfUseGrowthData } returns setOf(calendarStart, tomorrow, thirdDay).toStrings()
every { settings.firstWeekSeriesGrowthSent } returns false
val result = storage.shouldTrack(Event.GrowthData.FirstWeekSeriesActivity)
assertTrue(result)
}
@Test
fun `GIVEN that app has been used for 3 days but not consecutively WHEN checked for first week activity THEN event will be not sent`() = runTest(dispatcher) {
val tomorrow = calendarStart.createNextDay()
val fourDaysFromNow = tomorrow.createNextDay().createNextDay()
every { settings.firstWeekDaysOfUseGrowthData = any() } returns Unit
every { settings.firstWeekDaysOfUseGrowthData } returns setOf(calendarStart, tomorrow, fourDaysFromNow).toStrings()
every { settings.firstWeekSeriesGrowthSent } returns false
val result = storage.shouldTrack(Event.GrowthData.FirstWeekSeriesActivity)
assertFalse(result)
}
@Test
fun `GIVEN that app has been used for 3 days consecutively but not within first week WHEN checked for first week activity THEN event will be not sent`() = runTest(dispatcher) {
val tomorrow = calendarStart.createNextDay()
val thirdDay = tomorrow.createNextDay()
val installTime9DaysEarlier = calendarStart.timeInMillis - (dayMillis * 9)
every { settings.firstWeekDaysOfUseGrowthData } returns setOf(calendarStart, tomorrow, thirdDay).toStrings()
every { settings.firstWeekSeriesGrowthSent } returns false
installTime = installTime9DaysEarlier
val result = storage.shouldTrack(Event.GrowthData.FirstWeekSeriesActivity)
assertFalse(result)
}
@Test
fun `GIVEN that first week activity has already been sent WHEN checked for first week activity THEN event will be not sent`() = runTest(dispatcher) {
val tomorrow = calendarStart.createNextDay()
val thirdDay = tomorrow.createNextDay()
every { settings.firstWeekDaysOfUseGrowthData } returns setOf(calendarStart, tomorrow, thirdDay).toStrings()
every { settings.firstWeekSeriesGrowthSent } returns true
val result = storage.shouldTrack(Event.GrowthData.FirstWeekSeriesActivity)
assertFalse(result)
}
@Test
fun `GIVEN that first week activity is not sent WHEN checked to send THEN current day is added to rolling days`() = runTest(dispatcher) {
val captureRolling = slot<Set<String>>()
val previousDay = calendarStart.createPreviousDay()
every { settings.firstWeekDaysOfUseGrowthData } returns setOf(previousDay).toStrings()
every { settings.firstWeekDaysOfUseGrowthData = capture(captureRolling) } returns Unit
every { settings.firstWeekSeriesGrowthSent } returns false
storage.shouldTrack(Event.GrowthData.FirstWeekSeriesActivity)
assertTrue(captureRolling.captured.contains(formatter.format(calendarStart.time)))
}
@Test
fun `WHEN first week activity state updated THEN settings updated accordingly`() = runTest(dispatcher) {
val captureSent = slot<Boolean>()
every { settings.firstWeekSeriesGrowthSent } returns false
every { settings.firstWeekSeriesGrowthSent = capture(captureSent) } returns Unit
storage.updateSentState(Event.GrowthData.FirstWeekSeriesActivity)
assertTrue(captureSent.captured)
}
@Test
fun `GIVEN not yet in recording window WHEN checking to track THEN days of use still updated`() = runTest(dispatcher) {
shouldSendGenerally = false
val captureSlot = slot<Set<String>>()
every { settings.firstWeekDaysOfUseGrowthData } returns setOf()
every { settings.firstWeekDaysOfUseGrowthData = capture(captureSlot) } returns Unit
storage.shouldTrack(Event.GrowthData.FirstWeekSeriesActivity)
assertTrue(captureSlot.captured.isNotEmpty())
}
@Test
fun `GIVEN outside first week after install WHEN checking to track THEN days of use is not updated`() = runTest(dispatcher) {
val captureSlot = slot<Set<String>>()
every { settings.firstWeekDaysOfUseGrowthData } returns setOf()
every { settings.firstWeekDaysOfUseGrowthData = capture(captureSlot) } returns Unit
installTime = calendarStart.timeInMillis - (dayMillis * 9)
storage.shouldTrack(Event.GrowthData.FirstWeekSeriesActivity)
assertFalse(captureSlot.isCaptured)
}
@Test
fun `GIVEN serp ad clicked event already sent WHEN checking to track serp ad clicked THEN event will not be sent`() = runTest(dispatcher) {
every { settings.adClickGrowthSent } returns true
val result = storage.shouldTrack(Event.GrowthData.SerpAdClicked)
assertFalse(result)
}
@Test
fun `GIVEN serp ad clicked event not sent WHEN checking to track serp ad clicked THEN event will be sent`() = runTest(dispatcher) {
every { settings.adClickGrowthSent } returns false
val result = storage.shouldTrack(Event.GrowthData.SerpAdClicked)
assertTrue(result)
}
@Test
fun `GIVEN usage time has not passed threshold and has not been sent WHEN checking to track THEN event will not be sent`() = runTest(dispatcher) {
every { settings.usageTimeGrowthData } returns usageThresholdMillis - 1
every { settings.usageTimeGrowthSent } returns false
val result = storage.shouldTrack(Event.GrowthData.UsageThreshold)
assertFalse(result)
}
@Test
fun `GIVEN usage time has passed threshold and has not been sent WHEN checking to track THEN event will be sent`() = runTest(dispatcher) {
every { settings.usageTimeGrowthData } returns usageThresholdMillis + 1
every { settings.usageTimeGrowthSent } returns false
val result = storage.shouldTrack(Event.GrowthData.UsageThreshold)
assertTrue(result)
}
@Test
fun `GIVEN usage time growth has not been sent and within first day WHEN registering as usage recorder THEN will be registered`() {
val application = mockk<Application>()
every { settings.usageTimeGrowthSent } returns false
every { application.registerActivityLifecycleCallbacks(any()) } returns Unit
storage.tryRegisterAsUsageRecorder(application)
verify { application.registerActivityLifecycleCallbacks(any()) }
}
@Test
fun `GIVEN usage time growth has not been sent and not within first day WHEN registering as usage recorder THEN will not be registered`() {
val application = mockk<Application>()
installTime = System.currentTimeMillis() - dayMillis * 2
every { settings.usageTimeGrowthSent } returns false
storage.tryRegisterAsUsageRecorder(application)
verify(exactly = 0) { application.registerActivityLifecycleCallbacks(any()) }
}
@Test
fun `GIVEN usage time growth has been sent WHEN registering as usage recorder THEN will not be registered`() {
val application = mockk<Application>()
every { settings.usageTimeGrowthSent } returns true
storage.tryRegisterAsUsageRecorder(application)
verify(exactly = 0) { application.registerActivityLifecycleCallbacks(any()) }
}
@Test
fun `WHEN updating usage state THEN storage will be delegated to settings`() {
val initial = 10L
val update = 15L
val slot = slot<Long>()
every { settings.usageTimeGrowthData } returns initial
every { settings.usageTimeGrowthData = capture(slot) } returns Unit
storage.updateUsageState(update)
assertEquals(slot.captured, initial + update)
}
@Test
fun `WHEN usage recorder receives onResume and onPause callbacks THEN it will store usage length`() {
val storage = mockk<MetricsStorage>()
val activity = mockk<Activity>()
val slot = slot<Long>()
every { storage.updateUsageState(capture(slot)) } returns Unit
every { activity.componentName } returns mock()
val usageRecorder = DefaultMetricsStorage.UsageRecorder(storage)
val startTime = System.currentTimeMillis()
usageRecorder.onActivityResumed(activity)
usageRecorder.onActivityPaused(activity)
val stopTime = System.currentTimeMillis()
assertTrue(slot.captured < stopTime - startTime)
}
@Test
fun `GIVEN that it has been less than 24 hours since last resumed sent WHEN checked for sending THEN will not be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
every { settings.resumeGrowthLastSent } returns currentTime
val result = storage.shouldTrack(Event.GrowthData.FirstAppOpenForDay)
assertFalse(result)
}
@Test
fun `GIVEN that it has been more than 24 hours since last resumed sent WHEN checked for sending THEN will be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis + 1)
every { settings.resumeGrowthLastSent } returns currentTime - 1000 * 60 * 60 * 24 * 2
val result = storage.shouldTrack(Event.GrowthData.FirstAppOpenForDay)
assertTrue(result)
}
@Test
fun `WHEN last resumed state updated THEN settings updated accordingly`() = runTest(dispatcher) {
val updateSlot = slot<Long>()
every { settings.resumeGrowthLastSent } returns 0
every { settings.resumeGrowthLastSent = capture(updateSlot) } returns Unit
storage.updateSentState(Event.GrowthData.FirstAppOpenForDay)
assertTrue(updateSlot.captured > 0)
}
@Test
fun `GIVEN that it has been less than 24 hours since uri load sent WHEN checked for sending THEN will not be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
every { settings.uriLoadGrowthLastSent } returns currentTime
val result = storage.shouldTrack(Event.GrowthData.FirstUriLoadForDay)
assertFalse(result)
}
@Test
fun `GIVEN that it has been more than 24 hours since uri load sent WHEN checked for sending THEN will be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis + 1)
every { settings.uriLoadGrowthLastSent } returns currentTime - 1000 * 60 * 60 * 24 * 2
val result = storage.shouldTrack(Event.GrowthData.FirstUriLoadForDay)
assertTrue(result)
}
@Test
fun `WHEN uri load updated THEN settings updated accordingly`() = runTest(dispatcher) {
val updateSlot = slot<Long>()
every { settings.uriLoadGrowthLastSent } returns 0
every { settings.uriLoadGrowthLastSent = capture(updateSlot) } returns Unit
storage.updateSentState(Event.GrowthData.FirstUriLoadForDay)
assertTrue(updateSlot.captured > 0)
}
@Test
fun `GIVEN first week activated days of use and search use thresholds reached THEN will be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 5)
every { settings.growthEarlyUseCount.value } returns 3
every { settings.growthEarlySearchUsed } returns true
every { settings.growthUserActivatedSent } returns false
val result = storage.shouldTrack(Event.GrowthData.UserActivated(fromSearch = false))
assertTrue(result)
}
@Test
fun `GIVEN first week activated days of use threshold not reached THEN will not be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 5)
every { settings.growthEarlyUseCount.value } returns 1
every { settings.growthEarlySearchUsed } returns true
every { settings.growthUserActivatedSent } returns false
val result = storage.shouldTrack(Event.GrowthData.UserActivated(fromSearch = false))
assertFalse(result)
}
@Test
fun `GIVEN first week activated search use threshold not reached THEN will not be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 5)
every { settings.growthEarlyUseCount.value } returns 3
every { settings.growthEarlySearchUsed } returns false
every { settings.growthUserActivatedSent } returns false
val result = storage.shouldTrack(Event.GrowthData.UserActivated(fromSearch = false))
assertFalse(result)
}
@Test
fun `GIVEN first week activated already sent WHEN first week activated signal sent THEN userActivated will not be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 5)
every { settings.growthEarlyUseCount.value } returns 3
every { settings.growthEarlySearchUsed } returns true
every { settings.growthUserActivatedSent } returns true
val result = storage.shouldTrack(Event.GrowthData.UserActivated(fromSearch = false))
assertFalse(result)
}
@Test
fun `WHEN first week usage signal is sent a full day after last sent THEN settings will be updated accordingly`() = runTest(dispatcher) {
val captureSent = slot<Long>()
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 3)
every { settings.growthEarlyUseCount.value } returns 1
every { settings.growthEarlyUseCount.increment() } just Runs
every { settings.growthEarlyUseCountLastIncrement } returns 0L
every { settings.growthEarlyUseCountLastIncrement = capture(captureSent) } returns Unit
storage.updatePersistentState(Event.GrowthData.UserActivated(fromSearch = false))
assertTrue(captureSent.captured > 0L)
}
@Test
fun `WHEN first week usage signal is sent less than a full day after last sent THEN settings will not be updated`() = runTest(dispatcher) {
val captureSent = slot<Long>()
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 3)
val lastUsageIncrementTime = currentTime - (dayMillis / 2)
every { settings.growthEarlyUseCount.value } returns 1
every { settings.growthEarlyUseCountLastIncrement } returns lastUsageIncrementTime
every { settings.growthEarlyUseCountLastIncrement = capture(captureSent) } returns Unit
storage.updatePersistentState(Event.GrowthData.UserActivated(fromSearch = false))
assertFalse(captureSent.isCaptured)
}
@Test
fun `WHEN first week search activity is sent in second half of first week THEN settings will be updated`() = runTest(dispatcher) {
val captureSent = slot<Boolean>()
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 3) - 100
every { settings.growthEarlySearchUsed } returns false
every { settings.growthEarlySearchUsed = capture(captureSent) } returns Unit
storage.updatePersistentState(Event.GrowthData.UserActivated(fromSearch = true))
assertTrue(captureSent.captured)
}
@Test
fun `WHEN first week search activity is sent in first half of first week THEN settings will not be updated`() = runTest(dispatcher) {
val captureSent = slot<Boolean>()
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 3) + 100
every { settings.growthEarlySearchUsed } returns false
every { settings.growthEarlySearchUsed = capture(captureSent) } returns Unit
storage.updatePersistentState(Event.GrowthData.UserActivated(fromSearch = true))
assertFalse(captureSent.isCaptured)
}
private fun Calendar.copy() = clone() as Calendar
private fun Calendar.createNextDay() = copy().apply {
add(Calendar.DAY_OF_MONTH, 1)
}
private fun Calendar.createPreviousDay() = copy().apply {
add(Calendar.DAY_OF_MONTH, -1)
}
private fun Set<Calendar>.toStrings() = map {
formatter.format(it.time)
}.toSet()
}