parent
51a8fb4a39
commit
02f6e6868e
|
@ -2073,6 +2073,19 @@ installation:
|
|||
notification_emails:
|
||||
- fenix-core@mozilla.com
|
||||
expires: "2020-09-01"
|
||||
identifier:
|
||||
send_in_pings:
|
||||
- installation
|
||||
type: string
|
||||
description: |
|
||||
The hashed and salted GAID. Used for a short term installation validation test.
|
||||
bugs:
|
||||
- https://github.com/mozilla-mobile/fenix/issues/10426
|
||||
data_reviews:
|
||||
- https://github.com/mozilla-mobile/fenix/pull/10446#issuecomment-624816258
|
||||
notification_emails:
|
||||
- fenix-core@mozilla.com
|
||||
expires: "2020-05-10"
|
||||
|
||||
browser.search:
|
||||
with_ads:
|
||||
|
|
|
@ -6,23 +6,14 @@ package org.mozilla.fenix.components.metrics
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Base64
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.google.android.gms.ads.identifier.AdvertisingIdClient
|
||||
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import org.mozilla.fenix.GleanMetrics.Activation
|
||||
import org.mozilla.fenix.GleanMetrics.Pings
|
||||
import java.io.IOException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
import org.mozilla.fenix.components.metrics.MetricsUtils.getHashedIdentifier
|
||||
|
||||
class ActivationPing(private val context: Context) {
|
||||
companion object {
|
||||
|
@ -62,92 +53,6 @@ class ActivationPing(private val context: Context) {
|
|||
prefs.edit().putBoolean("ping_sent", true).apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the Google Advertising API to get the Google Advertising ID.
|
||||
*
|
||||
* This is meant to be used off the main thread. The API will throw an
|
||||
* exception and we will print a log message otherwise.
|
||||
*
|
||||
* @return a String containing the Google Advertising ID or null.
|
||||
*/
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun getAdvertisingID(): String? {
|
||||
return try {
|
||||
AdvertisingIdClient.getAdvertisingIdInfo(context).id
|
||||
} catch (e: GooglePlayServicesNotAvailableException) {
|
||||
Logger.debug("ActivationPing - Google Play not installed on the device")
|
||||
null
|
||||
} catch (e: GooglePlayServicesRepairableException) {
|
||||
Logger.debug("ActivationPing - recoverable error connecting to Google Play Services")
|
||||
null
|
||||
} catch (e: IllegalStateException) {
|
||||
// This is unlikely to happen, as this should be running off the main thread.
|
||||
Logger.debug("ActivationPing - AdvertisingIdClient must be called off the main thread")
|
||||
null
|
||||
} catch (e: IOException) {
|
||||
Logger.debug("ActivationPing - unable to connect to Google Play Services")
|
||||
null
|
||||
} catch (e: NullPointerException) {
|
||||
Logger.debug("ActivationPing - no Google Advertising ID available")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the salt to use for hashing. This is a convenience
|
||||
* function to help with unit tests.
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun getHashingSalt(): String = "org.mozilla.fenix-salt"
|
||||
|
||||
/**
|
||||
* Produces an hashed version of the Google Advertising ID.
|
||||
* We want users using more than one of our products to report a different
|
||||
* ID in each of them. This function runs off the main thread and is CPU-bound.
|
||||
*
|
||||
* @return an hashed and salted Google Advertising ID or null if it was not possible
|
||||
* to get the Google Advertising ID.
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal suspend fun getHashedIdentifier(): String? = withContext(Dispatchers.Default) {
|
||||
getAdvertisingID()?.let { unhashedID ->
|
||||
// Add some salt to the ID, before hashing. For this specific use-case, it's ok
|
||||
// to use the same salt value for all the hashes. We want hashes to be stable
|
||||
// within a single product, but we don't want hashes to be the same across different
|
||||
// products (e.g. Fennec vs Fenix).
|
||||
val salt = getHashingSalt()
|
||||
|
||||
// Apply hashing.
|
||||
try {
|
||||
// Note that we intentionally want to use slow hashing functions here in order
|
||||
// to increase the cost of potentially repeatedly guess the original unhashed
|
||||
// identifier.
|
||||
val keySpec = PBEKeySpec(
|
||||
unhashedID.toCharArray(),
|
||||
salt.toByteArray(),
|
||||
PBKDF2_ITERATIONS,
|
||||
PBKDF2_KEY_LEN_BITS)
|
||||
|
||||
val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
val hashedBytes = keyFactory.generateSecret(keySpec).encoded
|
||||
Base64.encodeToString(hashedBytes, Base64.NO_WRAP)
|
||||
} catch (e: java.lang.NullPointerException) {
|
||||
Logger.error("ActivationPing - missing or wrong salt parameter")
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Logger.error("ActivationPing - wrong parameter", e)
|
||||
null
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
Logger.error("ActivationPing - algorithm not available")
|
||||
null
|
||||
} catch (e: InvalidKeySpecException) {
|
||||
Logger.error("ActivationPing - invalid key spec")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the metrics and triggers the 'activation' ping.
|
||||
* This is a separate function to simplify unit-testing.
|
||||
|
@ -158,7 +63,7 @@ class ActivationPing(private val context: Context) {
|
|||
Activation.activationId.generateAndSet()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val hashedId = getHashedIdentifier()
|
||||
val hashedId = getHashedIdentifier(context)
|
||||
if (hashedId != null) {
|
||||
Logger.info("ActivationPing - generating ping with the hashed id")
|
||||
// We have a valid, hashed Google Advertising ID.
|
||||
|
|
|
@ -11,6 +11,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import org.mozilla.fenix.GleanMetrics.Activation
|
||||
import org.mozilla.fenix.GleanMetrics.Installation
|
||||
import org.mozilla.fenix.GleanMetrics.Pings
|
||||
import org.mozilla.fenix.ext.settings
|
||||
|
@ -64,6 +65,10 @@ class InstallationPing(private val context: Context) {
|
|||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
MetricsUtils.getHashedIdentifier(context)?.let {
|
||||
Activation.identifier.set(it)
|
||||
}
|
||||
|
||||
Pings.installation.submit()
|
||||
markAsTriggered()
|
||||
}
|
||||
|
|
|
@ -5,10 +5,23 @@
|
|||
package org.mozilla.fenix.components.metrics
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.google.android.gms.ads.identifier.AdvertisingIdClient
|
||||
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint
|
||||
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
|
||||
import org.mozilla.fenix.ext.searchEngineManager
|
||||
import java.io.IOException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
|
||||
object MetricsUtils {
|
||||
fun createSearchEvent(
|
||||
|
@ -51,4 +64,91 @@ object MetricsUtils {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the salt to use for hashing. This is a convenience
|
||||
* function to help with unit tests.
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun getHashingSalt(): String = "org.mozilla.fenix-salt"
|
||||
|
||||
/**
|
||||
* Query the Google Advertising API to get the Google Advertising ID.
|
||||
*
|
||||
* This is meant to be used off the main thread. The API will throw an
|
||||
* exception and we will print a log message otherwise.
|
||||
*
|
||||
* @return a String containing the Google Advertising ID or null.
|
||||
*/
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun getAdvertisingID(context: Context): String? {
|
||||
return try {
|
||||
AdvertisingIdClient.getAdvertisingIdInfo(context).id
|
||||
} catch (e: GooglePlayServicesNotAvailableException) {
|
||||
Logger.debug("ActivationPing - Google Play not installed on the device")
|
||||
null
|
||||
} catch (e: GooglePlayServicesRepairableException) {
|
||||
Logger.debug("ActivationPing - recoverable error connecting to Google Play Services")
|
||||
null
|
||||
} catch (e: IllegalStateException) {
|
||||
// This is unlikely to happen, as this should be running off the main thread.
|
||||
Logger.debug("ActivationPing - AdvertisingIdClient must be called off the main thread")
|
||||
null
|
||||
} catch (e: IOException) {
|
||||
Logger.debug("ActivationPing - unable to connect to Google Play Services")
|
||||
null
|
||||
} catch (e: NullPointerException) {
|
||||
Logger.debug("ActivationPing - no Google Advertising ID available")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a hashed version of the Google Advertising ID.
|
||||
* We want users using more than one of our products to report a different
|
||||
* ID in each of them. This function runs off the main thread and is CPU-bound.
|
||||
*
|
||||
* @return an hashed and salted Google Advertising ID or null if it was not possible
|
||||
* to get the Google Advertising ID.
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
suspend fun getHashedIdentifier(context: Context): String? = withContext(Dispatchers.Default) {
|
||||
getAdvertisingID(context)?.let { unhashedID ->
|
||||
// Add some salt to the ID, before hashing. For this specific use-case, it's ok
|
||||
// to use the same salt value for all the hashes. We want hashes to be stable
|
||||
// within a single product, but we don't want hashes to be the same across different
|
||||
// products (e.g. Fennec vs Fenix).
|
||||
val salt = getHashingSalt()
|
||||
|
||||
// Apply hashing.
|
||||
try {
|
||||
// Note that we intentionally want to use slow hashing functions here in order
|
||||
// to increase the cost of potentially repeatedly guess the original unhashed
|
||||
// identifier.
|
||||
val keySpec = PBEKeySpec(
|
||||
unhashedID.toCharArray(),
|
||||
salt.toByteArray(),
|
||||
ActivationPing.PBKDF2_ITERATIONS,
|
||||
ActivationPing.PBKDF2_KEY_LEN_BITS
|
||||
)
|
||||
|
||||
val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
val hashedBytes = keyFactory.generateSecret(keySpec).encoded
|
||||
Base64.encodeToString(hashedBytes, Base64.NO_WRAP)
|
||||
} catch (e: java.lang.NullPointerException) {
|
||||
Logger.error("ActivationPing - missing or wrong salt parameter")
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Logger.error("ActivationPing - wrong parameter", e)
|
||||
null
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
Logger.error("ActivationPing - algorithm not available")
|
||||
null
|
||||
} catch (e: InvalidKeySpecException) {
|
||||
Logger.error("ActivationPing - invalid key spec")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,104 +4,15 @@
|
|||
|
||||
package org.mozilla.fenix.components.metrics
|
||||
|
||||
import android.util.Base64
|
||||
import com.google.android.gms.ads.identifier.AdvertisingIdClient
|
||||
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyString
|
||||
import java.io.IOException
|
||||
|
||||
internal class ActivationPingTest {
|
||||
@Ignore("This test has side-effects that cause it to fail other unrelated tests.")
|
||||
@Test
|
||||
fun `getAdvertisingID() returns null if the API throws`() {
|
||||
mockkStatic(AdvertisingIdClient::class)
|
||||
|
||||
val exceptions = listOf(
|
||||
GooglePlayServicesNotAvailableException(1),
|
||||
GooglePlayServicesRepairableException(0, anyString(), any()),
|
||||
IllegalStateException(),
|
||||
IOException()
|
||||
)
|
||||
|
||||
val ap = ActivationPing(mockk())
|
||||
exceptions.forEach {
|
||||
every {
|
||||
AdvertisingIdClient.getAdvertisingIdInfo(any())
|
||||
} throws it
|
||||
|
||||
assertNull(ap.getAdvertisingID())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAdvertisingID() returns null if the API returns null info`() {
|
||||
mockkStatic(AdvertisingIdClient::class)
|
||||
every { AdvertisingIdClient.getAdvertisingIdInfo(any()) } returns null
|
||||
|
||||
val ap = ActivationPing(mockk())
|
||||
assertNull(ap.getAdvertisingID())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAdvertisingID() returns a valid string if the API returns a valid ID`() {
|
||||
val testId = "test-value-id"
|
||||
|
||||
mockkStatic(AdvertisingIdClient::class)
|
||||
every {
|
||||
AdvertisingIdClient.getAdvertisingIdInfo(any())
|
||||
} returns AdvertisingIdClient.Info(testId, false)
|
||||
|
||||
val ap = ActivationPing(mockk())
|
||||
assertEquals(testId, ap.getAdvertisingID())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getHashedIdentifier() returns an hashed identifier`() {
|
||||
val testId = "test-value-id"
|
||||
val testPackageName = "org.mozilla-test.fenix"
|
||||
val mockedHexReturn = "mocked-HEX"
|
||||
|
||||
// Mock the Base64 to record the byte array that is passed in,
|
||||
// which is the actual digest. We can't simply test the return value
|
||||
// of |getHashedIdentifier| as these Android tests require us to mock
|
||||
// Android-specific APIs.
|
||||
mockkStatic(Base64::class)
|
||||
val shaDigest = slot<ByteArray>()
|
||||
every {
|
||||
Base64.encodeToString(capture(shaDigest), any())
|
||||
} returns mockedHexReturn
|
||||
|
||||
// Get the hash identifier.
|
||||
val mockAp = spyk(ActivationPing(mockk()))
|
||||
every { mockAp.getAdvertisingID() } returns testId
|
||||
every { mockAp.getHashingSalt() } returns testPackageName
|
||||
runBlocking {
|
||||
assertEquals(mockedHexReturn, mockAp.getHashedIdentifier())
|
||||
}
|
||||
|
||||
// Check that the digest of the identifier matches with what we expect.
|
||||
// Please note that in the real world, Base64.encodeToString would encode
|
||||
// this to something much shorter, which we'd send with the ping.
|
||||
val expectedDigestBytes =
|
||||
"[52, -79, -84, 79, 101, 22, -82, -44, -44, -14, 21, 15, 48, 88, -94, -74, -8, 25, -72, -120, -37, 108, 47, 16, 2, -37, 126, 41, 102, -92, 103, 24]"
|
||||
assertEquals(expectedDigestBytes, shaDigest.captured.contentToString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `checkAndSend() triggers the ping if it wasn't marked as triggered`() {
|
||||
val mockAp = spyk(ActivationPing(mockk()), recordPrivateCalls = true)
|
||||
|
|
|
@ -1,56 +1,96 @@
|
|||
package org.mozilla.fenix.components.metrics
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import com.google.android.gms.ads.identifier.AdvertisingIdClient
|
||||
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.slot
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mockito.ArgumentMatchers
|
||||
import java.io.IOException
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class MetricsUtilsTest {
|
||||
|
||||
private val context: Context = mockk(relaxed = true)
|
||||
|
||||
@Ignore("This test has side-effects that cause it to fail other unrelated tests.")
|
||||
@Test
|
||||
fun createSearchEvent() {
|
||||
val engine: SearchEngine = mockk(relaxed = true)
|
||||
val context = testContext
|
||||
fun `getAdvertisingID() returns null if the API throws`() {
|
||||
val exceptions = listOf(
|
||||
GooglePlayServicesNotAvailableException(1),
|
||||
GooglePlayServicesRepairableException(0, ArgumentMatchers.anyString(), ArgumentMatchers.any()),
|
||||
IllegalStateException(),
|
||||
IOException()
|
||||
)
|
||||
|
||||
every { engine.identifier } returns ENGINE_SOURCE_IDENTIFIER
|
||||
exceptions.forEach {
|
||||
every {
|
||||
AdvertisingIdClient.getAdvertisingIdInfo(any())
|
||||
} throws it
|
||||
|
||||
assertEquals(
|
||||
"$ENGINE_SOURCE_IDENTIFIER.suggestion",
|
||||
MetricsUtils.createSearchEvent(
|
||||
engine,
|
||||
context,
|
||||
Event.PerformedSearch.SearchAccessPoint.SUGGESTION
|
||||
)?.eventSource?.countLabel
|
||||
)
|
||||
assertEquals(
|
||||
"$ENGINE_SOURCE_IDENTIFIER.action",
|
||||
MetricsUtils.createSearchEvent(
|
||||
engine,
|
||||
context,
|
||||
Event.PerformedSearch.SearchAccessPoint.ACTION
|
||||
)?.eventSource?.countLabel
|
||||
)
|
||||
assertEquals(
|
||||
"$ENGINE_SOURCE_IDENTIFIER.widget",
|
||||
MetricsUtils.createSearchEvent(
|
||||
engine,
|
||||
context,
|
||||
Event.PerformedSearch.SearchAccessPoint.WIDGET
|
||||
)?.eventSource?.countLabel
|
||||
)
|
||||
assertEquals(
|
||||
"$ENGINE_SOURCE_IDENTIFIER.shortcut",
|
||||
MetricsUtils.createSearchEvent(
|
||||
engine,
|
||||
context,
|
||||
Event.PerformedSearch.SearchAccessPoint.SHORTCUT
|
||||
)?.eventSource?.countLabel
|
||||
)
|
||||
Assert.assertNull(MetricsUtils.getAdvertisingID(context))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAdvertisingID() returns null if the API returns null info`() {
|
||||
mockkStatic(AdvertisingIdClient::class)
|
||||
every { AdvertisingIdClient.getAdvertisingIdInfo(any()) } returns null
|
||||
|
||||
Assert.assertNull(MetricsUtils.getAdvertisingID(context))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAdvertisingID() returns a valid string if the API returns a valid ID`() {
|
||||
val testId = "test-value-id"
|
||||
|
||||
mockkStatic(AdvertisingIdClient::class)
|
||||
every {
|
||||
AdvertisingIdClient.getAdvertisingIdInfo(any())
|
||||
} returns AdvertisingIdClient.Info(testId, false)
|
||||
|
||||
assertEquals(testId, MetricsUtils.getAdvertisingID(context))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getHashedIdentifier() returns a hashed identifier`() {
|
||||
val testId = "test-value-id"
|
||||
val testPackageName = "org.mozilla-test.fenix"
|
||||
val mockedHexReturn = "mocked-HEX"
|
||||
|
||||
// Mock the Base64 to record the byte array that is passed in,
|
||||
// which is the actual digest. We can't simply test the return value
|
||||
// of |getHashedIdentifier| as these Android tests require us to mock
|
||||
// Android-specific APIs.
|
||||
mockkStatic(Base64::class)
|
||||
val shaDigest = slot<ByteArray>()
|
||||
every {
|
||||
Base64.encodeToString(capture(shaDigest), any())
|
||||
} returns mockedHexReturn
|
||||
|
||||
// Get the hash identifier.
|
||||
mockkObject(MetricsUtils)
|
||||
every { MetricsUtils.getAdvertisingID(context) } returns testId
|
||||
every { MetricsUtils.getHashingSalt() } returns testPackageName
|
||||
runBlocking {
|
||||
assertEquals(mockedHexReturn, MetricsUtils.getHashedIdentifier(context))
|
||||
}
|
||||
|
||||
// Check that the digest of the identifier matches with what we expect.
|
||||
// Please note that in the real world, Base64.encodeToString would encode
|
||||
// this to something much shorter, which we'd send with the ping.
|
||||
val expectedDigestBytes =
|
||||
"[52, -79, -84, 79, 101, 22, -82, -44, -44, -14, 21, 15, 48, 88, -94, -74, -8, 25, -72, -120, -37, 108, 47, 16, 2, -37, 126, 41, 102, -92, 103, 24]"
|
||||
assertEquals(expectedDigestBytes, shaDigest.captured.contentToString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/* 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 io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
/**
|
||||
* Just the Roboelectric tests for MetricsUtil. Splitting these files out means our other tests will run more quickly.
|
||||
* FenixRobolectricTestRunner also breaks our ability to use mockkStatic on Base64.
|
||||
*/
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class MetricsUtilsTestRoboelectric {
|
||||
|
||||
@Test
|
||||
fun createSearchEvent() {
|
||||
val context = testContext
|
||||
val engine: SearchEngine = mockk(relaxed = true)
|
||||
|
||||
every { engine.identifier } returns MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER
|
||||
|
||||
Assert.assertEquals(
|
||||
"${MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER}.suggestion",
|
||||
MetricsUtils.createSearchEvent(
|
||||
engine,
|
||||
context,
|
||||
Event.PerformedSearch.SearchAccessPoint.SUGGESTION
|
||||
)?.eventSource?.countLabel
|
||||
)
|
||||
Assert.assertEquals(
|
||||
"${MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER}.action",
|
||||
MetricsUtils.createSearchEvent(
|
||||
engine,
|
||||
context,
|
||||
Event.PerformedSearch.SearchAccessPoint.ACTION
|
||||
)?.eventSource?.countLabel
|
||||
)
|
||||
Assert.assertEquals(
|
||||
"${MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER}.widget",
|
||||
MetricsUtils.createSearchEvent(
|
||||
engine,
|
||||
context,
|
||||
Event.PerformedSearch.SearchAccessPoint.WIDGET
|
||||
)?.eventSource?.countLabel
|
||||
)
|
||||
Assert.assertEquals(
|
||||
"${MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER}.shortcut",
|
||||
MetricsUtils.createSearchEvent(
|
||||
engine,
|
||||
context,
|
||||
Event.PerformedSearch.SearchAccessPoint.SHORTCUT
|
||||
)?.eventSource?.countLabel
|
||||
)
|
||||
}
|
||||
}
|
|
@ -223,6 +223,7 @@ The following metrics are added to the ping:
|
|||
| installation.adgroup |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |The name of the AdGroup that was used to source this installation. |[1](https://github.com/mozilla-mobile/fenix/pull/8074#issuecomment-586480836)||2020-09-01 |
|
||||
| installation.campaign |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |The name of the campaign that is responsible for this installation. |[1](https://github.com/mozilla-mobile/fenix/pull/8074#issuecomment-586512202)||2020-09-01 |
|
||||
| installation.creative |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |The identifier of the creative material that the user interacted with. |[1](https://github.com/mozilla-mobile/fenix/pull/8074#issuecomment-586512202)||2020-09-01 |
|
||||
| installation.identifier |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |The hashed and salted GAID. Used for a short term installation validation test. |[1](https://github.com/mozilla-mobile/fenix/pull/10446#issuecomment-624816258)||2020-05-10 |
|
||||
| installation.network |[string](https://mozilla.github.io/glean/book/user/metrics/string.html) |The name of the Network that sourced this installation. |[1](https://github.com/mozilla-mobile/fenix/pull/8074#issuecomment-586512202)||2020-09-01 |
|
||||
| installation.timestamp |[datetime](https://mozilla.github.io/glean/book/user/metrics/datetime.html) |The date and time of the installation. |[1](https://github.com/mozilla-mobile/fenix/pull/8074#issuecomment-586512202)||2020-09-01 |
|
||||
|
||||
|
|
Loading…
Reference in New Issue