diff --git a/content-scope-scripts/content-scope-scripts-impl/build.gradle b/content-scope-scripts/content-scope-scripts-impl/build.gradle index b18186e66829..8a070dc103b0 100644 --- a/content-scope-scripts/content-scope-scripts-impl/build.gradle +++ b/content-scope-scripts/content-scope-scripts-impl/build.gradle @@ -18,6 +18,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'com.squareup.anvil' + id 'com.google.devtools.ksp' } apply from: "$rootProject.projectDir/gradle/android-library.gradle" @@ -47,6 +48,11 @@ dependencies { implementation Google.dagger implementation AndroidX.core.ktx + // Room + implementation AndroidX.room.runtime + implementation AndroidX.room.ktx + ksp AndroidX.room.compiler + // Testing dependencies testImplementation "org.mockito.kotlin:mockito-kotlin:_" testImplementation Testing.junit4 diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeContentScopeConfigPlugin.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeContentScopeConfigPlugin.kt new file mode 100644 index 000000000000..334490739f15 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeContentScopeConfigPlugin.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge + +import com.duckduckgo.contentscopescripts.api.ContentScopeConfigPlugin +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.MessageBridgeFeatureName.MessageBridge +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeRepository +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class MessageBridgeContentScopeConfigPlugin @Inject constructor( + private val messageBridgeRepository: MessageBridgeRepository, +) : ContentScopeConfigPlugin { + + override fun config(): String { + val featureName = MessageBridge.value + val config = messageBridgeRepository.messageBridgeEntity.json + return "\"$featureName\":$config" + } + + override fun preferences(): String? { + return null + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeatureName.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeatureName.kt new file mode 100644 index 000000000000..f1ee5bc52ef3 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeatureName.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge + +enum class MessageBridgeFeatureName(val value: String) { + MessageBridge("messageBridge"), +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeatureNameUtil.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeatureNameUtil.kt new file mode 100644 index 000000000000..6d4815f45b89 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeatureNameUtil.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge + +/** + * Convenience method to get the [MessageBridgeFeatureName] from its [String] value + */ +fun messageBridgeFeatureValueOf(value: String): MessageBridgeFeatureName? { + return MessageBridgeFeatureName.entries.find { it.value == value } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeaturePlugin.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeaturePlugin.kt new file mode 100644 index 000000000000..46cb9cb8ec37 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeaturePlugin.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge + +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.MessageBridgeFeatureName.MessageBridge +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeEntity +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeRepository +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class MessageBridgeFeaturePlugin @Inject constructor( + private val messageBridgeRepository: MessageBridgeRepository, +) : PrivacyFeaturePlugin { + + override fun store(featureName: String, jsonString: String): Boolean { + val messageBridgeFeatureName = messageBridgeFeatureValueOf(featureName) ?: return false + if (messageBridgeFeatureName.value == this.featureName) { + val entity = MessageBridgeEntity(json = jsonString) + messageBridgeRepository.updateAll(messageBridgeEntity = entity) + return true + } + return false + } + + override val featureName: String = MessageBridge.value +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/di/MessageBridgeModule.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/di/MessageBridgeModule.kt new file mode 100644 index 000000000000..0852f34acfdf --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/di/MessageBridgeModule.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge.di + +import android.content.Context +import androidx.room.Room +import com.duckduckgo.app.di.AppCoroutineScope +import com.duckduckgo.common.utils.DispatcherProvider +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.ALL_MIGRATIONS +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeDatabase +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeRepository +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.RealMessageBridgeRepository +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesTo +import dagger.Module +import dagger.Provides +import dagger.SingleInstanceIn +import kotlinx.coroutines.CoroutineScope + +@Module +@ContributesTo(AppScope::class) +object MessageBridgeModule { + + @SingleInstanceIn(AppScope::class) + @Provides + fun provideMessageBridgeDatabase(context: Context): MessageBridgeDatabase { + return Room.databaseBuilder(context, MessageBridgeDatabase::class.java, "message_bridge.db") + .enableMultiInstanceInvalidation() + .fallbackToDestructiveMigration() + .addMigrations(*ALL_MIGRATIONS) + .build() + } + + @SingleInstanceIn(AppScope::class) + @Provides + fun provideMessageBridgeRepository( + database: MessageBridgeDatabase, + @AppCoroutineScope coroutineScope: CoroutineScope, + dispatcherProvider: DispatcherProvider, + ): MessageBridgeRepository { + return RealMessageBridgeRepository(database, coroutineScope, dispatcherProvider) + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeDao.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeDao.kt new file mode 100644 index 000000000000..5480107186ec --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeDao.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge.store + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction + +@Dao +abstract class MessageBridgeDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insert(messageBridgeEntity: MessageBridgeEntity) + + @Transaction + open fun updateAll( + messageBridgeEntity: MessageBridgeEntity, + ) { + delete() + insert(messageBridgeEntity) + } + + @Query("select * from message_bridge") + abstract fun get(): MessageBridgeEntity? + + @Query("delete from message_bridge") + abstract fun delete() +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeDatabase.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeDatabase.kt new file mode 100644 index 000000000000..e4b06fa1508a --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeDatabase.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge.store + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.migration.Migration + +@Database( + exportSchema = true, + version = 1, + entities = [ + MessageBridgeEntity::class, + ], +) +abstract class MessageBridgeDatabase : RoomDatabase() { + abstract fun messageBridgeDao(): MessageBridgeDao +} + +val ALL_MIGRATIONS = emptyArray() diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeEntity.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeEntity.kt new file mode 100644 index 000000000000..9d6a9caf6861 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeEntity.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge.store + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "message_bridge") +data class MessageBridgeEntity( + @PrimaryKey val id: Int = 1, + val json: String, +) diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeRepository.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeRepository.kt new file mode 100644 index 000000000000..3cd4dceb8dbe --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/MessageBridgeRepository.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge.store + +import com.duckduckgo.common.utils.DispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +interface MessageBridgeRepository { + fun updateAll( + messageBridgeEntity: MessageBridgeEntity, + ) + var messageBridgeEntity: MessageBridgeEntity +} + +class RealMessageBridgeRepository( + val database: MessageBridgeDatabase, + val coroutineScope: CoroutineScope, + val dispatcherProvider: DispatcherProvider, +) : MessageBridgeRepository { + + private val messageBridgeDao: MessageBridgeDao = database.messageBridgeDao() + override var messageBridgeEntity = MessageBridgeEntity(json = EMPTY_JSON) + + init { + coroutineScope.launch(dispatcherProvider.io()) { + loadToMemory() + } + } + + override fun updateAll(messageBridgeEntity: MessageBridgeEntity) { + messageBridgeDao.updateAll(messageBridgeEntity) + loadToMemory() + } + + private fun loadToMemory() { + messageBridgeEntity = + messageBridgeDao.get() ?: MessageBridgeEntity(json = EMPTY_JSON) + } + + companion object { + const val EMPTY_JSON = "{}" + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceContentScopeConfigPlugin.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceContentScopeConfigPlugin.kt new file mode 100644 index 000000000000..7a2a50993997 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceContentScopeConfigPlugin.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface + +import com.duckduckgo.contentscopescripts.api.ContentScopeConfigPlugin +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.NavigatorInterfaceFeatureName.NavigatorInterface +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.NavigatorInterfaceRepository +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class NavigatorInterfaceContentScopeConfigPlugin @Inject constructor( + private val navigatorInterfaceRepository: NavigatorInterfaceRepository, +) : ContentScopeConfigPlugin { + + override fun config(): String { + val featureName = NavigatorInterface.value + val config = navigatorInterfaceRepository.navigatorInterfaceEntity.json + return "\"$featureName\":$config" + } + + override fun preferences(): String? { + return null + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeatureName.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeatureName.kt new file mode 100644 index 000000000000..f0d5855c1048 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeatureName.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface + +enum class NavigatorInterfaceFeatureName(val value: String) { + NavigatorInterface("navigatorInterface"), +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeatureNameUtil.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeatureNameUtil.kt new file mode 100644 index 000000000000..5d423324d3e2 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeatureNameUtil.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface + +/** + * Convenience method to get the [NavigatorInterfaceFeatureName] from its [String] value + */ +fun navigatorInterfaceFeatureValueOf(value: String): NavigatorInterfaceFeatureName? { + return NavigatorInterfaceFeatureName.entries.find { it.value == value } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeaturePlugin.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeaturePlugin.kt new file mode 100644 index 000000000000..5338bbc037f9 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeaturePlugin.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface + +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.NavigatorInterfaceFeatureName.NavigatorInterface +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.NavigatorInterfaceEntity +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.NavigatorInterfaceRepository +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class NavigatorInterfaceFeaturePlugin @Inject constructor( + private val navigatorInterfaceRepository: NavigatorInterfaceRepository, +) : PrivacyFeaturePlugin { + + override fun store(featureName: String, jsonString: String): Boolean { + val navigatorInterfaceFeatureName = navigatorInterfaceFeatureValueOf(featureName) ?: return false + if (navigatorInterfaceFeatureName.value == this.featureName) { + val entity = NavigatorInterfaceEntity(json = jsonString) + navigatorInterfaceRepository.updateAll(navigatorInterfaceEntity = entity) + return true + } + return false + } + + override val featureName: String = NavigatorInterface.value +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/di/NavigatorInterfaceModule.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/di/NavigatorInterfaceModule.kt new file mode 100644 index 000000000000..76d050113fe7 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/di/NavigatorInterfaceModule.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.di + +import android.content.Context +import androidx.room.Room +import com.duckduckgo.app.di.AppCoroutineScope +import com.duckduckgo.common.utils.DispatcherProvider +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.ALL_MIGRATIONS +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.NavigatorInterfaceDatabase +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.NavigatorInterfaceRepository +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.RealNavigatorInterfaceRepository +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesTo +import dagger.Module +import dagger.Provides +import dagger.SingleInstanceIn +import kotlinx.coroutines.CoroutineScope + +@Module +@ContributesTo(AppScope::class) +object NavigatorInterfaceModule { + + @SingleInstanceIn(AppScope::class) + @Provides + fun provideNavigatorInterfaceDatabase(context: Context): NavigatorInterfaceDatabase { + return Room.databaseBuilder(context, NavigatorInterfaceDatabase::class.java, "navigator_interface.db") + .enableMultiInstanceInvalidation() + .fallbackToDestructiveMigration() + .addMigrations(*ALL_MIGRATIONS) + .build() + } + + @SingleInstanceIn(AppScope::class) + @Provides + fun provideNavigatorInterfaceRepository( + database: NavigatorInterfaceDatabase, + @AppCoroutineScope coroutineScope: CoroutineScope, + dispatcherProvider: DispatcherProvider, + ): NavigatorInterfaceRepository { + return RealNavigatorInterfaceRepository(database, coroutineScope, dispatcherProvider) + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceDao.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceDao.kt new file mode 100644 index 000000000000..8d1bb85e0ac6 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceDao.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction + +@Dao +abstract class NavigatorInterfaceDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insert(navigatorInterfaceEntity: NavigatorInterfaceEntity) + + @Transaction + open fun updateAll( + navigatorInterfaceEntity: NavigatorInterfaceEntity, + ) { + delete() + insert(navigatorInterfaceEntity) + } + + @Query("select * from navigator_interface") + abstract fun get(): NavigatorInterfaceEntity? + + @Query("delete from navigator_interface") + abstract fun delete() +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceDatabase.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceDatabase.kt new file mode 100644 index 000000000000..6d35d250fc65 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceDatabase.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.migration.Migration + +@Database( + exportSchema = true, + version = 1, + entities = [ + NavigatorInterfaceEntity::class, + ], +) +abstract class NavigatorInterfaceDatabase : RoomDatabase() { + abstract fun navigatorInterfaceDao(): NavigatorInterfaceDao +} + +val ALL_MIGRATIONS = emptyArray() diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceEntity.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceEntity.kt new file mode 100644 index 000000000000..aa9e4283cc5d --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceEntity.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "navigator_interface") +data class NavigatorInterfaceEntity( + @PrimaryKey val id: Int = 1, + val json: String, +) diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceRepository.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceRepository.kt new file mode 100644 index 000000000000..7156ef727697 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/NavigatorInterfaceRepository.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store + +import com.duckduckgo.common.utils.DispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +interface NavigatorInterfaceRepository { + fun updateAll( + navigatorInterfaceEntity: NavigatorInterfaceEntity, + ) + var navigatorInterfaceEntity: NavigatorInterfaceEntity +} + +class RealNavigatorInterfaceRepository( + val database: NavigatorInterfaceDatabase, + val coroutineScope: CoroutineScope, + val dispatcherProvider: DispatcherProvider, +) : NavigatorInterfaceRepository { + + private val navigatorInterfaceDao: NavigatorInterfaceDao = database.navigatorInterfaceDao() + override var navigatorInterfaceEntity = NavigatorInterfaceEntity(json = EMPTY_JSON) + + init { + coroutineScope.launch(dispatcherProvider.io()) { + loadToMemory() + } + } + + override fun updateAll(navigatorInterfaceEntity: NavigatorInterfaceEntity) { + navigatorInterfaceDao.updateAll(navigatorInterfaceEntity) + loadToMemory() + } + + private fun loadToMemory() { + navigatorInterfaceEntity = + navigatorInterfaceDao.get() ?: NavigatorInterfaceEntity(json = EMPTY_JSON) + } + + companion object { + const val EMPTY_JSON = "{}" + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeContentScopeConfigPluginTest.kt b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeContentScopeConfigPluginTest.kt new file mode 100644 index 000000000000..3517e031103c --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeContentScopeConfigPluginTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge + +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeEntity +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeRepository +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNull +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class MessageBridgeContentScopeConfigPluginTest { + + private lateinit var testee: MessageBridgeContentScopeConfigPlugin + + private val mockMessageBridgeRepository: MessageBridgeRepository = mock() + + @Before + fun before() { + testee = MessageBridgeContentScopeConfigPlugin(mockMessageBridgeRepository) + } + + @Test + fun whenGetConfigThenReturnCorrectlyFormattedJson() { + whenever(mockMessageBridgeRepository.messageBridgeEntity).thenReturn( + MessageBridgeEntity(json = CONFIG), + ) + assertEquals("\"messageBridge\":$CONFIG", testee.config()) + } + + @Test + fun whenGetPreferencesThenReturnNull() { + assertNull(testee.preferences()) + } + + companion object { + const val CONFIG = "{\"key\":\"value\"}" + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeaturePluginTest.kt b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeaturePluginTest.kt new file mode 100644 index 000000000000..2870a86ffe77 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/MessageBridgeFeaturePluginTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge + +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeEntity +import com.duckduckgo.contentscopescripts.impl.features.messagebridge.store.MessageBridgeRepository +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class MessageBridgeFeaturePluginTest { + private lateinit var testee: MessageBridgeFeaturePlugin + + private val mockMessageBridgeRepository: MessageBridgeRepository = mock() + + @Before + fun before() { + testee = MessageBridgeFeaturePlugin(mockMessageBridgeRepository) + } + + @Test + fun whenFeatureNameDoesNotMatchMessageBridgeThenReturnFalse() { + MessageBridgeFeatureName.entries.filter { it != FEATURE_NAME }.forEach { + assertFalse(testee.store(it.value, JSON_STRING)) + } + } + + @Test + fun whenFeatureNameMatchesMessageBridgeThenReturnTrue() { + assertTrue(testee.store(FEATURE_NAME_VALUE, JSON_STRING)) + } + + @Test + fun whenFeatureNameMatchesMessageBridgeThenUpdateAll() { + testee.store(FEATURE_NAME_VALUE, JSON_STRING) + val captor = argumentCaptor() + verify(mockMessageBridgeRepository).updateAll(captor.capture()) + assertEquals(JSON_STRING, captor.firstValue.json) + } + + companion object { + private val FEATURE_NAME = MessageBridgeFeatureName.MessageBridge + private val FEATURE_NAME_VALUE = FEATURE_NAME.value + private const val JSON_STRING = "{\"key\":\"value\"}" + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/RealMessageBridgeRepositoryTest.kt b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/RealMessageBridgeRepositoryTest.kt new file mode 100644 index 000000000000..659c8d99f63a --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/messagebridge/store/RealMessageBridgeRepositoryTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.messagebridge.store + +import com.duckduckgo.common.test.CoroutineTestRule +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class RealMessageBridgeRepositoryTest { + @get:Rule var coroutineRule = CoroutineTestRule() + + private lateinit var testee: RealMessageBridgeRepository + + private val mockDatabase: MessageBridgeDatabase = mock() + private val mockMessageBridgeDao: MessageBridgeDao = mock() + + @Before + fun before() { + whenever(mockMessageBridgeDao.get()).thenReturn(null) + whenever(mockDatabase.messageBridgeDao()).thenReturn(mockMessageBridgeDao) + } + + @Test + fun whenInitializedAndDoesNotHaveStoredValueThenLoadEmptyJsonToMemory() = + runTest { + testee = + RealMessageBridgeRepository( + mockDatabase, + TestScope(), + coroutineRule.testDispatcherProvider, + ) + + verify(mockMessageBridgeDao).get() + assertEquals("{}", testee.messageBridgeEntity.json) + } + + @Test + fun whenInitializedAndHasStoredValueThenLoadStoredJsonToMemory() = + runTest { + whenever(mockMessageBridgeDao.get()).thenReturn(messageBridgeEntity) + testee = + RealMessageBridgeRepository( + mockDatabase, + TestScope(), + coroutineRule.testDispatcherProvider, + ) + + verify(mockMessageBridgeDao).get() + assertEquals(messageBridgeEntity.json, testee.messageBridgeEntity.json) + } + + @Test + fun whenUpdateAllThenUpdateAllCalled() = + runTest { + testee = + RealMessageBridgeRepository( + mockDatabase, + TestScope(), + coroutineRule.testDispatcherProvider, + ) + + testee.updateAll(messageBridgeEntity) + + verify(mockMessageBridgeDao).updateAll(messageBridgeEntity) + } + + companion object { + val messageBridgeEntity = MessageBridgeEntity(json = "{\"key\":\"value\"}") + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceContentScopeConfigPluginTest.kt b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceContentScopeConfigPluginTest.kt new file mode 100644 index 000000000000..b930f0581859 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceContentScopeConfigPluginTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface + +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.NavigatorInterfaceEntity +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.NavigatorInterfaceRepository +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNull +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class NavigatorInterfaceContentScopeConfigPluginTest { + + private lateinit var testee: NavigatorInterfaceContentScopeConfigPlugin + + private val mockNavigatorInterfaceRepository: NavigatorInterfaceRepository = mock() + + @Before + fun before() { + testee = NavigatorInterfaceContentScopeConfigPlugin(mockNavigatorInterfaceRepository) + } + + @Test + fun whenGetConfigThenReturnCorrectlyFormattedJson() { + whenever(mockNavigatorInterfaceRepository.navigatorInterfaceEntity).thenReturn( + NavigatorInterfaceEntity(json = CONFIG), + ) + assertEquals("\"navigatorInterface\":$CONFIG", testee.config()) + } + + @Test + fun whenGetPreferencesThenReturnNull() { + assertNull(testee.preferences()) + } + + companion object { + const val CONFIG = "{\"key\":\"value\"}" + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeaturePluginTest.kt b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeaturePluginTest.kt new file mode 100644 index 000000000000..6367dbb86a34 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/NavigatorInterfaceFeaturePluginTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface + +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.NavigatorInterfaceEntity +import com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store.NavigatorInterfaceRepository +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class NavigatorInterfaceFeaturePluginTest { + private lateinit var testee: NavigatorInterfaceFeaturePlugin + + private val mockNavigatorInterfaceRepository: NavigatorInterfaceRepository = mock() + + @Before + fun before() { + testee = NavigatorInterfaceFeaturePlugin(mockNavigatorInterfaceRepository) + } + + @Test + fun whenFeatureNameDoesNotMatchNavigatorInterfaceThenReturnFalse() { + NavigatorInterfaceFeatureName.entries.filter { it != FEATURE_NAME }.forEach { + assertFalse(testee.store(it.value, JSON_STRING)) + } + } + + @Test + fun whenFeatureNameMatchesNavigatorInterfaceThenReturnTrue() { + assertTrue(testee.store(FEATURE_NAME_VALUE, JSON_STRING)) + } + + @Test + fun whenFeatureNameMatchesNavigatorInterfaceThenUpdateAll() { + testee.store(FEATURE_NAME_VALUE, JSON_STRING) + val captor = argumentCaptor() + verify(mockNavigatorInterfaceRepository).updateAll(captor.capture()) + assertEquals(JSON_STRING, captor.firstValue.json) + } + + companion object { + private val FEATURE_NAME = NavigatorInterfaceFeatureName.NavigatorInterface + private val FEATURE_NAME_VALUE = FEATURE_NAME.value + private const val JSON_STRING = "{\"key\":\"value\"}" + } +} diff --git a/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/RealNavigatorInterfaceRepositoryTest.kt b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/RealNavigatorInterfaceRepositoryTest.kt new file mode 100644 index 000000000000..6d399eb67a58 --- /dev/null +++ b/content-scope-scripts/content-scope-scripts-impl/src/test/java/com/duckduckgo/contentscopescripts/impl/features/navigatorinterface/store/RealNavigatorInterfaceRepositoryTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.contentscopescripts.impl.features.navigatorinterface.store + +import com.duckduckgo.common.test.CoroutineTestRule +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class RealNavigatorInterfaceRepositoryTest { + @get:Rule var coroutineRule = CoroutineTestRule() + + private lateinit var testee: RealNavigatorInterfaceRepository + + private val mockDatabase: NavigatorInterfaceDatabase = mock() + private val mockNavigatorInterfaceDao: NavigatorInterfaceDao = mock() + + @Before + fun before() { + whenever(mockNavigatorInterfaceDao.get()).thenReturn(null) + whenever(mockDatabase.navigatorInterfaceDao()).thenReturn(mockNavigatorInterfaceDao) + } + + @Test + fun whenInitializedAndDoesNotHaveStoredValueThenLoadEmptyJsonToMemory() = + runTest { + testee = + RealNavigatorInterfaceRepository( + mockDatabase, + TestScope(), + coroutineRule.testDispatcherProvider, + ) + + verify(mockNavigatorInterfaceDao).get() + assertEquals("{}", testee.navigatorInterfaceEntity.json) + } + + @Test + fun whenInitializedAndHasStoredValueThenLoadStoredJsonToMemory() = + runTest { + whenever(mockNavigatorInterfaceDao.get()).thenReturn(navigatorInterfaceEntity) + testee = + RealNavigatorInterfaceRepository( + mockDatabase, + TestScope(), + coroutineRule.testDispatcherProvider, + ) + + verify(mockNavigatorInterfaceDao).get() + assertEquals(navigatorInterfaceEntity.json, testee.navigatorInterfaceEntity.json) + } + + @Test + fun whenUpdateAllThenUpdateAllCalled() = + runTest { + testee = + RealNavigatorInterfaceRepository( + mockDatabase, + TestScope(), + coroutineRule.testDispatcherProvider, + ) + + testee.updateAll(navigatorInterfaceEntity) + + verify(mockNavigatorInterfaceDao).updateAll(navigatorInterfaceEntity) + } + + companion object { + val navigatorInterfaceEntity = NavigatorInterfaceEntity(json = "{\"key\":\"value\"}") + } +}