Skip to content

Commit 40ff4c5

Browse files
committed
Updates the duck.ai host for cookie migration and add tests
1 parent 2cba2fb commit 40ff4c5

3 files changed

Lines changed: 117 additions & 9 deletions

File tree

browser-mode/browser-mode-impl/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dependencies {
4343
implementation project(path: ':browser-mode-api')
4444
implementation project(path: ':common-utils')
4545
implementation project(path: ':di')
46+
implementation project(path: ':duckchat-api')
4647
implementation project(path: ':feature-toggles-api')
4748

4849
implementation Google.dagger

browser-mode/browser-mode-impl/src/main/java/com/duckduckgo/browsermode/impl/profile/WebViewProfileMigrationManager.kt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.duckduckgo.app.fire.FireproofRepository
2222
import com.duckduckgo.common.utils.AppUrl
2323
import com.duckduckgo.common.utils.DispatcherProvider
2424
import com.duckduckgo.di.scopes.AppScope
25+
import com.duckduckgo.duckchat.api.DuckAiHostProvider
2526
import com.squareup.anvil.annotations.ContributesBinding
2627
import dagger.SingleInstanceIn
2728
import kotlinx.coroutines.withContext
@@ -35,12 +36,14 @@ interface WebViewProfileMigrationManager {
3536
@ContributesBinding(AppScope::class)
3637
class RealWebViewProfileMigrationManager @Inject constructor(
3738
private val fireproofRepository: FireproofRepository,
39+
private val duckAiHostProvider: DuckAiHostProvider,
3840
private val dispatchers: DispatcherProvider,
3941
) : WebViewProfileMigrationManager {
4042

4143
@SuppressLint("RequiresFeature")
4244
override suspend fun migrate(old: Profile, new: Profile) {
43-
val domains = (fireproofRepository.fireproofWebsites() + DDG_DOMAINS).distinct()
45+
val ddgDomains = listOf(AppUrl.Url.COOKIES, AppUrl.Url.SURVEY_COOKIES, "https://${duckAiHostProvider.getHost()}")
46+
val domains = (fireproofRepository.fireproofWebsites() + ddgDomains).distinct()
4447
withContext(dispatchers.main()) {
4548
val oldCookies = old.cookieManager
4649
val newCookies = new.cookieManager
@@ -53,12 +56,4 @@ class RealWebViewProfileMigrationManager @Inject constructor(
5356
}
5457
}
5558
}
56-
57-
private companion object {
58-
val DDG_DOMAINS = listOf(
59-
AppUrl.Url.COOKIES,
60-
AppUrl.Url.SURVEY_COOKIES,
61-
"https://duck.ai",
62-
)
63-
}
6459
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright (c) 2026 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.browsermode.impl.profile
18+
19+
import android.webkit.CookieManager
20+
import androidx.test.ext.junit.runners.AndroidJUnit4
21+
import androidx.webkit.Profile
22+
import com.duckduckgo.app.fire.FireproofRepository
23+
import com.duckduckgo.common.test.CoroutineTestRule
24+
import com.duckduckgo.common.utils.AppUrl
25+
import com.duckduckgo.duckchat.api.DuckAiHostProvider
26+
import kotlinx.coroutines.test.runTest
27+
import org.junit.Before
28+
import org.junit.Rule
29+
import org.junit.Test
30+
import org.junit.runner.RunWith
31+
import org.mockito.kotlin.any
32+
import org.mockito.kotlin.eq
33+
import org.mockito.kotlin.mock
34+
import org.mockito.kotlin.never
35+
import org.mockito.kotlin.stub
36+
import org.mockito.kotlin.verify
37+
import org.mockito.kotlin.whenever
38+
39+
@RunWith(AndroidJUnit4::class)
40+
class RealWebViewProfileMigrationManagerTest {
41+
42+
@get:Rule val coroutineRule = CoroutineTestRule()
43+
44+
private val fireproofRepository: FireproofRepository = mock()
45+
private val duckAiHostProvider: DuckAiHostProvider = mock()
46+
private val oldCookies: CookieManager = mock()
47+
private val newCookies: CookieManager = mock()
48+
private val old: Profile = mock()
49+
private val new: Profile = mock()
50+
51+
private val testee = RealWebViewProfileMigrationManager(
52+
fireproofRepository,
53+
duckAiHostProvider,
54+
coroutineRule.testDispatcherProvider,
55+
)
56+
57+
@Before
58+
fun setUp() {
59+
whenever(old.cookieManager).thenReturn(oldCookies)
60+
whenever(new.cookieManager).thenReturn(newCookies)
61+
whenever(duckAiHostProvider.getHost()).thenReturn("duck.ai")
62+
fireproofRepository.stub { onBlocking { fireproofWebsites() }.thenReturn(emptyList()) }
63+
}
64+
65+
@Test
66+
fun `migrates fireproofed cookies prepending https for bare domains`() = runTest {
67+
fireproofRepository.stub { onBlocking { fireproofWebsites() }.thenReturn(listOf("example.com")) }
68+
whenever(oldCookies.getCookie("https://example.com")).thenReturn("a=1")
69+
70+
testee.migrate(old, new)
71+
72+
verify(newCookies).setCookie("https://example.com", "a=1")
73+
}
74+
75+
@Test
76+
fun `migrates ddg and duck ai cookies using the injected duck ai host`() = runTest {
77+
whenever(duckAiHostProvider.getHost()).thenReturn("custom.duck.ai")
78+
whenever(oldCookies.getCookie(AppUrl.Url.COOKIES)).thenReturn("b=2")
79+
whenever(oldCookies.getCookie(AppUrl.Url.SURVEY_COOKIES)).thenReturn("c=3")
80+
whenever(oldCookies.getCookie("https://custom.duck.ai")).thenReturn("d=4")
81+
82+
testee.migrate(old, new)
83+
84+
verify(newCookies).setCookie(AppUrl.Url.COOKIES, "b=2")
85+
verify(newCookies).setCookie(AppUrl.Url.SURVEY_COOKIES, "c=3")
86+
verify(newCookies).setCookie("https://custom.duck.ai", "d=4")
87+
verify(newCookies, never()).setCookie(eq("https://duck.ai"), any())
88+
}
89+
90+
@Test
91+
fun `splits multi-cookie strings and skips empty entries`() = runTest {
92+
fireproofRepository.stub { onBlocking { fireproofWebsites() }.thenReturn(listOf("example.com")) }
93+
whenever(oldCookies.getCookie("https://example.com")).thenReturn("a=1; b=2; ; c=3")
94+
95+
testee.migrate(old, new)
96+
97+
verify(newCookies).setCookie("https://example.com", "a=1")
98+
verify(newCookies).setCookie("https://example.com", "b=2")
99+
verify(newCookies).setCookie("https://example.com", "c=3")
100+
verify(newCookies, never()).setCookie("https://example.com", "")
101+
}
102+
103+
@Test
104+
fun `does nothing when source profile has no cookies for any domain`() = runTest {
105+
fireproofRepository.stub { onBlocking { fireproofWebsites() }.thenReturn(listOf("example.com")) }
106+
whenever(oldCookies.getCookie(any())).thenReturn(null)
107+
108+
testee.migrate(old, new)
109+
110+
verify(newCookies, never()).setCookie(any(), any())
111+
}
112+
}

0 commit comments

Comments
 (0)