For #6558 - cleanup + added unit tests
parent
f03d65b13d
commit
c4d76dce5a
|
@ -2007,11 +2007,12 @@ browser.search:
|
||||||
description: >
|
description: >
|
||||||
Records counts of SERP pages with adverts displayed. The key format is ‘<provider-name>’.
|
Records counts of SERP pages with adverts displayed. The key format is ‘<provider-name>’.
|
||||||
send_in_pings:
|
send_in_pings:
|
||||||
|
- baseline
|
||||||
- metrics
|
- metrics
|
||||||
bugs:
|
bugs:
|
||||||
- https://github.com/mozilla-mobile/fenix/issues/6558
|
- https://github.com/mozilla-mobile/fenix/issues/6558
|
||||||
data_reviews:
|
data_reviews:
|
||||||
- https://github.com/mozilla-mobile/fenix/issues/6558
|
- https://github.com/mozilla-mobile/fenix/pull/10112
|
||||||
notification_emails:
|
notification_emails:
|
||||||
- fenix-core@mozilla.com
|
- fenix-core@mozilla.com
|
||||||
expires: "2020-09-01"
|
expires: "2020-09-01"
|
||||||
|
@ -2020,11 +2021,12 @@ browser.search:
|
||||||
description: >
|
description: >
|
||||||
Records clicks of adverts on SERP pages. The key format is ‘<provider-name>’.
|
Records clicks of adverts on SERP pages. The key format is ‘<provider-name>’.
|
||||||
send_in_pings:
|
send_in_pings:
|
||||||
|
- baseline
|
||||||
- metrics
|
- metrics
|
||||||
bugs:
|
bugs:
|
||||||
- https://github.com/mozilla-mobile/fenix/issues/6558
|
- https://github.com/mozilla-mobile/fenix/issues/6558
|
||||||
data_reviews:
|
data_reviews:
|
||||||
- https://github.com/mozilla-mobile/fenix/issues/6558
|
- https://github.com/mozilla-mobile/fenix/pull/10112
|
||||||
notification_emails:
|
notification_emails:
|
||||||
- fenix-core@mozilla.com
|
- fenix-core@mozilla.com
|
||||||
expires: "2020-09-01"
|
expires: "2020-09-01"
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
function collect_urls(urls) {
|
/* 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/. */
|
||||||
|
|
||||||
|
const ADLINK_CHECK_TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
function collectLinks(urls) {
|
||||||
let anchors = document.getElementsByTagName("a");
|
let anchors = document.getElementsByTagName("a");
|
||||||
for (let anchor of anchors) {
|
for (let anchor of anchors) {
|
||||||
if (!anchor.href) {
|
if (!anchor.href) {
|
||||||
|
@ -7,11 +13,30 @@ function collect_urls(urls) {
|
||||||
urls.push(anchor.href);
|
urls.push(anchor.href);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let urls = [];
|
|
||||||
collect_urls(urls)
|
|
||||||
|
|
||||||
let message = {
|
function sendLinks() {
|
||||||
'url': document.location.href,
|
let urls = [];
|
||||||
'urls': urls
|
collectLinks(urls);
|
||||||
|
|
||||||
|
let message = {
|
||||||
|
'url': document.location.href,
|
||||||
|
'urls': urls
|
||||||
|
};
|
||||||
|
browser.runtime.sendNativeMessage("MozacBrowserAds", message);
|
||||||
}
|
}
|
||||||
browser.runtime.sendNativeMessage("MozacBrowserAds", message);
|
|
||||||
|
var timeout;
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
timeout = setTimeout(sendLinks, ADLINK_CHECK_TIMEOUT_MS);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onpageshow = function(event) {
|
||||||
|
if (event.persisted) {
|
||||||
|
timeout = setTimeout(sendLinks, ADLINK_CHECK_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onunload = function() {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package org.mozilla.fenix.ads
|
|
||||||
|
|
||||||
data class SearchProviderCookie(
|
|
||||||
val extraCodeParam: String,
|
|
||||||
val extraCodePrefixes: List<String>,
|
|
||||||
val host: String,
|
|
||||||
val name: String,
|
|
||||||
val codeParam: String,
|
|
||||||
val codePrefixes: List<String>
|
|
||||||
)
|
|
|
@ -1,10 +1,14 @@
|
||||||
|
/* 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.browser
|
package org.mozilla.fenix.browser
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import mozilla.components.browser.session.Session
|
import mozilla.components.browser.session.Session
|
||||||
import org.mozilla.fenix.ads.AdsTelemetry
|
|
||||||
import org.mozilla.fenix.components.metrics.Event
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
import org.mozilla.fenix.components.metrics.MetricController
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
|
||||||
|
|
||||||
class TelemetrySessionObserver(
|
class TelemetrySessionObserver(
|
||||||
private val metrics: MetricController,
|
private val metrics: MetricController,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import mozilla.components.browser.session.Session
|
import mozilla.components.browser.session.Session
|
||||||
import mozilla.components.browser.session.SessionManager
|
import mozilla.components.browser.session.SessionManager
|
||||||
import org.mozilla.fenix.ads.AdsTelemetry
|
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
|
||||||
import org.mozilla.fenix.components.metrics.MetricController
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.metrics
|
import org.mozilla.fenix.ext.metrics
|
||||||
|
|
|
@ -42,7 +42,7 @@ import org.mozilla.fenix.AppRequestInterceptor
|
||||||
import org.mozilla.fenix.Config
|
import org.mozilla.fenix.Config
|
||||||
import org.mozilla.fenix.HomeActivity
|
import org.mozilla.fenix.HomeActivity
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.ads.AdsTelemetry
|
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.media.MediaService
|
import org.mozilla.fenix.media.MediaService
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
/* 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.ext
|
package org.mozilla.fenix.ext
|
||||||
|
|
||||||
import org.mozilla.fenix.ads.SearchProviderModel
|
import org.mozilla.fenix.search.telemetry.SearchProviderModel
|
||||||
|
|
||||||
fun SearchProviderModel.containsAds(urlList: List<String>): Boolean {
|
fun SearchProviderModel.containsAds(urlList: List<String>): Boolean {
|
||||||
return urlList.containsAds(this.extraAdServersRegexps)
|
return urlList.containsAds(this.extraAdServersRegexps)
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package org.mozilla.fenix.ext
|
|
||||||
|
|
||||||
import org.json.JSONArray
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
fun <T> JSONArray.toList(): List<T> {
|
|
||||||
val result = ArrayList<T>()
|
|
||||||
for (i in 0 until length()) {
|
|
||||||
result.add(get(i) as T)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* 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.search.telemetry
|
||||||
|
|
||||||
|
data class SearchProviderCookie(
|
||||||
|
val extraCodeParam: String,
|
||||||
|
val extraCodePrefixes: List<String>,
|
||||||
|
val host: String,
|
||||||
|
val name: String,
|
||||||
|
val codeParam: String,
|
||||||
|
val codePrefixes: List<String>
|
||||||
|
)
|
|
@ -1,4 +1,8 @@
|
||||||
package org.mozilla.fenix.ads
|
/* 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.search.telemetry
|
||||||
|
|
||||||
data class SearchProviderModel(
|
data class SearchProviderModel(
|
||||||
val name: String,
|
val name: String,
|
|
@ -1,5 +1,10 @@
|
||||||
package org.mozilla.fenix.ads
|
/* 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.search.telemetry.ads
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
@ -11,16 +16,19 @@ import mozilla.components.concept.engine.webextension.MessageHandler
|
||||||
import mozilla.components.concept.engine.webextension.WebExtension
|
import mozilla.components.concept.engine.webextension.WebExtension
|
||||||
import mozilla.components.lib.state.ext.flowScoped
|
import mozilla.components.lib.state.ext.flowScoped
|
||||||
import mozilla.components.support.base.log.logger.Logger
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
|
import mozilla.components.support.ktx.android.org.json.toList
|
||||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.filterChanged
|
import mozilla.components.support.ktx.kotlinx.coroutines.flow.filterChanged
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.mozilla.fenix.components.metrics.Event
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
import org.mozilla.fenix.components.metrics.MetricController
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
import org.mozilla.fenix.ext.containsAds
|
import org.mozilla.fenix.ext.containsAds
|
||||||
import org.mozilla.fenix.ext.toList
|
import org.mozilla.fenix.search.telemetry.SearchProviderCookie
|
||||||
|
import org.mozilla.fenix.search.telemetry.SearchProviderModel
|
||||||
|
|
||||||
class AdsTelemetry(private val metrics: MetricController) {
|
class AdsTelemetry(private val metrics: MetricController) {
|
||||||
|
|
||||||
private val providerList = listOf(
|
@VisibleForTesting
|
||||||
|
internal val providerList = listOf(
|
||||||
SearchProviderModel(
|
SearchProviderModel(
|
||||||
name = "google",
|
name = "google",
|
||||||
regexp = "^https:\\/\\/www\\.google\\.(?:.+)\\/search",
|
regexp = "^https:\\/\\/www\\.google\\.(?:.+)\\/search",
|
||||||
|
@ -38,7 +46,8 @@ class AdsTelemetry(private val metrics: MetricController) {
|
||||||
codePrefixes = listOf("ff"),
|
codePrefixes = listOf("ff"),
|
||||||
followOnParams = listOf("oq", "ved", "ei"),
|
followOnParams = listOf("oq", "ved", "ei"),
|
||||||
extraAdServersRegexps = listOf(
|
extraAdServersRegexps = listOf(
|
||||||
"^https:\\/\\/duckduckgo.com\\/y\\.js"
|
"^https:\\/\\/duckduckgo.com\\/y\\.js",
|
||||||
|
"^https:\\/\\/www\\.amazon\\.(?:[a-z.]{2,24}).*(?:tag=duckduckgo-)"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
SearchProviderModel(
|
SearchProviderModel(
|
||||||
|
@ -48,7 +57,7 @@ class AdsTelemetry(private val metrics: MetricController) {
|
||||||
),
|
),
|
||||||
SearchProviderModel(
|
SearchProviderModel(
|
||||||
name = "baidu",
|
name = "baidu",
|
||||||
regexp = "^https:\\/\\/www\\.baidu\\.com\\/(?:s|baidu)",
|
regexp = "^https:\\/\\/www\\.baidu\\.com\\/from=844b\\/(?:s|baidu)",
|
||||||
queryParam = "wd",
|
queryParam = "wd",
|
||||||
codeParam = "tn",
|
codeParam = "tn",
|
||||||
codePrefixes = listOf("34046034_", "monline_"),
|
codePrefixes = listOf("34046034_", "monline_"),
|
||||||
|
@ -108,7 +117,8 @@ class AdsTelemetry(private val metrics: MetricController) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getProviderForUrl(url: String): SearchProviderModel? {
|
@VisibleForTesting
|
||||||
|
internal fun getProviderForUrl(url: String): SearchProviderModel? {
|
||||||
for (provider in providerList) {
|
for (provider in providerList) {
|
||||||
if (Regex(provider.regexp).containsMatchIn(url)) {
|
if (Regex(provider.regexp).containsMatchIn(url)) {
|
||||||
return provider
|
return provider
|
||||||
|
@ -138,7 +148,8 @@ class AdsTelemetry(private val metrics: MetricController) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class AdsTelemetryContentMessageHandler : MessageHandler {
|
@VisibleForTesting
|
||||||
|
internal inner class AdsTelemetryContentMessageHandler : MessageHandler {
|
||||||
|
|
||||||
override fun onMessage(message: Any, source: EngineSession?): Any? {
|
override fun onMessage(message: Any, source: EngineSession?): Any? {
|
||||||
if (message is JSONObject) {
|
if (message is JSONObject) {
|
||||||
|
@ -164,10 +175,14 @@ class AdsTelemetry(private val metrics: MetricController) {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ADS_EXTENSION_ID = "mozacBrowserAds"
|
@VisibleForTesting
|
||||||
private const val ADS_EXTENSION_RESOURCE_URL = "resource://android/assets/extensions/ads/"
|
internal const val ADS_EXTENSION_ID = "mozacBrowserAds"
|
||||||
|
@VisibleForTesting
|
||||||
|
internal const val ADS_EXTENSION_RESOURCE_URL = "resource://android/assets/extensions/ads/"
|
||||||
|
@VisibleForTesting
|
||||||
|
internal const val ADS_MESSAGE_SESSION_URL_KEY = "url"
|
||||||
|
@VisibleForTesting
|
||||||
|
internal const val ADS_MESSAGE_DOCUMENT_URLS_KEY = "urls"
|
||||||
private const val ADS_MESSAGE_ID = "MozacBrowserAds"
|
private const val ADS_MESSAGE_ID = "MozacBrowserAds"
|
||||||
private const val ADS_MESSAGE_SESSION_URL_KEY = "url"
|
|
||||||
private const val ADS_MESSAGE_DOCUMENT_URLS_KEY = "urls"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/* 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.browser
|
||||||
|
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import mozilla.components.browser.session.Session
|
||||||
|
import mozilla.components.browser.session.SessionManager
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
|
||||||
|
|
||||||
|
class TelemetrySessionObserverTest {
|
||||||
|
|
||||||
|
private val owner: LifecycleOwner = mockk(relaxed = true)
|
||||||
|
private val sessionManager: SessionManager = mockk(relaxed = true)
|
||||||
|
private val metrics: MetricController = mockk(relaxed = true)
|
||||||
|
private val ads: AdsTelemetry = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private lateinit var singleSessionObserver: TelemetrySessionObserver
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
singleSessionObserver =
|
||||||
|
UriOpenedObserver(owner, sessionManager, metrics, ads).singleSessionObserver
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `tracks that a url was loaded`() {
|
||||||
|
val session: Session = mockk(relaxed = true)
|
||||||
|
every { session.url } returns "https://mozilla.com"
|
||||||
|
|
||||||
|
singleSessionObserver.onLoadingStateChanged(session, loading = false)
|
||||||
|
verify(exactly = 0) { metrics.track(Event.UriOpened) }
|
||||||
|
|
||||||
|
singleSessionObserver.onLoadingStateChanged(session, loading = true)
|
||||||
|
singleSessionObserver.onLoadingStateChanged(session, loading = false)
|
||||||
|
verify { metrics.track(Event.UriOpened) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `add originSessionUrl on first link of redirect chain and start chain`() {
|
||||||
|
val session: Session = mockk(relaxed = true)
|
||||||
|
val sessionUrl = "https://www.google.com/search"
|
||||||
|
val url = "www.aaa.com"
|
||||||
|
every { session.url } returns sessionUrl
|
||||||
|
singleSessionObserver.onLoadRequest(
|
||||||
|
session,
|
||||||
|
url,
|
||||||
|
triggeredByRedirect = false,
|
||||||
|
triggeredByWebContent = false
|
||||||
|
)
|
||||||
|
Assert.assertEquals(sessionUrl, singleSessionObserver.originSessionUrl)
|
||||||
|
Assert.assertEquals(url, singleSessionObserver.redirectChain[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `add to redirect chain on subsequent onLoadRequests`() {
|
||||||
|
val session: Session = mockk(relaxed = true)
|
||||||
|
val url = "https://www.google.com/search"
|
||||||
|
val newUrl = "www.aaa.com"
|
||||||
|
every { session.url } returns url
|
||||||
|
singleSessionObserver.originSessionUrl = url
|
||||||
|
singleSessionObserver.redirectChain.add(url)
|
||||||
|
singleSessionObserver.onLoadRequest(
|
||||||
|
session,
|
||||||
|
newUrl,
|
||||||
|
triggeredByRedirect = false,
|
||||||
|
triggeredByWebContent = false
|
||||||
|
)
|
||||||
|
Assert.assertEquals(url, singleSessionObserver.originSessionUrl)
|
||||||
|
Assert.assertEquals(url, singleSessionObserver.redirectChain[0])
|
||||||
|
Assert.assertEquals(newUrl, singleSessionObserver.redirectChain[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `do nothing onLoadRequest when it's the first url of the session`() {
|
||||||
|
val session: Session = mockk(relaxed = true)
|
||||||
|
val url = "https://www.google.com/search"
|
||||||
|
every { session.url } returns url
|
||||||
|
singleSessionObserver.onLoadRequest(
|
||||||
|
session,
|
||||||
|
url,
|
||||||
|
triggeredByRedirect = false,
|
||||||
|
triggeredByWebContent = false
|
||||||
|
)
|
||||||
|
Assert.assertNull(singleSessionObserver.originSessionUrl)
|
||||||
|
Assert.assertEquals(0, singleSessionObserver.redirectChain.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `check if metric for ad clicked should be sent`() {
|
||||||
|
val session: Session = mockk(relaxed = true)
|
||||||
|
val sessionUrl = "doesn't matter"
|
||||||
|
val originSessionUrl = "https://www.google.com/search"
|
||||||
|
val url = "www.aaa.com"
|
||||||
|
every { session.url } returns sessionUrl
|
||||||
|
val redirectChain = mutableListOf(url)
|
||||||
|
singleSessionObserver.redirectChain = redirectChain
|
||||||
|
singleSessionObserver.originSessionUrl = originSessionUrl
|
||||||
|
|
||||||
|
singleSessionObserver.onUrlChanged(session, url)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
ads.trackAdClickedMetric(
|
||||||
|
originSessionUrl,
|
||||||
|
redirectChain
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Assert.assertNull(singleSessionObserver.originSessionUrl)
|
||||||
|
Assert.assertEquals(0, singleSessionObserver.redirectChain.size)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,42 +4,36 @@
|
||||||
|
|
||||||
package org.mozilla.fenix.browser
|
package org.mozilla.fenix.browser
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import mozilla.components.browser.session.Session
|
import mozilla.components.browser.session.Session
|
||||||
import mozilla.components.browser.session.SessionManager
|
import mozilla.components.browser.session.SessionManager
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.components.metrics.Event
|
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
|
||||||
import org.mozilla.fenix.components.metrics.MetricController
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
|
||||||
class UriOpenedObserverTest {
|
class UriOpenedObserverTest {
|
||||||
|
|
||||||
private lateinit var context: Context
|
private val owner: LifecycleOwner = mockk(relaxed = true)
|
||||||
private lateinit var owner: LifecycleOwner
|
private val sessionManager: SessionManager = mockk(relaxed = true)
|
||||||
private lateinit var sessionManager: SessionManager
|
private val metrics: MetricController = mockk()
|
||||||
private lateinit var metrics: MetricController
|
private val ads: AdsTelemetry = mockk()
|
||||||
|
private lateinit var observer: UriOpenedObserver
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
context = mockk(relaxed = true)
|
observer = UriOpenedObserver(owner, sessionManager, metrics, ads)
|
||||||
owner = mockk(relaxed = true)
|
|
||||||
sessionManager = mockk(relaxed = true)
|
|
||||||
metrics = mockk(relaxed = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `registers self as observer`() {
|
fun `registers self as observer`() {
|
||||||
val observer = UriOpenedObserver(context, owner, sessionManager, metrics)
|
|
||||||
verify { sessionManager.register(observer, owner) }
|
verify { sessionManager.register(observer, owner) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `registers single session observer`() {
|
fun `registers single session observer`() {
|
||||||
val observer = UriOpenedObserver(context, owner, sessionManager, metrics)
|
|
||||||
val session: Session = mockk(relaxed = true)
|
val session: Session = mockk(relaxed = true)
|
||||||
|
|
||||||
observer.onSessionAdded(session)
|
observer.onSessionAdded(session)
|
||||||
|
@ -48,18 +42,4 @@ class UriOpenedObserverTest {
|
||||||
observer.onSessionRemoved(session)
|
observer.onSessionRemoved(session)
|
||||||
verify { session.unregister(observer.singleSessionObserver) }
|
verify { session.unregister(observer.singleSessionObserver) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `tracks that a url was loaded`() {
|
|
||||||
val observer = UriOpenedObserver(context, owner, sessionManager, metrics).singleSessionObserver
|
|
||||||
val session: Session = mockk(relaxed = true)
|
|
||||||
every { session.url } returns "https://mozilla.com"
|
|
||||||
|
|
||||||
observer.onLoadingStateChanged(session, loading = false)
|
|
||||||
verify(exactly = 0) { metrics.track(Event.UriOpened) }
|
|
||||||
|
|
||||||
observer.onLoadingStateChanged(session, loading = true)
|
|
||||||
observer.onLoadingStateChanged(session, loading = false)
|
|
||||||
verify { metrics.track(Event.UriOpened) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/* 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.ext
|
||||||
|
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.search.telemetry.SearchProviderModel
|
||||||
|
|
||||||
|
class AdsTest {
|
||||||
|
|
||||||
|
private val testSearchProvider =
|
||||||
|
SearchProviderModel(
|
||||||
|
name = "test",
|
||||||
|
regexp = "test",
|
||||||
|
queryParam = "test",
|
||||||
|
codeParam = "test",
|
||||||
|
codePrefixes = listOf(),
|
||||||
|
followOnParams = listOf(),
|
||||||
|
extraAdServersRegexps = listOf(
|
||||||
|
"^https:\\/\\/www\\.bing\\.com\\/acli?c?k",
|
||||||
|
"^https:\\/\\/www\\.bing\\.com\\/fd\\/ls\\/GLinkPingPost\\.aspx.*acli?c?k"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test search provider contains ads`() {
|
||||||
|
val ad = "https://www.bing.com/aclick"
|
||||||
|
val nonAd = "https://www.bing.com/notanad"
|
||||||
|
assertTrue(testSearchProvider.containsAds(listOf(ad, nonAd)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test search provider does not contain ads`() {
|
||||||
|
val nonAd1 = "https://www.yahoo.com/notanad"
|
||||||
|
val nonAd2 = "https://www.google.com/"
|
||||||
|
assertFalse(testSearchProvider.containsAds(listOf(nonAd1, nonAd2)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/* 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.search.telemetry.ads
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.slot
|
||||||
|
import io.mockk.spyk
|
||||||
|
import io.mockk.verify
|
||||||
|
import mozilla.components.browser.state.store.BrowserStore
|
||||||
|
import mozilla.components.concept.engine.Engine
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_EXTENSION_ID
|
||||||
|
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_EXTENSION_RESOURCE_URL
|
||||||
|
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_MESSAGE_DOCUMENT_URLS_KEY
|
||||||
|
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry.Companion.ADS_MESSAGE_SESSION_URL_KEY
|
||||||
|
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class AdsTelemetryTest {
|
||||||
|
|
||||||
|
private val metrics: MetricController = mockk(relaxed = true)
|
||||||
|
private lateinit var ads: AdsTelemetry
|
||||||
|
private lateinit var adsMessageHandler: AdsTelemetry.AdsTelemetryContentMessageHandler
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
ads = spyk(AdsTelemetry(metrics))
|
||||||
|
adsMessageHandler = ads.AdsTelemetryContentMessageHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `don't track with null session url`() {
|
||||||
|
ads.trackAdClickedMetric(null, listOf())
|
||||||
|
|
||||||
|
verify(exactly = 0) { ads.getProviderForUrl(any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `don't track when no ads are in the redirect path`() {
|
||||||
|
val sessionUrl = "https://www.google.com/search?q=aaa"
|
||||||
|
|
||||||
|
ads.trackAdClickedMetric(sessionUrl, listOf("https://www.aaa.com"))
|
||||||
|
|
||||||
|
verify(exactly = 0) { metrics.track(any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `track when ads are in the redirect path`() {
|
||||||
|
val metricEvent = slot<Event.SearchAdClicked>()
|
||||||
|
val sessionUrl = "https://www.google.com/search?q=aaa"
|
||||||
|
|
||||||
|
ads.trackAdClickedMetric(
|
||||||
|
sessionUrl,
|
||||||
|
listOf("https://www.google.com/aclk", "https://www.aaa.com")
|
||||||
|
)
|
||||||
|
|
||||||
|
verify { metrics.track(capture(metricEvent)) }
|
||||||
|
assertEquals(ads.providerList[0].name, metricEvent.captured.label)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun install() {
|
||||||
|
val engine = mockk<Engine>(relaxed = true)
|
||||||
|
val store = mockk<BrowserStore>(relaxed = true)
|
||||||
|
|
||||||
|
ads.install(engine, store)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
engine.installWebExtension(
|
||||||
|
id = ADS_EXTENSION_ID,
|
||||||
|
url = ADS_EXTENSION_RESOURCE_URL,
|
||||||
|
allowContentMessaging = true,
|
||||||
|
onSuccess = any(),
|
||||||
|
onError = any()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `message handler processes the document urls and reports an ad`() {
|
||||||
|
val metricEvent = slot<Event.SearchWithAds>()
|
||||||
|
val first = "https://www.google.com/aclk"
|
||||||
|
val second = "https://www.google.com/aaa"
|
||||||
|
val array = JSONArray()
|
||||||
|
array.put(first)
|
||||||
|
array.put(second)
|
||||||
|
val message = JSONObject()
|
||||||
|
message.put(ADS_MESSAGE_DOCUMENT_URLS_KEY, array)
|
||||||
|
message.put(ADS_MESSAGE_SESSION_URL_KEY, "https://www.google.com/search?q=aaa")
|
||||||
|
|
||||||
|
assertEquals("", adsMessageHandler.onMessage(message, mockk()))
|
||||||
|
|
||||||
|
verify { metrics.track(capture(metricEvent)) }
|
||||||
|
assertEquals(ads.providerList[0].name, metricEvent.captured.label)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `message handler processes the document urls and doesn't find ads`() {
|
||||||
|
val first = "https://www.google.com/aaaaaa"
|
||||||
|
val second = "https://www.google.com/aaa"
|
||||||
|
val array = JSONArray()
|
||||||
|
array.put(first)
|
||||||
|
array.put(second)
|
||||||
|
val message = JSONObject()
|
||||||
|
message.put(ADS_MESSAGE_DOCUMENT_URLS_KEY, array)
|
||||||
|
message.put(ADS_MESSAGE_SESSION_URL_KEY, "https://www.google.com/search?q=aaa")
|
||||||
|
|
||||||
|
assertEquals("", adsMessageHandler.onMessage(message, mockk()))
|
||||||
|
|
||||||
|
verify(exactly = 0) { metrics.track(any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException::class)
|
||||||
|
fun `message handler finds no json object`() {
|
||||||
|
val message = "message"
|
||||||
|
|
||||||
|
adsMessageHandler.onMessage(message, mockk())
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue