diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserWebViewClientTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserWebViewClientTest.kt index c96b3fd19478..ebe72722bb9b 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserWebViewClientTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserWebViewClientTest.kt @@ -58,6 +58,8 @@ import com.duckduckgo.app.browser.pageloadpixel.PageLoadedHandler import com.duckduckgo.app.browser.pageloadpixel.firstpaint.PagePaintedHandler import com.duckduckgo.app.browser.print.PrintInjector import com.duckduckgo.app.browser.trafficquality.AndroidFeaturesHeaderPlugin +import com.duckduckgo.app.browser.trafficquality.CustomHeaderAllowedChecker +import com.duckduckgo.app.browser.trafficquality.remote.AndroidFeaturesHeaderProvider import com.duckduckgo.app.browser.uriloaded.UriLoadedManager import com.duckduckgo.app.global.model.Site import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature @@ -148,10 +150,18 @@ class BrowserWebViewClientTest { private val mockDuckPlayer: DuckPlayer = mock() private val navigationHistory: NavigationHistory = mock() private val mockDuckDuckGoUrlDetector: DuckDuckGoUrlDetector = mock() + private val mockCustomHeaderGracePeriodChecker: CustomHeaderAllowedChecker = mock() + private val mockFeaturesHeaderProvider: AndroidFeaturesHeaderProvider = mock() private val openInNewTabFlow: MutableSharedFlow = MutableSharedFlow() private val mockUriLoadedManager: UriLoadedManager = mock() private val mockAndroidBrowserConfigFeature: AndroidBrowserConfigFeature = mock() - private val mockAndroidFeaturesHeaderPlugin = AndroidFeaturesHeaderPlugin(mockDuckDuckGoUrlDetector, mockAndroidBrowserConfigFeature, mock()) + private val mockAndroidFeaturesHeaderPlugin = AndroidFeaturesHeaderPlugin( + mockDuckDuckGoUrlDetector, + mockCustomHeaderGracePeriodChecker, + mockAndroidBrowserConfigFeature, + mockFeaturesHeaderProvider, + mock(), + ) @UiThreadTest @Before diff --git a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AndroidAppVersionPixelSender.kt b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AndroidAppVersionPixelSender.kt deleted file mode 100644 index 87a9cddd9d2b..000000000000 --- a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AndroidAppVersionPixelSender.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2024 DuckDuckGo - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.duckduckgo.app.browser.trafficquality - -import com.duckduckgo.app.di.AppCoroutineScope -import com.duckduckgo.app.pixels.AppPixelName -import com.duckduckgo.app.statistics.api.AtbLifecyclePlugin -import com.duckduckgo.app.statistics.pixels.Pixel -import com.duckduckgo.common.utils.DispatcherProvider -import com.duckduckgo.di.scopes.AppScope -import com.squareup.anvil.annotations.ContributesMultibinding -import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -@ContributesMultibinding(AppScope::class) -class AndroidAppVersionPixelSender @Inject constructor( - private val appVersionProvider: QualityAppVersionProvider, - private val pixel: Pixel, - @AppCoroutineScope private val coroutineScope: CoroutineScope, - private val dispatcherProvider: DispatcherProvider, -) : AtbLifecyclePlugin { - - override fun onSearchRetentionAtbRefreshed(oldAtb: String, newAtb: String) { - coroutineScope.launch(dispatcherProvider.io()) { - val params = mutableMapOf() - params[PARAM_APP_VERSION] = appVersionProvider.provide() - pixel.fire(AppPixelName.APP_VERSION_AT_SEARCH_TIME, params) - } - } - - companion object { - internal const val PARAM_APP_VERSION = "app_version" - } -} diff --git a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AndroidFeaturesHeader.kt b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AndroidFeaturesHeader.kt index 2652d2e7b078..2c86dcad0171 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AndroidFeaturesHeader.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AndroidFeaturesHeader.kt @@ -17,6 +17,8 @@ package com.duckduckgo.app.browser.trafficquality import com.duckduckgo.app.browser.DuckDuckGoUrlDetector +import com.duckduckgo.app.browser.trafficquality.Result.Allowed +import com.duckduckgo.app.browser.trafficquality.Result.NotAllowed import com.duckduckgo.app.browser.trafficquality.remote.AndroidFeaturesHeaderProvider import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature import com.duckduckgo.common.utils.plugins.headers.CustomHeadersProvider.CustomHeadersPlugin @@ -27,23 +29,42 @@ import javax.inject.Inject @ContributesMultibinding(scope = AppScope::class) class AndroidFeaturesHeaderPlugin @Inject constructor( private val duckDuckGoUrlDetector: DuckDuckGoUrlDetector, + private val customHeaderAllowedChecker: CustomHeaderAllowedChecker, private val androidBrowserConfigFeature: AndroidBrowserConfigFeature, private val androidFeaturesHeaderProvider: AndroidFeaturesHeaderProvider, + private val appVersionProvider: AppVersionHeaderProvider, ) : CustomHeadersPlugin { override fun getHeaders(url: String): Map { - if (androidBrowserConfigFeature.self().isEnabled() && - androidBrowserConfigFeature.featuresRequestHeader().isEnabled() && - duckDuckGoUrlDetector.isDuckDuckGoQueryUrl(url) - ) { - androidFeaturesHeaderProvider.provide()?.let { headerValue -> - return mapOf(X_DUCKDUCKGO_ANDROID_HEADER to headerValue) + if (isFeatureEnabled() && duckDuckGoUrlDetector.isDuckDuckGoQueryUrl(url)) { + return when (val result = customHeaderAllowedChecker.isAllowed()) { + is Allowed -> { + val headers = mutableMapOf() + androidFeaturesHeaderProvider.provide(result.config)?.let { headerValue -> + headers.put(X_DUCKDUCKGO_ANDROID_HEADER, headerValue) + } + appVersionProvider.provide(isStub = false).let { headerValue -> + headers.put(X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER, headerValue) + } + headers + } + + NotAllowed -> { + mapOf(X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER to appVersionProvider.provide(isStub = true)) + } } + } else { + return emptyMap() } - return emptyMap() + } + + private fun isFeatureEnabled(): Boolean { + return androidBrowserConfigFeature.self().isEnabled() && + androidBrowserConfigFeature.featuresRequestHeader().isEnabled() } companion object { internal const val X_DUCKDUCKGO_ANDROID_HEADER = "x-duckduckgo-android" + internal const val X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER = "x-duckduckgo-android-version" } } diff --git a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AppVersionHeaderProvider.kt b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AppVersionHeaderProvider.kt new file mode 100644 index 000000000000..37bc5de15bb9 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/AppVersionHeaderProvider.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.browser.trafficquality + +import com.duckduckgo.appbuildconfig.api.AppBuildConfig +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject + +interface AppVersionHeaderProvider { + fun provide(isStub: Boolean): String +} + +@ContributesBinding(AppScope::class) +class RealAppVersionHeaderProvider @Inject constructor( + private val appBuildConfig: AppBuildConfig, + +) : AppVersionHeaderProvider { + override fun provide(isStub: Boolean): String { + return if (isStub) { + APP_VERSION_QUALITY_DEFAULT_VALUE + } else { + appBuildConfig.versionName + } + } + + companion object { + const val APP_VERSION_QUALITY_DEFAULT_VALUE = "other_versions" + } +} diff --git a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/CustomHeaderAllowedChecker.kt b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/CustomHeaderAllowedChecker.kt new file mode 100644 index 000000000000..9e2d086287a9 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/CustomHeaderAllowedChecker.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.browser.trafficquality + +import com.duckduckgo.app.browser.trafficquality.Result.Allowed +import com.duckduckgo.app.browser.trafficquality.Result.NotAllowed +import com.duckduckgo.app.browser.trafficquality.remote.FeaturesRequestHeaderStore +import com.duckduckgo.app.browser.trafficquality.remote.TrafficQualityAppVersion +import com.duckduckgo.appbuildconfig.api.AppBuildConfig +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.time.temporal.ChronoUnit +import javax.inject.Inject + +interface CustomHeaderAllowedChecker { + fun isAllowed(): Result +} + +sealed class Result { + data class Allowed(val config: TrafficQualityAppVersion) : Result() + data object NotAllowed : Result() +} + +@ContributesBinding(AppScope::class) +class RealCustomHeaderGracePeriodChecker @Inject constructor( + private val appBuildConfig: AppBuildConfig, + private val featuresRequestHeaderStore: FeaturesRequestHeaderStore, +) : CustomHeaderAllowedChecker { + override fun isAllowed(): Result { + val config = featuresRequestHeaderStore.getConfig() + val versionConfig = config.find { it.appVersion == appBuildConfig.versionCode } + return if (versionConfig != null) { + if (shouldSendHeader(versionConfig)) { + Allowed(versionConfig) + } else { + NotAllowed + } + } else { + NotAllowed + } + } + + private fun shouldSendHeader(versionConfig: TrafficQualityAppVersion): Boolean { + val appBuildDateMillis = appBuildConfig.buildDateTimeMillis + if (appBuildDateMillis == 0L) { + return false + } + + val appBuildDate = LocalDateTime.ofEpochSecond(appBuildDateMillis / 1000, 0, ZoneOffset.UTC) + val now = LocalDateTime.now(ZoneOffset.UTC) + + val daysSinceBuild = ChronoUnit.DAYS.between(appBuildDate, now) + val daysUntilLoggingStarts = versionConfig.daysUntilLoggingStarts + val daysForAppVersionLogging = daysUntilLoggingStarts + versionConfig.daysLogging + + return daysSinceBuild in daysUntilLoggingStarts..daysForAppVersionLogging + } +} diff --git a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/QualityAppVersionProvider.kt b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/QualityAppVersionProvider.kt deleted file mode 100644 index f77772f83197..000000000000 --- a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/QualityAppVersionProvider.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2024 DuckDuckGo - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.duckduckgo.app.browser.trafficquality - -import com.duckduckgo.appbuildconfig.api.AppBuildConfig -import com.duckduckgo.di.scopes.AppScope -import com.squareup.anvil.annotations.ContributesBinding -import java.time.LocalDateTime -import java.time.ZoneOffset -import java.time.temporal.ChronoUnit -import javax.inject.Inject - -interface QualityAppVersionProvider { - fun provide(): String -} - -@ContributesBinding(AppScope::class) -class RealQualityAppVersionProvider @Inject constructor( - private val appBuildConfig: AppBuildConfig, -) : QualityAppVersionProvider { - override fun provide(): String { - val appBuildDateMillis = appBuildConfig.buildDateTimeMillis - - if (appBuildDateMillis == 0L) { - return APP_VERSION_QUALITY_DEFAULT_VALUE - } - - val appBuildDate = LocalDateTime.ofEpochSecond(appBuildDateMillis / 1000, 0, ZoneOffset.UTC) - val now = LocalDateTime.now(ZoneOffset.UTC) - val daysSinceBuild = ChronoUnit.DAYS.between(appBuildDate, now) - - if (daysSinceBuild < DAYS_AFTER_APP_BUILD_WITH_DEFAULT_VALUE) { - return APP_VERSION_QUALITY_DEFAULT_VALUE - } - - if (daysSinceBuild > DAYS_FOR_APP_VERSION_LOGGING) { - return APP_VERSION_QUALITY_DEFAULT_VALUE - } - - return appBuildConfig.versionName - } - - companion object { - const val APP_VERSION_QUALITY_DEFAULT_VALUE = "other_versions" - const val DAYS_AFTER_APP_BUILD_WITH_DEFAULT_VALUE = 6 - const val DAYS_FOR_APP_VERSION_LOGGING = DAYS_AFTER_APP_BUILD_WITH_DEFAULT_VALUE + 10 - } -} diff --git a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/remote/AndroidFeaturesHeaderProvider.kt b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/remote/AndroidFeaturesHeaderProvider.kt index e9a6e4fec526..fe805c9835ef 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/trafficquality/remote/AndroidFeaturesHeaderProvider.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/trafficquality/remote/AndroidFeaturesHeaderProvider.kt @@ -16,57 +16,29 @@ package com.duckduckgo.app.browser.trafficquality.remote -import com.duckduckgo.appbuildconfig.api.AppBuildConfig import com.duckduckgo.autoconsent.api.Autoconsent import com.duckduckgo.di.scopes.AppScope import com.duckduckgo.mobile.android.app.tracking.AppTrackingProtection import com.duckduckgo.networkprotection.api.NetworkProtectionState import com.duckduckgo.privacy.config.api.Gpc import com.squareup.anvil.annotations.ContributesBinding -import java.time.LocalDateTime -import java.time.ZoneOffset -import java.time.temporal.ChronoUnit import javax.inject.Inject import kotlinx.coroutines.runBlocking interface AndroidFeaturesHeaderProvider { - fun provide(): String? + fun provide(config: TrafficQualityAppVersion): String? } @ContributesBinding(AppScope::class) class RealAndroidFeaturesHeaderProvider @Inject constructor( - private val appBuildConfig: AppBuildConfig, - private val featuresRequestHeaderStore: FeaturesRequestHeaderStore, private val autoconsent: Autoconsent, private val gpc: Gpc, private val appTrackingProtection: AppTrackingProtection, private val networkProtectionState: NetworkProtectionState, ) : AndroidFeaturesHeaderProvider { - override fun provide(): String? { - val config = featuresRequestHeaderStore.getConfig() - val versionConfig = config.find { it.appVersion == appBuildConfig.versionCode } - return if (versionConfig != null && shouldLogValue(versionConfig)) { - logFeature(versionConfig) - } else { - null - } - } - - private fun shouldLogValue(versionConfig: TrafficQualityAppVersion): Boolean { - val appBuildDateMillis = appBuildConfig.buildDateTimeMillis - if (appBuildDateMillis == 0L) { - return false - } - - val appBuildDate = LocalDateTime.ofEpochSecond(appBuildDateMillis / 1000, 0, ZoneOffset.UTC) - val now = LocalDateTime.now(ZoneOffset.UTC) - - val daysSinceBuild = ChronoUnit.DAYS.between(appBuildDate, now) - val daysUntilLoggingStarts = versionConfig.daysUntilLoggingStarts - val daysForAppVersionLogging = versionConfig.daysUntilLoggingStarts + versionConfig.daysLogging - - return daysSinceBuild in daysUntilLoggingStarts..daysForAppVersionLogging + override fun provide(config: TrafficQualityAppVersion): String? { + return logFeature(config) } private fun logFeature(versionConfig: TrafficQualityAppVersion): String? { diff --git a/app/src/test/java/com/duckduckgo/app/browser/AndroidFeaturesHeaderPluginTest.kt b/app/src/test/java/com/duckduckgo/app/browser/AndroidFeaturesHeaderPluginTest.kt index bd3caa0b71a6..6729f947a92c 100644 --- a/app/src/test/java/com/duckduckgo/app/browser/AndroidFeaturesHeaderPluginTest.kt +++ b/app/src/test/java/com/duckduckgo/app/browser/AndroidFeaturesHeaderPluginTest.kt @@ -1,7 +1,13 @@ package com.duckduckgo.app.browser import com.duckduckgo.app.browser.trafficquality.AndroidFeaturesHeaderPlugin +import com.duckduckgo.app.browser.trafficquality.AndroidFeaturesHeaderPlugin.Companion.X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER import com.duckduckgo.app.browser.trafficquality.AndroidFeaturesHeaderPlugin.Companion.X_DUCKDUCKGO_ANDROID_HEADER +import com.duckduckgo.app.browser.trafficquality.AppVersionHeaderProvider +import com.duckduckgo.app.browser.trafficquality.CustomHeaderAllowedChecker +import com.duckduckgo.app.browser.trafficquality.Result.Allowed +import com.duckduckgo.app.browser.trafficquality.Result.NotAllowed +import com.duckduckgo.app.browser.trafficquality.configEnabledForCurrentVersion import com.duckduckgo.app.browser.trafficquality.remote.AndroidFeaturesHeaderProvider import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature import com.duckduckgo.feature.toggles.api.Toggle @@ -24,42 +30,56 @@ class AndroidFeaturesHeaderPluginTest { private val mockEnabledToggle: Toggle = mock { on { it.isEnabled() } doReturn true } private val mockDisabledToggle: Toggle = mock { on { it.isEnabled() } doReturn false } private val mockAndroidFeaturesHeaderProvider: AndroidFeaturesHeaderProvider = mock() + private val mockCustomHeaderGracePeriodChecker: CustomHeaderAllowedChecker = mock() + private val mockAppVersionHeaderProvider: AppVersionHeaderProvider = mock() - private val SAMPLE_HEADER = "header" + private val SAMPLE_FEATURE_HEADER = "feature_header" + private val SAMPLE_APP_VERSION_HEADER = "app_version_header" @Before fun setup() { - testee = AndroidFeaturesHeaderPlugin(mockDuckDuckGoUrlDetector, mockAndroidBrowserConfigFeature, mockAndroidFeaturesHeaderProvider) + testee = AndroidFeaturesHeaderPlugin( + mockDuckDuckGoUrlDetector, + mockCustomHeaderGracePeriodChecker, + mockAndroidBrowserConfigFeature, + mockAndroidFeaturesHeaderProvider, + mockAppVersionHeaderProvider, + ) + + whenever(mockCustomHeaderGracePeriodChecker.isAllowed()).thenReturn(Allowed(configEnabledForCurrentVersion)) + whenever(mockAppVersionHeaderProvider.provide(any())).thenReturn(SAMPLE_APP_VERSION_HEADER) } @Test - fun whenGetHeadersCalledWithDuckDuckGoUrlAndFeatureEnabledAndHeaderProvidedThenReturnCorrectHeader() = runTest { + fun whenGetHeadersCalledWithDuckDuckGoUrlAndHeaderAllowedWithBothHeadersProvidedThenReturnCorrectHeader() = runTest { val url = "duckduckgo_search_url" whenever(mockDuckDuckGoUrlDetector.isDuckDuckGoQueryUrl(any())).thenReturn(true) whenever(mockAndroidBrowserConfigFeature.self()).thenReturn(mockEnabledToggle) whenever(mockAndroidBrowserConfigFeature.featuresRequestHeader()).thenReturn(mockEnabledToggle) - whenever(mockAndroidFeaturesHeaderProvider.provide()).thenReturn(SAMPLE_HEADER) + whenever(mockAndroidFeaturesHeaderProvider.provide(any())).thenReturn(SAMPLE_FEATURE_HEADER) val headers = testee.getHeaders(url) - assertEquals(SAMPLE_HEADER, headers[X_DUCKDUCKGO_ANDROID_HEADER]) + assertEquals(SAMPLE_FEATURE_HEADER, headers[X_DUCKDUCKGO_ANDROID_HEADER]) + assertEquals(SAMPLE_APP_VERSION_HEADER, headers[X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER]) } @Test - fun whenGetHeadersCalledWithDuckDuckGoUrlAndFeatureEnabledAndHeaderNotProvidedThenReturnEmptyMap() = runTest { + fun whenGetHeadersCalledWithDuckDuckGoUrlAndHeaderAllowedWithOnlyAppVersionProvidedThenReturnCorrectHeader() = runTest { val url = "duckduckgo_search_url" whenever(mockDuckDuckGoUrlDetector.isDuckDuckGoQueryUrl(any())).thenReturn(true) whenever(mockAndroidBrowserConfigFeature.self()).thenReturn(mockEnabledToggle) whenever(mockAndroidBrowserConfigFeature.featuresRequestHeader()).thenReturn(mockEnabledToggle) - whenever(mockAndroidFeaturesHeaderProvider.provide()).thenReturn(null) + whenever(mockAndroidFeaturesHeaderProvider.provide(any())).thenReturn(null) val headers = testee.getHeaders(url) - assertTrue(headers.isEmpty()) + assertEquals(null, headers[X_DUCKDUCKGO_ANDROID_HEADER]) + assertEquals(SAMPLE_APP_VERSION_HEADER, headers[X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER]) } @Test - fun whenGetHeadersCalledWithDuckDuckGoUrlAndFeatureDisabledThenReturnEmptyMap() { + fun whenGetHeadersCalledWithDuckDuckGoUrlAndFeatureDisabledThenReturnEmptyHeaders() { val url = "duckduckgo_search_url" whenever(mockDuckDuckGoUrlDetector.isDuckDuckGoQueryUrl(any())).thenReturn(true) whenever(mockAndroidBrowserConfigFeature.self()).thenReturn(mockEnabledToggle) @@ -70,6 +90,20 @@ class AndroidFeaturesHeaderPluginTest { assertTrue(headers.isEmpty()) } + @Test + fun whenGetHeadersCalledWithDuckDuckGoUrlAndHeaderNotAllowedThenReturnCorrectHeader() { + val url = "duckduckgo_search_url" + whenever(mockDuckDuckGoUrlDetector.isDuckDuckGoQueryUrl(any())).thenReturn(true) + whenever(mockAndroidBrowserConfigFeature.self()).thenReturn(mockEnabledToggle) + whenever(mockAndroidBrowserConfigFeature.featuresRequestHeader()).thenReturn(mockEnabledToggle) + whenever(mockCustomHeaderGracePeriodChecker.isAllowed()).thenReturn(NotAllowed) + + val headers = testee.getHeaders(url) + + assertEquals(null, headers[X_DUCKDUCKGO_ANDROID_HEADER]) + assertEquals(SAMPLE_APP_VERSION_HEADER, headers[X_DUCKDUCKGO_ANDROID_APP_VERSION_HEADER]) + } + @Test fun whenGetHeadersCalledWithNonDuckDuckGoUrlAndFeatureEnabledThenReturnEmptyMap() { val url = "non_duckduckgo_search_url" diff --git a/app/src/test/java/com/duckduckgo/app/browser/trafficquality/AndroidAppVersionPixelSenderTest.kt b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/AndroidAppVersionPixelSenderTest.kt deleted file mode 100644 index 82f2de0dd875..000000000000 --- a/app/src/test/java/com/duckduckgo/app/browser/trafficquality/AndroidAppVersionPixelSenderTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.duckduckgo.app.browser.trafficquality - -import com.duckduckgo.app.browser.trafficquality.RealQualityAppVersionProvider.Companion.APP_VERSION_QUALITY_DEFAULT_VALUE -import com.duckduckgo.app.pixels.AppPixelName -import com.duckduckgo.app.statistics.pixels.Pixel -import com.duckduckgo.common.test.CoroutineTestRule -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.mockito.kotlin.mock -import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoMoreInteractions -import org.mockito.kotlin.whenever - -class AndroidAppVersionPixelSenderTest { - @get:Rule - var coroutineRule = CoroutineTestRule() - - private val mockAppVersionProvider = mock() - private val mockPixel = mock() - - private lateinit var pixelSender: AndroidAppVersionPixelSender - - @Before - fun setup() { - pixelSender = AndroidAppVersionPixelSender( - mockAppVersionProvider, - mockPixel, - coroutineRule.testScope, - coroutineRule.testDispatcherProvider, - ) - } - - @Test - fun reportFeaturesEnabledOrDisabledWhenEnabledOrDisabled() = runTest { - whenever(mockAppVersionProvider.provide()).thenReturn(APP_VERSION_QUALITY_DEFAULT_VALUE) - - pixelSender.onSearchRetentionAtbRefreshed("v123-1", "v123-2") - - verify(mockPixel).fire( - AppPixelName.APP_VERSION_AT_SEARCH_TIME, - mapOf( - AndroidAppVersionPixelSender.PARAM_APP_VERSION to APP_VERSION_QUALITY_DEFAULT_VALUE, - ), - ) - verifyNoMoreInteractions(mockPixel) - } -} diff --git a/app/src/test/java/com/duckduckgo/app/browser/trafficquality/AndroidFeaturesHeaderProviderTest.kt b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/AndroidFeaturesHeaderProviderTest.kt index 3614c53f5c58..e47146c5d499 100644 --- a/app/src/test/java/com/duckduckgo/app/browser/trafficquality/AndroidFeaturesHeaderProviderTest.kt +++ b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/AndroidFeaturesHeaderProviderTest.kt @@ -16,17 +16,12 @@ package com.duckduckgo.app.browser.trafficquality -import com.duckduckgo.app.browser.trafficquality.remote.FeaturesRequestHeaderStore import com.duckduckgo.app.browser.trafficquality.remote.RealAndroidFeaturesHeaderProvider import com.duckduckgo.app.browser.trafficquality.remote.TrafficQualityAppVersion -import com.duckduckgo.app.browser.trafficquality.remote.TrafficQualityAppVersionFeatures -import com.duckduckgo.appbuildconfig.api.AppBuildConfig import com.duckduckgo.autoconsent.api.Autoconsent import com.duckduckgo.mobile.android.app.tracking.AppTrackingProtection import com.duckduckgo.networkprotection.api.NetworkProtectionState import com.duckduckgo.privacy.config.api.Gpc -import java.time.LocalDateTime -import java.time.ZoneId import kotlinx.coroutines.test.runTest import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -38,10 +33,7 @@ import org.mockito.kotlin.whenever class AndroidFeaturesHeaderProviderTest { private val currentVersion = 5210000 - private val anotherVersion = 5220000 - private val appBuildConfig: AppBuildConfig = mock() - private val featuresRequestHeaderStore: FeaturesRequestHeaderStore = mock() private val mockAutoconsent: Autoconsent = mock() private val mockGpc: Gpc = mock() private val mockAppTrackingProtection: AppTrackingProtection = mock() @@ -52,172 +44,111 @@ class AndroidFeaturesHeaderProviderTest { @Before fun setup() { testee = RealAndroidFeaturesHeaderProvider( - appBuildConfig, - featuresRequestHeaderStore, mockAutoconsent, mockGpc, mockAppTrackingProtection, mockNetworkProtectionState, ) - - whenever(appBuildConfig.versionCode).thenReturn(currentVersion) - givenBuildDateDaysAgo(6) - } - - @Test - fun whenNoVersionsPresentThenNoValueProvided() { - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(emptyList()) - - val result = testee.provide() - assertNull(result) - } - - @Test - fun whenCurrentVersionNotPresentThenNoValueProvided() { - val noFeaturesEnabled = TrafficQualityAppVersion(anotherVersion, 5, 5, noFeaturesEnabled()) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(noFeaturesEnabled)) - - val result = testee.provide() - assertNull(result) } @Test - fun whenCurrentVersionPresentAndNoFeaturesEnabledThenNoValueProvided() { + fun whenNoFeaturesEnabledThenNoValueProvided() { val noFeaturesEnabled = TrafficQualityAppVersion(currentVersion, 5, 5, noFeaturesEnabled()) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(noFeaturesEnabled)) - val result = testee.provide() + val result = testee.provide(noFeaturesEnabled) + assertNull(result) } @Test - fun whenCurrentVersionPresentAndGPCFeatureEnabledAndGPCDisabledThenValueProvided() { + fun whenGPCFeatureEnabledAndGPCDisabledThenValueProvided() { whenever(mockGpc.isEnabled()).thenReturn(false) - val gpcEnabled = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(gpcEnabled)) + val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true)) + + val result = testee.provide(config) - val result = testee.provide() assertTrue(result == "gpc_enabled=false") } @Test - fun whenCurrentVersionPresentAndGPCFeatureEnabledAndGPCEnabledThenValueProvided() { + fun whenGPCFeatureEnabledAndGPCEnabledThenValueProvided() { whenever(mockGpc.isEnabled()).thenReturn(true) - val gpcEnabled = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(gpcEnabled)) + val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true)) + + val result = testee.provide(config) - val result = testee.provide() assertTrue(result == "gpc_enabled=true") } @Test - fun whenCurrentVersionPresentAndCPMFeatureEnabledAndCPMDisabledThenValueProvided() { + fun whenCPMFeatureEnabledAndCPMDisabledThenValueProvided() { whenever(mockAutoconsent.isAutoconsentEnabled()).thenReturn(false) - val gpcEnabled = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(cpm = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(gpcEnabled)) + val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(cpm = true)) + + val result = testee.provide(config) - val result = testee.provide() assertTrue(result == "cpm_enabled=false") } @Test - fun whenCurrentVersionPresentAndCPMFeatureEnabledAndCPMEnabledThenValueProvided() { + fun whenCPMFeatureEnabledAndCPMEnabledThenValueProvided() { whenever(mockAutoconsent.isAutoconsentEnabled()).thenReturn(true) - val gpcEnabled = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(cpm = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(gpcEnabled)) + val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(cpm = true)) + + val result = testee.provide(config) - val result = testee.provide() assertTrue(result == "cpm_enabled=true") } @Test - fun whenCurrentVersionPresentAndAppTPFeatureEnabledAndAppTPDisabledThenValueProvided() = runTest { + fun whenAppTPFeatureEnabledAndAppTPDisabledThenValueProvided() = runTest { whenever(mockAppTrackingProtection.isEnabled()).thenReturn(false) - val gpcEnabled = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(appTP = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(gpcEnabled)) + val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(appTP = true)) + + val result = testee.provide(config) - val result = testee.provide() assertTrue(result == "atp_enabled=false") } @Test - fun whenCurrentVersionPresentAndAppTPFeatureEnabledAndAppTPEnabledThenValueProvided() = runTest { + fun whenAppTPFeatureEnabledAndAppTPEnabledThenValueProvided() = runTest { whenever(mockAppTrackingProtection.isEnabled()).thenReturn(true) - val gpcEnabled = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(appTP = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(gpcEnabled)) + val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(appTP = true)) + + val result = testee.provide(config) - val result = testee.provide() assertTrue(result == "atp_enabled=true") } @Test - fun whenCurrentVersionPresentAndVPNFeatureEnabledAndVPNDisabledThenValueProvided() = runTest { + fun whenVPNFeatureEnabledAndVPNDisabledThenValueProvided() = runTest { whenever(mockNetworkProtectionState.isEnabled()).thenReturn(false) - val gpcEnabled = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(netP = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(gpcEnabled)) + val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(netP = true)) + + val result = testee.provide(config) - val result = testee.provide() assertTrue(result == "vpn_enabled=false") } @Test - fun whenCurrentVersionPresentAndVPNFeatureEnabledAndVPNEnabledThenValueProvided() = runTest { + fun whenVPNFeatureEnabledAndVPNEnabledThenValueProvided() = runTest { whenever(mockNetworkProtectionState.isEnabled()).thenReturn(true) - val gpcEnabled = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(netP = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(gpcEnabled)) + val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(netP = true)) + + val result = testee.provide(config) - val result = testee.provide() assertTrue(result == "vpn_enabled=true") } @Test - fun whenCurrentVersionPresentAndSeveralFeaturesEnabledThenOnlyOneValueProvided() = runTest { + fun whenSeveralFeaturesEnabledThenOnlyOneValueProvided() = runTest { whenever(mockNetworkProtectionState.isEnabled()).thenReturn(true) whenever(mockAppTrackingProtection.isEnabled()).thenReturn(true) whenever(mockAutoconsent.isAutoconsentEnabled()).thenReturn(true) whenever(mockGpc.isEnabled()).thenReturn(true) - val features = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true, cpm = true, appTP = true, netP = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(features)) + val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true, cpm = true, appTP = true, netP = true)) - val result = testee.provide() + val result = testee.provide(config) assertTrue(result == "vpn_enabled=true" || result == "cpm_enabled=true" || result == "gpc_enabled=true" || result == "atp_enabled=true") } - - @Test - fun whenItsTooEarlyToLogThenNoValueProvided() = runTest { - givenBuildDateDaysAgo(1) - val features = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true, cpm = true, appTP = true, netP = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(features)) - - val result = testee.provide() - assertNull(result) - } - - @Test - fun whenItsTooLateToLogThenNoValueProvided() = runTest { - givenBuildDateDaysAgo(20) - val features = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true, cpm = true, appTP = true, netP = true)) - whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(features)) - - val result = testee.provide() - assertNull(result) - } - - private fun noFeaturesEnabled(): TrafficQualityAppVersionFeatures { - return TrafficQualityAppVersionFeatures(gpc = false, cpm = false, appTP = false, netP = false) - } - - private fun featuresEnabled( - gpc: Boolean = false, - cpm: Boolean = false, - appTP: Boolean = false, - netP: Boolean = false, - ): TrafficQualityAppVersionFeatures { - return TrafficQualityAppVersionFeatures(gpc = gpc, cpm = cpm, appTP = appTP, netP = netP) - } - - private fun givenBuildDateDaysAgo(days: Long) { - val daysAgo = LocalDateTime.now().minusDays(days).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - whenever(appBuildConfig.buildDateTimeMillis).thenReturn(daysAgo) - } } diff --git a/app/src/test/java/com/duckduckgo/app/browser/trafficquality/CustomHeaderAllowedCheckerTest.kt b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/CustomHeaderAllowedCheckerTest.kt new file mode 100644 index 000000000000..5ef299b6dc3f --- /dev/null +++ b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/CustomHeaderAllowedCheckerTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.browser.trafficquality + +import com.duckduckgo.app.browser.trafficquality.Result.Allowed +import com.duckduckgo.app.browser.trafficquality.Result.NotAllowed +import com.duckduckgo.app.browser.trafficquality.remote.FeaturesRequestHeaderStore +import com.duckduckgo.appbuildconfig.api.AppBuildConfig +import java.time.LocalDateTime +import java.time.ZoneId +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class CustomHeaderAllowedCheckerTest { + + private val appBuildConfig: AppBuildConfig = mock() + private val featuresRequestHeaderStore: FeaturesRequestHeaderStore = mock() + + private lateinit var testee: CustomHeaderAllowedChecker + + @Before + fun setup() { + testee = RealCustomHeaderGracePeriodChecker(appBuildConfig, featuresRequestHeaderStore) + whenever(appBuildConfig.versionCode).thenReturn(currentVersion) + } + + @Test + fun whenNoConfigAvailableThenNotAllowed() = runTest { + whenever(featuresRequestHeaderStore.getConfig()).thenReturn(emptyList()) + + val result = testee.isAllowed() + + assert(result is NotAllowed) + } + + @Test + fun whenNoConfigForCurrentVersionAvailableThenNotAllowed() = runTest { + whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(configEnabledForAnotherVersion)) + + val result = testee.isAllowed() + + assert(result is NotAllowed) + } + + @Test + fun whenAskingAtStartOfGracePeriodThenIsAllowed() = runTest { + givenBuildDateDaysAgo(5) + whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(configEnabledForCurrentVersion)) + + val result = testee.isAllowed() + + assert(result is Allowed) + } + + @Test + fun whenAskingDuringGracePeriodThenReturnIsAllowed() = runTest { + givenBuildDateDaysAgo(8) + whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(configEnabledForCurrentVersion)) + + val result = testee.isAllowed() + + assert(result is Allowed) + } + + @Test + fun whenAskingAtTheEndOfGracePeriodThenIsAllowed() = runTest { + givenBuildDateDaysAgo(10) + whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(configEnabledForCurrentVersion)) + + val result = testee.isAllowed() + + assert(result is Allowed) + } + + @Test + fun whenItsTooEarlyToLogThenIsNotAllowed() = runTest { + givenBuildDateDaysAgo(1) + + whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(configEnabledForCurrentVersion)) + + val result = testee.isAllowed() + + assert(result is NotAllowed) + } + + @Test + fun whenItsTooLateToLogThenIsNotAllowed() = runTest { + givenBuildDateDaysAgo(20) + + whenever(featuresRequestHeaderStore.getConfig()).thenReturn(listOf(configEnabledForCurrentVersion)) + + val result = testee.isAllowed() + + assert(result is NotAllowed) + } + + private fun givenBuildDateDaysAgo(days: Long) { + val daysAgo = LocalDateTime.now().minusDays(days).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() + whenever(appBuildConfig.buildDateTimeMillis).thenReturn(daysAgo) + } +} diff --git a/app/src/test/java/com/duckduckgo/app/browser/trafficquality/RealAppVersionHeaderProviderTest.kt b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/RealAppVersionHeaderProviderTest.kt new file mode 100644 index 000000000000..4f3025e49657 --- /dev/null +++ b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/RealAppVersionHeaderProviderTest.kt @@ -0,0 +1,41 @@ +package com.duckduckgo.app.browser.trafficquality + +import com.duckduckgo.app.browser.trafficquality.RealAppVersionHeaderProvider.Companion.APP_VERSION_QUALITY_DEFAULT_VALUE +import com.duckduckgo.appbuildconfig.api.AppBuildConfig +import com.duckduckgo.appbuildconfig.api.BuildFlavor +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class RealAppVersionHeaderProviderTest { + + private val appBuildConfig = mock().apply { + whenever(this.flavor).thenReturn(BuildFlavor.PLAY) + } + + private lateinit var testee: RealAppVersionHeaderProvider + + @Before + fun setup() { + testee = RealAppVersionHeaderProvider(appBuildConfig) + } + + @Test + fun whenStubRequiredThenStubProvided() { + val appVersion = testee.provide(isStub = true) + + assertTrue(appVersion == APP_VERSION_QUALITY_DEFAULT_VALUE) + } + + @Test + fun whenStubNotRequiredThenVersionProvided() { + val versionName = "5.212.0" + whenever(appBuildConfig.versionName).thenReturn(versionName) + + val appVersion = testee.provide(isStub = false) + + assertTrue(appVersion == versionName) + } +} diff --git a/app/src/test/java/com/duckduckgo/app/browser/trafficquality/RealQualityAppVersionProviderTest.kt b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/RealQualityAppVersionProviderTest.kt deleted file mode 100644 index 2d79d6606f8e..000000000000 --- a/app/src/test/java/com/duckduckgo/app/browser/trafficquality/RealQualityAppVersionProviderTest.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.duckduckgo.app.browser.trafficquality - -import com.duckduckgo.app.browser.trafficquality.RealQualityAppVersionProvider.Companion.APP_VERSION_QUALITY_DEFAULT_VALUE -import com.duckduckgo.appbuildconfig.api.AppBuildConfig -import com.duckduckgo.appbuildconfig.api.BuildFlavor -import java.time.LocalDateTime -import java.time.ZoneId -import org.junit.Assert.* -import org.junit.Before -import org.junit.Test -import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever - -class RealQualityAppVersionProviderTest { - - private val appBuildConfig = mock().apply { - whenever(this.flavor).thenReturn(BuildFlavor.PLAY) - } - - private lateinit var testee: RealQualityAppVersionProvider - - @Before - fun setup() { - testee = RealQualityAppVersionProvider(appBuildConfig) - } - - @Test - fun whenBuildDateEmptyThenReturnDefault() { - whenever(appBuildConfig.buildDateTimeMillis).thenReturn(0L) - val appVersion = testee.provide() - assertTrue(appVersion == APP_VERSION_QUALITY_DEFAULT_VALUE) - } - - @Test - fun whenBuildDateTodayThenReturnDefault() { - givenBuildDateDaysAgo(0) - val appVersion = testee.provide() - assertTrue(appVersion == APP_VERSION_QUALITY_DEFAULT_VALUE) - } - - @Test - fun whenNotYetTimeToLogThenReturnDefault() { - givenBuildDateDaysAgo(2) - val appVersion = testee.provide() - assertTrue(appVersion == APP_VERSION_QUALITY_DEFAULT_VALUE) - } - - @Test - fun whenTimeToLogThenReturnAppVersion() { - givenBuildDateDaysAgo(6) - val versionName = "5.212.0" - givenVersionName(versionName) - val appVersion = testee.provide() - assertTrue(appVersion == versionName) - } - - @Test - fun whenTimeToLogAndNotOverLoggingPeriodThenReturnAppVersion() { - val versionName = "5.212.0" - givenVersionName(versionName) - givenBuildDateDaysAgo(8) - val appVersion = testee.provide() - assertTrue(appVersion == versionName) - } - - @Test - fun whenTimeToLogAndOverLoggingPeriodThenReturnDefault() { - val versionName = "5.212.0" - givenVersionName(versionName) - givenBuildDateDaysAgo(20) - val appVersion = testee.provide() - assertTrue(appVersion == APP_VERSION_QUALITY_DEFAULT_VALUE) - } - - @Test - fun whenTimeToLogAndJustOnLoggingPeriodThenReturnVersionName() { - val versionName = "5.212.0" - givenVersionName(versionName) - givenBuildDateDaysAgo(16) - val appVersion = testee.provide() - assertTrue(appVersion == versionName) - } - - private fun givenBuildDateDaysAgo(days: Long) { - val daysAgo = LocalDateTime.now().minusDays(days).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - whenever(appBuildConfig.buildDateTimeMillis).thenReturn(daysAgo) - } - - private fun givenVersionName(versionName: String) { - whenever(appBuildConfig.versionName).thenReturn(versionName) - } -} diff --git a/app/src/test/java/com/duckduckgo/app/browser/trafficquality/TrafficQualityTestData.kt b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/TrafficQualityTestData.kt new file mode 100644 index 000000000000..fe7d3b05f8b7 --- /dev/null +++ b/app/src/test/java/com/duckduckgo/app/browser/trafficquality/TrafficQualityTestData.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.browser.trafficquality + +import com.duckduckgo.app.browser.trafficquality.remote.TrafficQualityAppVersion +import com.duckduckgo.app.browser.trafficquality.remote.TrafficQualityAppVersionFeatures + +val currentVersion = 5210000 +private val anotherVersion = 5220000 + +val configEnabledForAnotherVersion = TrafficQualityAppVersion(anotherVersion, 5, 5, featuresEnabled()) +val configEnabledForCurrentVersion = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled()) + +fun noFeaturesEnabled(): TrafficQualityAppVersionFeatures { + return TrafficQualityAppVersionFeatures(gpc = false, cpm = false, appTP = false, netP = false) +} + +fun featuresEnabled( + gpc: Boolean = false, + cpm: Boolean = false, + appTP: Boolean = false, + netP: Boolean = false, +): TrafficQualityAppVersionFeatures { + return TrafficQualityAppVersionFeatures(gpc = gpc, cpm = cpm, appTP = appTP, netP = netP) +}