Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.duckduckgo.anrs.api.CrashLogger
import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore
import com.duckduckgo.app.browser.indexeddb.IndexedDBManager
import com.duckduckgo.app.browser.session.WebViewSessionInMemoryStorage
import com.duckduckgo.app.browser.weblocalstorage.WebLocalStorageManager
import com.duckduckgo.app.global.file.FileDeleter
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
import com.duckduckgo.app.settings.db.SettingsDataStore
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.appbuildconfig.api.BuildFlavor.INTERNAL
import com.duckduckgo.appbuildconfig.api.BuildFlavor.PLAY
Expand All @@ -39,6 +39,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.mockito.kotlin.mock
Expand All @@ -48,7 +49,6 @@ import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.whenever
import java.io.File

@Suppress("RemoveExplicitTypeArguments")
@SuppressLint("NoHardcodedCoroutineDispatcher")
class WebViewDataManagerTest {

Expand All @@ -61,11 +61,11 @@ class WebViewDataManagerTest {
private val mockIndexedDBManager: IndexedDBManager = mock()
private val mockCrashLogger: CrashLogger = mock()
private val mockAppBuildConfig: AppBuildConfig = mock()
private val mockSettingsDataStore: SettingsDataStore = mock()
private val feature = FakeFeatureToggleFactory.create(AndroidBrowserConfigFeature::class.java)

private val testee = WebViewDataManager(
context,
WebViewSessionInMemoryStorage(),
mockCookieManager,
mockFileDeleter,
mockWebViewHttpAuthStore,
Expand All @@ -76,6 +76,7 @@ class WebViewDataManagerTest {
TestScope(),
CoroutineTestRule().testDispatcherProvider,
mockAppBuildConfig,
mockSettingsDataStore,
)

@Test
Expand Down Expand Up @@ -246,6 +247,7 @@ class WebViewDataManagerTest {
fun whenClearDataAndIndexedDBFeatureEnabledThenDefaultContentsDeletedExceptCookiesAndIndexedDB() = runTest {
withContext(Dispatchers.Main) {
feature.indexedDB().setRawStoredState(State(enable = true))
whenever(mockSettingsDataStore.clearDuckAiData).thenReturn(false)
val webView = TestWebView(context)

testee.clearData(webView, mockStorage)
Expand All @@ -254,14 +256,15 @@ class WebViewDataManagerTest {
File(context.applicationInfo.dataDir, "app_webview/Default"),
listOf("Cookies", "IndexedDB"),
)
verify(mockIndexedDBManager).clearIndexedDB()
verify(mockIndexedDBManager).clearIndexedDB(false)
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataAndIndexedDBThrowsExceptionThenDefaultContentsDeletedExceptCookies() = runTest {
whenever(mockIndexedDBManager.clearIndexedDB()).thenThrow(RuntimeException())
whenever(mockSettingsDataStore.clearDuckAiData).thenReturn(false)
whenever(mockIndexedDBManager.clearIndexedDB(false)).thenThrow(RuntimeException())
withContext(Dispatchers.Main) {
feature.indexedDB().setRawStoredState(State(enable = true))
val webView = TestWebView(context)
Expand All @@ -275,6 +278,276 @@ class WebViewDataManagerTest {
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataAndIndexedDBFeatureEnabledAndClearDuckAiDataTrueThenClearIndexedDBWithDuckAiData() = runTest {
withContext(Dispatchers.Main) {
feature.indexedDB().setRawStoredState(State(enable = true))
whenever(mockSettingsDataStore.clearDuckAiData).thenReturn(true)
val webView = TestWebView(context)

testee.clearData(webView, mockStorage)

verify(mockFileDeleter).deleteContents(
File(context.applicationInfo.dataDir, "app_webview/Default"),
listOf("Cookies", "IndexedDB"),
)
verify(mockIndexedDBManager).clearIndexedDB(true)
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithShouldClearDataTrueAndShouldClearChatsFalseThenWebStorageAndOtherDataCleared() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = true))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = true, shouldClearDuckAiData = false)

verify(mockWebLocalStorageManager).clearWebLocalStorage(true, false)
verify(mockStorage, never()).deleteAllData()
assertTrue(webView.historyCleared)
assertTrue(webView.cacheCleared)
assertTrue(webView.clearedFormData)
verify(mockWebViewHttpAuthStore).clearHttpAuthUsernamePassword(webView)
verify(mockCookieManager).removeExternalCookies()
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithShouldClearDataFalseAndShouldClearChatsTrueThenOnlyWebStorageCleared() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = true))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = false, shouldClearDuckAiData = true)

verify(mockWebLocalStorageManager).clearWebLocalStorage(false, true)
verify(mockStorage, never()).deleteAllData()
assertFalse(webView.historyCleared)
assertFalse(webView.cacheCleared)
assertFalse(webView.clearedFormData)
verify(mockWebViewHttpAuthStore, never()).clearHttpAuthUsernamePassword(webView)
verify(mockCookieManager, never()).removeExternalCookies()
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithBothTrueThenAllDataCleared() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = true))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = true, shouldClearDuckAiData = true)

verify(mockWebLocalStorageManager).clearWebLocalStorage(true, true)
verify(mockStorage, never()).deleteAllData()
assertTrue(webView.historyCleared)
assertTrue(webView.cacheCleared)
assertTrue(webView.clearedFormData)
verify(mockWebViewHttpAuthStore).clearHttpAuthUsernamePassword(webView)
verify(mockCookieManager).removeExternalCookies()
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithBothFalseThenNothingCleared() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = true))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = false, shouldClearDuckAiData = false)

