Skip to content

Commit d388e19

Browse files
author
Marcos Holgado
committed
Merge branch 'release/5.74.0' into main
2 parents 0211e63 + 354885e commit d388e19

File tree

17 files changed

+425
-78
lines changed

17 files changed

+425
-78
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ ext {
121121
ankoVersion = "0.10.4"
122122
glide = "4.11.0"
123123
lottieVersion = "3.4.0"
124-
okHttp = "3.14.9"
124+
okHttp = "4.9.0"
125125
rxJava = "2.1.10"
126126
rxAndroid = "2.0.2"
127127
timber = "4.7.1"

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3027,6 +3027,41 @@ class BrowserTabViewModelTest {
30273027
verify(mockNavigationAwareLoginDetector).onEvent(NavigationEvent.GpcRedirect)
30283028
}
30293029

3030+
@Test
3031+
fun whenProgressIs100ThenRefreshUserAgentCommandSent() {
3032+
loadUrl("http://duckduckgo.com")
3033+
testee.progressChanged(100)
3034+
3035+
assertCommandNotIssued<Command.RefreshUserAgent>()
3036+
}
3037+
3038+
@Test
3039+
fun whenPageChangesAndNewPageCanChangeBrowsingModeThenCanChangeBrowsingModeIsTrue() {
3040+
givenCurrentSite("https://www.example.com/")
3041+
3042+
loadUrl("https://www.example2.com", isBrowserShowing = true)
3043+
3044+
assertTrue(browserViewState().canChangeBrowsingMode)
3045+
}
3046+
3047+
@Test
3048+
fun whenPageChangesAndNewPageCannotChangeBrowsingModeThenCanChangeBrowsingModeIsFalse() {
3049+
givenCurrentSite("https://www.example.com/")
3050+
3051+
loadUrl("https://www.facebook.com", isBrowserShowing = true)
3052+
3053+
assertFalse(browserViewState().canChangeBrowsingMode)
3054+
}
3055+
3056+
@Test
3057+
fun whenPageChangesAndNewPageCanChangeBrowsingModeButContainsExcludedPathThenCanChangeBrowsingModeIsFalse() {
3058+
givenCurrentSite("https://www.example.com/")
3059+
3060+
loadUrl("https://www.facebook.com/dialog", isBrowserShowing = true)
3061+
3062+
assertFalse(browserViewState().canChangeBrowsingMode)
3063+
}
3064+
30303065
private suspend fun givenFireButtonPulsing() {
30313066
whenever(mockUserStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
30323067
dismissedCtaDaoChannel.send(listOf(DismissedCta(CtaId.DAX_DIALOG_TRACKERS_FOUND)))

app/src/androidTest/java/com/duckduckgo/app/browser/WebViewRequestInterceptorTest.kt

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,13 @@
1919
package com.duckduckgo.app.browser
2020

2121
import android.net.Uri
22-
import android.webkit.WebBackForwardList
23-
import android.webkit.WebHistoryItem
24-
import android.webkit.WebResourceRequest
25-
import android.webkit.WebResourceResponse
26-
import android.webkit.WebView
22+
import android.webkit.*
2723
import androidx.test.annotation.UiThreadTest
2824
import com.duckduckgo.app.CoroutineTestRule
2925
import com.duckduckgo.app.globalprivacycontrol.GlobalPrivacyControl
3026
import com.duckduckgo.app.globalprivacycontrol.GlobalPrivacyControlManager
27+
import com.duckduckgo.app.browser.useragent.MobileUrlReWriter
28+
import com.duckduckgo.app.browser.useragent.UserAgentProvider
3129
import com.duckduckgo.app.httpsupgrade.HttpsUpgrader
3230
import com.duckduckgo.app.privacy.db.PrivacyProtectionCountDao
3331
import com.duckduckgo.app.surrogates.ResourceSurrogates
@@ -59,20 +57,26 @@ class WebViewRequestInterceptorTest {
5957
private val mockPrivacyProtectionCountDao: PrivacyProtectionCountDao = mock()
6058
private val mockGlobalPrivacyControl: GlobalPrivacyControl = mock()
6159
private val mockWebBackForwardList: WebBackForwardList = mock()
60+
private val userAgentProvider: UserAgentProvider = UserAgentProvider(DEFAULT, mock())
61+
private val mobileUrlReWriter = MobileUrlReWriter()
6262

6363
private var webView: WebView = mock()
6464

6565
@UiThreadTest
6666
@Before
6767
fun setup() {
6868
MockitoAnnotations.openMocks(this)
69+
configureUserAgent()
70+
configureStack()
6971

7072
testee = WebViewRequestInterceptor(
7173
trackerDetector = mockTrackerDetector,
7274
httpsUpgrader = mockHttpsUpgrader,
7375
resourceSurrogates = mockResourceSurrogates,
7476
privacyProtectionCountDao = mockPrivacyProtectionCountDao,
75-
globalPrivacyControl = mockGlobalPrivacyControl
77+
globalPrivacyControl = mockGlobalPrivacyControl,
78+
userAgentProvider = userAgentProvider,
79+
mobileUrlReWriter = mobileUrlReWriter
7680
)
7781
}
7882

@@ -440,6 +444,69 @@ class WebViewRequestInterceptorTest {
440444
verify(webView, never()).loadUrl(any(), any())
441445
}
442446

447+
@Test
448+
fun whenUrlShouldChangeToMobileUrlThenLoadUrlWithMobileSubDomain() = runBlocking<Unit> {
449+
configureShouldChangeToMobileUrl()
450+
451+
val mockWebViewClientListener: WebViewClientListener = mock()
452+
testee.shouldIntercept(
453+
request = mockRequest,
454+
documentUrl = null,
455+
webView = webView,
456+
webViewClientListener = mockWebViewClientListener
457+
)
458+
459+
verify(webView).loadUrl("https://m.facebook.com", emptyMap())
460+
}
461+
462+
@Test
463+
fun whenUserAgentShouldChangeThenReloadUrl() = runBlocking<Unit> {
464+
configureUserAgentShouldChange()
465+
configureUrlDoesNotExistInTheStack()
466+
467+
val mockWebViewClientListener: WebViewClientListener = mock()
468+
testee.shouldIntercept(
469+
request = mockRequest,
470+
documentUrl = null,
471+
webView = webView,
472+
webViewClientListener = mockWebViewClientListener
473+
)
474+
475+
verify(webView).loadUrl(any(), any())
476+
}
477+
478+
@Test
479+
fun whenUserAgentShouldChangeAndUrlAlreadyWasInTheStackButIsNotTheLastElementThenDoNotReloadUrl() = runBlocking<Unit> {
480+
configureUserAgentShouldChange()
481+
configureUrlExistsInTheStack()
482+
483+
val mockWebViewClientListener: WebViewClientListener = mock()
484+
testee.shouldIntercept(
485+
request = mockRequest,
486+
documentUrl = null,
487+
webView = webView,
488+
webViewClientListener = mockWebViewClientListener
489+
)
490+
491+
verify(webView, never()).loadUrl(any())
492+
}
493+
494+
@Test
495+
fun whenUserAgentHasNotChangedThenDoNotReloadUrl() = runBlocking<Unit> {
496+
configureShouldNotUpgrade()
497+
configureUrlDoesNotExistInTheStack()
498+
499+
val mockWebViewClientListener: WebViewClientListener = mock()
500+
testee.shouldIntercept(
501+
request = mockRequest,
502+
documentUrl = null,
503+
webView = webView,
504+
webViewClientListener = mockWebViewClientListener
505+
)
506+
507+
verify(webView, never()).loadUrl(any())
508+
}
509+
443510
private fun assertRequestCanContinueToLoad(response: WebResourceResponse?) {
444511
assertNull(response)
445512
}
@@ -470,6 +537,10 @@ class WebViewRequestInterceptorTest {
470537
whenever(webView.copyBackForwardList()).thenReturn(mockWebBackForwardList)
471538
}
472539

540+
private fun configureStack() {
541+
configureUrlExistsInTheStack()
542+
}
543+
473544
private fun configureRequestContainsGcpHeader() = runBlocking<Unit> {
474545
whenever(mockGlobalPrivacyControl.isGpcActive()).thenReturn(true)
475546
whenever(mockRequest.method).thenReturn("GET")
@@ -491,6 +562,18 @@ class WebViewRequestInterceptorTest {
491562
whenever(mockRequest.method).thenReturn("GET")
492563
}
493564

565+
private fun configureUserAgentShouldChange() = runBlocking<Unit> {
566+
whenever(mockRequest.url).thenReturn(Uri.parse("https://m.facebook.com"))
567+
whenever(mockRequest.isForMainFrame).thenReturn(true)
568+
whenever(mockRequest.method).thenReturn("GET")
569+
}
570+
571+
private fun configureShouldChangeToMobileUrl() = runBlocking<Unit> {
572+
whenever(mockRequest.url).thenReturn((Uri.parse("https://facebook.com")))
573+
whenever(mockRequest.isForMainFrame).thenReturn(true)
574+
whenever(mockRequest.method).thenReturn("GET")
575+
}
576+
494577
private fun configureShouldUpgrade() = runBlocking<Unit> {
495578
whenever(mockHttpsUpgrader.shouldUpgrade(any())).thenReturn(true)
496579
whenever(mockHttpsUpgrader.upgrade(any())).thenReturn(validHttpsUri())
@@ -515,4 +598,14 @@ class WebViewRequestInterceptorTest {
515598
assertNull(response.encoding)
516599
}
517600

601+
private fun configureUserAgent() {
602+
val settings: WebSettings = mock()
603+
whenever(webView.settings).thenReturn(settings)
604+
whenever(settings.userAgentString).thenReturn(userAgentProvider.userAgent())
605+
}
606+
607+
companion object {
608+
const val DEFAULT =
609+
"Mozilla/5.0 (Linux; Android 8.1.0; Nexus 6P Build/OPM3.171019.014) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36"
610+
}
518611
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) 2020 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.browser.useragent
18+
19+
import androidx.core.net.toUri
20+
import org.junit.Assert.*
21+
import org.junit.Test
22+
23+
class MobileUrlReWriterTest {
24+
25+
private val testee = MobileUrlReWriter()
26+
27+
@Test
28+
fun whenMobileSiteOnlyForUriAndSiteBelongsToMobileSiteHostsThenReturnObject() {
29+
val domain = MobileUrlReWriter.strictlyMobileSiteHosts.first().host
30+
val url = "https://$domain".toUri()
31+
32+
assertNotNull(testee.mobileSiteOnlyForUri(url))
33+
}
34+
35+
@Test
36+
fun whenMobileSiteOnlyForUriAndSiteBelongsToMobileSiteHostsWithExclusionPathThenReturnNull() {
37+
val domain = MobileUrlReWriter.strictlyMobileSiteHosts.first().host
38+
val path = MobileUrlReWriter.strictlyMobileSiteHosts.first().excludedPaths.first()
39+
val url = "https://$domain/$path".toUri()
40+
41+
assertNull(testee.mobileSiteOnlyForUri(url))
42+
}
43+
44+
@Test
45+
fun whenMobileSiteOnlyForUriAndSiteDoesNotBelongsToMobileSiteHostsThenReturnNull() {
46+
assertNull(testee.mobileSiteOnlyForUri("https://example.com".toUri()))
47+
}
48+
}

app/src/androidTest/java/com/duckduckgo/app/browser/useragent/UserAgentProviderTest.kt

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,28 @@ class UserAgentProviderTest {
118118
assertTrue("$actual does not match expected regex", ValidationRegex.converted.matches(actual))
119119
}
120120

121+
@Test
122+
fun whenUserAgentIsForASiteThatShouldUseDesktopAgentThenReturnDesktopUserAgent() {
123+
testee = UserAgentProvider(Agent.DEFAULT, deviceInfo)
124+
val actual = testee.userAgent(DESKTOP_ONLY_SITE)
125+
assertTrue("$actual does not match expected regex", ValidationRegex.desktop.matches(actual))
126+
}
127+
128+
@Test
129+
fun whenUserAgentIsForASiteThatShouldUseDesktopAgentButContainsAnExclusionThenDoNotReturnConvertedUserAgent() {
130+
testee = UserAgentProvider(Agent.DEFAULT, deviceInfo)
131+
val actual = testee.userAgent(DESKTOP_ONLY_SITE_EXCEPTION)
132+
assertTrue("$actual does not match expected regex", ValidationRegex.converted.matches(actual))
133+
}
134+
121135
companion object {
122-
const val DOMAIN = "example.com"
123-
const val NO_APPLICATION_DOMAIN = "cvs.com"
124-
const val NO_APPLICATION_SUBDOMAIN = "subdomain.cvs.com"
125-
const val NO_VERSION_DOMAIN = "ing.nl"
126-
const val NO_VERSION_SUBDOMAIN = "subdomain.ing.nl"
136+
const val DOMAIN = "http://example.com"
137+
const val NO_APPLICATION_DOMAIN = "http://cvs.com"
138+
const val NO_APPLICATION_SUBDOMAIN = "http://subdomain.cvs.com"
139+
const val NO_VERSION_DOMAIN = "http://ing.nl"
140+
const val NO_VERSION_SUBDOMAIN = "http://subdomain.ing.nl"
141+
const val DESKTOP_ONLY_SITE = "http://m.facebook.com"
142+
const val DESKTOP_ONLY_SITE_EXCEPTION = "http://m.facebook.com/dialog/"
127143
}
128144

129145
private object Agent {

app/src/androidTest/java/com/duckduckgo/app/global/api/ApiRequestInterceptorTest.kt

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,54 +17,74 @@
1717
package com.duckduckgo.app.global.api
1818

1919
import androidx.test.platform.app.InstrumentationRegistry
20-
import com.nhaarman.mockitokotlin2.any
21-
import com.nhaarman.mockitokotlin2.verify
22-
import com.nhaarman.mockitokotlin2.whenever
23-
import okhttp3.Interceptor
24-
import okhttp3.Protocol
25-
import okhttp3.Request
26-
import okhttp3.Response
20+
import okhttp3.*
2721
import org.junit.Assert.assertTrue
2822
import org.junit.Before
2923
import org.junit.Test
30-
import org.mockito.ArgumentCaptor
31-
import org.mockito.Mock
32-
import org.mockito.MockitoAnnotations
24+
import java.util.concurrent.TimeUnit
3325

3426
class ApiRequestInterceptorTest {
3527

3628
private lateinit var testee: ApiRequestInterceptor
3729

38-
@Mock
39-
private lateinit var mockChain: Interceptor.Chain
30+
private val fakeChain: Interceptor.Chain = FakeChain()
4031

4132
@Before
4233
fun before() {
43-
MockitoAnnotations.initMocks(this)
4434
testee = ApiRequestInterceptor(InstrumentationRegistry.getInstrumentation().context)
4535
}
4636

4737
@Test
4838
fun whenAPIRequestIsMadeThenUserAgentIsAdded() {
49-
whenever(mockChain.request()).thenReturn(request())
50-
whenever(mockChain.proceed(any())).thenReturn(response())
51-
5239
val packageName = InstrumentationRegistry.getInstrumentation().context.applicationInfo.packageName
5340

54-
val captor = ArgumentCaptor.forClass(Request::class.java)
55-
testee.intercept(mockChain)
56-
verify(mockChain).proceed(captor.capture())
41+
val response = testee.intercept(fakeChain)
5742

5843
val regex = "ddg_android/.*\\($packageName; Android API .*\\)".toRegex()
59-
val result = captor.value.header(Header.USER_AGENT)!!
44+
val result = response.request.header(Header.USER_AGENT)!!
6045
assertTrue(result.matches(regex))
6146
}
6247

63-
private fun request(): Request {
64-
return Request.Builder().url("http://example.com").build()
65-
}
48+
// implement just request and proceed methods
49+
private class FakeChain : Interceptor.Chain {
50+
override fun call(): Call {
51+
TODO("Not yet implemented")
52+
}
53+
54+
override fun connectTimeoutMillis(): Int {
55+
TODO("Not yet implemented")
56+
}
57+
58+
override fun connection(): Connection? {
59+
TODO("Not yet implemented")
60+
}
61+
62+
override fun proceed(request: Request): Response {
63+
return Response.Builder().request(request).protocol(Protocol.HTTP_2).code(200).message("").build()
64+
}
65+
66+
override fun readTimeoutMillis(): Int {
67+
TODO("Not yet implemented")
68+
}
69+
70+
override fun request(): Request {
71+
return Request.Builder().url("http://example.com").build()
72+
}
73+
74+
override fun withConnectTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain {
75+
TODO("Not yet implemented")
76+
}
77+
78+
override fun withReadTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain {
79+
TODO("Not yet implemented")
80+
}
81+
82+
override fun withWriteTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain {
83+
TODO("Not yet implemented")
84+
}
6685

67-
private fun response(): Response {
68-
return Response.Builder().request(request()).protocol(Protocol.HTTP_2).code(200).message("").build()
86+
override fun writeTimeoutMillis(): Int {
87+
TODO("Not yet implemented")
88+
}
6989
}
7090
}

0 commit comments

Comments
 (0)