Skip to content

Commit acf5df2

Browse files
committed
Add tests
1 parent e629a48 commit acf5df2

File tree

3 files changed

+309
-5
lines changed

3 files changed

+309
-5
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.duckduckgo.app.browser
2+
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
4+
import androidx.test.platform.app.InstrumentationRegistry
5+
import androidx.webkit.WebViewCompat.WebMessageListener
6+
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker
7+
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker.WebViewCapability
8+
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker.WebViewCapability.DocumentStartJavaScript
9+
import com.duckduckgo.contentscopescripts.impl.WebViewCompatWrapper
10+
import kotlinx.coroutines.test.runTest
11+
import org.junit.Before
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
import org.mockito.Mockito.mock
15+
import org.mockito.Mockito.verify
16+
import org.mockito.kotlin.never
17+
import org.mockito.kotlin.whenever
18+
19+
@RunWith(AndroidJUnit4::class)
20+
class DuckDuckGoWebViewTest {
21+
22+
val testee: DuckDuckGoWebView = DuckDuckGoWebView(InstrumentationRegistry.getInstrumentation().targetContext)
23+
24+
private val mockWebViewCapabilityChecker: WebViewCapabilityChecker = mock()
25+
private val mockWebViewCompatWrapper: WebViewCompatWrapper = mock()
26+
private val mockWebMessageListener: WebMessageListener = mock()
27+
28+
@Before
29+
fun setUp() {
30+
testee.webViewCompatWrapper = mockWebViewCompatWrapper
31+
testee.webViewCapabilityChecker = mockWebViewCapabilityChecker
32+
}
33+
34+
@Test
35+
fun whenSafeAddDocumentStartJavaScriptWithFeatureEnabledThenAddScript() = runTest {
36+
whenever(mockWebViewCapabilityChecker.isSupported(DocumentStartJavaScript)).thenReturn(true)
37+
38+
testee.safeAddDocumentStartJavaScript("script", setOf("*"))
39+
40+
verify(mockWebViewCompatWrapper).addDocumentStartJavaScript(testee, "script", setOf("*"))
41+
}
42+
43+
@Test
44+
fun whenSafeAddDocumentStartJavaScriptWithFeatureDisabledThenDoNotAddScript() = runTest {
45+
whenever(mockWebViewCapabilityChecker.isSupported(DocumentStartJavaScript)).thenReturn(false)
46+
47+
testee.safeAddDocumentStartJavaScript("script", setOf("*"))
48+
49+
verify(mockWebViewCompatWrapper, never()).addDocumentStartJavaScript(testee, "script", setOf("*"))
50+
}
51+
52+
@Test
53+
fun whenSafeAddWebMessageListenerWithFeatureEnabledThenAddListener() = runTest {
54+
whenever(mockWebViewCapabilityChecker.isSupported(WebViewCapability.WebMessageListener)).thenReturn(true)
55+
56+
testee.safeAddWebMessageListener("test", setOf("*"), mockWebMessageListener)
57+
verify(mockWebViewCompatWrapper)
58+
.addWebMessageListener(testee, "test", setOf("*"), mockWebMessageListener)
59+
}
60+
61+
@Test
62+
fun whenSafeAddWebMessageListenerWithFeatureDisabledThenDoNotAddListener() = runTest {
63+
whenever(mockWebViewCapabilityChecker.isSupported(WebViewCapability.WebMessageListener)).thenReturn(false)
64+
65+
testee.safeAddWebMessageListener("test", setOf("*"), mockWebMessageListener)
66+
verify(mockWebViewCompatWrapper, never())
67+
.addWebMessageListener(testee, "test", setOf("*"), mockWebMessageListener)
68+
}
69+
70+
@Test
71+
fun whenSafeRemoveWebMessageListenerWithFeatureEnabledThenRemoveListener() = runTest {
72+
whenever(mockWebViewCapabilityChecker.isSupported(WebViewCapability.WebMessageListener)).thenReturn(true)
73+
74+
testee.safeRemoveWebMessageListener("test")
75+
76+
verify(mockWebViewCompatWrapper).removeWebMessageListener(testee, "test")
77+
}
78+
79+
@Test
80+
fun whenSafeRemoveWebMessageListenerWithFeatureDisabledThenDoNotRemoveListener() = runTest {
81+
whenever(mockWebViewCapabilityChecker.isSupported(WebViewCapability.WebMessageListener)).thenReturn(false)
82+
83+
testee.safeRemoveWebMessageListener("test")
84+
verify(mockWebViewCompatWrapper, never()).removeWebMessageListener(testee, "test")
85+
}
86+
}