verify(mockWebLocalStorageManager).clearWebLocalStorage(false, false)
verify(mockStorage, never()).deleteAllData()
assertFalse(webView.historyCleared)
assertFalse(webView.cacheCleared)
assertFalse(webView.clearedFormData)
verify(mockWebViewHttpAuthStore, never()).clearHttpAuthUsernamePassword(webView)
verify(mockCookieManager, never()).removeExternalCookies()
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithParametersAndThrowsExceptionAndShouldClearDataTrueThenFallbackToDeleteAllData() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = true))
val exception = RuntimeException("test")
val webView = TestWebView(context)
whenever(mockAppBuildConfig.flavor).thenReturn(INTERNAL)
whenever(mockWebLocalStorageManager.clearWebLocalStorage(true, false)).thenThrow(exception)

testee.clearData(webView, mockStorage, shouldClearBrowserData = true, shouldClearDuckAiData = false)

verify(mockWebLocalStorageManager).clearWebLocalStorage(true, false)
verify(mockCrashLogger).logCrash(CrashLogger.Crash(shortName = "web_storage_on_clear_error", t = exception))
verify(mockStorage).deleteAllData()
assertTrue(webView.historyCleared)
assertTrue(webView.cacheCleared)
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithParametersAndThrowsExceptionAndShouldClearDataFalseThenDoNotFallbackToDeleteAllData() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = true))
val exception = RuntimeException("test")
val webView = TestWebView(context)
whenever(mockAppBuildConfig.flavor).thenReturn(INTERNAL)
whenever(mockWebLocalStorageManager.clearWebLocalStorage(false, true)).thenThrow(exception)

testee.clearData(webView, mockStorage, shouldClearBrowserData = false, shouldClearDuckAiData = true)

verify(mockWebLocalStorageManager).clearWebLocalStorage(false, true)
verify(mockCrashLogger).logCrash(CrashLogger.Crash(shortName = "web_storage_on_clear_error", t = exception))
verify(mockStorage, never()).deleteAllData()
assertFalse(webView.historyCleared)
assertFalse(webView.cacheCleared)
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithParametersAndWebLocalStorageFeatureDisabledAndShouldClearDataTrueThenDeleteAllData() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = false))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = true, shouldClearDuckAiData = false)

verifyNoInteractions(mockWebLocalStorageManager)
verify(mockStorage).deleteAllData()
assertTrue(webView.historyCleared)
assertTrue(webView.cacheCleared)
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithParametersAndWebLocalStorageFeatureDisabledAndShouldClearDataFalseThenDoNotDeleteAllData() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = false))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = false, shouldClearDuckAiData = true)

