From c852301c93283074ab3c7b3829091f3907053a72 Mon Sep 17 00:00:00 2001 From: Christian Sadilek Date: Wed, 14 Oct 2020 17:25:10 -0400 Subject: [PATCH] Closes #11286: Add TelemetryMiddleware to remove Session[Manager] observers --- .../java/org/mozilla/fenix/HomeActivity.kt | 5 - .../org/mozilla/fenix/TelemetryMiddleware.kt | 106 +++++++++ .../fenix/browser/TelemetrySessionObserver.kt | 84 ------- .../fenix/browser/UriOpenedObserver.kt | 75 ------ .../java/org/mozilla/fenix/components/Core.kt | 14 +- .../mozilla/fenix/TelemetryMiddlewareTest.kt | 219 ++++++++++++++++++ .../browser/TelemetrySessionObserverTest.kt | 123 ---------- .../fenix/browser/UriOpenedObserverTest.kt | 88 ------- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 9 files changed, 338 insertions(+), 378 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/browser/TelemetrySessionObserver.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/browser/UriOpenedObserver.kt create mode 100644 app/src/test/java/org/mozilla/fenix/TelemetryMiddlewareTest.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/browser/TelemetrySessionObserverTest.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/browser/UriOpenedObserverTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 43a0bfb349..99e692cafb 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -38,7 +38,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.session.SessionManager import mozilla.components.browser.state.selector.getNormalOrPrivateTabs import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.WebExtensionState @@ -62,7 +61,6 @@ import mozilla.components.support.webextensions.WebExtensionPopupFeature import org.mozilla.fenix.GleanMetrics.Metrics import org.mozilla.fenix.addons.AddonDetailsFragmentDirections import org.mozilla.fenix.addons.AddonPermissionsDetailsFragmentDirections -import org.mozilla.fenix.browser.UriOpenedObserver import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager @@ -123,7 +121,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { private var webExtScope: CoroutineScope? = null lateinit var themeManager: ThemeManager lateinit var browsingModeManager: BrowsingModeManager - private lateinit var sessionObserver: SessionManager.Observer private var isVisuallyComplete = false @@ -183,8 +180,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { .attachViewToRunVisualCompletenessQueueLater(WeakReference(rootContainer)) } - sessionObserver = UriOpenedObserver(this) - checkPrivateShortcutEntryPoint(intent) privateNotificationObserver = PrivateNotificationFeature( applicationContext, diff --git a/app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt b/app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt new file mode 100644 index 0000000000..88904292f7 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt @@ -0,0 +1,106 @@ +/* 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 + +import androidx.annotation.VisibleForTesting +import mozilla.components.browser.state.action.BrowserAction +import mozilla.components.browser.state.action.ContentAction +import mozilla.components.browser.state.action.TabListAction +import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.selector.normalTabs +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.lib.state.Middleware +import mozilla.components.lib.state.MiddlewareContext +import mozilla.components.support.base.log.logger.Logger +import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry +import org.mozilla.fenix.utils.Settings + +/** + * [Middleware] to record telemetry in response to [BrowserAction]s. + * + * @property settings reference to the application [Settings]. + * @property adsTelemetry reference to [AdsTelemetry] use to record search telemetry. + * @property metrics reference to the configured [MetricController] to record general page load events. + */ +class TelemetryMiddleware( + private val settings: Settings, + private val adsTelemetry: AdsTelemetry, + private val metrics: MetricController +) : Middleware { + + private val logger = Logger("TelemetryMiddleware") + + @VisibleForTesting + internal val redirectChains = mutableMapOf() + + /** + * Utility to collect URLs / load requests in between location changes. + */ + internal class RedirectChain(internal val root: String) { + internal val chain = mutableListOf() + + fun add(url: String) { + chain.add(url) + } + } + + @Suppress("TooGenericExceptionCaught") + override fun invoke( + context: MiddlewareContext, + next: (BrowserAction) -> Unit, + action: BrowserAction + ) { + // Pre process actions + when (action) { + is ContentAction.UpdateLoadingStateAction -> { + context.state.findTab(action.sessionId)?.let { tab -> + // Record UriOpened event when a non-private page finishes loading + if (tab.content.loading && !action.loading && !tab.content.private) { + metrics.track(Event.UriOpened) + } + } + } + is ContentAction.UpdateLoadRequestAction -> { + context.state.findTab(action.sessionId)?.let { tab -> + // Collect all load requests in between location changes + if (!redirectChains.containsKey(action.sessionId) && action.loadRequest.url != tab.content.url) { + redirectChains[action.sessionId] = RedirectChain(tab.content.url) + } + + redirectChains[action.sessionId]?.add(action.loadRequest.url) + } + } + is ContentAction.UpdateUrlAction -> { + redirectChains[action.sessionId]?.let { + // Record ads telemetry providing all redirects + try { + adsTelemetry.trackAdClickedMetric(it.root, it.chain) + } catch (t: Throwable) { + logger.info("Failed to record search telemetry", t) + } finally { + redirectChains.remove(action.sessionId) + } + } + } + } + + next(action) + + // Post process actions + when (action) { + is TabListAction.AddTabAction, + is TabListAction.AddMultipleTabsAction, + is TabListAction.RemoveTabAction, + is TabListAction.RemoveAllNormalTabsAction, + is TabListAction.RemoveAllTabsAction, + is TabListAction.RestoreAction -> { + // Update/Persist tabs count whenever it changes + settings.openTabsCount = context.state.normalTabs.count() + } + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/browser/TelemetrySessionObserver.kt b/app/src/main/java/org/mozilla/fenix/browser/TelemetrySessionObserver.kt deleted file mode 100644 index 46733f0797..0000000000 --- a/app/src/main/java/org/mozilla/fenix/browser/TelemetrySessionObserver.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* 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.annotation.VisibleForTesting -import mozilla.components.browser.session.Session -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry - -class TelemetrySessionObserver( - private val metrics: MetricController, - private val ads: AdsTelemetry -) : Session.Observer { - private var urlLoading: String? = null - @VisibleForTesting - var redirectChain = mutableListOf() - @VisibleForTesting - var originSessionUrl: String? = null - - private val temporaryFix = TemporaryFix() - - override fun onLoadingStateChanged(session: Session, loading: Boolean) { - if (loading) { - urlLoading = session.url - } else if (urlLoading != null && !session.private && temporaryFix.shouldSendEvent(session.url)) { - temporaryFix.eventSentFor = session.url - metrics.track(Event.UriOpened) - } - } - - /** - * When a link is clicked, record its redirect chain as well as origin url - */ - override fun onLoadRequest( - session: Session, - url: String, - triggeredByRedirect: Boolean, - triggeredByWebContent: Boolean - ) { - if (isFirstLinkInRedirectChain(url, session.url)) { - originSessionUrl = session.url - } - if (canStartChain()) { - redirectChain.add(url) - } - } - - private fun canStartChain(): Boolean { - return originSessionUrl != null - } - - private fun isFirstLinkInRedirectChain(url: String, sessionUrl: String): Boolean { - return originSessionUrl == null && url != sessionUrl - } - - /** - * After the redirect chain has finished, check if we encountered an ad on the way and clear - * the stored info for that chain - */ - override fun onUrlChanged(session: Session, url: String) { - ads.trackAdClickedMetric(originSessionUrl, redirectChain) - originSessionUrl = null - redirectChain.clear() - } - - /** - * Currently, [Session.Observer.onLoadingStateChanged] is called multiple times the first - * time a new session loads a page. This is inflating our telemetry numbers, so we need to - * handle it, but we will be able to remove this code when [onLoadingStateChanged] has - * been fixed. - * - * See Fenix #3676 - * See AC https://github.com/mozilla-mobile/android-components/issues/4795 - * TODO remove this class after AC #4795 has been fixed - */ - private class TemporaryFix { - var eventSentFor: String? = null - - fun shouldSendEvent(newUrl: String): Boolean = eventSentFor != newUrl - } -} diff --git a/app/src/main/java/org/mozilla/fenix/browser/UriOpenedObserver.kt b/app/src/main/java/org/mozilla/fenix/browser/UriOpenedObserver.kt deleted file mode 100644 index 0907188fee..0000000000 --- a/app/src/main/java/org/mozilla/fenix/browser/UriOpenedObserver.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* 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.annotation.VisibleForTesting -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.LifecycleOwner -import mozilla.components.browser.session.Session -import mozilla.components.browser.session.SessionManager -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.metrics -import org.mozilla.fenix.ext.sessionsOfType -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry -import org.mozilla.fenix.utils.Settings - -class UriOpenedObserver( - private val settings: Settings, - private val owner: LifecycleOwner, - private val sessionManager: SessionManager, - metrics: MetricController, - ads: AdsTelemetry -) : SessionManager.Observer { - - constructor(activity: FragmentActivity) : this( - activity.applicationContext.settings(), - activity, - activity.components.core.sessionManager, - activity.metrics, - activity.components.core.adsTelemetry - ) - - @VisibleForTesting - internal val singleSessionObserver = TelemetrySessionObserver(metrics, ads) - - init { - sessionManager.register(this, owner) - sessionManager.selectedSession?.register(singleSessionObserver, owner) - } - - override fun onSessionSelected(session: Session) { - session.register(singleSessionObserver, owner) - } - - private fun saveOpenTabsCount() { - settings.openTabsCount = sessionManager.sessionsOfType(private = false).count() - } - - override fun onAllSessionsRemoved() { - saveOpenTabsCount() - sessionManager.sessions.forEach { - it.unregister(singleSessionObserver) - } - } - - override fun onSessionAdded(session: Session) { - saveOpenTabsCount() - session.register(singleSessionObserver, owner) - } - - override fun onSessionRemoved(session: Session) { - saveOpenTabsCount() - session.unregister(singleSessionObserver) - } - - override fun onSessionsRestored() { - saveOpenTabsCount() - sessionManager.sessions.forEach { - it.register(singleSessionObserver, owner) - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 8a8d3a57f6..291715c871 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -62,6 +62,7 @@ import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.StrictModeManager +import org.mozilla.fenix.TelemetryMiddleware import org.mozilla.fenix.downloads.DownloadService import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings @@ -154,6 +155,11 @@ class Core( MediaMiddleware(context, MediaService::class.java), DownloadMiddleware(context, DownloadService::class.java), ReaderViewMiddleware(), + TelemetryMiddleware( + context.settings(), + adsTelemetry, + metrics + ), ThumbnailsMiddleware(thumbnailStorage), UndoMiddleware(::lookupSessionManager, context.getUndoDelay()) ) + EngineMiddleware.create(engine, ::findSessionById) @@ -250,12 +256,16 @@ class Core( BrowserIcons(context, client) } + val metrics by lazy { + context.components.analytics.metrics + } + val adsTelemetry by lazy { - AdsTelemetry(context.components.analytics.metrics) + AdsTelemetry(metrics) } val searchTelemetry by lazy { - InContentTelemetry(context.components.analytics.metrics) + InContentTelemetry(metrics) } /** diff --git a/app/src/test/java/org/mozilla/fenix/TelemetryMiddlewareTest.kt b/app/src/test/java/org/mozilla/fenix/TelemetryMiddlewareTest.kt new file mode 100644 index 0000000000..10fe132097 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/TelemetryMiddlewareTest.kt @@ -0,0 +1,219 @@ +/* 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 + +import io.mockk.mockk +import io.mockk.verify +import mozilla.components.browser.state.action.ContentAction +import mozilla.components.browser.state.action.TabListAction +import mozilla.components.browser.state.state.LoadRequestState +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.support.test.ext.joinBlocking +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +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 +import org.mozilla.fenix.utils.Settings + +@RunWith(FenixRobolectricTestRunner::class) +class TelemetryMiddlewareTest { + + private lateinit var store: BrowserStore + private lateinit var settings: Settings + private lateinit var telemetryMiddleware: TelemetryMiddleware + private lateinit var metrics: MetricController + private lateinit var adsTelemetry: AdsTelemetry + + @Before + fun setUp() { + settings = Settings(testContext) + metrics = mockk() + adsTelemetry = mockk() + telemetryMiddleware = TelemetryMiddleware( + settings, + adsTelemetry, + metrics + ) + store = BrowserStore(middleware = listOf(telemetryMiddleware)) + } + + @Test + fun `WHEN a tab is added THEN the open tab count is updated`() { + assertEquals(0, settings.openTabsCount) + + store.dispatch(TabListAction.AddTabAction(createTab("https://mozilla.org"))).joinBlocking() + assertEquals(1, settings.openTabsCount) + } + + @Test + fun `WHEN a private tab is added THEN the open tab count is not updated`() { + assertEquals(0, settings.openTabsCount) + + store.dispatch(TabListAction.AddTabAction(createTab("https://mozilla.org", private = true))).joinBlocking() + assertEquals(0, settings.openTabsCount) + } + + @Test + fun `WHEN multiple tabs are added THEN the open tab count is updated`() { + assertEquals(0, settings.openTabsCount) + store.dispatch( + TabListAction.AddMultipleTabsAction(listOf( + createTab("https://mozilla.org"), + createTab("https://firefox.com")) + ) + ).joinBlocking() + + assertEquals(2, settings.openTabsCount) + } + + @Test + fun `WHEN a tab is removed THEN the open tab count is updated`() { + store.dispatch( + TabListAction.AddMultipleTabsAction(listOf( + createTab(id = "1", url = "https://mozilla.org"), + createTab(id = "2", url = "https://firefox.com")) + ) + ).joinBlocking() + assertEquals(2, settings.openTabsCount) + + store.dispatch(TabListAction.RemoveTabAction("1")).joinBlocking() + assertEquals(1, settings.openTabsCount) + } + + @Test + fun `WHEN all tabs are removed THEN the open tab count is updated`() { + store.dispatch( + TabListAction.AddMultipleTabsAction(listOf( + createTab("https://mozilla.org"), + createTab("https://firefox.com")) + ) + ).joinBlocking() + assertEquals(2, settings.openTabsCount) + + store.dispatch(TabListAction.RemoveAllTabsAction).joinBlocking() + assertEquals(0, settings.openTabsCount) + } + + @Test + fun `WHEN all normal tabs are removed THEN the open tab count is updated`() { + store.dispatch( + TabListAction.AddMultipleTabsAction(listOf( + createTab("https://mozilla.org"), + createTab("https://firefox.com"), + createTab("https://getpocket.com", private = true)) + ) + ).joinBlocking() + assertEquals(2, settings.openTabsCount) + + store.dispatch(TabListAction.RemoveAllNormalTabsAction).joinBlocking() + assertEquals(0, settings.openTabsCount) + } + + @Test + fun `WHEN tabs are restored THEN the open tab count is updated`() { + assertEquals(0, settings.openTabsCount) + val tabsToRestore = listOf( + createTab("https://mozilla.org"), + createTab("https://firefox.com") + ) + + store.dispatch(TabListAction.RestoreAction(tabsToRestore)).joinBlocking() + assertEquals(2, settings.openTabsCount) + } + + @Test + fun `GIVEN a page is loading WHEN loading is complete THEN we record a UriOpened event`() { + val tab = createTab(id = "1", url = "https://mozilla.org") + store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() + store.dispatch(ContentAction.UpdateLoadingStateAction(tab.id, true)).joinBlocking() + verify(exactly = 0) { metrics.track(Event.UriOpened) } + + store.dispatch(ContentAction.UpdateLoadingStateAction(tab.id, false)).joinBlocking() + verify(exactly = 1) { metrics.track(Event.UriOpened) } + } + + @Test + fun `GIVEN a private page is loading WHEN loading is complete THEN we never record a UriOpened event`() { + val tab = createTab(id = "1", url = "https://mozilla.org", private = true) + store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() + store.dispatch(ContentAction.UpdateLoadingStateAction(tab.id, true)).joinBlocking() + verify(exactly = 0) { metrics.track(Event.UriOpened) } + + store.dispatch(ContentAction.UpdateLoadingStateAction(tab.id, false)).joinBlocking() + verify(exactly = 0) { metrics.track(Event.UriOpened) } + } + + @Test + fun `GIVEN a load request WHEN no redirect chain is available THEN a new chain will be created`() { + val tab = createTab(id = "1", url = "http://mozilla.org") + store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() + store.dispatch(ContentAction.UpdateLoadRequestAction( + tab.id, LoadRequestState(tab.content.url, true, true)) + ).joinBlocking() + + assertNull(telemetryMiddleware.redirectChains[tab.id]) + + store.dispatch(ContentAction.UpdateLoadRequestAction( + tab.id, LoadRequestState("https://mozilla.org", true, true)) + ).joinBlocking() + + assertNotNull(telemetryMiddleware.redirectChains[tab.id]) + assertEquals(tab.content.url, telemetryMiddleware.redirectChains[tab.id]!!.root) + } + + @Test + fun `GIVEN a load request WHEN a redirect chain is available THEN url is added to chain`() { + val tab = createTab(id = "1", url = "http://mozilla.org") + store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() + store.dispatch(ContentAction.UpdateLoadRequestAction( + tab.id, LoadRequestState("https://mozilla.org", true, true)) + ).joinBlocking() + + assertNotNull(telemetryMiddleware.redirectChains[tab.id]) + assertEquals(tab.content.url, telemetryMiddleware.redirectChains[tab.id]!!.root) + assertEquals("https://mozilla.org", telemetryMiddleware.redirectChains[tab.id]!!.chain.first()) + } + + @Test + fun `GIVEN a location update WHEN no redirect chain is available THEN no ads telemetry is recorded`() { + val tab = createTab(id = "1", url = "http://mozilla.org") + store.dispatch(ContentAction.UpdateUrlAction(tab.id, "http://mozilla.org")).joinBlocking() + verify(exactly = 0) { adsTelemetry.trackAdClickedMetric(any(), any()) } + } + + @Test + fun `GIVEN a location update WHEN a redirect chain is available THEN ads telemetry is recorded`() { + val tab = createTab(id = "1", url = "http://mozilla.org") + store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() + store.dispatch(ContentAction.UpdateLoadRequestAction( + tab.id, LoadRequestState("https://mozilla.org", true, true)) + ).joinBlocking() + + store.dispatch(ContentAction.UpdateUrlAction(tab.id, "https://mozilla.org")).joinBlocking() + verify(exactly = 1) { adsTelemetry.trackAdClickedMetric(tab.content.url, listOf("https://mozilla.org")) } + } + + @Test + fun `GIVEN a location update WHEN ads telemetry is recorded THEN redirect chain is reset`() { + val tab = createTab(id = "1", url = "http://mozilla.org") + store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() + store.dispatch(ContentAction.UpdateLoadRequestAction( + tab.id, LoadRequestState("https://mozilla.org", true, true)) + ).joinBlocking() + + assertNotNull(telemetryMiddleware.redirectChains[tab.id]) + + store.dispatch(ContentAction.UpdateUrlAction(tab.id, "https://mozilla.org")).joinBlocking() + assertNull(telemetryMiddleware.redirectChains[tab.id]) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/browser/TelemetrySessionObserverTest.kt b/app/src/test/java/org/mozilla/fenix/browser/TelemetrySessionObserverTest.kt deleted file mode 100644 index 5f914bb54e..0000000000 --- a/app/src/test/java/org/mozilla/fenix/browser/TelemetrySessionObserverTest.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* 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.assertEquals -import org.junit.Assert.assertNull -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 -import org.mozilla.fenix.utils.Settings - -class TelemetrySessionObserverTest { - - private val settings: Settings = mockk(relaxed = true) - 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(settings, 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 - ) - assertEquals(sessionUrl, singleSessionObserver.originSessionUrl) - 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 - ) - assertEquals(url, singleSessionObserver.originSessionUrl) - assertEquals(url, singleSessionObserver.redirectChain[0]) - 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 - ) - assertNull(singleSessionObserver.originSessionUrl) - 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 - ) - } - assertNull(singleSessionObserver.originSessionUrl) - assertEquals(0, singleSessionObserver.redirectChain.size) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/browser/UriOpenedObserverTest.kt b/app/src/test/java/org/mozilla/fenix/browser/UriOpenedObserverTest.kt deleted file mode 100644 index 82748f936f..0000000000 --- a/app/src/test/java/org/mozilla/fenix/browser/UriOpenedObserverTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* 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.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import io.mockk.verify -import mozilla.components.browser.session.Session -import mozilla.components.browser.session.SessionManager -import org.junit.Before -import org.junit.Test -import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry -import org.mozilla.fenix.utils.Settings - -class UriOpenedObserverTest { - - @MockK(relaxed = true) private lateinit var settings: Settings - @MockK(relaxed = true) private lateinit var owner: LifecycleOwner - @MockK(relaxed = true) private lateinit var sessionManager: SessionManager - @MockK private lateinit var metrics: MetricController - @MockK private lateinit var ads: AdsTelemetry - private lateinit var observer: UriOpenedObserver - - @Before - fun setup() { - MockKAnnotations.init(this) - observer = UriOpenedObserver(settings, owner, sessionManager, metrics, ads) - } - - @Test - fun `registers self as observer`() { - verify { sessionManager.register(observer, owner) } - } - - @Test - fun `registers single session observer`() { - every { sessionManager.sessions } returns listOf( - mockk { - every { private } returns false - }, - mockk { - every { private } returns true - } - ) - val session: Session = mockk(relaxed = true) - - observer.onSessionAdded(session) - verify { session.register(observer.singleSessionObserver, owner) } - - observer.onSessionSelected(session) - verify { session.register(observer.singleSessionObserver, owner) } - - observer.onSessionRemoved(session) - verify { session.unregister(observer.singleSessionObserver) } - - verify { settings.openTabsCount = 1 } - } - - @Test - fun `registers when all sessions are restored`() { - val session1: Session = mockk(relaxed = true) - val session2: Session = mockk(relaxed = true) - every { sessionManager.sessions } returns listOf(session1, session2) - - observer.onSessionsRestored() - - verify { session1.register(observer.singleSessionObserver, owner) } - verify { session2.register(observer.singleSessionObserver, owner) } - } - - @Test - fun `unregisters when all sessions are removed`() { - val session1: Session = mockk(relaxed = true) - val session2: Session = mockk(relaxed = true) - every { sessionManager.sessions } returns listOf(session1, session2) - - observer.onAllSessionsRemoved() - - verify { session1.unregister(observer.singleSessionObserver) } - verify { session2.unregister(observer.singleSessionObserver) } - } -} diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index fe82f5a67c..1403d99b83 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "63.0.20201014190200" + const val VERSION = "63.0.20201015143124" }