content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/messaging/WebCompatMessagingPlugin.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.duckduckgo.contentscopescripts.impl.messaging
1818

1919
import android.webkit.WebView
20+
import androidx.annotation.VisibleForTesting
2021
import androidx.webkit.WebViewCompat.WebMessageListener
2122
import com.duckduckgo.common.utils.DispatcherProvider
2223
import com.duckduckgo.common.utils.plugins.PluginPoint
@@ -30,7 +31,7 @@ import com.duckduckgo.js.messaging.api.JsMessageCallback
3031
import com.squareup.anvil.annotations.ContributesBinding
3132
import com.squareup.moshi.Moshi
3233
import kotlinx.coroutines.withContext
33-
import logcat.LogPriority
34+
import logcat.LogPriority.ERROR
3435
import logcat.asLog
3536
import logcat.logcat
3637
import javax.inject.Inject
@@ -52,7 +53,8 @@ class WebCompatMessagingPlugin @Inject constructor(
5253
private val context: String = "contentScopeScripts"
5354
private val allowedDomains: Set<String> = setOf("*")
5455

55-
private fun process(
56+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
57+
internal fun process(
5658
message: String,
5759
jsMessageCallback: JsMessageCallback,
5860
) {
@@ -77,7 +79,7 @@ class WebCompatMessagingPlugin @Inject constructor(
7779
}
7880
}
7981
} catch (e: Exception) {
80-
logcat(LogPriority.ERROR) { "Exception is ${e.asLog()}" }
82+
logcat(ERROR) { "Exception is ${e.asLog()}" }
8183
}
8284
}
8385