verifyNoInteractions(mockWebLocalStorageManager)
verify(mockStorage, never()).deleteAllData()
assertFalse(webView.historyCleared)
assertFalse(webView.cacheCleared)
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithShouldClearDataTrueAndShouldClearChatsFalseThenWebViewDirectoriesCleared() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = true))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = true, shouldClearDuckAiData = false)

verify(mockFileDeleter).deleteContents(
File(context.applicationInfo.dataDir, "app_webview"),
listOf("Default", "Cookies"),
)
verify(mockFileDeleter).deleteContents(
File(context.applicationInfo.dataDir, "app_webview/Default"),
listOf("Cookies", "Local Storage"),
)
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithShouldClearDataFalseAndShouldClearChatsTrueThenWebViewDirectoriesNotCleared() = runTest {
withContext(Dispatchers.Main) {
feature.webLocalStorage().setRawStoredState(State(enable = true))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = false, shouldClearDuckAiData = true)

verifyNoInteractions(mockFileDeleter)
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithShouldClearChatsTrueThenClearOnlyDuckAiDataFromIndexedDB() = runTest {
withContext(Dispatchers.Main) {
feature.indexedDB().setRawStoredState(State(enable = true))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = false, shouldClearDuckAiData = true)

verify(mockIndexedDBManager).clearOnlyDuckAiData()
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithShouldClearChatsTrueAndIndexedDBFeatureDisabledThenDoNotClearIndexedDB() = runTest {
withContext(Dispatchers.Main) {
feature.indexedDB().setRawStoredState(State(enable = false))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = false, shouldClearDuckAiData = true)

verifyNoInteractions(mockIndexedDBManager)
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithShouldClearChatsTrueAndIndexedDBThrowsExceptionThenDoNotCrash() = runTest {
withContext(Dispatchers.Main) {
feature.indexedDB().setRawStoredState(State(enable = true))
whenever(mockIndexedDBManager.clearOnlyDuckAiData()).thenThrow(RuntimeException("test"))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = false, shouldClearDuckAiData = true)

verify(mockIndexedDBManager).clearOnlyDuckAiData()
// Test passes if no exception is thrown
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithBothFlagsAndIndexedDBEnabledThenClearBothTypesOfIndexedDBData() = runTest {
withContext(Dispatchers.Main) {
feature.indexedDB().setRawStoredState(State(enable = true))
feature.webLocalStorage().setRawStoredState(State(enable = true))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = true, shouldClearDuckAiData = true)

verify(mockIndexedDBManager).clearIndexedDB(false)
verify(mockIndexedDBManager).clearOnlyDuckAiData()
}
}

@SuppressLint("DenyListedApi")
@Test
fun whenClearDataWithShouldClearDataTrueAndIndexedDBEnabledThenClearIndexedDBWithoutDuckAiData() = runTest {
withContext(Dispatchers.Main) {
feature.indexedDB().setRawStoredState(State(enable = true))
feature.webLocalStorage().setRawStoredState(State(enable = true))
val webView = TestWebView(context)

testee.clearData(webView, mockStorage, shouldClearBrowserData = true, shouldClearDuckAiData = false)

verify(mockIndexedDBManager).clearIndexedDB(false)
verify(mockIndexedDBManager, never()).clearOnlyDuckAiData()
}
}

private class TestWebView(context: Context) : WebView(context) {

var historyCleared: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -573,11 +573,11 @@ class AutomaticDataClearerTest {
}

private suspend fun verifyTabsCleared() {
verify(mockClearAction).clearTabsAsync(any())
verify(mockClearAction).clearTabsOnly(any())
}

private suspend fun verifyTabsNotCleared() {
verify(mockClearAction, never()).clearTabsAsync(any())
verify(mockClearAction, never()).clearTabsOnly(any())
}

private suspend fun verifyEverythingCleared() {
Expand Down
Loading
Loading