Skip to content

Commit 601bbdb

Browse files
authored
Feature/google analytics surrogates (#167)
* WIP implementation of google analytics surrogates * Quieter logging * Surrogate logic occurs only after identifying a url as one to block * Biggish refactor to allow better testing of web view request intercepor * Add more tests for the "should intercept request" logic * Lowering noise of log statement * Mark AnalyticsSurrogates as a singleton * Clear the string builder between surrogates * Update surrogate test files to be just test functions; not the real thing * Remove word Analytics; more generically replaced with ResourceSurrogate * Replace all real comments with fake comments for surrogate test data * Add extra tests and catch all errors for extra parsing safety
1 parent 3f49b3a commit 601bbdb

36 files changed

+1397
-103
lines changed
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
/*
2+
* Copyright (c) 2018 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
18+
19+
import android.net.Uri
20+
import android.support.test.InstrumentationRegistry
21+
import android.support.test.annotation.UiThreadTest
22+
import android.webkit.WebResourceRequest
23+
import android.webkit.WebResourceResponse
24+
import android.webkit.WebView
25+
import com.duckduckgo.app.surrogates.ResourceSurrogates
26+
import com.duckduckgo.app.surrogates.SurrogateResponse
27+
import com.duckduckgo.app.httpsupgrade.HttpsUpgrader
28+
import com.duckduckgo.app.trackerdetection.TrackerDetector
29+
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
30+
import com.nhaarman.mockito_kotlin.*
31+
import org.junit.Assert.*
32+
import org.junit.Before
33+
import org.junit.Test
34+
import org.mockito.ArgumentMatchers.anyString
35+
import org.mockito.Mock
36+
import org.mockito.MockitoAnnotations
37+
38+
class WebViewRequestInterceptorTest {
39+
40+
private lateinit var testee: WebViewRequestInterceptor
41+
42+
@Mock
43+
private lateinit var mockTrackerDetector: TrackerDetector
44+
@Mock
45+
private lateinit var mockHttpsUpgrader: HttpsUpgrader
46+
@Mock
47+
private lateinit var mockResourceSurrogates: ResourceSurrogates
48+
@Mock
49+
private lateinit var mockRequest: WebResourceRequest
50+
51+
private lateinit var webView: WebView
52+
53+
@UiThreadTest
54+
@Before
55+
fun setup() {
56+
MockitoAnnotations.initMocks(this)
57+
58+
testee = WebViewRequestInterceptor(
59+
trackerDetector = mockTrackerDetector,
60+
httpsUpgrader = mockHttpsUpgrader,
61+
resourceSurrogates = mockResourceSurrogates
62+
)
63+
64+
val context = InstrumentationRegistry.getTargetContext()
65+
66+
webView = WebView(context)
67+
}
68+
69+
@Test
70+
fun whenUrlShouldBeUpgradedThenUpgraderInvoked() {
71+
configureShouldUpgrade()
72+
testee.shouldIntercept(
73+
request = mockRequest,
74+
currentUrl = null,
75+
webView = webView,
76+
webViewClientListener = null)
77+
78+
verify(mockHttpsUpgrader).upgrade(any())
79+
}
80+
81+
@Test
82+
fun whenUrlShouldBeUpgradedThenCancelledResponseReturned() {
83+
configureShouldUpgrade()
84+
val response = testee.shouldIntercept(
85+
request = mockRequest,
86+
currentUrl = null,
87+
webView = webView,
88+
webViewClientListener = null)
89+
90+
assertCancelledResponse(response)
91+
}
92+
93+
@Test
94+
fun whenUrlShouldBeUpgradedButNotOnMainFrameThenNotUpgraded() {
95+
configureShouldUpgrade()
96+
whenever(mockRequest.isForMainFrame).thenReturn(false)
97+
testee.shouldIntercept(
98+
request = mockRequest,
99+
currentUrl = null,
100+
webView = webView,
101+
webViewClientListener = null)
102+
103+
verify(mockHttpsUpgrader, never()).upgrade(any())
104+
}
105+
106+
@Test
107+
fun whenUrlShouldBeUpgradedButUrlIsNullThenNotUpgraded() {
108+
configureShouldUpgrade()
109+
whenever(mockRequest.url).thenReturn(null)
110+
testee.shouldIntercept(
111+
request = mockRequest,
112+
currentUrl = null,
113+
webView = webView,
114+
webViewClientListener = null)
115+
116+
verify(mockHttpsUpgrader, never()).upgrade(any())
117+
}
118+
119+
@Test
120+
fun whenUrlShouldNotBeUpgradedThenUpgraderNotInvoked() {
121+
whenever(mockHttpsUpgrader.shouldUpgrade(any())).thenReturn(false)
122+
testee.shouldIntercept(
123+
request = mockRequest,
124+
currentUrl = null,
125+
webView = webView,
126+
webViewClientListener = null)
127+
128+
verify(mockHttpsUpgrader, never()).upgrade(any())
129+
}
130+
131+
@Test
132+
fun whenCurrentUrlIsNullThenShouldContinueToLoad() {
133+
configureShouldNotUpgrade()
134+
val response = testee.shouldIntercept(
135+
request = mockRequest,
136+
currentUrl = null,
137+
webView = webView,
138+
webViewClientListener = null)
139+
assertRequestCanContinueToLoad(response)
140+
}
141+
142+
@Test
143+
fun whenIsTrustedSite_DuckDuckGo_ThenShouldContinueToLoad() {
144+
configureShouldNotUpgrade()
145+
val response = testee.shouldIntercept(
146+
request = mockRequest,
147+
currentUrl = "duckduckgo.com/a/b/c?q=123",
148+
webView = webView,
149+
webViewClientListener = null)
150+
151+
assertRequestCanContinueToLoad(response)
152+
}
153+
154+
@Test
155+
fun whenIsTrustedSite_DontTrack_ThenShouldContinueToLoad() {
156+
configureShouldNotUpgrade()
157+
val response = testee.shouldIntercept(
158+
request = mockRequest,
159+
currentUrl = "donttrack.us/a/b/c?q=123",
160+
webView = webView,
161+
webViewClientListener = null)
162+
163+
assertRequestCanContinueToLoad(response)
164+
}
165+
166+
@Test
167+
fun whenIsTrustedSite_SpreadPrivacy_ThenShouldContinueToLoad() {
168+
configureShouldNotUpgrade()
169+
val response = testee.shouldIntercept(
170+
request = mockRequest,
171+
currentUrl = "spreadprivacy.com/a/b/c?q=123",
172+
webView = webView,
173+
webViewClientListener = null)
174+
175+
assertRequestCanContinueToLoad(response)
176+
}
177+
178+
@Test
179+
fun whenIsTrustedSite_DuckDuckHack_ThenShouldContinueToLoad() {
180+
configureShouldNotUpgrade()
181+
val response = testee.shouldIntercept(
182+
request = mockRequest,
183+
currentUrl = "duckduckhack.com/a/b/c?q=123",
184+
webView = webView,
185+
webViewClientListener = null)
186+
187+
assertRequestCanContinueToLoad(response)
188+
}
189+
190+
@Test
191+
fun whenIsTrustedSite_PrivateBrowsingMyths_ThenShouldContinueToLoad() {
192+
configureShouldNotUpgrade()
193+
val response = testee.shouldIntercept(
194+
request = mockRequest,
195+
currentUrl = "privatebrowsingmyths.com/a/b/c?q=123",
196+
webView = webView,
197+
webViewClientListener = null)
198+
199+
assertRequestCanContinueToLoad(response)
200+
}
201+
202+
@Test
203+
fun whenIsTrustedSite_DuckDotCo_ThenShouldContinueToLoad() {
204+
configureShouldNotUpgrade()
205+
val response = testee.shouldIntercept(
206+
request = mockRequest,
207+
currentUrl = "duck.co/a/b/c?q=123",
208+
webView = webView,
209+
webViewClientListener = null)
210+
211+
assertRequestCanContinueToLoad(response)
212+
}
213+
214+
@Test
215+
fun whenIsHttpRequestThenHttpRequestListenerCalled() {
216+
configureShouldNotUpgrade()
217+
whenever(mockRequest.url).thenReturn(Uri.parse("http://example.com"))
218+
val mockListener = mock<WebViewClientListener>()
219+
220+
val response = testee.shouldIntercept(
221+
request = mockRequest,
222+
currentUrl = "foo.com",
223+
webView = webView,
224+
webViewClientListener = mockListener)
225+
226+
verify(mockListener).pageHasHttpResources(anyString())
227+
assertRequestCanContinueToLoad(response)
228+
}
229+
230+
@Test
231+
fun whenIsHttpsRequestThenHttpRequestListenerNotCalled() {
232+
configureShouldNotUpgrade()
233+
whenever(mockRequest.url).thenReturn(Uri.parse("https://example.com"))
234+
val mockListener = mock<WebViewClientListener>()
235+
236+
val response = testee.shouldIntercept(
237+
request = mockRequest,
238+
currentUrl = "foo.com",
239+
webView = webView,
240+
webViewClientListener = mockListener)
241+
242+
verify(mockListener, never()).pageHasHttpResources(anyString())
243+
assertRequestCanContinueToLoad(response)
244+
}
245+
246+
247+
@Test
248+
fun whenRequestShouldBlockAndNoSurrogateThenCancellingResponseReturned() {
249+
whenever(mockResourceSurrogates.get(any())).thenReturn(SurrogateResponse(responseAvailable = false))
250+
251+
configureShouldNotUpgrade()
252+
configureShouldBlock()
253+
val response = testee.shouldIntercept(
254+
request = mockRequest,
255+
currentUrl = "foo.com",
256+
webView = webView,
257+
webViewClientListener = null)
258+
259+
assertCancelledResponse(response)
260+
}
261+
262+
@Test
263+
fun whenRequestShouldBlockButThereIsASurrogateThen() {
264+
val availableSurrogate = SurrogateResponse(
265+
responseAvailable = true,
266+
mimeType = "application/javascript",
267+
jsFunction = "javascript replacement function goes here")
268+
whenever(mockResourceSurrogates.get(any())).thenReturn(availableSurrogate)
269+
270+
configureShouldNotUpgrade()
271+
configureShouldBlock()
272+
val response = testee.shouldIntercept(
273+
request = mockRequest,
274+
currentUrl = "foo.com",
275+
webView = webView,
276+
webViewClientListener = null)
277+
278+
assertEquals(availableSurrogate.jsFunction.byteInputStream().read(), response!!.data.read())
279+
}
280+
281+
private fun assertRequestCanContinueToLoad(response: WebResourceResponse?) {
282+
assertNull(response)
283+
}
284+
285+
private fun configureShouldBlock() {
286+
val blockTrackingEvent = TrackingEvent(blocked = true,
287+
documentUrl = "",
288+
trackerUrl = "",
289+
trackerNetwork = null)
290+
whenever(mockRequest.isForMainFrame).thenReturn(false)
291+
whenever(mockTrackerDetector.evaluate(any(), any(), any())).thenReturn(blockTrackingEvent)
292+
}
293+
294+
private fun configureShouldUpgrade() {
295+
whenever(mockHttpsUpgrader.shouldUpgrade(any())).thenReturn(true)
296+
whenever(mockRequest.url).thenReturn(validUri())
297+
whenever(mockRequest.isForMainFrame).thenReturn(true)
298+
}
299+
300+
private fun configureShouldNotUpgrade() {
301+
whenever(mockHttpsUpgrader.shouldUpgrade(any())).thenReturn(false)
302+
whenever(mockRequest.url).thenReturn(validUri())
303+
whenever(mockRequest.isForMainFrame).thenReturn(true)
304+
}
305+
306+
private fun validUri() = Uri.parse("example.com")
307+
308+
private fun assertCancelledResponse(response: WebResourceResponse?) {
309+
assertNotNull(response)
310+
assertNull(response!!.data)
311+
assertNull(response.mimeType)
312+
assertNull(response.encoding)
313+
}
314+
315+
}

app/src/androidTest/java/com/duckduckgo/app/httpsupgrade/HttpsUpgraderTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class HttpsUpgraderTest {
3131
@Before
3232
fun before() {
3333
mockDao = mock()
34-
testee = HttpsUpgrader(mockDao)
34+
testee = HttpsUpgraderImpl(mockDao)
3535
}
3636

3737
@Test

app/src/androidTest/java/com/duckduckgo/app/httpsupgrade/db/HttpsUpgradePerformanceTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import android.net.Uri
3737
import android.support.test.InstrumentationRegistry
3838
import com.duckduckgo.app.global.db.AppDatabase
3939
import com.duckduckgo.app.httpsupgrade.HttpsUpgrader
40+
import com.duckduckgo.app.httpsupgrade.HttpsUpgraderImpl
4041
import org.junit.Assert.assertEquals
4142
import org.junit.Assert.assertFalse
4243
import org.junit.Before
@@ -55,7 +56,7 @@ class HttpsUpgraderPerformanceTest {
5556
fun setup() {
5657
db = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), AppDatabase::class.java).build()
5758
dao = db.httpsUpgradeDomainDao()
58-
httpsUpgrader = HttpsUpgrader(dao)
59+
httpsUpgrader = HttpsUpgraderImpl(dao)
5960
}
6061

6162
@Test

0 commit comments

Comments
 (0)