@@ -96,7 +98,7 @@ class WebCompatMessagingPlugin @Inject constructor(
9698
WebMessageListener { _, message, _, _, _ -> process(message.data ?: "", jsMessageCallback) }
9799
)
98100
}.getOrElse { exception ->
99-
logcat(LogPriority.ERROR) { "Error adding WebMessageListener for contentScopeAdsjs: ${exception.asLog()}" }
101+
logcat(ERROR) { "Error adding WebMessageListener for contentScopeAdsjs: ${exception.asLog()}" }
100102
false
101103
}
102104
}
@@ -109,7 +111,7 @@ class WebCompatMessagingPlugin @Inject constructor(
109111
runCatching {
110112
return@runCatching unregister(unregisterer)
111113
}.getOrElse { exception ->
112-
logcat(LogPriority.ERROR) {
114+
logcat(ERROR) {
113115
"Error removing WebMessageListener for contentScopeAdsjs: ${exception.asLog()}"
114116
}
115117
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* Copyright (c) 2023 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.contentscopescripts.impl.messaging
18+
19+
import androidx.test.ext.junit.runners.AndroidJUnit4
20+
import com.duckduckgo.app.browser.api.DuckDuckGoWebView
21+
import com.duckduckgo.common.test.CoroutineTestRule
22+
import com.duckduckgo.common.utils.plugins.PluginPoint
23+
import com.duckduckgo.contentscopescripts.api.AdsjsContentScopeJsMessageHandlersPlugin
24+
import com.duckduckgo.contentscopescripts.api.GlobalContentScopeJsMessageHandlersPlugin
25+
import com.duckduckgo.contentscopescripts.api.GlobalJsMessageHandler
26+
import com.duckduckgo.contentscopescripts.impl.AdsJsContentScopeScripts
27+
import com.duckduckgo.js.messaging.api.AdsjsMessageHandler
28+
import com.duckduckgo.js.messaging.api.JsMessage
29+
import com.duckduckgo.js.messaging.api.JsMessageCallback
30+
import junit.framework.TestCase.assertEquals
31+
import kotlinx.coroutines.test.runTest
32+
import org.json.JSONObject
33+
import org.junit.Before
34+
import org.junit.Rule
35+
import org.junit.Test
36+
import org.junit.runner.RunWith
37+
import org.mockito.kotlin.any
38+
import org.mockito.kotlin.mock
39+
import org.mockito.kotlin.never
40+
import org.mockito.kotlin.verify
41+
import org.mockito.kotlin.whenever
42+
43+
@RunWith(AndroidJUnit4::class)
44+
class AdsjsContentScopeScriptsJsMessagingTest {
45+
@get:Rule var coroutineRule = CoroutineTestRule()
46+
47+
private val mockWebView: DuckDuckGoWebView = mock()
48+
private val adsJsContentScopeScripts: AdsJsContentScopeScripts = mock()
49+
private val handlers: PluginPoint<AdsjsContentScopeJsMessageHandlersPlugin> = FakePluginPoint()
50+
private val globalHandlers: PluginPoint<GlobalContentScopeJsMessageHandlersPlugin> = FakeGlobalHandlersPluginPoint()
51+
private lateinit var contentScopeScriptsJsMessaging: AdsjsContentScopeMessaging
52+
53+
private class FakePluginPoint : PluginPoint<AdsjsContentScopeJsMessageHandlersPlugin> {
54+
override fun getPlugins(): Collection<AdsjsContentScopeJsMessageHandlersPlugin> {
55+
return listOf(FakePlugin())
56+
}
57+
58+
inner class FakePlugin : AdsjsContentScopeJsMessageHandlersPlugin {
59+
override fun getJsMessageHandler(): AdsjsMessageHandler {
60+
return object : AdsjsMessageHandler {
61+
override fun process(
62+
jsMessage: JsMessage,
63+
jsMessageCallback: JsMessageCallback?,
64+
) {
65+
jsMessageCallback?.process(jsMessage.featureName, jsMessage.method, jsMessage.id, jsMessage.params)
66+
}
67+
68+
override val featureName: String = "webCompat"
69+
override val methods: List<String> = listOf("webShare", "permissionsQuery")
70+
}
71+
}
72+
}
73+
}
74+
75+
private class FakeGlobalHandlersPluginPoint : PluginPoint<GlobalContentScopeJsMessageHandlersPlugin> {
76+
override fun getPlugins(): Collection<GlobalContentScopeJsMessageHandlersPlugin> {
77+
return listOf(FakeGlobalHandlerPlugin())
78+
}
79+
80+
inner class FakeGlobalHandlerPlugin : GlobalContentScopeJsMessageHandlersPlugin {
81+
82+
override fun getGlobalJsMessageHandler(): GlobalJsMessageHandler {
83+
return object : GlobalJsMessageHandler {
84+
85+
override fun process(
86+
jsMessage: JsMessage,
87+
jsMessageCallback: JsMessageCallback,
88+
) {
89+
jsMessageCallback.process(jsMessage.featureName, jsMessage.method, jsMessage.id, jsMessage.params)
90+
}
91+
92+
override val method: String = "addDebugFlag"
93+
}
94+
}
95+
}
96+
}
97+
98+
@Before
99+
fun setUp() = runTest {
100+
whenever(adsJsContentScopeScripts.isEnabled()).thenReturn(true)
101+
contentScopeScriptsJsMessaging = AdsjsContentScopeMessaging(
102+
handlers = handlers,
103+
globalHandlers = globalHandlers,
104+
adsJsContentScopeScripts = adsJsContentScopeScripts,
105+
coroutineRule.testDispatcherProvider,
106+
)
107+
}
108+
109+
@Test
110+
fun `when process and message can be handled then execute callback`() = runTest {
111+
givenInterfaceIsRegistered()
112+
113+
val message = """
114+
{"context":"contentScopeScripts","featureName":"webCompat","id":"myId","method":"webShare","params":{}}
115+
""".trimIndent()
116+
117+
contentScopeScriptsJsMessaging.process(message, callback)
118+
119+
assertEquals(1, callback.counter)
120+
}
121+
122+
@Test
123+
fun `when processing unknown message do nothing`() = runTest {
124+
givenInterfaceIsRegistered()
125+
126+
contentScopeScriptsJsMessaging.process("", callback)
127+
128+
assertEquals(0, callback.counter)
129+
}
130+
131+
@Test
132+
fun `when feature does not match do nothing`() = runTest {
133+
givenInterfaceIsRegistered()
134+
135+
val message = """
136+
{"context":"contentScopeScripts","featureName":"test","id":"myId","method":"webShare","params":{}}
137+
""".trimIndent()
138+
139+
contentScopeScriptsJsMessaging.process(message, callback)
140+
141+
assertEquals(0, callback.counter)
142+
}
143+
144+
@Test
145+
fun `when id does not exist do nothing`() = runTest {
146+
givenInterfaceIsRegistered()
147+
148+
val message = """
149+
{"context":"contentScopeScripts","webCompat":"test","method":"webShare","params":{}}
150+
""".trimIndent()
151+
152+
contentScopeScriptsJsMessaging.process(message, callback)
153+
154+
assertEquals(0, callback.counter)
155+
}
156+
157+
@Test
158+
fun `when processing addDebugFlag message then process message`() = runTest {
159+
givenInterfaceIsRegistered()
160+
161+
val message = """
162+
{"context":"contentScopeScripts","featureName":"debugFeature","id":"debugId","method":"addDebugFlag","params":{}}
163+
""".trimIndent()
164+
165+
contentScopeScriptsJsMessaging.process(message, callback)
166+
167+
assertEquals(1, callback.counter)
168+
}
169+
170+
@Test
171+
fun `when registering and adsjs is disabled then do not register`() = runTest {
172+
whenever(adsJsContentScopeScripts.isEnabled()).thenReturn(false)
173+
174+
contentScopeScriptsJsMessaging.register(mockWebView, callback)
175+
176+
verify(mockWebView, never()).safeAddWebMessageListener(any(), any(), any())
177+
}
178+
179+
@Test
180+
fun `when registering and adsjs is enabled then register`() = runTest {
181+
whenever(adsJsContentScopeScripts.isEnabled()).thenReturn(true)
182+
183+
contentScopeScriptsJsMessaging.register(mockWebView, callback)
184+
185+
verify(mockWebView).safeAddWebMessageListener(any(), any(), any())
186+
}
187+
188+
@Test
189+
fun `when unregistering and adsjs is disabled then do not unregister`() = runTest {
190+
whenever(adsJsContentScopeScripts.isEnabled()).thenReturn(false)
191+
192+
contentScopeScriptsJsMessaging.unregister(mockWebView)
193+
194+
verify(mockWebView, never()).safeRemoveWebMessageListener(any())
195+
}
196+
197+
@Test
198+
fun `when unregistering and adsjs is enabled then unregister`() = runTest {
199+
whenever(adsJsContentScopeScripts.isEnabled()).thenReturn(true)
200+
201+
contentScopeScriptsJsMessaging.unregister(mockWebView)
202+
203+
verify(mockWebView).safeRemoveWebMessageListener(any())
204+
}
205+
206+
private val callback = object : JsMessageCallback() {
207+
var counter = 0
208+
override fun process(featureName: String, method: String, id: String?, data: JSONObject?) {
209+
counter++
210+
}
211+
}
212+
213+
private fun givenInterfaceIsRegistered() = runTest {
214+
contentScopeScriptsJsMessaging.register(mockWebView, callback)
215+
}
216+
}

0 commit comments

Comments
 (0)