Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8160,7 +8160,7 @@ class BrowserTabViewModelTest {
class FakeCustomHeadersProvider(
var headers: Map<String, String>,
) : CustomHeadersProvider {
override fun getCustomHeaders(url: String): Map<String, String> = headers
override suspend fun getCustomHeaders(url: String): Map<String, String> = headers
}

class FakeContentScopeScriptsSubscriptionEventPlugin(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ class BrowserWebViewClientTest {
mockAndroidBrowserConfigFeature,
mockFeaturesHeaderProvider,
mock(),
coroutinesTestRule.testDispatcherProvider,
)
private val mockDuckChat: DuckChat = mock()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,12 @@ class WebViewRequestInterceptorTest {

@UiThreadTest
@Before
fun setup() {
fun setup() = runTest {
configureUserAgent()
configureStack()
whenever(mockGpc.canUrlAddHeaders(anyString(), anyMap<String, String>())).thenReturn(false)
whenever(mockRequest.requestHeaders).thenReturn(mutableMapOf())
whenever(mockGpc.getHeaders(anyString())).thenReturn(mapOf())

testee = WebViewRequestInterceptor(
trackerDetector = mockTrackerDetector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1194,7 +1194,9 @@ class BrowserTabViewModel @Inject constructor(
}

site?.nextUrl = urlToNavigate
command.value = NavigationCommand.Navigate(urlToNavigate, getUrlHeaders(urlToNavigate))
viewModelScope.launch {
command.value = NavigationCommand.Navigate(urlToNavigate, getUrlHeaders(urlToNavigate))
}
}
}

Expand All @@ -1219,7 +1221,7 @@ class BrowserTabViewModel @Inject constructor(
currentAutoCompleteViewState().copy(showSuggestions = false, showFavorites = false, searchResults = AutoCompleteResult("", emptyList()))
}

private fun getUrlHeaders(url: String?): Map<String, String> = url?.let { customHeadersProvider.getCustomHeaders(it) } ?: emptyMap()
private suspend fun getUrlHeaders(url: String?): Map<String, String> = url?.let { customHeadersProvider.getCustomHeaders(it) } ?: emptyMap()

private fun extractVerticalParameter(currentUrl: String?): String? {
val url = currentUrl ?: return null
Expand Down Expand Up @@ -2795,7 +2797,9 @@ class BrowserTabViewModel @Inject constructor(
if (desktopSiteRequested && uri.isMobileSite) {
val desktopUrl = uri.toDesktopUri().toString()
logcat(INFO) { "Original URL $url - attempting $desktopUrl with desktop site UA string" }
command.value = NavigationCommand.Navigate(desktopUrl, getUrlHeaders(desktopUrl))
viewModelScope.launch {
command.value = NavigationCommand.Navigate(desktopUrl, getUrlHeaders(desktopUrl))
}
} else {
command.value = NavigationCommand.Refresh
}
Expand Down Expand Up @@ -3165,7 +3169,9 @@ class BrowserTabViewModel @Inject constructor(

fun nonHttpAppLinkClicked(appLink: NonHttpAppLink) {
if (nonHttpAppLinkChecker.isPermitted(appLink.intent)) {
command.value = HandleNonHttpAppLink(appLink, getUrlHeaders(appLink.fallbackUrl))
viewModelScope.launch {
command.value = HandleNonHttpAppLink(appLink, getUrlHeaders(appLink.fallbackUrl))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,13 +363,13 @@ class WebViewRequestInterceptor(
return WebResourceResponse(null, null, null)
}

private fun getHeaders(request: WebResourceRequest): Map<String, String> {
private suspend fun getHeaders(request: WebResourceRequest): Map<String, String> {
return request.requestHeaders.apply {
putAll(gpc.getHeaders(request.url.toString()))
}
}

private fun shouldAddGcpHeaders(request: WebResourceRequest): Boolean {
private suspend fun shouldAddGcpHeaders(request: WebResourceRequest): Boolean {
val existingHeaders = request.requestHeaders
return (request.isForMainFrame && request.method == "GET" && gpc.canUrlAddHeaders(request.url.toString(), existingHeaders))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ 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.DispatcherProvider
import com.duckduckgo.common.utils.plugins.headers.CustomHeadersProvider.CustomHeadersPlugin
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesMultibinding
import kotlinx.coroutines.withContext
import javax.inject.Inject

@ContributesMultibinding(scope = AppScope::class)
Expand All @@ -33,9 +35,10 @@ class AndroidFeaturesHeaderPlugin @Inject constructor(
private val androidBrowserConfigFeature: AndroidBrowserConfigFeature,
private val androidFeaturesHeaderProvider: AndroidFeaturesHeaderProvider,
private val appVersionProvider: AppVersionHeaderProvider,
private val dispatchers: DispatcherProvider,
) : CustomHeadersPlugin {

override fun getHeaders(url: String): Map<String, String> {
override suspend fun getHeaders(url: String): Map<String, String> {
if (isFeatureEnabled() && duckDuckGoUrlDetector.isDuckDuckGoQueryUrl(url)) {
return when (val result = customHeaderAllowedChecker.isAllowed()) {
is Allowed -> {
Expand All @@ -58,9 +61,11 @@ class AndroidFeaturesHeaderPlugin @Inject constructor(
}
}

private fun isFeatureEnabled(): Boolean {
return androidBrowserConfigFeature.self().isEnabled() &&
androidBrowserConfigFeature.featuresRequestHeader().isEnabled()
private suspend fun isFeatureEnabled(): Boolean {
return withContext(dispatchers.io()) {
androidBrowserConfigFeature.self().isEnabled() &&
androidBrowserConfigFeature.featuresRequestHeader().isEnabled()
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import java.time.temporal.ChronoUnit
import javax.inject.Inject

interface CustomHeaderAllowedChecker {
fun isAllowed(): Result<TrafficQualityAppVersion>
suspend fun isAllowed(): Result<TrafficQualityAppVersion>
}

sealed class Result<out R> {
Expand All @@ -42,7 +42,7 @@ class RealCustomHeaderGracePeriodChecker @Inject constructor(
private val appBuildConfig: AppBuildConfig,
private val featuresRequestHeaderStore: FeaturesRequestHeaderStore,
) : CustomHeaderAllowedChecker {
override fun isAllowed(): Result<TrafficQualityAppVersion> {
override suspend fun isAllowed(): Result<TrafficQualityAppVersion> {
val config = featuresRequestHeaderStore.getConfig()
val versionConfig = config.find { it.appVersion == appBuildConfig.versionCode }
return if (versionConfig != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@
package com.duckduckgo.app.browser.trafficquality.remote

import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import kotlinx.coroutines.withContext
import javax.inject.Inject

interface FeaturesRequestHeaderStore {
fun getConfig(): List<TrafficQualityAppVersion>
suspend fun getConfig(): List<TrafficQualityAppVersion>
}

data class TrafficQualitySettingsJson(
Expand All @@ -49,19 +51,22 @@ data class TrafficQualityAppVersionFeatures(
class FeaturesRequestHeaderSettingStore @Inject constructor(
private val androidBrowserConfigFeature: AndroidBrowserConfigFeature,
private val moshi: Moshi,
private val dispatcherProvider: DispatcherProvider,
) : FeaturesRequestHeaderStore {

private val jsonAdapter: JsonAdapter<TrafficQualitySettingsJson> by lazy {
moshi.adapter(TrafficQualitySettingsJson::class.java)
}

override fun getConfig(): List<TrafficQualityAppVersion> {
val config = androidBrowserConfigFeature.featuresRequestHeader().getSettings()?.let {
runCatching {
val configJson = jsonAdapter.fromJson(it)
configJson?.versions
}.getOrDefault(emptyList())
} ?: emptyList()
return config
override suspend fun getConfig(): List<TrafficQualityAppVersion> {
return withContext(dispatcherProvider.io()) {
val config = androidBrowserConfigFeature.featuresRequestHeader().getSettings()?.let {
runCatching {
val configJson = jsonAdapter.fromJson(it)
configJson?.versions
}.getOrDefault(emptyList())
} ?: emptyList()
config
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.browser.R
import com.duckduckgo.app.pixels.AppPixelName.*
Expand All @@ -29,6 +30,7 @@ import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.feature.toggles.api.FeatureToggle
import com.duckduckgo.privacy.config.api.Gpc
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import kotlinx.coroutines.launch
import javax.inject.Inject

@ContributesViewModel(ActivityScope::class)
Expand All @@ -55,10 +57,13 @@ class GlobalPrivacyControlViewModel @Inject constructor(
val command: SingleLiveEvent<Command> = SingleLiveEvent()

init {
_viewState.value = ViewState(
globalPrivacyControlEnabled = gpc.isEnabled(),
globalPrivacyControlFeatureEnabled = featureToggle.isFeatureEnabled(PrivacyFeatureName.GpcFeatureName.value, true),
)
viewModelScope.launch {
_viewState.value = ViewState(
globalPrivacyControlEnabled = gpc.isEnabled(),
globalPrivacyControlFeatureEnabled = featureToggle.isFeatureEnabled(PrivacyFeatureName.GpcFeatureName.value, true),
)
}

pixel.fire(SETTINGS_DO_NOT_SELL_SHOWN)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class BrokenSiteSubmitterTest {
private lateinit var testee: BrokenSiteSubmitter

@Before
fun before() {
fun before() = runTest {
whenever(mockAppBuildConfig.deviceLocale).thenReturn(Locale.ENGLISH)
whenever(mockAppBuildConfig.sdkInt).thenReturn(1)
whenever(mockAppBuildConfig.manufacturer).thenReturn("manufacturer")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ 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.common.test.CoroutineTestRule
import com.duckduckgo.feature.toggles.api.Toggle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
Expand All @@ -23,6 +25,9 @@ import org.mockito.kotlin.whenever

class AndroidFeaturesHeaderPluginTest {

@get:Rule
val coroutineRule = CoroutineTestRule()

private lateinit var testee: AndroidFeaturesHeaderPlugin

private val mockDuckDuckGoUrlDetector: DuckDuckGoUrlDetector = mock()
Expand All @@ -37,13 +42,14 @@ class AndroidFeaturesHeaderPluginTest {
private val SAMPLE_APP_VERSION_HEADER = "app_version_header"

@Before
fun setup() {
fun setup() = runTest {
testee = AndroidFeaturesHeaderPlugin(
mockDuckDuckGoUrlDetector,
mockCustomHeaderGracePeriodChecker,
mockAndroidBrowserConfigFeature,
mockAndroidFeaturesHeaderProvider,
mockAppVersionHeaderProvider,
coroutineRule.testDispatcherProvider,
)

whenever(mockCustomHeaderGracePeriodChecker.isAllowed()).thenReturn(Allowed(configEnabledForCurrentVersion))
Expand Down Expand Up @@ -79,7 +85,7 @@ class AndroidFeaturesHeaderPluginTest {
}

@Test
fun whenGetHeadersCalledWithDuckDuckGoUrlAndFeatureDisabledThenReturnEmptyHeaders() {
fun whenGetHeadersCalledWithDuckDuckGoUrlAndFeatureDisabledThenReturnEmptyHeaders() = runTest {
val url = "duckduckgo_search_url"
whenever(mockDuckDuckGoUrlDetector.isDuckDuckGoQueryUrl(any())).thenReturn(true)
whenever(mockAndroidBrowserConfigFeature.self()).thenReturn(mockEnabledToggle)
Expand All @@ -91,7 +97,7 @@ class AndroidFeaturesHeaderPluginTest {
}

@Test
fun whenGetHeadersCalledWithDuckDuckGoUrlAndHeaderNotAllowedThenReturnCorrectHeader() {
fun whenGetHeadersCalledWithDuckDuckGoUrlAndHeaderNotAllowedThenReturnCorrectHeader() = runTest {
val url = "duckduckgo_search_url"
whenever(mockDuckDuckGoUrlDetector.isDuckDuckGoQueryUrl(any())).thenReturn(true)
whenever(mockAndroidBrowserConfigFeature.self()).thenReturn(mockEnabledToggle)
Expand All @@ -105,7 +111,7 @@ class AndroidFeaturesHeaderPluginTest {
}

@Test
fun whenGetHeadersCalledWithNonDuckDuckGoUrlAndFeatureEnabledThenReturnEmptyMap() {
fun whenGetHeadersCalledWithNonDuckDuckGoUrlAndFeatureEnabledThenReturnEmptyMap() = runTest {
val url = "non_duckduckgo_search_url"
whenever(mockDuckDuckGoUrlDetector.isDuckDuckGoQueryUrl(any())).thenReturn(false)
whenever(mockAndroidBrowserConfigFeature.self()).thenReturn(mockEnabledToggle)
Expand All @@ -117,7 +123,7 @@ class AndroidFeaturesHeaderPluginTest {
}

@Test
fun whenGetHeadersCalledWithNonDuckDuckGoUrlAndFeatureDisabledThenReturnEmptyMap() {
fun whenGetHeadersCalledWithNonDuckDuckGoUrlAndFeatureDisabledThenReturnEmptyMap() = runTest {
val url = "non_duckduckgo_search_url"
whenever(mockDuckDuckGoUrlDetector.isDuckDuckGoQueryUrl(any())).thenReturn(false)
whenever(mockAndroidBrowserConfigFeature.self()).thenReturn(mockEnabledToggle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class AndroidFeaturesHeaderProviderTest {
}

@Test
fun whenGPCFeatureEnabledAndGPCDisabledThenValueProvided() {
fun whenGPCFeatureEnabledAndGPCDisabledThenValueProvided() = runTest {
whenever(mockGpc.isEnabled()).thenReturn(false)
val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true))

Expand All @@ -71,7 +71,7 @@ class AndroidFeaturesHeaderProviderTest {
}

@Test
fun whenGPCFeatureEnabledAndGPCEnabledThenValueProvided() {
fun whenGPCFeatureEnabledAndGPCEnabledThenValueProvided() = runTest {
whenever(mockGpc.isEnabled()).thenReturn(true)
val config = TrafficQualityAppVersion(currentVersion, 5, 5, featuresEnabled(gpc = true))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,27 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.common.test.InstantSchedulersRule
import com.duckduckgo.feature.toggles.api.FeatureToggle
import com.duckduckgo.privacy.config.api.Gpc
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.lastValue
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class GlobalPrivacyControlViewModelTest {

@get:Rule
val coroutineRule = CoroutineTestRule()

@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()

Expand All @@ -52,7 +57,8 @@ class GlobalPrivacyControlViewModelTest {
lateinit var testee: GlobalPrivacyControlViewModel

@Before
fun setup() {
fun setup() = runTest {
whenever(mockGpc.isEnabled()).thenReturn(false)
testee = GlobalPrivacyControlViewModel(mockPixel, mockFeatureToggle, mockGpc)
testee.command.observeForever(mockCommandObserver)
testee.viewState.observeForever(mockViewStateObserver)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
Expand Down Expand Up @@ -167,7 +168,7 @@ class BrokenSitesReferenceTest(private val testCase: TestCase) {
}

@Test
fun whenReferenceTestRunsItReturnsTheExpectedResult() {
fun whenReferenceTestRunsItReturnsTheExpectedResult() = runTest {
whenever(mockAppBuildConfig.sdkInt).thenReturn(testCase.os?.toInt() ?: 1)
whenever(mockAppBuildConfig.manufacturer).thenReturn(testCase.manufacturer)
whenever(mockAppBuildConfig.model).thenReturn(testCase.model)
Expand Down
Loading
Loading