Skip to content

Commit fff0825

Browse files
committed
Tests
1 parent a8dab6c commit fff0825

File tree

14 files changed

+664
-121
lines changed

14 files changed

+664
-121
lines changed

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/RealDuckChat.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,9 @@ interface DuckChatInternal : DuckChat {
215215

216216
/**
217217
* This method takes a [url] and returns `true` or `false`.
218-
* @return `true` if the given [url] belongs to the duck.ai domain (apex or subdomain) or if it's the revoke url and `false` otherwise.
218+
* @return `true` if the given [url] can be handled in the duck ai webview and `false` otherwise.
219219
*/
220-
fun isDuckAiUrl(url: String): Boolean
220+
fun canHandleOnAiWebView(url: String): Boolean
221221

222222
/**
223223
* Indicates whether Input Screen will present the input box at the bottom, if user has the omnibar also set to the bottom position.
@@ -437,7 +437,7 @@ class RealDuckChat @Inject constructor(
437437
}
438438
}
439439

440-
override fun isDuckAiUrl(url: String): Boolean {
440+
override fun canHandleOnAiWebView(url: String): Boolean {
441441
return runCatching { HOST_DUCK_AI == url.toHttpUrl().topPrivateDomain() || url == REVOKE_URL }.getOrElse { false }
442442
}
443443

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/helper/DuckChatJSHelper.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class RealDuckChatJSHelper @Inject constructor(
116116
}
117117
null
118118
}
119+
119120
else -> null
120121
}
121122

@@ -190,6 +191,7 @@ class RealDuckChatJSHelper @Inject constructor(
190191
}
191192
}
192193
}
194+
193195
companion object {
194196
const val DUCK_CHAT_FEATURE_NAME = "aiChat"
195197
private const val METHOD_GET_AI_CHAT_NATIVE_HANDOFF_DATA = "getAIChatNativeHandoffData"

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/messaging/DuckChatStandaloneJsMessageHandlers.kt

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,21 @@ class StoreMigrationDataHandler @Inject constructor(
7575
jsMessageCallback: JsMessageCallback?,
7676
) {
7777
if (jsMessage.id.isNullOrEmpty()) return
78-
7978
val item = jsMessage.params.optString(SERIALIZED_MIGRATION_FILE)
80-
val jsonPayload = JSONObject()
81-
if (item != null && item != JSONObject.NULL) {
79+
80+
val result = if (!item.isNullOrEmpty() && item != JSONObject.NULL.toString()) {
8281
standaloneDuckChatStore.storeMigrationItem(item)
83-
jsonPayload.put(OK, true)
82+
true
8483
} else {
85-
jsonPayload.put(OK, false)
86-
jsonPayload.put(REASON, "Missing or invalid serializedMigrationFile")
84+
false
8785
}
88-
jsMessaging.onResponse(JsCallbackData(jsonPayload, featureName, jsMessage.method, jsMessage.id!!))
86+
87+
val payload = JSONObject().apply {
88+
put(OK, result)
89+
if (!result) put(REASON, "Missing or invalid serializedMigrationFile")
90+
}
91+
92+
jsMessaging.onResponse(JsCallbackData(payload, featureName, jsMessage.method, jsMessage.id!!))
8993
}
9094

9195
override val allowedDomains: List<String> =
@@ -113,12 +117,12 @@ class GetMigrationInfoHandler @Inject constructor(
113117
if (jsMessage.id.isNullOrEmpty()) return
114118

115119
val count = standaloneDuckChatStore.getMigrationItemCount()
116-
val jsonPayload = JSONObject().apply {
120+
val payload = JSONObject().apply {
117121
put(OK, true)
118122
put(COUNT, count)
119123
}
120124

121-
jsMessaging.onResponse(JsCallbackData(jsonPayload, featureName, jsMessage.method, jsMessage.id!!))
125+
jsMessaging.onResponse(JsCallbackData(payload, featureName, jsMessage.method, jsMessage.id!!))
122126
}
123127

124128
override val allowedDomains: List<String> =
@@ -146,15 +150,17 @@ class GetMigrationDataByIndexHandler @Inject constructor(
146150
if (jsMessage.id.isNullOrEmpty()) return
147151
val index = jsMessage.params.optInt(INDEX, -1)
148152
val value = standaloneDuckChatStore.getMigrationItemByIndex(index)
149-
val jsonPayload = JSONObject()
150-
if (value == null) {
151-
jsonPayload.put(OK, false)
152-
jsonPayload.put(REASON, "nothing at index: $index")
153-
} else {
154-
jsonPayload.put(OK, true)
155-
jsonPayload.put(SERIALIZED_MIGRATION_FILE, value)
153+
154+
val payload = JSONObject().apply {
155+
if (value == null) {
156+
put(OK, false)
157+
put(REASON, "nothing at index: $index")
158+
} else {
159+
put(OK, true)
160+
put(SERIALIZED_MIGRATION_FILE, value)
161+
}
156162
}
157-
jsMessaging.onResponse(JsCallbackData(jsonPayload, featureName, jsMessage.method, jsMessage.id!!))
163+
jsMessaging.onResponse(JsCallbackData(payload, featureName, jsMessage.method, jsMessage.id!!))
158164
}
159165

160166
override val allowedDomains: List<String> =

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatWebViewClient.kt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import android.webkit.WebViewClient
2222
import androidx.annotation.UiThread
2323
import com.duckduckgo.browser.api.JsInjectorPlugin
2424
import com.duckduckgo.common.utils.plugins.PluginPoint
25-
import logcat.logcat
2625
import javax.inject.Inject
2726

2827
class DuckChatWebViewClient @Inject constructor(
@@ -39,12 +38,4 @@ class DuckChatWebViewClient @Inject constructor(
3938
it.onPageStarted(webView, url, null)
4039
}
4140
}
42-
43-
override fun onPageFinished(
44-
view: WebView?,
45-
url: String?,
46-
) {
47-
logcat { "Marcos url is $url and ${view?.url}" }
48-
super.onPageFinished(view, url)
49-
}
5041
}

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatWebViewViewModel.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import androidx.lifecycle.viewModelScope
2121
import com.duckduckgo.anvil.annotations.ContributesViewModel
2222
import com.duckduckgo.di.scopes.FragmentScope
2323
import com.duckduckgo.duckchat.impl.DuckChatInternal
24-
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.ViewState
2524
import com.duckduckgo.subscriptions.api.Subscriptions
2625
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
2726
import kotlinx.coroutines.channels.Channel
@@ -67,7 +66,7 @@ class DuckChatWebViewViewModel @Inject constructor(
6766

6867
fun handleOnSameWebView(url: String): Boolean {
6968
// Allow Duck.ai links to load within the same WebView (in-sheet navigation)
70-
return duckChat.isDuckAiUrl(url)
69+
return duckChat.canHandleOnAiWebView(url)
7170
}
7271

7372
private fun observeSubscriptionChanges() {

duckchat/duckchat-impl/src/test/java/com/duckduckgo/duckchat/impl/ui/DuckChatWebViewViewModelTest.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import org.junit.Rule
3737
import org.junit.Test
3838
import org.junit.runner.RunWith
3939
import org.mockito.kotlin.mock
40+
import org.mockito.kotlin.verify
4041
import org.mockito.kotlin.whenever
4142

4243
@RunWith(AndroidJUnit4::class)
@@ -188,4 +189,10 @@ class DuckChatWebViewViewModelTest {
188189
assertFalse(state.isFullScreenModeEnabled)
189190
}
190191
}
192+
193+
@Test
194+
fun whenHandleOnSameWebViewCalledThenCallDuckChat() {
195+
viewModel.handleOnSameWebView("https://duck.ai/somepath")
196+
verify(duckChat).canHandleOnAiWebView("https://duck.ai/somepath")
197+
}
191198
}

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/RealDuckChatTest.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,18 @@ class RealDuckChatTest {
10901090
assertTrue(testee.showInputScreenOnSystemSearchLaunch.value)
10911091
}
10921092

1093+
@Test
1094+
fun `when url is from duck ai return true`() {
1095+
assertTrue(testee.isDuckChatUrl("https://duck.ai/somepath".toUri()))
1096+
assertTrue(testee.isDuckChatUrl("https://duck.ai/somepath/someotherpath?test=1".toUri()))
1097+
assertTrue(testee.isDuckChatUrl("https://duck.ai".toUri()))
1098+
}
1099+
1100+
@Test
1101+
fun `when url is revoke url return true`() {
1102+
assertTrue(testee.isDuckChatUrl("https://duckduckgo.com/revoke-duckai-access".toUri()))
1103+
}
1104+
10931105
companion object {
10941106
val SETTINGS_JSON = """
10951107
{

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/helper/RealDuckChatJSHelperTest.kt

Lines changed: 53 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ class RealDuckChatJSHelperTest {
5858
dataStore = mockDataStore,
5959
duckChatPixels = mockDuckChatPixels,
6060
duckAiMetricCollector = mockDuckAiMetricCollector,
61-
dispatchers = coroutineRule.testDispatcherProvider,
6261
)
6362

6463
@Test
@@ -257,6 +256,35 @@ class RealDuckChatJSHelperTest {
257256
assertEquals(expected.params.toString(), result.params.toString())
258257
}
259258

259+
@Test
260+
fun whenGetAIChatNativeConfigValuesAnStandaloneMigrationEnabledThenReturnJsCallbackDataWithCorrectData() = runTest {
261+
val featureName = "aiChat"
262+
val method = "getAIChatNativeConfigValues"
263+
val id = "123"
264+
265+
whenever(mockDuckChat.isStandaloneMigrationEnabled()).thenReturn(true)
266+
267+
val result = testee.processJsCallbackMessage(featureName, method, id, null)
268+
269+
val jsonPayload = JSONObject().apply {
270+
put("platform", "android")
271+
put("isAIChatHandoffEnabled", false)
272+
put("supportsClosingAIChat", true)
273+
put("supportsOpeningSettings", true)
274+
put("supportsNativeChatInput", false)
275+
put("supportsURLChatIDRestoration", false)
276+
put("supportsImageUpload", false)
277+
put("supportsStandaloneMigration", true)
278+
}
279+
280+
val expected = JsCallbackData(jsonPayload, featureName, method, id)
281+
282+
assertEquals(expected.id, result!!.id)
283+
assertEquals(expected.method, result.method)
284+
assertEquals(expected.featureName, result.featureName)
285+
assertEquals(expected.params.toString(), result.params.toString())
286+
}
287+
260288
@Test
261289
fun whenOpenAIChatAndHasPayloadThenUpdateStoreAndOpenDuckChat() = runTest {
262290
val featureName = "aiChat"
@@ -518,89 +546,28 @@ class RealDuckChatJSHelperTest {
518546
assertEquals(expectedPayload.toString(), result!!.params.toString())
519547
}
520548

521-
@Test
522-
fun whenStoreMigrationDataThenItemIsStoredAndInfoCountReflectsIt() = runTest {
523-
val featureName = "aiChat"
524-
val id = "1"
525-
526-
// store two items
527-
val item1 = JSONObject(mapOf("serializedMigrationFile" to "file-1"))
528-
val item2 = JSONObject(mapOf("serializedMigrationFile" to "file-2"))
529-
testee.processJsCallbackMessage(featureName, "storeMigrationData", id, item1)
530-
testee.processJsCallbackMessage(featureName, "storeMigrationData", id, item2)
531-
532-
// get count
533-
val info = testee.processJsCallbackMessage(featureName, "getMigrationInfo", id, null)
534-
val expected = JSONObject().apply {
535-
put("ok", true)
536-
put("count", 2)
537-
}
538-
assertEquals(expected.toString(), info!!.params.toString())
539-
}
540-
541-
@Test
542-
fun whenGetMigrationDataByIndexWithValidIndexThenReturnItem() = runTest {
543-
val featureName = "aiChat"
544-
val id = "1"
545-
546-
// store items
547-
testee.processJsCallbackMessage(featureName, "storeMigrationData", id, JSONObject(mapOf("serializedMigrationFile" to "file-1")))
548-
testee.processJsCallbackMessage(featureName, "storeMigrationData", id, JSONObject(mapOf("serializedMigrationFile" to "file-2")))
549-
550-
val result = testee.processJsCallbackMessage(featureName, "getMigrationDataByIndex", id, JSONObject(mapOf("index" to 1)))
551-
val expected = JSONObject().apply {
552-
put("ok", true)
553-
put("serializedMigrationFile", "file-2")
554-
}
555-
assertEquals(expected.toString(), result!!.params.toString())
556-
}
557-
558-
@Test
559-
fun whenGetMigrationDataByIndexWithInvalidIndexThenReturnEmptyPayload() = runTest {
560-
val featureName = "aiChat"
561-
val id = "1"
562-
563-
// store one item
564-
testee.processJsCallbackMessage(featureName, "storeMigrationData", id, JSONObject(mapOf("serializedMigrationFile" to "file-1")))
565-
566-
// negative index
567-
val negative = testee.processJsCallbackMessage(featureName, "getMigrationDataByIndex", id, JSONObject(mapOf("index" to -1)))
568-
val expectedNegative = JSONObject().apply {
569-
put("ok", false)
570-
put("reason", "nothing at index: -1")
571-
}
572-
assertEquals(expectedNegative.toString(), negative!!.params.toString())
573-
574-
// out of range index
575-
val outOfRange = testee.processJsCallbackMessage(featureName, "getMigrationDataByIndex", id, JSONObject(mapOf("index" to 5)))
576-
val expectedOutOfRange = JSONObject().apply {
577-
put("ok", false)
578-
put("reason", "nothing at index: 5")
579-
}
580-
assertEquals(expectedOutOfRange.toString(), outOfRange!!.params.toString())
581-
}
582-
583-
@Test
584-
fun whenClearMigrationDataThenItemsRemovedAndCountZero() = runTest {
585-
val featureName = "aiChat"
586-
val id = "1"
587-
588-
// store items
589-
testee.processJsCallbackMessage(featureName, "storeMigrationData", id, JSONObject(mapOf("serializedMigrationFile" to "file-1")))
590-
testee.processJsCallbackMessage(featureName, "storeMigrationData", id, JSONObject(mapOf("serializedMigrationFile" to "file-2")))
591-
592-
// clear
593-
val clearResult = testee.processJsCallbackMessage(featureName, "clearMigrationData", id, null)
594-
// clear returns ok true
595-
val expectedClear = JSONObject().apply { put("ok", true) }
596-
assertEquals(expectedClear.toString(), clearResult!!.params.toString())
597-
598-
// count is zero
599-
val info = testee.processJsCallbackMessage(featureName, "getMigrationInfo", id, null)
600-
val expected = JSONObject().apply {
601-
put("ok", true)
602-
put("count", 0)
603-
}
604-
assertEquals(expected.toString(), info!!.params.toString())
605-
}
549+
//
550+
// @Test
551+
// fun whenClearMigrationDataThenItemsRemovedAndCountZero() = runTest {
552+
// val featureName = "aiChat"
553+
// val id = "1"
554+
//
555+
// // store items
556+
// testee.processJsCallbackMessage(featureName, "storeMigrationData", id, JSONObject(mapOf("serializedMigrationFile" to "file-1")))
557+
// testee.processJsCallbackMessage(featureName, "storeMigrationData", id, JSONObject(mapOf("serializedMigrationFile" to "file-2")))
558+
//
559+
// // clear
560+
// val clearResult = testee.processJsCallbackMessage(featureName, "clearMigrationData", id, null)
561+
// // clear returns ok true
562+
// val expectedClear = JSONObject().apply { put("ok", true) }
563+
// assertEquals(expectedClear.toString(), clearResult!!.params.toString())
564+
//
565+
// // count is zero
566+
// val info = testee.processJsCallbackMessage(featureName, "getMigrationInfo", id, null)
567+
// val expected = JSONObject().apply {
568+
// put("ok", true)
569+
// put("count", 0)
570+
// }
571+
// assertEquals(expected.toString(), info!!.params.toString())
572+
// }
606573
}

0 commit comments

Comments
 (0)